Compare commits
170 commits
6c72698d8f
...
c38f62700a
Author | SHA1 | Date | |
---|---|---|---|
Erin Shepherd | c38f62700a | ||
c813df2ebb | |||
0d43d9926a | |||
1ce29aeabf | |||
fdfacb0ec0 | |||
73b68fcabb | |||
6df9d388e7 | |||
ee7e49d1b1 | |||
c4d2c72924 | |||
099b3011aa | |||
65b6c4f6df | |||
c722c4cce8 | |||
41ea39903d | |||
d3a29a136c | |||
c118918520 | |||
8fdbb4d00d | |||
7bdb2433f1 | |||
16122761c5 | |||
ef582dc4f2 | |||
e37e8deb0f | |||
9965a23b04 | |||
78a6b871fe | |||
0cd0786aef | |||
b280a255c4 | |||
45ce858fd9 | |||
a5394980f2 | |||
cd0a87f170 | |||
104157bd01 | |||
029b5cd5b1 | |||
5333447be0 | |||
e98833748e | |||
53817294fc | |||
dd7176a4b5 | |||
6ba52306f9 | |||
6f1559ed0f | |||
d055d75172 | |||
d70303bba6 | |||
b1a48e05b6 | |||
c476dfc725 | |||
476e74b4c4 | |||
f4b78028a3 | |||
f7613febb3 | |||
fd3c482104 | |||
d3afd7a2f1 | |||
9358fd295d | |||
c374729225 | |||
b2a25d446a | |||
89e1974f30 | |||
455a754081 | |||
68d9dcd425 | |||
c989faaa62 | |||
36b0ff57b7 | |||
9b6d6a919f | |||
fe1b694128 | |||
6eac1cfccd | |||
a8c854b3ea | |||
782b6835f7 | |||
833d9c2f1c | |||
9f4930ec11 | |||
608343c135 | |||
ca80beb653 | |||
0beb095a4b | |||
bbf74498f5 | |||
ac219dd1f6 | |||
2bc22be66c | |||
be7032b9cf | |||
3114c826a7 | |||
106648b456 | |||
86a80acf40 | |||
622f603ac7 | |||
1e1289b024 | |||
e1227457f1 | |||
8515bc7962 | |||
5925a31b78 | |||
c493c967d6 | |||
b67e0c94a6 | |||
7ba13dddfa | |||
71e68dac4e | |||
d13a2f7901 | |||
6c0a6097ff | |||
a70e2cd649 | |||
4b7f32a2a6 | |||
02a34252ba | |||
ffe735344b | |||
34c269310d | |||
4cb2323458 | |||
54f0f1b9ef | |||
8c81db5a41 | |||
e53fc34e9a | |||
54101563bb | |||
21d6bc1689 | |||
0be6da42d3 | |||
c199387558 | |||
cbfa5ad5dd | |||
c883799a1f | |||
d29172a682 | |||
b6c0ef70a2 | |||
0437159056 | |||
fa293f03fa | |||
bd220c32f1 | |||
01e0cb1cd5 | |||
8368f4857c | |||
93ccb4a29e | |||
81334e2bfb | |||
6b498fae46 | |||
e42875d195 | |||
0ad919b192 | |||
5187e4e758 | |||
3151b260e2 | |||
58fc889c6f | |||
ca8d52c2a4 | |||
18ac5f1cc8 | |||
b5b1a202cc | |||
c8bf6192e4 | |||
3a41fccc43 | |||
d0c9ac3919 | |||
d54e7ee61e | |||
c64be9758f | |||
d1de7fb7fa | |||
a442f481f8 | |||
7c65f52692 | |||
30e786225e | |||
312d616371 | |||
0498b106c9 | |||
29604763d7 | |||
887976814a | |||
c95d9aab56 | |||
9616f5bb22 | |||
2f8fb49d13 | |||
1e7ea50f4c | |||
34ba7612d1 | |||
c4b92b1aee | |||
bb89f83cc0 | |||
03b991de6c | |||
0165449e3a | |||
c2170991c7 | |||
e02812d5b6 | |||
b1a219552e | |||
f002878c95 | |||
5f9e47be34 | |||
b8f6f03956 | |||
4fb0aae636 | |||
20aa8881dc | |||
139ea4c981 | |||
bfafb114a2 | |||
1c3192df6b | |||
5825402ed5 | |||
7c8e2b9859 | |||
053dac2afa | |||
9387beb3b3 | |||
1dca08b76f | |||
cbb440bbc2 | |||
125322718b | |||
74d40c7d8f | |||
e0eb39d41b | |||
e91418436a | |||
cb27d89997 | |||
f359b15303 | |||
0f5e6dd02b | |||
56eb1da0f5 | |||
ae07cfb868 | |||
6804228fdf | |||
15bae3e0e4 | |||
c68e6b52d9 | |||
55f54be23c | |||
d0ba77047e | |||
03d9618595 | |||
fea142fb9a | |||
bb1ef11c30 | |||
2d9a85db6e |
|
@ -279,6 +279,10 @@ MAX_POLL_OPTION_CHARS=100
|
|||
# Only relevant when elasticsearch is installed
|
||||
# MAX_SEARCH_RESULTS=20
|
||||
|
||||
# Maximum hashtags to display
|
||||
# Customize the number of hashtags shown in 'Explore'
|
||||
# MAX_TRENDING_TAGS=10
|
||||
|
||||
# Maximum custom emoji file sizes
|
||||
# If undefined or smaller than MAX_EMOJI_SIZE, the value
|
||||
# of MAX_EMOJI_SIZE will be used for MAX_REMOTE_EMOJI_SIZE
|
||||
|
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -3,6 +3,3 @@ contact_links:
|
|||
- name: GitHub Discussions
|
||||
url: https://github.com/mastodon/mastodon/discussions
|
||||
about: Please ask and answer questions here.
|
||||
- name: Bug Bounty Program
|
||||
url: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||
about: Please report security vulnerabilities here.
|
||||
|
|
4
Aptfile
|
@ -1,8 +1,8 @@
|
|||
ffmpeg
|
||||
libicu[0-9][0-9]
|
||||
libicu-dev
|
||||
libidn11
|
||||
libidn11-dev
|
||||
libidn12
|
||||
libidn-dev
|
||||
libpq-dev
|
||||
libxdamage1
|
||||
libxfixes3
|
||||
|
|
42
CHANGELOG.md
|
@ -13,7 +13,7 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
- **Add ability to follow hashtags** ([Gargron](https://github.com/mastodon/mastodon/pull/18809), [Gargron](https://github.com/mastodon/mastodon/pull/18862), [Gargron](https://github.com/mastodon/mastodon/pull/19472), [noellabo](https://github.com/mastodon/mastodon/pull/18924))
|
||||
- Add ability to filter individual posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18945))
|
||||
- **Add ability to translate posts** ([Gargron](https://github.com/mastodon/mastodon/pull/19218), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19433), [Gargron](https://github.com/mastodon/mastodon/pull/19453), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19434), [Gargron](https://github.com/mastodon/mastodon/pull/19388), [ykzts](https://github.com/mastodon/mastodon/pull/19244), [Gargron](https://github.com/mastodon/mastodon/pull/19245))
|
||||
- Add featured tags to web UI ([noellabo](https://github.com/mastodon/mastodon/pull/19408), [noellabo](https://github.com/mastodon/mastodon/pull/19380), [noellabo](https://github.com/mastodon/mastodon/pull/19358), [noellabo](https://github.com/mastodon/mastodon/pull/19409), [Gargron](https://github.com/mastodon/mastodon/pull/19382), [ykzts](https://github.com/mastodon/mastodon/pull/19418), [noellabo](https://github.com/mastodon/mastodon/pull/19403), [noellabo](https://github.com/mastodon/mastodon/pull/19404), [Gargron](https://github.com/mastodon/mastodon/pull/19398))
|
||||
- Add featured tags to web UI ([noellabo](https://github.com/mastodon/mastodon/pull/19408), [noellabo](https://github.com/mastodon/mastodon/pull/19380), [noellabo](https://github.com/mastodon/mastodon/pull/19358), [noellabo](https://github.com/mastodon/mastodon/pull/19409), [Gargron](https://github.com/mastodon/mastodon/pull/19382), [ykzts](https://github.com/mastodon/mastodon/pull/19418), [noellabo](https://github.com/mastodon/mastodon/pull/19403), [noellabo](https://github.com/mastodon/mastodon/pull/19404), [Gargron](https://github.com/mastodon/mastodon/pull/19398), [Gargron](https://github.com/mastodon/mastodon/pull/19712))
|
||||
- **Add support for language preferences for trending statuses and links** ([Gargron](https://github.com/mastodon/mastodon/pull/18288), [Gargron](https://github.com/mastodon/mastodon/pull/19349), [ykzts](https://github.com/mastodon/mastodon/pull/19335))
|
||||
- Previously, you could only see trends in your current language
|
||||
- For less popular languages, that meant empty trends
|
||||
|
@ -23,9 +23,10 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
- Add `noopener` to links to remote profiles in web UI ([shleeable](https://github.com/mastodon/mastodon/pull/19014))
|
||||
- Add warning for sensitive audio posts in web UI ([rgroothuijsen](https://github.com/mastodon/mastodon/pull/17885))
|
||||
- Add language attribute to posts in web UI ([tribela](https://github.com/mastodon/mastodon/pull/18544))
|
||||
- Add meta tag for official iOS app ([Gargron](https://github.com/mastodon/mastodon/pull/16599))
|
||||
- Add support for uploading WebP files ([Saiv46](https://github.com/mastodon/mastodon/pull/18506))
|
||||
- Add support for uploading `audio/vnd.wave` files ([tribela](https://github.com/mastodon/mastodon/pull/18737))
|
||||
- Add support for uploading AVIF files ([txt-file](https://github.com/mastodon/mastodon/pull/19647))
|
||||
- Add support for uploading HEIC files ([Gargron](https://github.com/mastodon/mastodon/pull/19618))
|
||||
- Add more debug information when processing remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15605), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19209))
|
||||
- **Add retention policy for cached content and media** ([Gargron](https://github.com/mastodon/mastodon/pull/19232), [zunda](https://github.com/mastodon/mastodon/pull/19478), [Gargron](https://github.com/mastodon/mastodon/pull/19458), [Gargron](https://github.com/mastodon/mastodon/pull/19248))
|
||||
- Set for how long remote posts or media should be cached on your server
|
||||
|
@ -49,12 +50,15 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
- Add `EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18642))
|
||||
- Add `IP_RETENTION_PERIOD` and `SESSION_RETENTION_PERIOD` environment variables ([kescherCode](https://github.com/mastodon/mastodon/pull/18757))
|
||||
- Add `http_hidden_proxy` environment variable ([tribela](https://github.com/mastodon/mastodon/pull/18427))
|
||||
- Add caching for payload serialization during fan-out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19637), [Gargron](https://github.com/mastodon/mastodon/pull/19642), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19746), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19747))
|
||||
- Add assets from Twemoji 14.0 ([Gargron](https://github.com/mastodon/mastodon/pull/19733))
|
||||
- Add reputation and followers score boost to SQL-only account search ([Gargron](https://github.com/mastodon/mastodon/pull/19251))
|
||||
|
||||
### Changed
|
||||
|
||||
- **Change brand color and logotypes** ([Gargron](https://github.com/mastodon/mastodon/pull/18592), [Gargron](https://github.com/mastodon/mastodon/pull/18639), [Gargron](https://github.com/mastodon/mastodon/pull/18691), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18634), [Gargron](https://github.com/mastodon/mastodon/pull/19254), [mayaeh](https://github.com/mastodon/mastodon/pull/18710))
|
||||
- **Change post editing to be enabled in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/19103))
|
||||
- **Change web UI to work for logged-out users** ([Gargron](https://github.com/mastodon/mastodon/pull/18961), [Gargron](https://github.com/mastodon/mastodon/pull/19250), [Gargron](https://github.com/mastodon/mastodon/pull/19294), [Gargron](https://github.com/mastodon/mastodon/pull/19306), [Gargron](https://github.com/mastodon/mastodon/pull/19315), [ykzts](https://github.com/mastodon/mastodon/pull/19322), [Gargron](https://github.com/mastodon/mastodon/pull/19412), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19437), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19415), [Gargron](https://github.com/mastodon/mastodon/pull/19348), [Gargron](https://github.com/mastodon/mastodon/pull/19295), [Gargron](https://github.com/mastodon/mastodon/pull/19422), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19414), [Gargron](https://github.com/mastodon/mastodon/pull/19319), [Gargron](https://github.com/mastodon/mastodon/pull/19345), [Gargron](https://github.com/mastodon/mastodon/pull/19310), [Gargron](https://github.com/mastodon/mastodon/pull/19301), [Gargron](https://github.com/mastodon/mastodon/pull/19423), [ykzts](https://github.com/mastodon/mastodon/pull/19471), [ykzts](https://github.com/mastodon/mastodon/pull/19333), [ykzts](https://github.com/mastodon/mastodon/pull/19337), [ykzts](https://github.com/mastodon/mastodon/pull/19272), [ykzts](https://github.com/mastodon/mastodon/pull/19468), [Gargron](https://github.com/mastodon/mastodon/pull/19466), [Gargron](https://github.com/mastodon/mastodon/pull/19457), [Gargron](https://github.com/mastodon/mastodon/pull/19426), [Gargron](https://github.com/mastodon/mastodon/pull/19427), [Gargron](https://github.com/mastodon/mastodon/pull/19421), [Gargron](https://github.com/mastodon/mastodon/pull/19417), [Gargron](https://github.com/mastodon/mastodon/pull/19413), [Gargron](https://github.com/mastodon/mastodon/pull/19397), [Gargron](https://github.com/mastodon/mastodon/pull/19387), [Gargron](https://github.com/mastodon/mastodon/pull/19396), [Gargron](https://github.com/mastodon/mastodon/pull/19385), [ykzts](https://github.com/mastodon/mastodon/pull/19334), [ykzts](https://github.com/mastodon/mastodon/pull/19329), [Gargron](https://github.com/mastodon/mastodon/pull/19324), [Gargron](https://github.com/mastodon/mastodon/pull/19318), [Gargron](https://github.com/mastodon/mastodon/pull/19316), [Gargron](https://github.com/mastodon/mastodon/pull/19263), [trwnh](https://github.com/mastodon/mastodon/pull/19305), [ykzts](https://github.com/mastodon/mastodon/pull/19273))
|
||||
- **Change web UI to work for logged-out users** ([Gargron](https://github.com/mastodon/mastodon/pull/18961), [Gargron](https://github.com/mastodon/mastodon/pull/19250), [Gargron](https://github.com/mastodon/mastodon/pull/19294), [Gargron](https://github.com/mastodon/mastodon/pull/19306), [Gargron](https://github.com/mastodon/mastodon/pull/19315), [ykzts](https://github.com/mastodon/mastodon/pull/19322), [Gargron](https://github.com/mastodon/mastodon/pull/19412), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19437), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19415), [Gargron](https://github.com/mastodon/mastodon/pull/19348), [Gargron](https://github.com/mastodon/mastodon/pull/19295), [Gargron](https://github.com/mastodon/mastodon/pull/19422), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19414), [Gargron](https://github.com/mastodon/mastodon/pull/19319), [Gargron](https://github.com/mastodon/mastodon/pull/19345), [Gargron](https://github.com/mastodon/mastodon/pull/19310), [Gargron](https://github.com/mastodon/mastodon/pull/19301), [Gargron](https://github.com/mastodon/mastodon/pull/19423), [ykzts](https://github.com/mastodon/mastodon/pull/19471), [ykzts](https://github.com/mastodon/mastodon/pull/19333), [ykzts](https://github.com/mastodon/mastodon/pull/19337), [ykzts](https://github.com/mastodon/mastodon/pull/19272), [ykzts](https://github.com/mastodon/mastodon/pull/19468), [Gargron](https://github.com/mastodon/mastodon/pull/19466), [Gargron](https://github.com/mastodon/mastodon/pull/19457), [Gargron](https://github.com/mastodon/mastodon/pull/19426), [Gargron](https://github.com/mastodon/mastodon/pull/19427), [Gargron](https://github.com/mastodon/mastodon/pull/19421), [Gargron](https://github.com/mastodon/mastodon/pull/19417), [Gargron](https://github.com/mastodon/mastodon/pull/19413), [Gargron](https://github.com/mastodon/mastodon/pull/19397), [Gargron](https://github.com/mastodon/mastodon/pull/19387), [Gargron](https://github.com/mastodon/mastodon/pull/19396), [Gargron](https://github.com/mastodon/mastodon/pull/19385), [ykzts](https://github.com/mastodon/mastodon/pull/19334), [ykzts](https://github.com/mastodon/mastodon/pull/19329), [Gargron](https://github.com/mastodon/mastodon/pull/19324), [Gargron](https://github.com/mastodon/mastodon/pull/19318), [Gargron](https://github.com/mastodon/mastodon/pull/19316), [Gargron](https://github.com/mastodon/mastodon/pull/19263), [trwnh](https://github.com/mastodon/mastodon/pull/19305), [ykzts](https://github.com/mastodon/mastodon/pull/19273), [Gargron](https://github.com/mastodon/mastodon/pull/19801), [Gargron](https://github.com/mastodon/mastodon/pull/19790), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19773), [Gargron](https://github.com/mastodon/mastodon/pull/19798), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19724), [Gargron](https://github.com/mastodon/mastodon/pull/19709), [Gargron](https://github.com/mastodon/mastodon/pull/19514), [Gargron](https://github.com/mastodon/mastodon/pull/19562))
|
||||
- The web app can now be accessed without being logged in
|
||||
- No more `/web` prefix on web app paths
|
||||
- Profiles, posts, and other public pages now use the same interface for logged in and logged out users
|
||||
|
@ -77,15 +81,20 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
- You can peek inside filtered posts anyway
|
||||
- Change path of privacy policy page from `/terms` to `/privacy-policy` ([Gargron](https://github.com/mastodon/mastodon/pull/19249))
|
||||
- Change how hashtags are normalized ([Gargron](https://github.com/mastodon/mastodon/pull/18795), [Gargron](https://github.com/mastodon/mastodon/pull/18863), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18854))
|
||||
- Change public timelines to be filtered by current locale by default ([Gargron](https://github.com/mastodon/mastodon/pull/19291))
|
||||
- Change settings area to be separated into categories in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/19407))
|
||||
- Change public (but not hashtag) timelines to be filtered by current locale by default ([Gargron](https://github.com/mastodon/mastodon/pull/19291), [Gargron](https://github.com/mastodon/mastodon/pull/19563))
|
||||
- Change settings area to be separated into categories in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/19407), [Gargron](https://github.com/mastodon/mastodon/pull/19533))
|
||||
- Change "No accounts selected" errors to use the appropriate noun in admin UI ([prplecake](https://github.com/mastodon/mastodon/pull/19356))
|
||||
- Change e-mail domain blocks to match subdomains of blocked domains ([Gargron](https://github.com/mastodon/mastodon/pull/18979))
|
||||
- Change custom emoji file size limit from 50 KB to 256 KB ([Gargron](https://github.com/mastodon/mastodon/pull/18788))
|
||||
- Change "Allow trends without prior review" setting to also work for trending posts ([Gargron](https://github.com/mastodon/mastodon/pull/17977))
|
||||
- Change admin announcements form to use single inputs for date and time in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18321))
|
||||
- Change search API to be accessible without being logged in ([Gargron](https://github.com/mastodon/mastodon/pull/18963), [Gargron](https://github.com/mastodon/mastodon/pull/19326))
|
||||
- Change following and followers API to be accessible without being logged in ([Gargron](https://github.com/mastodon/mastodon/pull/18964))
|
||||
- Change `AUTHORIZED_FETCH` to not block unauthenticated REST API access ([Gargron](https://github.com/mastodon/mastodon/pull/19803))
|
||||
- Change Helm configuration ([deepy](https://github.com/mastodon/mastodon/pull/18997), [jgsmith](https://github.com/mastodon/mastodon/pull/18415), [deepy](https://github.com/mastodon/mastodon/pull/18941))
|
||||
- Change mentions of blocked users to not be processed ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19725))
|
||||
- Change max. thumbnail dimensions to 640x360px (360p) ([Gargron](https://github.com/mastodon/mastodon/pull/19619))
|
||||
- Change post-processing to be deferred only for large media types ([Gargron](https://github.com/mastodon/mastodon/pull/19617))
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -98,6 +107,25 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fix featured tags not saving preferred casing ([Gargron](https://github.com/mastodon/mastodon/pull/19732))
|
||||
- Fix language not being saved when editing status ([Gargron](https://github.com/mastodon/mastodon/pull/19543))
|
||||
- Fix not being able to input featured tag with hash symbol ([Gargron](https://github.com/mastodon/mastodon/pull/19535))
|
||||
- Fix user clean-up scheduler crash when an unconfirmed account has a moderation note ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19629))
|
||||
- Fix being unable to withdraw follow request when confirmation modal is disabled in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19687))
|
||||
- Fix inaccurate admin log entry for re-sending confirmation e-mails ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19674))
|
||||
- Fix edits not being immediately reflected ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19673))
|
||||
- Fix bookmark import stopping at the first failure ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19669))
|
||||
- Fix account action type validation ([Gargron](https://github.com/mastodon/mastodon/pull/19476))
|
||||
- Fix upload progress not communicating processing phase in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19530))
|
||||
- Fix wrong host being used for custom.css when asset host configured ([Gargron](https://github.com/mastodon/mastodon/pull/19521))
|
||||
- Fix account migration form ever using outdated account data ([Gargron](https://github.com/mastodon/mastodon/pull/18429))
|
||||
- Fix error when uploading malformed CSV import ([Gargron](https://github.com/mastodon/mastodon/pull/19509))
|
||||
- Fix avatars not using image tags in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19488))
|
||||
- Fix handling of duplicate and out-of-order notifications in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19693))
|
||||
- Fix reblogs being discarded after the reblogged status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19731))
|
||||
- Fix indexing scheduler trying to index when Elasticsearch is disabled ([Gargron](https://github.com/mastodon/mastodon/pull/19805))
|
||||
- Fix n+1 queries when rendering initial state JSON ([Gargron](https://github.com/mastodon/mastodon/pull/19795))
|
||||
- Fix n+1 query during status removal ([Gargron](https://github.com/mastodon/mastodon/pull/19753))
|
||||
- Fix OCR not working due to Content Security Policy in web UI ([prplecake](https://github.com/mastodon/mastodon/pull/18817))
|
||||
- Fix `nofollow` rel being removed in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19455))
|
||||
- Fix language dropdown causing zoom on mobile devices in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19428))
|
||||
|
@ -248,7 +276,7 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fix error resposes for `from` search prefix ([single-right-quote](https://github.com/mastodon/mastodon/pull/17963))
|
||||
- Fix error responses for `from` search prefix ([single-right-quote](https://github.com/mastodon/mastodon/pull/17963))
|
||||
- Fix dangling language-specific trends ([Gargron](https://github.com/mastodon/mastodon/pull/17997))
|
||||
- Fix extremely rare race condition when deleting a status or account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17994))
|
||||
- Fix trends returning less results per page when filtered in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17996))
|
||||
|
@ -383,7 +411,7 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
- Remove profile directory link from main navigation panel in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17688))
|
||||
- **Remove language detection through cld3** ([Gargron](https://github.com/mastodon/mastodon/pull/17478), [ykzts](https://github.com/mastodon/mastodon/pull/17539), [Gargron](https://github.com/mastodon/mastodon/pull/17496), [Gargron](https://github.com/mastodon/mastodon/pull/17722))
|
||||
- cld3 is very inaccurate on short-form content even with unique alphabets
|
||||
- Post language can be overriden individually using `language` param
|
||||
- Post language can be overridden individually using `language` param
|
||||
- Otherwise, it defaults to the user's interface language
|
||||
- Remove support for `OAUTH_REDIRECT_AT_SIGN_IN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17287))
|
||||
- Use `OMNIAUTH_ONLY` instead
|
||||
|
|
11
SECURITY.md
|
@ -1,6 +1,6 @@
|
|||
# Security Policy
|
||||
|
||||
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you should submit the report through our [Bug Bounty Program][bug-bounty]. Alternatively, you can reach us at <hello@joinmastodon.org>.
|
||||
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can reach us at <hello@joinmastodon.org>.
|
||||
|
||||
You should *not* report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
|
||||
|
||||
|
@ -11,10 +11,7 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
|||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| ------- | ----------|
|
||||
| 4.0.x | Yes |
|
||||
| 3.5.x | Yes |
|
||||
| 3.4.x | Yes |
|
||||
| 3.3.x | No |
|
||||
| < 3.3 | No |
|
||||
|
||||
[bug-bounty]: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||
| < 3.5 | No |
|
||||
|
|
|
@ -17,7 +17,7 @@ module Admin
|
|||
|
||||
@user.resend_confirmation_instructions
|
||||
|
||||
log_action :confirm, @user
|
||||
log_action :resend, @user
|
||||
|
||||
flash[:notice] = I18n.t('admin.accounts.resend_confirmation.success')
|
||||
redirect_to admin_accounts_path
|
||||
|
|
|
@ -133,7 +133,7 @@ class Api::BaseController < ApplicationController
|
|||
end
|
||||
|
||||
def disallow_unauthenticated_api_access?
|
||||
authorized_fetch_mode?
|
||||
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -8,7 +8,7 @@ class Api::V1::Accounts::PinsController < Api::BaseController
|
|||
before_action :set_account
|
||||
|
||||
def create
|
||||
AccountPin.create!(account: current_account, target_account: @account)
|
||||
AccountPin.find_or_create_by!(account: current_account, target_account: @account)
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||
end
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class Api::V1::FiltersController < Api::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
|
||||
params.permit(:phrase, :expires_in, :irreversible, context: [])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
|
|
|
@ -7,6 +7,10 @@ class Api::V1::ListsController < Api::BaseController
|
|||
before_action :require_user!
|
||||
before_action :set_list, except: [:index, :create]
|
||||
|
||||
rescue_from ArgumentError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
||||
def index
|
||||
@lists = List.where(account: current_account).all
|
||||
render json: @lists, each_serializer: REST::ListSerializer
|
||||
|
|
|
@ -79,7 +79,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
@status = Status.where(account: current_account).find(params[:id])
|
||||
authorize @status, :destroy?
|
||||
|
||||
@status.discard
|
||||
@status.discard_with_reblogs
|
||||
StatusPin.find_by(status: @status)&.destroy
|
||||
@status.account.statuses_count = @status.account.statuses_count - 1
|
||||
json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
|
||||
|
|
|
@ -24,7 +24,7 @@ class Api::V1::TagsController < Api::BaseController
|
|||
private
|
||||
|
||||
def set_or_create_tag
|
||||
return not_found unless /\A(#{Tag::HASHTAG_NAME_RE})\z/.match?(params[:id])
|
||||
return not_found unless Tag::HASHTAG_NAME_RE.match?(params[:id])
|
||||
@tag = Tag.find_normalized(params[:id]) || Tag.new(name: Tag.normalize(params[:id]), display_name: params[:id])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,7 +35,6 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
|||
def public_feed
|
||||
PublicFeed.new(
|
||||
current_account,
|
||||
locale: content_locale,
|
||||
local: truthy_param?(:local),
|
||||
remote: truthy_param?(:remote),
|
||||
only_media: truthy_param?(:only_media),
|
||||
|
|
|
@ -5,7 +5,7 @@ class Api::V1::Trends::TagsController < Api::BaseController
|
|||
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
DEFAULT_TAGS_LIMIT = 10
|
||||
DEFAULT_TAGS_LIMIT = (ENV['MAX_TRENDING_TAGS'] || 10).to_i
|
||||
|
||||
def index
|
||||
render json: @tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@tags, current_user&.account_id)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class Api::V2::MediaController < Api::V1::MediaController
|
||||
def create
|
||||
@media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params))
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: 202
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: @media_attachment.not_processed? ? 202 : 200
|
||||
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
||||
render json: file_type_error, status: 422
|
||||
rescue Paperclip::Error
|
||||
|
|
|
@ -18,7 +18,8 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
)
|
||||
|
||||
sign_in_and_redirect @user, event: :authentication
|
||||
set_flash_message(:notice, :success, kind: Devise.omniauth_configs[provider].strategy.display_name.capitalize) if is_navigational_format?
|
||||
label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
|
||||
set_flash_message(:notice, :success, kind: label) if is_navigational_format?
|
||||
else
|
||||
session["devise.#{provider}_data"] = request.env['omniauth.auth']
|
||||
redirect_to new_user_registration_url
|
||||
|
|
|
@ -23,7 +23,7 @@ class Settings::FeaturedTagsController < Settings::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
RemoveFeaturedTagWorker.perform_async(current_account.id, @featured_tag.id)
|
||||
RemoveFeaturedTagService.new.call(current_account, @featured_tag)
|
||||
redirect_to settings_featured_tags_path
|
||||
end
|
||||
|
||||
|
|
|
@ -4,15 +4,19 @@ module Admin::ActionLogsHelper
|
|||
def log_target(log)
|
||||
case log.target_type
|
||||
when 'Account'
|
||||
link_to log.human_identifier, admin_account_path(log.target_id)
|
||||
link_to (log.human_identifier.presence || I18n.t('admin.action_logs.deleted_account')), admin_account_path(log.target_id)
|
||||
when 'User'
|
||||
if log.route_param.present?
|
||||
link_to log.human_identifier, admin_account_path(log.route_param)
|
||||
else
|
||||
I18n.t('admin.action_logs.deleted_account')
|
||||
end
|
||||
when 'UserRole'
|
||||
link_to log.human_identifier, admin_roles_path(log.target_id)
|
||||
when 'Report'
|
||||
link_to "##{log.human_identifier}", admin_report_path(log.target_id)
|
||||
link_to "##{log.human_identifier.presence || log.target_id}", admin_report_path(log.target_id)
|
||||
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
|
||||
link_to log.human_identifier, "https://#{log.human_identifier}"
|
||||
link_to log.human_identifier, "https://#{log.human_identifier.presence}"
|
||||
when 'Status'
|
||||
link_to log.human_identifier, log.permalink
|
||||
when 'AccountWarning'
|
||||
|
@ -22,9 +26,13 @@ module Admin::ActionLogsHelper
|
|||
when 'IpBlock', 'Instance', 'CustomEmoji'
|
||||
log.human_identifier
|
||||
when 'CanonicalEmailBlock'
|
||||
content_tag(:samp, log.human_identifier[0...7], title: log.human_identifier)
|
||||
content_tag(:samp, (log.human_identifier.presence || '')[0...7], title: log.human_identifier)
|
||||
when 'Appeal'
|
||||
link_to log.human_identifier, disputes_strike_path(log.route_param)
|
||||
if log.route_param.present?
|
||||
link_to log.human_identifier, disputes_strike_path(log.route_param.presence)
|
||||
else
|
||||
I18n.t('admin.action_logs.deleted_account')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -204,7 +204,7 @@ module ApplicationHelper
|
|||
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
|
||||
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
|
||||
|
||||
if user_signed_in?
|
||||
if user_signed_in? && current_user.functional?
|
||||
state_params[:settings] = state_params[:settings].merge(Web::Setting.find_by(user: current_user)&.data || {})
|
||||
state_params[:push_subscription] = current_account.user.web_push_subscription(current_session)
|
||||
state_params[:current_account] = current_account
|
||||
|
@ -212,6 +212,11 @@ module ApplicationHelper
|
|||
state_params[:admin] = Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, ''))
|
||||
end
|
||||
|
||||
if user_signed_in? && !current_user.functional?
|
||||
state_params[:disabled_account] = current_account
|
||||
state_params[:moved_to_account] = current_account.moved_to_account
|
||||
end
|
||||
|
||||
if single_user_mode?
|
||||
state_params[:owner] = Account.local.without_suspended.where('id > 0').first
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
# rubocop:disable Metrics/ModuleLength, Style/WordArray
|
||||
|
||||
module LanguagesHelper
|
||||
ISO_639_1 = {
|
||||
|
@ -189,8 +190,13 @@ module LanguagesHelper
|
|||
ISO_639_3 = {
|
||||
ast: ['Asturian', 'Asturianu'].freeze,
|
||||
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
|
||||
jbo: ['Lojban', 'la .lojban.'].freeze,
|
||||
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
||||
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
|
||||
ldn: ['Láadan', 'Láadan'].freeze,
|
||||
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
|
||||
tok: ['Toki Pona', 'toki pona'].freeze,
|
||||
zba: ['Balaibalan', 'باليبلن'].freeze,
|
||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||
}.freeze
|
||||
|
||||
|
@ -259,3 +265,5 @@ module LanguagesHelper
|
|||
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable Metrics/ModuleLength, Style/WordArray
|
||||
|
|
|
@ -8,7 +8,7 @@ import { recoverHashtags } from 'flavours/glitch/utils/hashtag';
|
|||
import resizeImage from 'flavours/glitch/utils/resize_image';
|
||||
import { showAlert, showAlertForError } from './alerts';
|
||||
import { useEmoji } from './emojis';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||
import { openModal } from './modal';
|
||||
import { updateTimeline } from './timelines';
|
||||
|
||||
|
@ -223,6 +223,10 @@ export function submitCompose(routerHistory) {
|
|||
}
|
||||
};
|
||||
|
||||
if (statusId) {
|
||||
dispatch(importFetchedStatus({ ...response.data }));
|
||||
}
|
||||
|
||||
if (statusId === null) {
|
||||
insertIfOnline('home');
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ export default @injectIntl
|
|||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
size: PropTypes.number,
|
||||
account: ImmutablePropTypes.map,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
|
@ -41,6 +42,10 @@ class Account extends ImmutablePureComponent {
|
|||
onActionClick: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
size: 36,
|
||||
};
|
||||
|
||||
handleFollow = () => {
|
||||
this.props.onFollow(this.props.account);
|
||||
}
|
||||
|
@ -75,6 +80,7 @@ class Account extends ImmutablePureComponent {
|
|||
actionIcon,
|
||||
actionTitle,
|
||||
defaultAction,
|
||||
size,
|
||||
} = this.props;
|
||||
|
||||
if (!account) {
|
||||
|
@ -163,7 +169,7 @@ class Account extends ImmutablePureComponent {
|
|||
<div className='account'>
|
||||
<div className='account__wrapper'>
|
||||
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
|
||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||
<div className='account__avatar-wrapper'><Avatar account={account} size={size} /></div>
|
||||
{mute_expires_at}
|
||||
<DisplayName account={account} />
|
||||
</Permalink>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedNumber } from 'react-intl';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { reduceMotion } from 'flavours/glitch/initial_state';
|
||||
|
@ -51,7 +51,7 @@ export default class AnimatedNumber extends React.PureComponent {
|
|||
const { direction } = this.state;
|
||||
|
||||
if (reduceMotion) {
|
||||
return obfuscate ? obfuscatedCount(value) : <FormattedNumber value={value} />;
|
||||
return obfuscate ? obfuscatedCount(value) : <ShortNumber value={value} />;
|
||||
}
|
||||
|
||||
const styles = [{
|
||||
|
@ -65,7 +65,7 @@ export default class AnimatedNumber extends React.PureComponent {
|
|||
{items => (
|
||||
<span className='animated-number'>
|
||||
{items.map(({ key, data, style }) => (
|
||||
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <FormattedNumber value={data} />}</span>
|
||||
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
|
|
|
@ -63,7 +63,7 @@ class ColumnHeader extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleTitleClick = () => {
|
||||
this.props.onClick();
|
||||
this.props.onClick?.();
|
||||
}
|
||||
|
||||
handleMoveLeft = () => {
|
||||
|
@ -157,7 +157,6 @@ class ColumnHeader extends React.PureComponent {
|
|||
className={collapsibleButtonClassName}
|
||||
title={formatMessage(collapsed ? messages.show : messages.hide)}
|
||||
aria-label={formatMessage(collapsed ? messages.show : messages.hide)}
|
||||
aria-pressed={collapsed ? 'false' : 'true'}
|
||||
onClick={this.handleToggleClick}
|
||||
>
|
||||
<i className='icon-with-badge'>
|
||||
|
|
|
@ -18,7 +18,6 @@ export default class IconButton extends React.PureComponent {
|
|||
onKeyPress: PropTypes.func,
|
||||
size: PropTypes.number,
|
||||
active: PropTypes.bool,
|
||||
pressed: PropTypes.bool,
|
||||
expanded: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
activeStyle: PropTypes.object,
|
||||
|
@ -111,7 +110,6 @@ export default class IconButton extends React.PureComponent {
|
|||
icon,
|
||||
inverted,
|
||||
overlay,
|
||||
pressed,
|
||||
tabIndex,
|
||||
title,
|
||||
counter,
|
||||
|
@ -156,7 +154,6 @@ export default class IconButton extends React.PureComponent {
|
|||
return (
|
||||
<button
|
||||
aria-label={title}
|
||||
aria-pressed={pressed}
|
||||
aria-expanded={expanded}
|
||||
title={title}
|
||||
className={classes}
|
||||
|
|
|
@ -21,7 +21,12 @@ class NavigationPortal extends React.PureComponent {
|
|||
render () {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path='/@:acct/(tagged/:tagged?)?' component={AccountNavigation} />
|
||||
<Route path='/@:acct' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/with_replies' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/followers' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/following' exact component={AccountNavigation} />
|
||||
<Route path='/@:acct/media' exact component={AccountNavigation} />
|
||||
<Route component={DefaultNavigation} />
|
||||
</Switch>
|
||||
);
|
||||
|
|
|
@ -61,7 +61,7 @@ class ServerBanner extends React.PureComponent {
|
|||
<div className='server-banner__meta__column'>
|
||||
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
|
||||
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} />
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} />
|
||||
</div>
|
||||
|
||||
<div className='server-banner__meta__column'>
|
||||
|
|
|
@ -42,6 +42,7 @@ const messages = defineMessages({
|
|||
hide: { id: 'status.hide', defaultMessage: 'Hide toot' },
|
||||
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
||||
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
|
@ -183,21 +184,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
handleCopy = () => {
|
||||
const url = this.props.status.get('url');
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
textarea.textContent = url;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
navigator.clipboard.writeText(url);
|
||||
}
|
||||
|
||||
handleHideClick = () => {
|
||||
|
@ -216,6 +203,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
||||
|
||||
let menu = [];
|
||||
let reblogIcon = 'retweet';
|
||||
|
@ -225,6 +213,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
||||
|
||||
if (publicStatus) {
|
||||
if (isRemote) {
|
||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
|
||||
}
|
||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
}
|
||||
|
@ -315,10 +306,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
counter={showReplyCount ? status.get('replies_count') : undefined}
|
||||
obfuscateCount
|
||||
/>
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
{shareButton}
|
||||
<IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
|
||||
<IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
|
||||
|
||||
{filterButton}
|
||||
|
||||
|
|
|
@ -332,7 +332,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
>
|
||||
<span dangerouslySetInnerHTML={spoilerContent} className='translate' lang={lang} />
|
||||
{' '}
|
||||
<button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>
|
||||
<button type='button' className='status__content__spoiler-link' onClick={this.handleSpoilerClick} aria-expanded={!hidden}>
|
||||
{toggleText}
|
||||
</button>
|
||||
</p>
|
||||
|
|
|
@ -33,6 +33,7 @@ store.dispatch(fetchCustomEmojis());
|
|||
const createIdentityContext = state => ({
|
||||
signedIn: !!state.meta.me,
|
||||
accountId: state.meta.me,
|
||||
disabledAccountId: state.meta.disabled_account_id,
|
||||
accessToken: state.meta.access_token,
|
||||
permissions: state.role ? state.role.permissions : 0,
|
||||
});
|
||||
|
@ -47,6 +48,7 @@ export default class Mastodon extends React.PureComponent {
|
|||
identity: PropTypes.shape({
|
||||
signedIn: PropTypes.bool.isRequired,
|
||||
accountId: PropTypes.string,
|
||||
disabledAccountId: PropTypes.string,
|
||||
accessToken: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import configureStore from 'flavours/glitch/store/configureStore';
|
||||
import { hydrateStore } from 'flavours/glitch/actions/store';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from 'mastodon/locales';
|
||||
import PublicTimeline from 'flavours/glitch/features/standalone/public_timeline';
|
||||
import HashtagTimeline from 'flavours/glitch/features/standalone/hashtag_timeline';
|
||||
import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container';
|
||||
import initialState from 'flavours/glitch/initial_state';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
||||
const store = configureStore();
|
||||
|
||||
if (initialState) {
|
||||
store.dispatch(hydrateStore(initialState));
|
||||
}
|
||||
|
||||
export default class TimelineContainer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
hashtag: PropTypes.string,
|
||||
local: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
local: !initialState.settings.known_fediverse,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { locale, hashtag, local } = this.props;
|
||||
|
||||
let timeline;
|
||||
|
||||
if (hashtag) {
|
||||
timeline = <HashtagTimeline hashtag={hashtag} local={local} />;
|
||||
} else {
|
||||
timeline = <PublicTimeline local={local} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<Provider store={store}>
|
||||
<Fragment>
|
||||
{timeline}
|
||||
|
||||
{ReactDOM.createPortal(
|
||||
<ModalContainer />,
|
||||
document.getElementById('modal-container'),
|
||||
)}
|
||||
</Fragment>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -125,7 +125,7 @@ class About extends React.PureComponent {
|
|||
<div className='about__meta__column'>
|
||||
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
|
||||
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} />
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} />
|
||||
</div>
|
||||
|
||||
<hr className='about__meta__divider' />
|
||||
|
@ -209,6 +209,11 @@ class About extends React.PureComponent {
|
|||
</Section>
|
||||
|
||||
<LinkFooter />
|
||||
|
||||
<div className='about__footer'>
|
||||
<p><FormattedMessage id='about.fork_disclaimer' defaultMessage='Glitch-soc is free open source software forked from Mastodon.' /></p>
|
||||
<p><FormattedMessage id='about.disclaimer' defaultMessage='Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.' /></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Helmet>
|
||||
|
|
|
@ -53,6 +53,7 @@ const messages = defineMessages({
|
|||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
add_account_note: { id: 'account.add_account_note', defaultMessage: 'Add note for @{name}' },
|
||||
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
});
|
||||
|
||||
const titleFromAccount = account => {
|
||||
|
@ -97,6 +98,7 @@ class Header extends ImmutablePureComponent {
|
|||
onEditAccountNote: PropTypes.func.isRequired,
|
||||
onChangeLanguages: PropTypes.func.isRequired,
|
||||
onInteractionModal: PropTypes.func.isRequired,
|
||||
onOpenAvatar: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
|
@ -132,6 +134,13 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleAvatarClick = e => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onOpenAvatar();
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, hidden, intl, domain } = this.props;
|
||||
const { signedIn } = this.context.identity;
|
||||
|
@ -143,6 +152,8 @@ class Header extends ImmutablePureComponent {
|
|||
const accountNote = account.getIn(['relationship', 'note']);
|
||||
|
||||
const suspended = account.get('suspended');
|
||||
const isRemote = account.get('acct') !== account.get('username');
|
||||
const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
|
||||
|
||||
let info = [];
|
||||
let actionBtn = '';
|
||||
|
@ -199,6 +210,11 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push(null);
|
||||
}
|
||||
|
||||
if (isRemote) {
|
||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if ('share' in navigator && !suspended) {
|
||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
||||
menu.push(null);
|
||||
|
@ -253,15 +269,13 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
|
||||
}
|
||||
|
||||
if (signedIn && account.get('acct') !== account.get('username')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
if (signedIn && isRemote) {
|
||||
menu.push(null);
|
||||
|
||||
if (account.getIn(['relationship', 'domain_blocking'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,7 +313,7 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
<div className='account__header__bar'>
|
||||
<div className='account__header__tabs'>
|
||||
<a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'>
|
||||
<a className='avatar' href={account.get('avatar')} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}>
|
||||
<Avatar account={suspended || hidden ? undefined : account} size={90} />
|
||||
</a>
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
onAddToList: PropTypes.func.isRequired,
|
||||
onChangeLanguages: PropTypes.func.isRequired,
|
||||
onInteractionModal: PropTypes.func.isRequired,
|
||||
onOpenAvatar: PropTypes.func.isRequired,
|
||||
hideTabs: PropTypes.bool,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
|
@ -102,6 +103,10 @@ export default class Header extends ImmutablePureComponent {
|
|||
this.props.onInteractionModal(this.props.account);
|
||||
}
|
||||
|
||||
handleOpenAvatar = () => {
|
||||
this.props.onOpenAvatar(this.props.account);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, hidden, hideTabs } = this.props;
|
||||
|
||||
|
@ -130,6 +135,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
onEditAccountNote={this.handleEditAccountNote}
|
||||
onChangeLanguages={this.handleChangeLanguages}
|
||||
onInteractionModal={this.handleInteractionModal}
|
||||
onOpenAvatar={this.handleOpenAvatar}
|
||||
domain={this.props.domain}
|
||||
hidden={hidden}
|
||||
/>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
|||
import { revealAccount } from 'flavours/glitch/actions/accounts';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Button from 'flavours/glitch/components/button';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
|
||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
||||
|
||||
|
@ -26,7 +27,7 @@ class LimitedAccountHint extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<div className='limited-account-hint'>
|
||||
<p><FormattedMessage id='limited_account_hint.title' defaultMessage='This profile has been hidden by the moderators of your server.' /></p>
|
||||
<p><FormattedMessage id='limited_account_hint.title' defaultMessage='This profile has been hidden by the moderators of {domain}.' values={{ domain }} /></p>
|
||||
<Button onClick={reveal}><FormattedMessage id='limited_account_hint.action' defaultMessage='Show profile anyway' /></Button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -61,6 +61,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
|
||||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
|
@ -159,6 +161,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
}));
|
||||
},
|
||||
|
||||
onOpenAvatar (account) {
|
||||
dispatch(openModal('IMAGE', {
|
||||
src: account.get('avatar'),
|
||||
alt: account.get('acct'),
|
||||
}));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
||||
|
|
|
@ -305,12 +305,12 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
const countText = this.getFulltextForCharacterCounting();
|
||||
|
||||
return (
|
||||
<div className='composer'>
|
||||
<div className='compose-form'>
|
||||
<WarningContainer />
|
||||
|
||||
<ReplyIndicatorContainer />
|
||||
|
||||
<div className={`composer--spoiler ${spoiler ? 'composer--spoiler--visible' : ''}`} ref={this.setRef}>
|
||||
<div className={`spoiler-input ${spoiler ? 'spoiler-input--visible' : ''}`} ref={this.setRef}>
|
||||
<AutosuggestInput
|
||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||
value={spoilerText}
|
||||
|
@ -352,7 +352,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
</div>
|
||||
</AutosuggestTextarea>
|
||||
|
||||
<div className='composer--options-wrapper'>
|
||||
<div className='compose-form__buttons-wrapper'>
|
||||
<OptionsContainer
|
||||
advancedOptions={advancedOptions}
|
||||
disabled={isSubmitting}
|
||||
|
@ -364,7 +364,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
|
||||
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
|
||||
/>
|
||||
<div className='compose--counter-wrapper'>
|
||||
<div className='character-counter__wrapper'>
|
||||
<CharacterCounter text={countText} max={maxChars} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,6 @@ import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
|
|||
export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
icon: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
|
@ -162,7 +161,6 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
// Rendering.
|
||||
render () {
|
||||
const {
|
||||
active,
|
||||
disabled,
|
||||
title,
|
||||
icon,
|
||||
|
@ -174,21 +172,18 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
closeOnChange,
|
||||
} = this.props;
|
||||
const { open, placement } = this.state;
|
||||
const computedClass = classNames('composer--options--dropdown', {
|
||||
active,
|
||||
open,
|
||||
top: placement === 'top',
|
||||
});
|
||||
|
||||
// The result.
|
||||
const active = value && items.findIndex(({ name }) => name === value) === (placement === 'bottom' ? 0 : (items.length - 1));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={computedClass}
|
||||
className={classNames('privacy-dropdown', placement, { active: open })}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
>
|
||||
<div className={classNames('privacy-dropdown__value', { active })}>
|
||||
<IconButton
|
||||
active={open || active}
|
||||
className='value'
|
||||
active={open}
|
||||
className='privacy-dropdown__value-icon'
|
||||
disabled={disabled}
|
||||
icon={icon}
|
||||
inverted
|
||||
|
@ -203,6 +198,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
}}
|
||||
title={title}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Overlay
|
||||
containerPadding={20}
|
||||
placement={placement}
|
||||
|
|
|
@ -156,7 +156,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
|||
|
||||
const active = (name === (this.props.value || this.state.value));
|
||||
|
||||
const computedClass = classNames('composer--options--dropdown--content--item', { active });
|
||||
const computedClass = classNames('privacy-dropdown__option', { active });
|
||||
|
||||
let contents = this.props.renderItemContents && this.props.renderItemContents(item, i);
|
||||
|
||||
|
@ -165,7 +165,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
|||
<React.Fragment>
|
||||
{icon && <Icon className='icon' fixedWidth id={icon} />}
|
||||
|
||||
<div className='content'>
|
||||
<div className='privacy-dropdown__option__content'>
|
||||
<strong>{text}</strong>
|
||||
{meta}
|
||||
</div>
|
||||
|
@ -218,7 +218,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
|||
// size will be used to determine the coordinate of the menu by
|
||||
// react-overlays
|
||||
<div
|
||||
className='composer--options--dropdown--content'
|
||||
className='privacy-dropdown__dropdown'
|
||||
ref={this.handleRef}
|
||||
role='listbox'
|
||||
style={{
|
||||
|
|
|
@ -51,6 +51,15 @@ class LanguageDropdownMenu extends React.PureComponent {
|
|||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
this.setState({ mounted: true });
|
||||
|
||||
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
|
||||
// to wait for a frame before focusing
|
||||
requestAnimationFrame(() => {
|
||||
if (this.node) {
|
||||
const element = this.node.querySelector('input[type="search"]');
|
||||
if (element) element.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
|
@ -226,7 +235,7 @@ class LanguageDropdownMenu extends React.PureComponent {
|
|||
// react-overlays
|
||||
<div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
||||
<div className='emoji-mart-search'>
|
||||
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
|
||||
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
|
||||
<button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ class ToggleOption extends ImmutablePureComponent {
|
|||
<React.Fragment>
|
||||
<Toggle checked={checked} onChange={this.handleChange} />
|
||||
|
||||
<div className='content'>
|
||||
<div className='privacy-dropdown__option__content'>
|
||||
<strong>{text}</strong>
|
||||
{meta}
|
||||
</div>
|
||||
|
@ -228,7 +228,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
|
||||
// The result.
|
||||
return (
|
||||
<div className='composer--options'>
|
||||
<div className='compose-form__buttons'>
|
||||
<input
|
||||
accept={acceptContentTypes}
|
||||
disabled={disabled || !allowMedia}
|
||||
|
@ -309,7 +309,6 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
)}
|
||||
<LanguageDropdown />
|
||||
<Dropdown
|
||||
active={advancedOptions && advancedOptions.some(value => !!value)}
|
||||
disabled={disabled || isEditing}
|
||||
icon='ellipsis-h'
|
||||
items={advancedOptions ? [
|
||||
|
|
|
@ -48,7 +48,7 @@ class Publisher extends ImmutablePureComponent {
|
|||
const { countText, disabled, intl, onSecondarySubmit, privacy, sideArm, isEditing } = this.props;
|
||||
|
||||
const diff = maxChars - length(countText || '');
|
||||
const computedClass = classNames('composer--publisher', {
|
||||
const computedClass = classNames('compose-form__publish', {
|
||||
disabled: disabled,
|
||||
over: diff < 0,
|
||||
});
|
||||
|
@ -72,6 +72,7 @@ class Publisher extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className={computedClass}>
|
||||
{sideArm && !isEditing && sideArm !== 'none' ? (
|
||||
<div className='compose-form__publish-button-wrapper'>
|
||||
<Button
|
||||
className='side_arm'
|
||||
disabled={disabled}
|
||||
|
@ -80,7 +81,9 @@ class Publisher extends ImmutablePureComponent {
|
|||
text={<Icon id={privacyIcons[sideArm]} />}
|
||||
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className='compose-form__publish-button-wrapper'>
|
||||
<Button
|
||||
className='primary'
|
||||
text={publishText}
|
||||
|
@ -89,6 +92,7 @@ class Publisher extends ImmutablePureComponent {
|
|||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -49,10 +49,10 @@ class ReplyIndicator extends ImmutablePureComponent {
|
|||
|
||||
// The result.
|
||||
return (
|
||||
<article className='composer--reply'>
|
||||
<header>
|
||||
<article className='reply-indicator'>
|
||||
<header className='reply-indicator__header'>
|
||||
<IconButton
|
||||
className='cancel'
|
||||
className='reply-indicator__cancel'
|
||||
icon='times'
|
||||
onClick={this.handleClick}
|
||||
title={intl.formatMessage(messages.cancel)}
|
||||
|
@ -66,7 +66,7 @@ class ReplyIndicator extends ImmutablePureComponent {
|
|||
)}
|
||||
</header>
|
||||
<div
|
||||
className='content translate'
|
||||
className='reply-indicator__content translate'
|
||||
dangerouslySetInnerHTML={{ __html: content || '' }}
|
||||
/>
|
||||
{attachments.size > 0 && (
|
||||
|
|
|
@ -38,7 +38,7 @@ class TextareaIcons extends ImmutablePureComponent {
|
|||
render () {
|
||||
const { advancedOptions, intl } = this.props;
|
||||
return (
|
||||
<div className='composer--textarea--icons'>
|
||||
<div className='compose-form__textarea-icons'>
|
||||
{advancedOptions ? iconMap.map(
|
||||
([key, icon, message]) => advancedOptions.get(key) ? (
|
||||
<span
|
||||
|
|
|
@ -18,7 +18,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
media: ImmutablePropTypes.map.isRequired,
|
||||
onUndo: PropTypes.func.isRequired,
|
||||
onOpenFocalPoint: PropTypes.func.isRequired,
|
||||
isEditingStatus: PropTypes.func.isRequired,
|
||||
isEditingStatus: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
handleUndoClick = e => {
|
||||
|
@ -39,17 +39,17 @@ export default class Upload extends ImmutablePureComponent {
|
|||
const y = ((focusY / -2) + .5) * 100;
|
||||
|
||||
return (
|
||||
<div className='composer--upload_form--item' tabIndex='0' role='button'>
|
||||
<div className='compose-form__upload' tabIndex='0' role='button'>
|
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12, }) }}>
|
||||
{({ scale }) => (
|
||||
<div style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||
<div className='composer--upload_form--actions'>
|
||||
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||
<div className='compose-form__upload__actions'>
|
||||
<button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
|
||||
{!isEditingStatus && (<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)}
|
||||
</div>
|
||||
|
||||
{(media.get('description') || '').length === 0 && (
|
||||
<div className='composer--upload_form--item__warning'>
|
||||
<div className='compose-form__upload__warning'>
|
||||
<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -14,11 +14,11 @@ export default class UploadForm extends ImmutablePureComponent {
|
|||
const { mediaIds } = this.props;
|
||||
|
||||
return (
|
||||
<div className='composer--upload_form'>
|
||||
<div className='compose-form__upload-wrapper'>
|
||||
<UploadProgressContainer />
|
||||
|
||||
{mediaIds.size > 0 && (
|
||||
<div className='content'>
|
||||
<div className='compose-form__uploads-wrapper'>
|
||||
{mediaIds.map(id => (
|
||||
<UploadContainer id={id} key={id} />
|
||||
))}
|
||||
|
|
|
@ -29,17 +29,18 @@ export default class UploadProgress extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='composer--upload_form--progress'>
|
||||
<div className='upload-progress'>
|
||||
<div className='upload-progress__icon'>
|
||||
<Icon id='upload' />
|
||||
</div>
|
||||
|
||||
<div className='message'>
|
||||
<div className='upload-progress__message'>
|
||||
{message}
|
||||
|
||||
<div className='backdrop'>
|
||||
<div className='upload-progress__backdrop'>
|
||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
|
||||
{({ width }) =>
|
||||
(<div className='tracker' style={{ width: `${width}%` }}
|
||||
/>)
|
||||
<div className='upload-progress__tracker' style={{ width: `${width}%` }} />
|
||||
}
|
||||
</Motion>
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@ export default class Warning extends React.PureComponent {
|
|||
return (
|
||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
||||
{({ opacity, scaleX, scaleY }) => (
|
||||
<div className='composer--warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
|
||||
<div className='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -25,6 +25,7 @@ const messages = defineMessages({
|
|||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
|
||||
cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' },
|
||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||
|
@ -45,10 +46,7 @@ const makeMapStateToProps = () => {
|
|||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onFollow(account) {
|
||||
if (
|
||||
account.getIn(['relationship', 'following']) ||
|
||||
account.getIn(['relationship', 'requested'])
|
||||
) {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(
|
||||
openModal('CONFIRM', {
|
||||
|
@ -66,6 +64,16 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
|
||||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
}));
|
||||
} else {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
}
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ class HashtagTimeline extends React.PureComponent {
|
|||
const following = tag.get('following');
|
||||
|
||||
followButton = (
|
||||
<button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-pressed={following ? 'true' : 'false'}>
|
||||
<button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}>
|
||||
<Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' />
|
||||
</button>
|
||||
);
|
||||
|
|
|
@ -131,7 +131,6 @@ class HomeTimeline extends React.PureComponent {
|
|||
className={classNames('column-header__button', { 'active': showAnnouncements })}
|
||||
title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
|
||||
aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
|
||||
aria-pressed={showAnnouncements ? 'true' : 'false'}
|
||||
onClick={this.handleToggleAnnouncementsClick}
|
||||
>
|
||||
<IconWithBadge id='bullhorn' count={unreadAnnouncements} />
|
||||
|
|
|
@ -150,7 +150,7 @@ class InteractionModal extends React.PureComponent {
|
|||
|
||||
<div className='interaction-modal__choices__choice'>
|
||||
<h3><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h3>
|
||||
<p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Simply copy and paste this URL into the search bar of your favourite app or the web interface where you are signed in.' /></p>
|
||||
<p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.' /></p>
|
||||
<Copypaste value={url} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
onRequestNotificationPermission: PropTypes.func,
|
||||
alertsEnabled: PropTypes.bool,
|
||||
browserSupport: PropTypes.bool,
|
||||
browserPermission: PropTypes.bool,
|
||||
browserPermission: PropTypes.string,
|
||||
};
|
||||
|
||||
onPushChange = (path, checked) => {
|
||||
|
|
|
@ -64,7 +64,7 @@ const mapStateToProps = state => ({
|
|||
showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
|
||||
notifications: getNotifications(state),
|
||||
localSettings: state.get('local_settings'),
|
||||
isLoading: state.getIn(['notifications', 'isLoading'], true),
|
||||
isLoading: state.getIn(['notifications', 'isLoading'], 0) > 0,
|
||||
isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
|
||||
hasMore: state.getIn(['notifications', 'hasMore']),
|
||||
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
|
||||
|
|
|
@ -207,8 +207,8 @@ class Footer extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='picture-in-picture__footer'>
|
||||
{replyButton}
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
||||
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={status.get('url')} />}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -35,6 +35,7 @@ const messages = defineMessages({
|
|||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
|
||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
|
@ -133,21 +134,7 @@ class ActionBar extends React.PureComponent {
|
|||
|
||||
handleCopy = () => {
|
||||
const url = this.props.status.get('url');
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
textarea.textContent = url;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
navigator.clipboard.writeText(url);
|
||||
}
|
||||
|
||||
render () {
|
||||
|
@ -158,10 +145,15 @@ class ActionBar extends React.PureComponent {
|
|||
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
|
||||
const mutingConversation = status.get('muted');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
||||
|
||||
let menu = [];
|
||||
|
||||
if (publicStatus) {
|
||||
if (isRemote) {
|
||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
menu.push(null);
|
||||
|
|
|
@ -648,7 +648,7 @@ class Status extends ImmutablePureComponent {
|
|||
showBackButton
|
||||
multiColumn={multiColumn}
|
||||
extraButton={(
|
||||
<button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={!isExpanded ? 'false' : 'true'}><Icon id={!isExpanded ? 'eye-slash' : 'eye'} /></button>
|
||||
<button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll}><Icon id={!isExpanded ? 'eye-slash' : 'eye'} /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge, transparent,
|
|||
return (
|
||||
<a href='#' onClick={onClick && handleOnClick} className={className} title={text} {...other} tabIndex='0'>
|
||||
{iconElement}
|
||||
{text}
|
||||
<span>{text}</span>
|
||||
{badgeElement}
|
||||
</a>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { disabledAccountId, movedToAccountId, domain } from 'flavours/glitch/initial_state';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { logOut } from 'flavours/glitch/utils/log_out';
|
||||
|
||||
const messages = defineMessages({
|
||||
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
|
||||
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']),
|
||||
movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onLogout () {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.logoutMessage),
|
||||
confirm: intl.formatMessage(messages.logoutConfirm),
|
||||
closeWhenConfirm: false,
|
||||
onConfirm: () => logOut(),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class DisabledAccountBanner extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
disabledAcct: PropTypes.string.isRequired,
|
||||
movedToAcct: PropTypes.string,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleLogOutClick = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.props.onLogout();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { disabledAcct, movedToAcct } = this.props;
|
||||
|
||||
const disabledAccountLink = (
|
||||
<Link to={`/@${disabledAcct}`}>
|
||||
{disabledAcct}@{domain}
|
||||
</Link>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='sign-in-banner'>
|
||||
<p>
|
||||
{movedToAcct ? (
|
||||
<FormattedMessage
|
||||
id='moved_to_account_banner.text'
|
||||
defaultMessage='Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.'
|
||||
values={{
|
||||
disabledAccount: disabledAccountLink,
|
||||
movedToAccount: <Link to={`/@${movedToAcct}`}>{movedToAcct.includes('@') ? movedToAcct : `${movedToAcct}@${domain}`}</Link>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='disabled_account_banner.text'
|
||||
defaultMessage='Your account {disabledAccount} is currently disabled.'
|
||||
values={{
|
||||
disabledAccount: disabledAccountLink,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<a href='/auth/edit' className='button button--block'>
|
||||
<FormattedMessage id='disabled_account_banner.account_settings' defaultMessage='Account settings' />
|
||||
</a>
|
||||
<button type='button' className='button button--block button-tertiary' onClick={this.handleLogOutClick}>
|
||||
<FormattedMessage id='confirmations.logout.confirm' defaultMessage='Log out' />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
|
@ -58,11 +58,11 @@ class FavouriteModal extends ImmutablePureComponent {
|
|||
const { status, intl } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal favourite-modal'>
|
||||
<div className='favourite-modal__container'>
|
||||
<div className='modal-root__modal boost-modal'>
|
||||
<div className='boost-modal__container'>
|
||||
<div className={classNames('status', `status-${status.get('visibility')}`, 'light')}>
|
||||
<div className='favourite-modal__status-header'>
|
||||
<div className='favourite-modal__status-time'>
|
||||
<div className='boost-modal__status-header'>
|
||||
<div className='boost-modal__status-time'>
|
||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<VisibilityIcon visibility={status.get('visibility')} />
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />
|
||||
|
@ -90,7 +90,7 @@ class FavouriteModal extends ImmutablePureComponent {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className='favourite-modal__action-bar'>
|
||||
<div className='boost-modal__action-bar'>
|
||||
<div><FormattedMessage id='favourite_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='star' /></span> }} /></div>
|
||||
<Button text={intl.formatMessage(messages.favourite)} onClick={this.handleFavourite} ref={this.setRef} />
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import IconButton from 'flavours/glitch/components/icon_button';
|
||||
import ImageLoader from './image_loader';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class ImageModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
alt: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
navigationHidden: false,
|
||||
};
|
||||
|
||||
toggleNavigation = () => {
|
||||
this.setState(prevState => ({
|
||||
navigationHidden: !prevState.navigationHidden,
|
||||
}));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, src, alt, onClose } = this.props;
|
||||
const { navigationHidden } = this.state;
|
||||
|
||||
const navigationClassName = classNames('media-modal__navigation', {
|
||||
'media-modal__navigation--hidden': navigationHidden,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal media-modal'>
|
||||
<div className='media-modal__closer' role='presentation' onClick={onClose} >
|
||||
<ImageLoader
|
||||
src={src}
|
||||
width={400}
|
||||
height={400}
|
||||
alt={alt}
|
||||
onClick={this.toggleNavigation}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={navigationClassName}>
|
||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { version, repository, source_url, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
|
||||
import { domain, version, source_url, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
|
||||
import { logOut } from 'flavours/glitch/utils/log_out';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
|
||||
|
@ -48,45 +48,44 @@ class LinkFooter extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const { signedIn, permissions } = this.context.identity;
|
||||
const items = [];
|
||||
|
||||
items.push(<a key='apps' href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Get the app' /></a>);
|
||||
items.push(<Link key='about' to='/about'><FormattedMessage id='navigation_bar.info' defaultMessage='About' /></Link>);
|
||||
items.push(<a key='mastodon' href='https://joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.what_is_mastodon' defaultMessage='About Mastodon' /></a>);
|
||||
items.push(<a key='docs' href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a>);
|
||||
items.push(<Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></Link>);
|
||||
items.push(<Link key='hotkeys' to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link>);
|
||||
|
||||
if (profileDirectory) {
|
||||
items.push(<Link key='directory' to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Directory' /></Link>);
|
||||
}
|
||||
|
||||
if (signedIn) {
|
||||
if ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) {
|
||||
items.push(<a key='invites' href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a>);
|
||||
}
|
||||
|
||||
items.push(<a key='security' href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a>);
|
||||
items.push(<a key='logout' href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a>);
|
||||
}
|
||||
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
|
||||
const canProfileDirectory = profileDirectory;
|
||||
|
||||
return (
|
||||
<div className='getting-started__footer'>
|
||||
<ul>
|
||||
{items.map((item, index, array) => (
|
||||
<li>{item} { index === array.length - 1 ? null : ' · ' }</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className='link-footer'>
|
||||
<p>
|
||||
<strong>{domain}</strong>:
|
||||
{' '}
|
||||
<Link key='about' to='/about'><FormattedMessage id='footer.about' defaultMessage='About' /></Link>
|
||||
{canInvite && (
|
||||
<>
|
||||
{' · '}
|
||||
<a key='invites' href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a>
|
||||
</>
|
||||
)}
|
||||
{canProfileDirectory && (
|
||||
<>
|
||||
{' · '}
|
||||
<Link key='directory' to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link>
|
||||
</>
|
||||
)}
|
||||
{' · '}
|
||||
<Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='getting_started.open_source_notice.queer_af'
|
||||
defaultMessage='queer.af is open source software, running on a fork of {Glitchsoc} and {Mastodon}. You can contribute or report issues at {github}.'
|
||||
values={{
|
||||
github: <span><a href={source_url} rel='noopener noreferrer' target='_blank'>{repository}</a> (v{version})</span>,
|
||||
Glitchsoc: <span><a href='https://github.com/glitch-soc/mastodon' rel='noopener noreferrer' target='_blank'>glitch-soc</a></span>,
|
||||
Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener noreferrer' target='_blank'>mastodon</a> }}
|
||||
/>
|
||||
<strong>Mastodon, queer.af edition</strong>:
|
||||
{' '}
|
||||
<a href='https://joinmastodon.org' target='_blank'><FormattedMessage id='footer.about' defaultMessage='About' /></a>
|
||||
{' · '}
|
||||
<a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='footer.get_app' defaultMessage='Get the app' /></a>
|
||||
{' · '}
|
||||
<Link to='/keyboard-shortcuts'><FormattedMessage id='footer.keyboard_shortcuts' defaultMessage='Keyboard shortcuts' /></Link>
|
||||
{' · '}
|
||||
<a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a>
|
||||
{' · '}
|
||||
v{version}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -15,6 +15,7 @@ import DoodleModal from './doodle_modal';
|
|||
import ConfirmationModal from './confirmation_modal';
|
||||
import FocalPointModal from './focal_point_modal';
|
||||
import DeprecatedSettingsModal from './deprecated_settings_modal';
|
||||
import ImageModal from './image_modal';
|
||||
import {
|
||||
OnboardingModal,
|
||||
MuteModal,
|
||||
|
@ -38,6 +39,7 @@ const MODAL_COMPONENTS = {
|
|||
'ONBOARDING': OnboardingModal,
|
||||
'VIDEO': () => Promise.resolve({ default: VideoModal }),
|
||||
'AUDIO': () => Promise.resolve({ default: AudioModal }),
|
||||
'IMAGE': () => Promise.resolve({ default: ImageModal }),
|
||||
'BOOST': () => Promise.resolve({ default: BoostModal }),
|
||||
'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }),
|
||||
'DOODLE': () => Promise.resolve({ default: DoodleModal }),
|
||||
|
|
|
@ -4,6 +4,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { timelinePreview, showTrends } from 'flavours/glitch/initial_state';
|
||||
import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
|
||||
import DisabledAccountBanner from './disabled_account_banner';
|
||||
import FollowRequestsColumnLink from './follow_requests_column_link';
|
||||
import ListPanel from './list_panel';
|
||||
import NotificationsCounterIcon from './notifications_counter_icon';
|
||||
|
@ -42,7 +43,7 @@ class NavigationPanel extends React.Component {
|
|||
|
||||
render() {
|
||||
const { intl, onOpenSettings } = this.props;
|
||||
const { signedIn } = this.context.identity;
|
||||
const { signedIn, disabledAccountId } = this.context.identity;
|
||||
|
||||
return (
|
||||
<div className='navigation-panel'>
|
||||
|
@ -70,7 +71,7 @@ class NavigationPanel extends React.Component {
|
|||
{!signedIn && (
|
||||
<div className='navigation-panel__sign-in-banner'>
|
||||
<hr />
|
||||
<SignInBanner />
|
||||
{ disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner /> }
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
* @property {boolean} crop_images
|
||||
* @property {boolean=} delete_modal
|
||||
* @property {boolean=} disable_swiping
|
||||
* @property {string=} disabled_account_id
|
||||
* @property {boolean} display_media
|
||||
* @property {string} domain
|
||||
* @property {boolean=} expand_spoilers
|
||||
|
@ -61,6 +62,7 @@
|
|||
* @property {string} locale
|
||||
* @property {string | null} mascot
|
||||
* @property {string=} me
|
||||
* @property {string=} moved_to_account_id
|
||||
* @property {string=} owner
|
||||
* @property {boolean} profile_directory
|
||||
* @property {boolean} registrations_open
|
||||
|
@ -111,6 +113,7 @@ export const boostModal = getMeta('boost_modal');
|
|||
export const cropImages = getMeta('crop_images');
|
||||
export const deleteModal = getMeta('delete_modal');
|
||||
export const disableSwiping = getMeta('disable_swiping');
|
||||
export const disabledAccountId = getMeta('disabled_account_id');
|
||||
export const displayMedia = getMeta('display_media');
|
||||
export const domain = getMeta('domain');
|
||||
export const expandSpoilers = getMeta('expand_spoilers');
|
||||
|
@ -118,6 +121,7 @@ export const forceSingleColumn = !getMeta('advanced_layout');
|
|||
export const limitedFederationMode = getMeta('limited_federation_mode');
|
||||
export const mascot = getMeta('mascot');
|
||||
export const me = getMeta('me');
|
||||
export const movedToAccountId = getMeta('moved_to_account_id');
|
||||
export const owner = getMeta('owner');
|
||||
export const profile_directory = getMeta('profile_directory');
|
||||
export const reduceMotion = getMeta('reduce_motion');
|
||||
|
|
|
@ -21,3 +21,12 @@ es:
|
|||
skins:
|
||||
glitch:
|
||||
default: Predeterminado
|
||||
|
||||
ja:
|
||||
flavours:
|
||||
glitch:
|
||||
description: GlitchSocインスタンスのデフォルトフレーバーです。
|
||||
name: Glitch Edition
|
||||
skins:
|
||||
glitch:
|
||||
default: デフォルト
|
||||
|
|
|
@ -573,6 +573,7 @@ export default function compose(state = initialState, action) {
|
|||
'advanced_options',
|
||||
map => map.merge(new ImmutableMap({ do_not_federate }))
|
||||
);
|
||||
map.set('id', null);
|
||||
|
||||
if (action.status.get('spoiler_text').length > 0) {
|
||||
map.set('spoiler', true);
|
||||
|
|
|
@ -52,20 +52,26 @@ const initialState = ImmutableMap({
|
|||
markNewForDelete: false,
|
||||
});
|
||||
|
||||
const notificationToMap = (state, notification) => ImmutableMap({
|
||||
const notificationToMap = (notification, markForDelete) => ImmutableMap({
|
||||
id: notification.id,
|
||||
type: notification.type,
|
||||
account: notification.account.id,
|
||||
markedForDelete: state.get('markNewForDelete'),
|
||||
markedForDelete: markForDelete,
|
||||
status: notification.status ? notification.status.id : null,
|
||||
report: notification.report ? fromJS(notification.report) : null,
|
||||
});
|
||||
|
||||
const normalizeNotification = (state, notification, usePendingItems) => {
|
||||
const markNewForDelete = state.get('markNewForDelete');
|
||||
const top = state.get('top');
|
||||
|
||||
// Under currently unknown conditions, the client may receive duplicates from the server
|
||||
if (state.get('pendingItems').some((item) => item?.get('id') === notification.id) || state.get('items').some((item) => item?.get('id') === notification.id)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (usePendingItems || !state.get('pendingItems').isEmpty()) {
|
||||
return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification))).update('unread', unread => unread + 1);
|
||||
return state.update('pendingItems', list => list.unshift(notificationToMap(notification, markNewForDelete))).update('unread', unread => unread + 1);
|
||||
}
|
||||
|
||||
if (shouldCountUnreadNotifications(state)) {
|
||||
|
@ -79,32 +85,79 @@ const normalizeNotification = (state, notification, usePendingItems) => {
|
|||
list = list.take(20);
|
||||
}
|
||||
|
||||
return list.unshift(notificationToMap(state, notification));
|
||||
return list.unshift(notificationToMap(notification, markNewForDelete));
|
||||
});
|
||||
};
|
||||
|
||||
const expandNormalizedNotifications = (state, notifications, next, isLoadingRecent, usePendingItems) => {
|
||||
const lastReadId = state.get('lastReadId');
|
||||
let items = ImmutableList();
|
||||
const expandNormalizedNotifications = (state, notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) => {
|
||||
// This method is pretty tricky because:
|
||||
// - existing notifications might be out of order
|
||||
// - the existing notifications may have gaps, most often explicitly noted with a `null` item
|
||||
// - ideally, we don't want it to reorder existing items
|
||||
// - `notifications` may include items that are already included
|
||||
// - this function can be called either to fill in a gap, or load newer items
|
||||
|
||||
notifications.forEach((n, i) => {
|
||||
items = items.set(i, notificationToMap(state, n));
|
||||
});
|
||||
const markNewForDelete = state.get('markNewForDelete');
|
||||
const lastReadId = state.get('lastReadId');
|
||||
const newItems = ImmutableList(notifications.map((notification) => notificationToMap(notification, markNewForDelete)));
|
||||
|
||||
return state.withMutations(mutable => {
|
||||
if (!items.isEmpty()) {
|
||||
if (!newItems.isEmpty()) {
|
||||
usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('pendingItems').isEmpty());
|
||||
|
||||
mutable.update(usePendingItems ? 'pendingItems' : 'items', list => {
|
||||
const lastIndex = 1 + list.findLastIndex(
|
||||
item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id')),
|
||||
);
|
||||
mutable.update(usePendingItems ? 'pendingItems' : 'items', oldItems => {
|
||||
// If called to poll *new* notifications, we just need to add them on top without duplicates
|
||||
if (isLoadingRecent) {
|
||||
const idsToCheck = oldItems.map(item => item?.get('id')).toSet();
|
||||
const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
|
||||
return insertedItems.concat(oldItems);
|
||||
}
|
||||
|
||||
const firstIndex = 1 + list.take(lastIndex).findLastIndex(
|
||||
item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0,
|
||||
);
|
||||
// If called to expand more (presumably older than any known to the WebUI), we just have to
|
||||
// add them to the bottom without duplicates
|
||||
if (isLoadingMore) {
|
||||
const idsToCheck = oldItems.map(item => item?.get('id')).toSet();
|
||||
const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
|
||||
return oldItems.concat(insertedItems);
|
||||
}
|
||||
|
||||
return list.take(firstIndex).concat(items, list.skip(lastIndex));
|
||||
// Now this gets tricky, as we don't necessarily know for sure where the gap to fill is,
|
||||
// and some items in the timeline may not be properly ordered.
|
||||
|
||||
// However, we know that `newItems.last()` is the oldest item that was requested and that
|
||||
// there is no “hole” between `newItems.last()` and `newItems.first()`.
|
||||
|
||||
// First, find the furthest (if properly sorted, oldest) item in the notifications that is
|
||||
// newer than the oldest fetched one, as it's most likely that it delimits the gap.
|
||||
// Start the gap *after* that item.
|
||||
const lastIndex = oldItems.findLastIndex(item => item !== null && compareId(item.get('id'), newItems.last().get('id')) >= 0) + 1;
|
||||
|
||||
// Then, try to find the furthest (if properly sorted, oldest) item in the notifications that
|
||||
// is newer than the most recent fetched one, as it delimits a section comprised of only
|
||||
// items older or within `newItems` (or that were deleted from the server, so should be removed
|
||||
// anyway).
|
||||
// Stop the gap *after* that item.
|
||||
const firstIndex = oldItems.take(lastIndex).findLastIndex(item => item !== null && compareId(item.get('id'), newItems.first().get('id')) > 0) + 1;
|
||||
|
||||
// At this point:
|
||||
// - no `oldItems` after `firstIndex` is newer than any of the `newItems`
|
||||
// - all `oldItems` after `lastIndex` are older than every of the `newItems`
|
||||
// - it is possible for items in the replaced slice to be older than every `newItems`
|
||||
// - it is possible for items before `firstIndex` to be in the `newItems` range
|
||||
// Therefore:
|
||||
// - to avoid losing items, items from the replaced slice that are older than `newItems`
|
||||
// should be added in the back.
|
||||
// - to avoid duplicates, `newItems` should be checked the first `firstIndex` items of
|
||||
// `oldItems`
|
||||
const idsToCheck = oldItems.take(firstIndex).map(item => item?.get('id')).toSet();
|
||||
const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
|
||||
const olderItems = oldItems.slice(firstIndex, lastIndex).filter(item => item !== null && compareId(item.get('id'), newItems.last().get('id')) < 0);
|
||||
|
||||
return oldItems.take(firstIndex).concat(
|
||||
insertedItems,
|
||||
olderItems,
|
||||
oldItems.skip(lastIndex),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -115,7 +168,7 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece
|
|||
if (shouldCountUnreadNotifications(state)) {
|
||||
mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), lastReadId) > 0));
|
||||
} else {
|
||||
const mostRecent = items.find(item => item !== null);
|
||||
const mostRecent = newItems.find(item => item !== null);
|
||||
if (mostRecent && compareId(lastReadId, mostRecent.get('id')) < 0) {
|
||||
mutable.set('lastReadId', mostRecent.get('id'));
|
||||
}
|
||||
|
@ -264,7 +317,7 @@ export default function notifications(state = initialState, action) {
|
|||
case NOTIFICATIONS_UPDATE:
|
||||
return normalizeNotification(state, action.notification, action.usePendingItems);
|
||||
case NOTIFICATIONS_EXPAND_SUCCESS:
|
||||
return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingRecent, action.usePendingItems);
|
||||
return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingMore, action.isLoadingRecent, action.usePendingItems);
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
return filterNotifications(state, [action.relationship.id]);
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
|
|
|
@ -29,22 +29,22 @@ $emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange'
|
|||
|
||||
.hicolor-privacy-icons {
|
||||
.status__visibility-icon.fa-globe,
|
||||
.composer--options--dropdown--content--item .fa-globe {
|
||||
.privacy-dropdown__option .fa-globe {
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.status__visibility-icon.fa-unlock,
|
||||
.composer--options--dropdown--content--item .fa-unlock {
|
||||
.privacy-dropdown__option .fa-unlock {
|
||||
color: #388e3c;
|
||||
}
|
||||
|
||||
.status__visibility-icon.fa-lock,
|
||||
.composer--options--dropdown--content--item .fa-lock {
|
||||
.privacy-dropdown__option .fa-lock {
|
||||
color: #ffa000;
|
||||
}
|
||||
|
||||
.status__visibility-icon.fa-envelope,
|
||||
.composer--options--dropdown--content--item .fa-envelope {
|
||||
.privacy-dropdown__option .fa-envelope {
|
||||
color: #d32f2f;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,7 +204,8 @@
|
|||
}
|
||||
|
||||
.account-role,
|
||||
.simple_form .recommended {
|
||||
.simple_form .recommended,
|
||||
.simple_form .not_recommended {
|
||||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
cursor: default;
|
||||
|
@ -229,6 +230,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.simple_form .not_recommended {
|
||||
color: lighten($error-red, 12%);
|
||||
background-color: rgba(lighten($error-red, 12%), 0.1);
|
||||
border-color: rgba(lighten($error-red, 12%), 0.5);
|
||||
}
|
||||
|
||||
.account__header__fields {
|
||||
max-width: 100vw;
|
||||
padding: 0;
|
||||
|
|
|
@ -30,6 +30,34 @@
|
|||
}
|
||||
}
|
||||
|
||||
.link-footer {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
z-index: 1;
|
||||
font-size: 13px;
|
||||
|
||||
p {
|
||||
color: $dark-text-color;
|
||||
margin-bottom: 20px;
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-text-color;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.about {
|
||||
padding: 20px;
|
||||
|
||||
|
@ -37,6 +65,14 @@
|
|||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
color: $dark-text-color;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
margin-bottom: 30px;
|
||||
|
||||
|
@ -157,7 +193,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.getting-started__footer {
|
||||
.link-footer {
|
||||
padding: 0;
|
||||
margin-top: 60px;
|
||||
text-align: center;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.composer {
|
||||
.compose-form {
|
||||
padding: 10px;
|
||||
|
||||
.emoji-picker-dropdown {
|
||||
|
@ -25,16 +25,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.no-reduce-motion .composer--spoiler {
|
||||
.no-reduce-motion .spoiler-input {
|
||||
transition: height 0.4s ease, opacity 0.4s ease;
|
||||
}
|
||||
|
||||
.composer--spoiler {
|
||||
.spoiler-input {
|
||||
height: 0;
|
||||
transform-origin: bottom;
|
||||
opacity: 0.0;
|
||||
|
||||
&.composer--spoiler--visible {
|
||||
&.spoiler-input--visible {
|
||||
height: 36px;
|
||||
margin-bottom: 11px;
|
||||
opacity: 1.0;
|
||||
|
@ -64,7 +64,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.composer--warning {
|
||||
.compose-form__warning {
|
||||
color: $inverted-text-color;
|
||||
margin-bottom: 15px;
|
||||
background: $ui-primary-color;
|
||||
|
@ -123,7 +123,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.composer--reply {
|
||||
.reply-indicator {
|
||||
margin: 0 0 10px;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
|
@ -131,20 +131,21 @@
|
|||
min-height: 23px;
|
||||
overflow-y: auto;
|
||||
flex: 0 2 auto;
|
||||
}
|
||||
|
||||
& > header {
|
||||
.reply-indicator__header {
|
||||
margin-bottom: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
& > .account.small { color: $inverted-text-color; }
|
||||
}
|
||||
|
||||
& > .cancel {
|
||||
.reply-indicator__cancel {
|
||||
float: right;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
& > .content {
|
||||
.reply-indicator__content {
|
||||
position: relative;
|
||||
margin: 10px 0;
|
||||
padding: 0 12px;
|
||||
|
@ -244,7 +245,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emojione {
|
||||
width: 20px;
|
||||
|
@ -253,8 +253,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.compose-form__autosuggest-wrapper,
|
||||
.autosuggest-input {
|
||||
.compose-form .compose-form__autosuggest-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.compose-form .autosuggest-textarea,
|
||||
.compose-form .autosuggest-input {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
|
@ -284,10 +288,6 @@
|
|||
all: unset;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: $ui-secondary-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
@ -304,7 +304,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.composer--textarea--icons {
|
||||
.compose-form__textarea-icons {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 29px;
|
||||
|
@ -401,10 +401,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.composer--upload_form {
|
||||
.compose-form__upload-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& > .content {
|
||||
.compose-form__uploads-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
@ -412,14 +413,13 @@
|
|||
padding: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.composer--upload_form--item {
|
||||
.compose-form__upload {
|
||||
flex: 1 1 0;
|
||||
margin: 5px;
|
||||
min-width: 40%;
|
||||
|
||||
& > div {
|
||||
.compose-form__upload-thumbnail {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
height: 140px;
|
||||
|
@ -459,43 +459,46 @@
|
|||
}
|
||||
}
|
||||
|
||||
.composer--upload_form--actions {
|
||||
.compose-form__upload__actions {
|
||||
background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.composer--upload_form--progress {
|
||||
.upload-progress {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
color: $darker-text-color;
|
||||
overflow: hidden;
|
||||
|
||||
& > .fa {
|
||||
.fa {
|
||||
font-size: 34px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
& > .message {
|
||||
flex: 1 1 auto;
|
||||
|
||||
& > span {
|
||||
span {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
& > .backdrop {
|
||||
.upload-progress__message {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.upload-progress__backdrop {
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: $ui-base-lighter-color;
|
||||
}
|
||||
|
||||
& > .tracker {
|
||||
.upload-progress__tracker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -503,9 +506,6 @@
|
|||
border-radius: 6px;
|
||||
background: $ui-highlight-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__modifiers {
|
||||
color: $inverted-text-color;
|
||||
|
@ -514,7 +514,7 @@
|
|||
background: $simple-background-color;
|
||||
}
|
||||
|
||||
.composer--options-wrapper {
|
||||
.compose-form__buttons-wrapper {
|
||||
padding: 10px;
|
||||
background: darken($simple-background-color, 8%);
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
@ -524,7 +524,7 @@
|
|||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.composer--options {
|
||||
.compose-form__buttons {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
|
||||
|
@ -551,30 +551,41 @@
|
|||
}
|
||||
}
|
||||
|
||||
.compose--counter-wrapper {
|
||||
.character-counter__wrapper {
|
||||
align-self: center;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.composer--options--dropdown {
|
||||
&.open {
|
||||
& > .value {
|
||||
.privacy-dropdown.active {
|
||||
.privacy-dropdown__value {
|
||||
background: $simple-background-color;
|
||||
border-radius: 4px 4px 0 0;
|
||||
box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1);
|
||||
color: $primary-text-color;
|
||||
background: $ui-highlight-color;
|
||||
|
||||
.icon-button {
|
||||
transition: none;
|
||||
}
|
||||
&.top {
|
||||
& > .value {
|
||||
border-radius: 0 0 4px 4px;
|
||||
box-shadow: 0 4px 4px rgba($base-shadow-color, 0.1);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $ui-highlight-color;
|
||||
|
||||
.icon-button {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.composer--options--dropdown--content {
|
||||
&.top .privacy-dropdown__value {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
.privacy-dropdown__dropdown {
|
||||
display: block;
|
||||
box-shadow: 2px 4px 6px rgba($base-shadow-color, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.privacy-dropdown__dropdown {
|
||||
position: absolute;
|
||||
border-radius: 4px;
|
||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||
|
@ -583,14 +594,14 @@
|
|||
transform-origin: 50% 0;
|
||||
}
|
||||
|
||||
.composer--options--dropdown--content--item {
|
||||
.privacy-dropdown__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
color: $inverted-text-color;
|
||||
cursor: pointer;
|
||||
|
||||
& > .content {
|
||||
.privacy-dropdown__option__content {
|
||||
flex: 1 1 auto;
|
||||
color: $lighter-text-color;
|
||||
|
||||
|
@ -608,7 +619,7 @@
|
|||
background: $ui-highlight-color;
|
||||
color: $primary-text-color;
|
||||
|
||||
& > .content {
|
||||
.privacy-dropdown__option__content {
|
||||
color: $primary-text-color;
|
||||
|
||||
strong { color: $primary-text-color }
|
||||
|
@ -618,31 +629,25 @@
|
|||
&.active:hover { background: lighten($ui-highlight-color, 4%) }
|
||||
}
|
||||
|
||||
.composer--publisher {
|
||||
padding-top: 10px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
.compose-form__publish {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
column-gap: 5px;
|
||||
|
||||
& > .primary {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
.compose-form__publish-button-wrapper {
|
||||
overflow: hidden;
|
||||
padding-top: 10px;
|
||||
|
||||
button {
|
||||
padding: 7px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& > .side_arm {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
padding: 7px 0;
|
||||
width: 36px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.over {
|
||||
& > .count { color: $warning-red }
|
||||
}
|
||||
}
|
||||
|
|
@ -132,6 +132,10 @@
|
|||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
&::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1044,43 +1044,6 @@
|
|||
color: $dark-text-color;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
z-index: 1;
|
||||
font-size: 13px;
|
||||
|
||||
ul {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $dark-text-color;
|
||||
margin-bottom: 20px;
|
||||
|
||||
a {
|
||||
color: $dark-text-color;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $darker-text-color;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__trends {
|
||||
flex: 0 1 auto;
|
||||
opacity: 1;
|
||||
|
@ -1775,7 +1738,7 @@ noscript {
|
|||
@import 'domains';
|
||||
@import 'status';
|
||||
@import 'modal';
|
||||
@import 'composer';
|
||||
@import 'compose_form';
|
||||
@import 'columns';
|
||||
@import 'regeneration_indicator';
|
||||
@import 'directory';
|
||||
|
|
|
@ -416,7 +416,6 @@
|
|||
}
|
||||
|
||||
.boost-modal,
|
||||
.favourite-modal,
|
||||
.confirmation-modal,
|
||||
.report-modal,
|
||||
.actions-modal,
|
||||
|
@ -461,7 +460,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.favourite-modal .status-direct {
|
||||
.boost-modal .status-direct {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
|
@ -478,8 +477,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.boost-modal__container,
|
||||
.favourite-modal__container {
|
||||
.boost-modal__container {
|
||||
overflow-x: scroll;
|
||||
padding: 10px;
|
||||
|
||||
|
@ -490,7 +488,6 @@
|
|||
}
|
||||
|
||||
.boost-modal__action-bar,
|
||||
.favourite-modal__action-bar,
|
||||
.confirmation-modal__action-bar,
|
||||
.mute-modal__action-bar,
|
||||
.block-modal__action-bar {
|
||||
|
@ -512,13 +509,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.boost-modal__status-header,
|
||||
.favourite-modal__status-header {
|
||||
.boost-modal__status-header {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.boost-modal__status-time,
|
||||
.favourite-modal__status-time {
|
||||
.boost-modal__status-time {
|
||||
float: right;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -1290,11 +1285,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.modal-root__container .composer--options--dropdown {
|
||||
.modal-root__container .privacy-dropdown {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.modal-root__container .composer--options--dropdown--content {
|
||||
.modal-root__container .privacy-dropdown__dropdown {
|
||||
pointer-events: auto;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,20 @@
|
|||
p {
|
||||
color: $darker-text-color;
|
||||
margin-bottom: 20px;
|
||||
|
||||
a {
|
||||
color: $secondary-text-color;
|
||||
text-decoration: none;
|
||||
unicode-bidi: isolate;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
|
||||
.fa {
|
||||
color: lighten($dark-text-color, 7%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
flex: 0 1 48px;
|
||||
}
|
||||
|
||||
.composer {
|
||||
.compose-form {
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
|
@ -59,10 +59,6 @@
|
|||
.autosuggest-textarea__textarea {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.compose-form__upload-thumbnail {
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-panel {
|
||||
|
|
|
@ -659,6 +659,7 @@
|
|||
display: inline-block;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
}
|
||||
|
||||
.compose-standalone {
|
||||
.composer {
|
||||
.compose-form {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 0;
|
||||
|
|
|
@ -1,36 +1,20 @@
|
|||
// components.scss
|
||||
.compose-form {
|
||||
.compose-form__modifiers {
|
||||
.compose-form__upload {
|
||||
&-description {
|
||||
input {
|
||||
&::placeholder {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status__content a,
|
||||
.reply-indicator__content a {
|
||||
color: lighten($ui-highlight-color, 12%);
|
||||
text-decoration: underline;
|
||||
|
||||
&.mention {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.mention span {
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.status__content__spoiler-link {
|
||||
color: $secondary-text-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.link-footer a,
|
||||
.reply-indicator__content a,
|
||||
.status__content__read-more-button {
|
||||
text-decoration: underline;
|
||||
|
||||
|
@ -39,31 +23,56 @@
|
|||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.getting-started__footer a {
|
||||
&.mention {
|
||||
text-decoration: none;
|
||||
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
span {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status__content a {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
|
||||
.nothing-here {
|
||||
color: $darker-text-color;
|
||||
}
|
||||
|
||||
.public-layout .public-account-header__tabs__tabs .counter.active::after {
|
||||
border-bottom: 4px solid $ui-highlight-color;
|
||||
}
|
||||
|
||||
.composer {
|
||||
.composer--spoiler input,
|
||||
.compose-form__autosuggest-wrapper textarea {
|
||||
&::placeholder {
|
||||
.compose-form__poll-wrapper .button.button-secondary,
|
||||
.compose-form .autosuggest-textarea__textarea::placeholder,
|
||||
.compose-form .spoiler-input__input::placeholder,
|
||||
.report-dialog-modal__textarea::placeholder,
|
||||
.language-dropdown__dropdown__results__item__common-name,
|
||||
.compose-form .icon-button {
|
||||
color: $inverted-text-color;
|
||||
}
|
||||
|
||||
.text-icon-button.active {
|
||||
color: $ui-highlight-color;
|
||||
}
|
||||
|
||||
.language-dropdown__dropdown__results__item.active {
|
||||
background: $ui-highlight-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.link-button:disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ $ui-highlight-color: $classic-highlight-color !default;
|
|||
$darker-text-color: lighten($ui-primary-color, 20%) !default;
|
||||
$dark-text-color: lighten($ui-primary-color, 12%) !default;
|
||||
$secondary-text-color: lighten($ui-secondary-color, 6%) !default;
|
||||
$highlight-text-color: lighten($ui-highlight-color, 8%) !default;
|
||||
$action-button-color: #8d9ac2;
|
||||
$highlight-text-color: lighten($ui-highlight-color, 10%) !default;
|
||||
$action-button-color: lighten($ui-base-color, 50%);
|
||||
|
||||
$inverted-text-color: $black !default;
|
||||
$lighter-text-color: darken($ui-base-color,6%) !default;
|
||||
|
|
|
@ -103,7 +103,8 @@ code {
|
|||
}
|
||||
}
|
||||
|
||||
.recommended {
|
||||
.recommended,
|
||||
.not_recommended {
|
||||
position: absolute;
|
||||
margin: 0 4px;
|
||||
margin-top: -2px;
|
||||
|
|
|
@ -1,161 +1,209 @@
|
|||
// Notes!
|
||||
// Sass color functions, "darken" and "lighten" are automatically replaced.
|
||||
|
||||
.glitch.local-settings {
|
||||
background: $ui-base-color;
|
||||
|
||||
&__navigation {
|
||||
background: darken($ui-base-color, 8%);
|
||||
html {
|
||||
scrollbar-color: $ui-base-color rgba($ui-base-color, 0.25);
|
||||
}
|
||||
|
||||
&__navigation__item {
|
||||
background: darken($ui-base-color, 8%);
|
||||
// Change the colors of button texts
|
||||
.button {
|
||||
color: $white;
|
||||
|
||||
&:hover {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
&.button-alternative-2 {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.notification__dismiss-overlay {
|
||||
.wrappy {
|
||||
box-shadow: unset;
|
||||
}
|
||||
.status-card__actions button,
|
||||
.status-card__actions a {
|
||||
color: rgba($white, 0.8);
|
||||
|
||||
.ckbox {
|
||||
text-shadow: unset;
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.status.status-direct {
|
||||
background: darken($ui-base-color, 8%);
|
||||
border-bottom-color: darken($ui-base-color, 12%);
|
||||
// Change default background colors of columns
|
||||
.column > .scrollable,
|
||||
.getting-started,
|
||||
.column-inline-form,
|
||||
.error-column,
|
||||
.regeneration-indicator {
|
||||
background: $white;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&.collapsed> .status__content:after {
|
||||
background: linear-gradient(rgba(darken($ui-base-color, 8%), 0), rgba(darken($ui-base-color, 8%), 1));
|
||||
.column > .scrollable.about {
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.about__meta,
|
||||
.about__section__title,
|
||||
.interaction-modal {
|
||||
background: $white;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.rules-list li::before {
|
||||
background: $ui-highlight-color;
|
||||
}
|
||||
|
||||
.directory__card__img {
|
||||
background: lighten($ui-base-color, 12%);
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
background: $white;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.column-back-button,
|
||||
.column-header {
|
||||
background: $white;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&--slim-button {
|
||||
top: -50px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.focusable:focus.status.status-direct {
|
||||
.column-header__back-button,
|
||||
.column-header__button,
|
||||
.column-header__button.active,
|
||||
.account__header {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.column-header__button.active {
|
||||
color: $ui-highlight-color;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $ui-highlight-color;
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.account__header__bar .avatar .account__avatar {
|
||||
border-color: $white;
|
||||
}
|
||||
|
||||
.getting-started__footer a {
|
||||
color: $ui-secondary-color;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.confirmation-modal__secondary-button,
|
||||
.confirmation-modal__cancel-button,
|
||||
.mute-modal__cancel-button,
|
||||
.block-modal__cancel-button {
|
||||
color: lighten($ui-base-color, 26%);
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.column-subheading {
|
||||
background: darken($ui-base-color, 4%);
|
||||
|
||||
&.collapsed> .status__content:after {
|
||||
background: linear-gradient(rgba(darken($ui-base-color, 4%), 0), rgba(darken($ui-base-color, 4%), 1));
|
||||
}
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
// Change columns' default background colors
|
||||
.column {
|
||||
> .scrollable {
|
||||
background: darken($ui-base-color, 13%);
|
||||
}
|
||||
}
|
||||
.getting-started,
|
||||
.scrollable {
|
||||
.column-link {
|
||||
background: $white;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
|
||||
.status.collapsed .status__content:after {
|
||||
background: linear-gradient(rgba(darken($ui-base-color, 13%), 0), rgba(darken($ui-base-color, 13%), 1));
|
||||
}
|
||||
|
||||
.drawer__inner {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.getting-started .navigation-bar {
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__autosuggest-wrapper,
|
||||
.poll__option input[type="text"],
|
||||
.compose-form .spoiler-input__input,
|
||||
.compose-form__poll-wrapper select,
|
||||
.search__input,
|
||||
.setting-text,
|
||||
.report-dialog-modal__textarea,
|
||||
.audio-player {
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.report-dialog-modal .dialog-option .poll__input {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.search__input {
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.list-editor .search .search__input {
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.compose-form__poll-wrapper select {
|
||||
background: $simple-background-color url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 8%))}'/></svg>") no-repeat right 8px center / auto 16px;
|
||||
}
|
||||
|
||||
.compose-form__poll-wrapper,
|
||||
.compose-form__poll-wrapper .poll__footer {
|
||||
border-top-color: lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.notification__filter-bar {
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.compose-form .compose-form__buttons-wrapper {
|
||||
background: $ui-base-color;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.drawer__header,
|
||||
.drawer__inner {
|
||||
background: $white;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.drawer__inner__mastodon {
|
||||
background: $ui-base-color url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color(darken($ui-base-color, 13%))}"/></svg>') no-repeat bottom / 100% auto !important;
|
||||
|
||||
.mastodon {
|
||||
filter: contrast(75%) brightness(75%) !important;
|
||||
}
|
||||
background: $white url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
|
||||
}
|
||||
|
||||
// Change the default appearance of the content warning button
|
||||
.status__content {
|
||||
|
||||
.status__content__spoiler-link {
|
||||
|
||||
background: lighten($ui-base-color, 30%);
|
||||
|
||||
&:hover {
|
||||
background: lighten($ui-base-color, 35%);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Change the background colors of media and video spoilers
|
||||
.media-spoiler,
|
||||
.video-player__spoiler,
|
||||
.account-gallery__item a {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
// Change the colors used in the dropdown menu
|
||||
.dropdown-menu {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
.dropdown-menu__arrow {
|
||||
|
||||
&.left {
|
||||
border-left-color: $ui-base-color;
|
||||
}
|
||||
|
||||
&.top {
|
||||
border-top-color: $ui-base-color;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
border-bottom-color: $ui-base-color;
|
||||
}
|
||||
|
||||
&.right {
|
||||
border-right-color: $ui-base-color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.dropdown-menu__item {
|
||||
a,
|
||||
button {
|
||||
background: $ui-base-color;
|
||||
color: $ui-secondary-color;
|
||||
}
|
||||
}
|
||||
|
||||
// Change the default color of several parts of the compose form
|
||||
.composer {
|
||||
|
||||
.composer--spoiler input, .compose-form__autosuggest-wrapper textarea {
|
||||
color: lighten($ui-base-color, 80%);
|
||||
|
||||
&:disabled { background: lighten($simple-background-color, 10%) }
|
||||
|
||||
&::placeholder {
|
||||
color: lighten($ui-base-color, 70%);
|
||||
}
|
||||
}
|
||||
|
||||
.composer--options-wrapper {
|
||||
background: lighten($ui-base-color, 10%);
|
||||
}
|
||||
|
||||
.composer--options > hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.composer--options--dropdown--content--item {
|
||||
color: $ui-primary-color;
|
||||
|
||||
strong {
|
||||
color: $ui-primary-color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.composer--upload_form--actions .icon-button {
|
||||
// Change the colors used in compose-form
|
||||
.compose-form {
|
||||
.compose-form__modifiers {
|
||||
.compose-form__upload__actions .icon-button {
|
||||
color: lighten($white, 7%);
|
||||
|
||||
&:active,
|
||||
|
@ -165,36 +213,38 @@
|
|||
}
|
||||
}
|
||||
|
||||
.language-dropdown__dropdown__results__item:hover,
|
||||
.language-dropdown__dropdown__results__item:focus,
|
||||
.language-dropdown__dropdown__results__item:active {
|
||||
background-color: $ui-base-color;
|
||||
.compose-form__upload-description input {
|
||||
color: lighten($white, 7%);
|
||||
|
||||
&::placeholder {
|
||||
color: lighten($white, 7%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu__separator,
|
||||
.dropdown-menu__item.edited-timestamp__history__item,
|
||||
.dropdown-menu__container__header,
|
||||
.compare-history-modal .report-modal__target,
|
||||
.report-dialog-modal .poll__option.dialog-option {
|
||||
border-bottom-color: lighten($ui-base-color, 12%);
|
||||
.compose-form__buttons-wrapper {
|
||||
background: darken($ui-base-color, 6%);
|
||||
}
|
||||
|
||||
.report-dialog-modal__container {
|
||||
border-bottom-color: lighten($ui-base-color, 12%);
|
||||
.autosuggest-textarea__suggestions {
|
||||
background: darken($ui-base-color, 6%);
|
||||
}
|
||||
|
||||
.status__content,
|
||||
.reply-indicator__content {
|
||||
a {
|
||||
color: $highlight-text-color;
|
||||
.autosuggest-textarea__suggestions__item {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&.selected {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-bar {
|
||||
border-color: darken($ui-base-color, 4%);
|
||||
border-color: lighten($ui-base-color, 4%);
|
||||
|
||||
&:first-child {
|
||||
background: lighten($ui-base-color, 10%);
|
||||
background: darken($ui-base-color, 6%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,35 +253,121 @@
|
|||
border-color: $ui-base-color;
|
||||
}
|
||||
|
||||
.autosuggest-textarea__suggestions {
|
||||
background: lighten($ui-base-color, 10%)
|
||||
// Change the background colors of statuses
|
||||
.focusable:focus {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
.autosuggest-textarea__suggestions__item {
|
||||
.detailed-status,
|
||||
.detailed-status__action-bar {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
// Change the background colors of status__content__spoiler-link
|
||||
.reply-indicator__content .status__content__spoiler-link,
|
||||
.status__content .status__content__spoiler-link {
|
||||
background: $ui-base-color;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&.selected {
|
||||
background: darken($ui-base-color, 4%);
|
||||
&:focus {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
}
|
||||
|
||||
.react-toggle-track {
|
||||
background: $ui-secondary-color;
|
||||
// Change the background colors of media and video spoilers
|
||||
.media-spoiler,
|
||||
.video-player__spoiler {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
|
||||
background: lighten($ui-secondary-color, 10%);
|
||||
.privacy-dropdown.active .privacy-dropdown__value.active .icon-button {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
|
||||
background: darken($ui-highlight-color, 10%);
|
||||
.account-gallery__item a {
|
||||
background-color: $ui-base-color;
|
||||
}
|
||||
|
||||
// Change the colors used in the dropdown menu
|
||||
.dropdown-menu {
|
||||
background: $white;
|
||||
|
||||
&__arrow {
|
||||
&.left {
|
||||
border-left-color: $white;
|
||||
}
|
||||
|
||||
&.top {
|
||||
border-top-color: $white;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
border-bottom-color: $white;
|
||||
}
|
||||
|
||||
&.right {
|
||||
border-right-color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
a,
|
||||
button {
|
||||
background: $white;
|
||||
color: $darker-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change the text colors on inverted background
|
||||
.privacy-dropdown__option.active,
|
||||
.privacy-dropdown__option:hover,
|
||||
.privacy-dropdown__option.active .privacy-dropdown__option__content,
|
||||
.privacy-dropdown__option.active .privacy-dropdown__option__content strong,
|
||||
.privacy-dropdown__option:hover .privacy-dropdown__option__content,
|
||||
.privacy-dropdown__option:hover .privacy-dropdown__option__content strong,
|
||||
.dropdown-menu__item a:active,
|
||||
.dropdown-menu__item a:focus,
|
||||
.dropdown-menu__item a:hover,
|
||||
.actions-modal ul li:not(:empty) a.active,
|
||||
.actions-modal ul li:not(:empty) a.active button,
|
||||
.actions-modal ul li:not(:empty) a:active,
|
||||
.actions-modal ul li:not(:empty) a:active button,
|
||||
.actions-modal ul li:not(:empty) a:focus,
|
||||
.actions-modal ul li:not(:empty) a:focus button,
|
||||
.actions-modal ul li:not(:empty) a:hover,
|
||||
.actions-modal ul li:not(:empty) a:hover button,
|
||||
.language-dropdown__dropdown__results__item.active,
|
||||
.admin-wrapper .sidebar ul .simple-navigation-active-leaf a,
|
||||
.simple_form .block-button,
|
||||
.simple_form .button,
|
||||
.simple_form button {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.language-dropdown__dropdown__results__item .language-dropdown__dropdown__results__item__common-name {
|
||||
color: lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.language-dropdown__dropdown__results__item.active .language-dropdown__dropdown__results__item__common-name {
|
||||
color: darken($ui-base-color, 12%);
|
||||
}
|
||||
|
||||
.dropdown-menu__separator,
|
||||
.dropdown-menu__item.edited-timestamp__history__item,
|
||||
.dropdown-menu__container__header,
|
||||
.compare-history-modal .report-modal__target,
|
||||
.report-dialog-modal .poll__option.dialog-option {
|
||||
border-bottom-color: lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
.report-dialog-modal__container {
|
||||
border-top-color: lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
// Change the background colors of modals
|
||||
.actions-modal,
|
||||
.boost-modal,
|
||||
.favourite-modal,
|
||||
.confirmation-modal,
|
||||
.mute-modal,
|
||||
.block-modal,
|
||||
|
@ -243,12 +379,43 @@
|
|||
.compare-history-modal,
|
||||
.report-modal__comment .setting-text__wrapper,
|
||||
.report-modal__comment .setting-text,
|
||||
.report-dialog-modal__textarea {
|
||||
.announcements,
|
||||
.picture-in-picture__header,
|
||||
.picture-in-picture__footer,
|
||||
.reactions-bar__item {
|
||||
background: $white;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.report-dialog-modal .dialog-option .poll__input {
|
||||
.reactions-bar__item:hover,
|
||||
.reactions-bar__item:focus,
|
||||
.reactions-bar__item:active,
|
||||
.language-dropdown__dropdown__results__item:hover,
|
||||
.language-dropdown__dropdown__results__item:focus,
|
||||
.language-dropdown__dropdown__results__item:active {
|
||||
background-color: $ui-base-color;
|
||||
}
|
||||
|
||||
.reactions-bar__item.active {
|
||||
background-color: mix($white, $ui-highlight-color, 80%);
|
||||
border-color: mix(lighten($ui-base-color, 8%), $ui-highlight-color, 80%);
|
||||
}
|
||||
|
||||
.media-modal__overlay .picture-in-picture__footer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.picture-in-picture__header {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.announcements,
|
||||
.picture-in-picture__footer {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.icon-with-badge__badge {
|
||||
border-color: $white;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
|
@ -260,8 +427,43 @@
|
|||
border-top-color: lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.column-header__collapsible-inner {
|
||||
background: darken($ui-base-color, 4%);
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.dashboard__quick-access,
|
||||
.focal-point__preview strong,
|
||||
.admin-wrapper .content__heading__tabs a.selected {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.button.button-tertiary {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.button.button-secondary {
|
||||
border-color: $darker-text-color;
|
||||
color: $darker-text-color;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
border-color: darken($darker-text-color, 8%);
|
||||
color: darken($darker-text-color, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.flash-message.warning {
|
||||
color: lighten($gold-star, 16%);
|
||||
}
|
||||
|
||||
.boost-modal__action-bar,
|
||||
.favourite-modal__action-bar,
|
||||
.confirmation-modal__action-bar,
|
||||
.mute-modal__action-bar,
|
||||
.block-modal__action-bar,
|
||||
|
@ -279,33 +481,134 @@
|
|||
}
|
||||
}
|
||||
|
||||
.display-case__case {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.embed-modal .embed-modal__container .embed-modal__html {
|
||||
background: $white;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
|
||||
&:focus {
|
||||
border-color: lighten($ui-base-color, 12%);
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.react-toggle-track {
|
||||
background: $ui-secondary-color;
|
||||
}
|
||||
|
||||
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
|
||||
background: darken($ui-secondary-color, 10%);
|
||||
}
|
||||
|
||||
.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
|
||||
background: lighten($ui-highlight-color, 10%);
|
||||
}
|
||||
|
||||
// Change the default color used for the text in an empty column or on the error column
|
||||
.empty-column-indicator,
|
||||
.error-column {
|
||||
color: lighten($ui-base-color, 60%);
|
||||
color: $primary-text-color;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
// Change the default colors used on some parts of the profile pages
|
||||
.activity-stream-tabs {
|
||||
|
||||
background: $account-background-color;
|
||||
border-bottom-color: lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
a {
|
||||
&.active {
|
||||
color: $ui-primary-color;
|
||||
.nothing-here,
|
||||
.page-header,
|
||||
.directory__tag > a,
|
||||
.directory__tag > div {
|
||||
background: $white;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.simple_form {
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
textarea {
|
||||
&:hover {
|
||||
border-color: lighten($ui-base-color, 12%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.picture-in-picture-placeholder {
|
||||
background: $white;
|
||||
border-color: lighten($ui-base-color, 8%);
|
||||
color: lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.directory__tag > a {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.directory__tag.active > a,
|
||||
.directory__tag.active > div {
|
||||
border-color: $ui-highlight-color;
|
||||
|
||||
&,
|
||||
h4,
|
||||
h4 small,
|
||||
.fa,
|
||||
.trends__item__current {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background: $ui-highlight-color;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-table {
|
||||
&__toolbar,
|
||||
&__row,
|
||||
.nothing-here {
|
||||
border-color: lighten($ui-base-color, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.activity-stream {
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
|
||||
&--under-tabs {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.entry {
|
||||
background: $account-background-color;
|
||||
|
||||
.detailed-status.light,
|
||||
.more.light,
|
||||
.status.light {
|
||||
border-bottom-color: lighten($ui-base-color, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.status.light {
|
||||
|
||||
.status__content {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
|
@ -315,17 +618,14 @@
|
|||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.accounts-grid {
|
||||
.account-grid-card {
|
||||
|
||||
.controls {
|
||||
.icon-button {
|
||||
color: $ui-secondary-color;
|
||||
color: $darker-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,13 +636,53 @@
|
|||
}
|
||||
|
||||
.username {
|
||||
color: $ui-secondary-color;
|
||||
color: $darker-text-color;
|
||||
}
|
||||
|
||||
.account__header__content {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.simple_form {
|
||||
.warning {
|
||||
box-shadow: none;
|
||||
background: rgba($error-red, 0.5);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.recommended {
|
||||
border-color: $ui-highlight-color;
|
||||
color: $ui-highlight-color;
|
||||
background-color: rgba($ui-highlight-color, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form .compose-form__warning {
|
||||
border-color: $ui-highlight-color;
|
||||
background-color: rgba($ui-highlight-color, 0.1);
|
||||
|
||||
&,
|
||||
a {
|
||||
color: $ui-highlight-color;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-indicator {
|
||||
background: transparent;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.dismissable-banner {
|
||||
border-left: 1px solid lighten($ui-base-color, 8%);
|
||||
border-right: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.status__content,
|
||||
.reply-indicator__content {
|
||||
a {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,6 +694,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.notification__filter-bar button.active::after,
|
||||
.account__section-headline a.active::after {
|
||||
border-color: transparent transparent $white;
|
||||
}
|
||||
|
@ -364,7 +705,10 @@
|
|||
.activity-stream,
|
||||
.nothing-here,
|
||||
.directory__tag > a,
|
||||
.directory__tag > div {
|
||||
.directory__tag > div,
|
||||
.card > a,
|
||||
.page-header,
|
||||
.compose-form .compose-form__warning {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
@ -372,3 +716,55 @@
|
|||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
background: $simple-background-color url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 8%))}'/></svg>") no-repeat right 8px center / auto 16px;
|
||||
}
|
||||
|
||||
// Glitch-soc-specific changes
|
||||
|
||||
.glitch.local-settings {
|
||||
background: $ui-base-color;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.glitch.local-settings__navigation {
|
||||
background: darken($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.glitch.local-settings__navigation__item {
|
||||
background: darken($ui-base-color, 8%);
|
||||
border-bottom: 1px lighten($ui-base-color, 8%) solid;
|
||||
|
||||
&:hover {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $ui-highlight-color;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&.close, &.close:hover {
|
||||
background: $error-value-color;
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.notification__dismiss-overlay {
|
||||
.wrappy {
|
||||
box-shadow: unset;
|
||||
|
||||
.ckbox {
|
||||
text-shadow: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status.collapsed .status__content:after {
|
||||
background: linear-gradient(rgba(darken($ui-base-color, 13%), 0), rgba(darken($ui-base-color, 13%), 1));
|
||||
}
|
||||
|
||||
.drawer__inner__mastodon {
|
||||
background: $white url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto !important;
|
||||
|
||||
.mastodon {
|
||||
filter: contrast(75%) brightness(75%) !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,17 @@ $classic-primary-color: #9baec8;
|
|||
$classic-secondary-color: #d9e1e8;
|
||||
$classic-highlight-color: #6364ff;
|
||||
|
||||
// Differences
|
||||
$success-green: lighten(#3c754d, 8%);
|
||||
|
||||
$base-overlay-background: $white !default;
|
||||
$valid-value-color: $success-green !default;
|
||||
|
||||
$ui-base-color: $classic-secondary-color !default;
|
||||
$ui-base-lighter-color: darken($ui-base-color, 57%);
|
||||
$ui-highlight-color: $classic-highlight-color !default;
|
||||
$ui-primary-color: $classic-primary-color !default;
|
||||
$ui-base-lighter-color: #b0c0cf;
|
||||
$ui-primary-color: #9bcbed;
|
||||
$ui-secondary-color: $classic-base-color !default;
|
||||
$ui-highlight-color: $classic-highlight-color !default;
|
||||
|
||||
$primary-text-color: $black !default;
|
||||
$darker-text-color: $classic-base-color !default;
|
||||
|
@ -19,14 +25,11 @@ $highlight-text-color: darken($ui-highlight-color, 8%) !default;
|
|||
$dark-text-color: #444b5d;
|
||||
$action-button-color: #606984;
|
||||
|
||||
$success-green: lighten(#3c754d, 8%);
|
||||
|
||||
$base-overlay-background: $white !default;
|
||||
|
||||
$inverted-text-color: $black !default;
|
||||
$lighter-text-color: $classic-base-color !default;
|
||||
$light-text-color: #444b5d;
|
||||
|
||||
// Newly added colors
|
||||
$account-background-color: $white !default;
|
||||
|
||||
// Invert darkened and lightened colors
|
||||
|
|
|
@ -36,17 +36,12 @@ body.rtl {
|
|||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.composer .compose--counter-wrapper {
|
||||
.compose-form .character-counter__wrapper {
|
||||
margin-right: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.composer--publisher {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.boost-modal__status-time,
|
||||
.favourite-modal__status-time {
|
||||
.boost-modal__status-time {
|
||||
float: left;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,3 +22,12 @@ es:
|
|||
skins:
|
||||
vanilla:
|
||||
default: Predeterminado
|
||||
|
||||
ja:
|
||||
flavours:
|
||||
vanilla:
|
||||
description: バニラのMastodonインスタンスで使われるテーマです。このテーマはGlitchSocのすべての機能をサポートしない可能性があります。
|
||||
name: Vanilla Mastodon
|
||||
skins:
|
||||
vanilla:
|
||||
default: デフォルト
|
||||
|
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 950 B |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 5.7 KiB |