Compare commits

...

29 Commits

Author SHA1 Message Date
khr 2a779205b7 Merge 2.7.3 (#8) 2019-02-24 01:55:42 +01:00
khr f9722a0aec Merge 2.7.1 (#7) 2019-02-11 00:49:05 +01:00
nightpool 631d141ad0 fix full-width embeds and light theme embeds 2019-01-21 10:47:03 -08:00
nightpool 5a4ab172e1 remove duplicate border radi 2019-01-21 10:46:48 -08:00
khr a0976ecb77 Roll up changes for 2.7.0 (also on feature branches) 2019-01-20 20:32:18 -08:00
khr f7030554de Merge branch 'theme_win95' into cybrespace 2019-01-20 13:27:41 -08:00
khr 60af6dd3f1 Merge branch 'theme_light' into cybrespace 2019-01-20 13:26:56 -08:00
khr 3591c7e5c4 Merge branches 'branding_cybre', 'dependency_whatinput', 'feature_512_char_toots', 'feature_cybrespace_locale', 'feature_disable_reply_counts', 'feature_doodlebox', 'feature_hotlink_twitter_mentions', 'feature_longer_bios', 'feature_machine_registration_task', 'theme_base' and 'theme_cybre' into cybrespace 2019-01-20 13:23:35 -08:00
khr 5d5a702103 Windows 95 theme 2019-01-20 13:21:05 -08:00
khr a4af65d84f Fix media height weirdness, handle new status cards 2019-01-20 13:21:05 -08:00
chr 785030c998 Base SCSS for cybrespace themes
Performs some baseline tweaks to the mastodon application scss that are
shared between all the cybrespace themes
2019-01-20 13:21:05 -08:00
khr 1139634e40 Cybrespace light theme 2019-01-20 13:20:43 -08:00
khr a0cd0b061b Fix media height weirdness, handle new status cards 2019-01-20 13:20:42 -08:00
chr d2e2033c03 Base SCSS for cybrespace themes
Performs some baseline tweaks to the mastodon application scss that are
shared between all the cybrespace themes
2019-01-20 13:20:42 -08:00
khr d45e803a36 Cybre dark theme 2019-01-20 13:20:17 -08:00
khr fed65044bc Fix media height weirdness, handle new status cards 2019-01-20 13:20:16 -08:00
chr 39607248fa Base SCSS for cybrespace themes
Performs some baseline tweaks to the mastodon application scss that are
shared between all the cybrespace themes
2019-01-20 13:20:16 -08:00
Andrew 0c9600eb14 Feature: add a rake task that's like register but also accepts password
and produces cleaner output
2019-01-20 13:18:48 -08:00
khr a2aea63ae9 Bio length -> 413 characters
Increase the cybre.space profile bio text length limit to 413
characters. Also modifies the account settings page to automatically
resize the textbox to the size of the contained text, so that it's
easier to type longer bios.
2019-01-20 13:16:46 -08:00
Andrew 4bfe7c37c2 Hotlink twitter mentions
Differentiate twitter mentions from normal mentions
2019-01-20 13:14:55 -08:00
khr ef859e16e8 Doodlebox
A modification to the Mastodon interface that allows you to select a
doodle icon in addition to uploading a media file from your computer.
The doodle interface allows you to draw using your mouse, finger, or
pointing device in several colors. It was originally developed by
Ondřej Hruška <ondra@ondrovo.com> for use on the GlitchSoc fork.
2019-01-20 13:14:17 -08:00
Andrew 5977b75650 Remove reply counts on non-expanded view 2019-01-20 13:12:34 -08:00
khr b5280695bd Cybrespace locale
An alternative locale that replaces "toot" with "ping", "favorite" with
"florp", and "boost" with "relay", as well as a few other miscellaneous
changes. Also includes alternate settings that make the en-CY locale
default instead of en-US, and fixes the timestamps.
2019-01-20 13:09:41 -08:00
Andrew 899d149d9e Feature: 512-character limit in the toot box on client and serverside 2019-01-20 13:08:55 -08:00
Andrew 3d5943d761 Add whatinput dependency for more responsive focus styling 2019-01-20 13:07:16 -08:00
khr 319488e77d Fix unnecessary scrollbar on compose drawer 2019-01-20 13:06:15 -08:00
khr 4cd6c2e7c5 Fix media height weirdness, handle new status cards 2019-01-20 13:06:15 -08:00
chr da9fbb477d Base SCSS for cybrespace themes
Performs some baseline tweaks to the mastodon application scss that are
shared between all the cybrespace themes
2019-01-20 13:06:15 -08:00
khr 5dc62786c0 Cybrespace branding changes 2019-01-20 13:03:49 -08:00
189 changed files with 8141 additions and 680 deletions

View File

@ -9,18 +9,18 @@ and provided thanks to the work of the following contributors:
* [akihikodaki](https://github.com/akihikodaki)
* [ThibG](https://github.com/ThibG)
* [mjankowski](https://github.com/mjankowski)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [unarist](https://github.com/unarist)
* [m4sk1n](https://github.com/m4sk1n)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [yiskah](https://github.com/yiskah)
* [nolanlawson](https://github.com/nolanlawson)
* [sorin-davidoi](https://github.com/sorin-davidoi)
* [ysksn](https://github.com/ysksn)
* [sorin-davidoi](https://github.com/sorin-davidoi)
* [abcang](https://github.com/abcang)
* [lynlynlynx](https://github.com/lynlynlynx)
* [alpaca-tc](https://github.com/alpaca-tc)
* [mayaeh](https://github.com/mayaeh)
* [renatolond](https://github.com/renatolond)
* [alpaca-tc](https://github.com/alpaca-tc)
* [nclm](https://github.com/nclm)
* [ineffyble](https://github.com/ineffyble)
* [jeroenpraat](https://github.com/jeroenpraat)
@ -28,9 +28,9 @@ and provided thanks to the work of the following contributors:
* [Quent-in](https://github.com/Quent-in)
* [JantsoP](https://github.com/JantsoP)
* [mabkenar](https://github.com/mabkenar)
* [Kjwon15](https://github.com/Kjwon15)
* [nullkal](https://github.com/nullkal)
* [yookoala](https://github.com/yookoala)
* [Kjwon15](https://github.com/Kjwon15)
* [shuheiktgw](https://github.com/shuheiktgw)
* [ashfurrow](https://github.com/ashfurrow)
* [Quenty31](https://github.com/Quenty31)
@ -48,16 +48,16 @@ and provided thanks to the work of the following contributors:
* [rkarabut](https://github.com/rkarabut)
* [yukimochi](https://github.com/yukimochi)
* [Artoria2e5](https://github.com/Artoria2e5)
* [nightpool](https://github.com/nightpool)
* [marrus-sh](https://github.com/marrus-sh)
* [krainboltgreene](https://github.com/krainboltgreene)
* [patf](https://github.com/patf)
* [pfigel](https://github.com/pfigel)
* [Aldarone](https://github.com/Aldarone)
* [BoFFire](https://github.com/BoFFire)
* [clworld](https://github.com/clworld)
* [dracos](https://github.com/dracos)
* [SerCom_KC](mailto:sercom-kc@users.noreply.github.com)
* [Sylvhem](https://github.com/Sylvhem)
* [nightpool](https://github.com/nightpool)
* [MasterGroosha](https://github.com/MasterGroosha)
* [JeanGauthier](https://github.com/JeanGauthier)
* [kschaper](https://github.com/kschaper)
@ -77,11 +77,14 @@ and provided thanks to the work of the following contributors:
* [johnsudaar](https://github.com/johnsudaar)
* [trebmuh](https://github.com/trebmuh)
* [Rakib Hasan](mailto:rmhasan@gmail.com)
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [lindwurm](https://github.com/lindwurm)
* [victorhck](mailto:victorhck@geeko.site)
* [voidsatisfaction](https://github.com/voidsatisfaction)
* [rinsuki](https://github.com/rinsuki)
* [hikari-no-yume](https://github.com/hikari-no-yume)
* [angristan](https://github.com/angristan)
* [hinaloe](https://github.com/hinaloe)
* [seefood](https://github.com/seefood)
* [jackjennings](https://github.com/jackjennings)
* [spla](mailto:spla@mastodont.cat)
@ -92,20 +95,20 @@ and provided thanks to the work of the following contributors:
* [dunn](https://github.com/dunn)
* [xqus](https://github.com/xqus)
* [hugogameiro](https://github.com/hugogameiro)
* [ariasuni](https://github.com/ariasuni)
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
* [fakenine](https://github.com/fakenine)
* [tsuwatch](https://github.com/tsuwatch)
* [victorhck](https://github.com/victorhck)
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [kedamaDQ](https://github.com/kedamaDQ)
* [puckipedia](https://github.com/puckipedia)
* [fvh-P](https://github.com/fvh-P)
* [contraexemplo](https://github.com/contraexemplo)
* [Aditoo17](https://github.com/Aditoo17)
* [kazu9su](https://github.com/kazu9su)
* [Komic](https://github.com/Komic)
* [lmorchard](https://github.com/lmorchard)
* [diomed](https://github.com/diomed)
* [ariasuni](https://github.com/ariasuni)
* [Neetshin](mailto:neetshin@neetsh.in)
* [rainyday](https://github.com/rainyday)
* [ProgVal](https://github.com/ProgVal)
@ -114,7 +117,8 @@ and provided thanks to the work of the following contributors:
* [goofy-bz](mailto:goofy@babelzilla.org)
* [kadiix](https://github.com/kadiix)
* [kodacs](https://github.com/kodacs)
* [rtucker](https://github.com/rtucker)
* [trwnh](https://github.com/trwnh)
* [JMendyk](https://github.com/JMendyk)
* [KScl](https://github.com/KScl)
* [sterdev](https://github.com/sterdev)
* [TheKinrar](https://github.com/TheKinrar)
@ -125,16 +129,16 @@ and provided thanks to the work of the following contributors:
* [fhemberger](https://github.com/fhemberger)
* [greysteil](https://github.com/greysteil)
* [hensmith](https://github.com/hensmith)
* [hinaloe](https://github.com/hinaloe)
* [d6rkaiz](https://github.com/d6rkaiz)
* [Reverite](https://github.com/Reverite)
* [JMendyk](https://github.com/JMendyk)
* [JohnD28](https://github.com/JohnD28)
* [znz](https://github.com/znz)
* [Naouak](https://github.com/Naouak)
* [pawelngei](https://github.com/pawelngei)
* [rtucker](https://github.com/rtucker)
* [reneklacan](https://github.com/reneklacan)
* [ekiru](https://github.com/ekiru)
* [noellabo](https://github.com/noellabo)
* [tcitworld](https://github.com/tcitworld)
* [geta6](https://github.com/geta6)
* [happycoloredbanana](https://github.com/happycoloredbanana)
@ -144,9 +148,9 @@ and provided thanks to the work of the following contributors:
* [noraworld](https://github.com/noraworld)
* [theboss](https://github.com/theboss)
* [178inaba](https://github.com/178inaba)
* [Aditoo17](https://github.com/Aditoo17)
* [alyssais](https://github.com/alyssais)
* [kodnaplakal](https://github.com/kodnaplakal)
* [hiphref](https://github.com/hiphref)
* [BenLubar](https://github.com/BenLubar)
* [stalker314314](https://github.com/stalker314314)
* [huertanix](https://github.com/huertanix)
* [genesixx](https://github.com/genesixx)
@ -157,6 +161,7 @@ and provided thanks to the work of the following contributors:
* [kmichl](https://github.com/kmichl)
* [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
* [saper](https://github.com/saper)
* [marek-lach](https://github.com/marek-lach)
* [nevillepark](https://github.com/nevillepark)
* [ornithocoder](https://github.com/ornithocoder)
* [pierreozoux](https://github.com/pierreozoux)
@ -164,7 +169,6 @@ and provided thanks to the work of the following contributors:
* [Ram Lmn](mailto:ramlmn@users.noreply.github.com)
* [harukasan](https://github.com/harukasan)
* [stamak](https://github.com/stamak)
* [noellabo](https://github.com/noellabo)
* [Technowix](mailto:technowix@users.noreply.github.com)
* [Eychics](https://github.com/Eychics)
* [Thor Harald Johansen](mailto:thj@thj.no)
@ -179,21 +183,20 @@ and provided thanks to the work of the following contributors:
* [hoodie](mailto:hoodiekitten@outlook.com)
* [luzi82](https://github.com/luzi82)
* [duxovni](https://github.com/duxovni)
* [trwnh](https://github.com/trwnh)
* [tmm576](https://github.com/tmm576)
* [unsmell](https://github.com/unsmell)
* [valerauko](https://github.com/valerauko)
* [chriswmartin](https://github.com/chriswmartin)
* [vahnj](https://github.com/vahnj)
* [ikuradon](https://github.com/ikuradon)
* [AndreLewin](https://github.com/AndreLewin)
* [rinsuki](https://github.com/rinsuki)
* [0xflotus](https://github.com/0xflotus)
* [redtachyons](https://github.com/redtachyons)
* [thurloat](https://github.com/thurloat)
* [aaribaud](https://github.com/aaribaud)
* [pointlessone](https://github.com/pointlessone)
* [Andrew](mailto:andrewlchronister@gmail.com)
* [estuans](https://github.com/estuans)
* [BenLubar](https://github.com/BenLubar)
* [dissolve](https://github.com/dissolve)
* [PurpleBooth](https://github.com/PurpleBooth)
* [bradurani](https://github.com/bradurani)
@ -216,6 +219,7 @@ and provided thanks to the work of the following contributors:
* [ErikXXon](https://github.com/ErikXXon)
* [ian-kelling](https://github.com/ian-kelling)
* [immae](https://github.com/immae)
* [J0WI](https://github.com/J0WI)
* [foozmeat](https://github.com/foozmeat)
* [jasonrhodes](https://github.com/jasonrhodes)
* [Jason Snell](mailto:jason@newrelic.com)
@ -230,6 +234,7 @@ and provided thanks to the work of the following contributors:
* [Lorenz Diener](mailto:halcyon@icosahedron.website)
* [alimony](https://github.com/alimony)
* [mig5](https://github.com/mig5)
* [moritzheiber](https://github.com/moritzheiber)
* [ndarville](https://github.com/ndarville)
* [Abzol](https://github.com/Abzol)
* [pwoolcoc](https://github.com/pwoolcoc)
@ -238,6 +243,7 @@ and provided thanks to the work of the following contributors:
* [ignisf](https://github.com/ignisf)
* [raymestalez](https://github.com/raymestalez)
* [remram44](https://github.com/remram44)
* [sts10](https://github.com/sts10)
* [sascha-sl](https://github.com/sascha-sl)
* [u1-liquid](https://github.com/u1-liquid)
* [sim6](https://github.com/sim6)
@ -288,6 +294,7 @@ and provided thanks to the work of the following contributors:
* [857b](https://github.com/857b)
* [insom](https://github.com/insom)
* [tachyons](https://github.com/tachyons)
* [acid-chicken](https://github.com/acid-chicken)
* [Esteth](https://github.com/Esteth)
* [unascribed](https://github.com/unascribed)
* [Aguay-val](https://github.com/Aguay-val)
@ -297,7 +304,6 @@ and provided thanks to the work of the following contributors:
* [unleashed](https://github.com/unleashed)
* [alxrcs](https://github.com/alxrcs)
* [console-cowboy](https://github.com/console-cowboy)
* [pointlessone](https://github.com/pointlessone)
* [Alkarex](https://github.com/Alkarex)
* [a2](https://github.com/a2)
* [0xa](https://github.com/0xa)
@ -329,6 +335,7 @@ and provided thanks to the work of the following contributors:
* [Motoma](https://github.com/Motoma)
* [chriswk](https://github.com/chriswk)
* [csu](https://github.com/csu)
* [clarcharr](https://github.com/clarcharr)
* [kklleemm](https://github.com/kklleemm)
* [colindean](https://github.com/colindean)
* [dachinat](https://github.com/dachinat)
@ -356,6 +363,7 @@ and provided thanks to the work of the following contributors:
* [espenronnevik](https://github.com/espenronnevik)
* [Finariel](https://github.com/Finariel)
* [siuying](https://github.com/siuying)
* [zoc](https://github.com/zoc)
* [fwenzel](https://github.com/fwenzel)
* [GenbuHase](https://github.com/GenbuHase)
* [hattori6789](https://github.com/hattori6789)
@ -416,6 +424,7 @@ and provided thanks to the work of the following contributors:
* [martymcguire](https://github.com/martymcguire)
* [marvinkopf](https://github.com/marvinkopf)
* [otsune](https://github.com/otsune)
* [mbugowski](https://github.com/mbugowski)
* [Mathias B](mailto:10813340+mathias-b@users.noreply.github.com)
* [matt-auckland](https://github.com/matt-auckland)
* [webroo](https://github.com/webroo)
@ -434,7 +443,6 @@ and provided thanks to the work of the following contributors:
* [premist](https://github.com/premist)
* [Mnkai](https://github.com/Mnkai)
* [mitchhentges](https://github.com/mitchhentges)
* [moritzheiber](https://github.com/moritzheiber)
* [mouse-reeve](https://github.com/mouse-reeve)
* [Mozinet-fr](https://github.com/Mozinet-fr)
* [lae](https://github.com/lae)
@ -458,17 +466,17 @@ and provided thanks to the work of the following contributors:
* [Pangoraw](https://github.com/Pangoraw)
* [peterkeen](https://github.com/peterkeen)
* [pgate](https://github.com/pgate)
* [retokromer](https://github.com/retokromer)
* [rfwatson](https://github.com/rfwatson)
* [rfreebern](https://github.com/rfreebern)
* [Reto Kromer](mailto:retokromer@users.noreply.github.com)
* [Rey Tucker](mailto:git@reytucker.us)
* [Rob Watson](mailto:rfwatson@users.noreply.github.com)
* [Ryan Freebern](mailto:ryan@freebern.org)
* [Ryan Wade](mailto:ryan.wade@protonmail.com)
* [sylph01](https://github.com/sylph01)
* [S-H-GAMELINKS](https://github.com/S-H-GAMELINKS)
* [staticsafe](https://github.com/staticsafe)
* [snwh](https://github.com/snwh)
* [sts10](https://github.com/sts10)
* [skoji](https://github.com/skoji)
* [ScienJus](https://github.com/ScienJus)
* [Ryo Kajiwara](mailto:kfe-fecn6.prussian@s01.info)
* [S.H](mailto:gamelinks007@gmail.com)
* [Sadiq Saif](mailto:staticsafe@users.noreply.github.com)
* [Sam Hewitt](mailto:hewittsamuel@gmail.com)
* [Satoshi KOJIMA](mailto:skoji@mac.com)
* [ScienJus](mailto:i@scienjus.com)
* [Scott Larkin](mailto:scott@codeclimate.com)
* [Sebastian Hübner](mailto:imolein@users.noreply.github.com)
* [Sebastian Morr](mailto:sebastian@morr.cc)
@ -483,6 +491,7 @@ and provided thanks to the work of the following contributors:
* [Sir-Boops](mailto:admin@boops.me)
* [Soshi Kato](mailto:mail@sossii.com)
* [Spanky](mailto:2788886+spankyworks@users.noreply.github.com)
* [Stanislas](mailto:angristan@pm.me)
* [StefOfficiel](mailto:pichard.stephane@free.fr)
* [Steven Tappert](mailto:admin@dark-it.net)
* [Svetlozar Todorov](mailto:svetlik@users.noreply.github.com)
@ -532,6 +541,7 @@ and provided thanks to the work of the following contributors:
* [fsubal](mailto:fsubal@users.noreply.github.com)
* [fusshi-](mailto:dikky1218@users.noreply.github.com)
* [gentaro](mailto:gentaroooo@gmail.com)
* [gol-cha](mailto:info@mevo.xyz)
* [hakoai](mailto:hk--76@qa2.so-net.ne.jp)
* [haosbvnker](mailto:github@chaosbunker.com)
* [isati](mailto:phil@juchnowi.cz)
@ -549,12 +559,12 @@ and provided thanks to the work of the following contributors:
* [luzpaz](mailto:luzpaz@users.noreply.github.com)
* [maxypy](mailto:maxime@mpigou.fr)
* [mhe](mailto:mail@marcus-herrmann.com)
* [mike castleman](mailto:m@mlcastle.net)
* [mimikun](mailto:dzdzble_effort_311@outlook.jp)
* [mshrtkch](mailto:mshrtkch@users.noreply.github.com)
* [muan](mailto:muan@github.com)
* [namelessGonbai](mailto:43787036+namelessgonbai@users.noreply.github.com)
* [neetshin](mailto:neetshin@neetsh.in)
* [nightpool](mailto:nightpool@users.noreply.github.com)
* [rch850](mailto:rich850@gmail.com)
* [roikale](mailto:roikale@users.noreply.github.com)
* [rysiekpl](mailto:rysiek@hackerspace.pl)

View File

@ -3,6 +3,78 @@ Changelog
All notable changes to this project will be documented in this file.
## [2.7.3] - 2019-02-23
### Added
- Add domain filter to the admin federation page ([ThibG](https://github.com/tootsuite/mastodon/pull/10071))
- Add quick link from admin account view to block/unblock instance ([ThibG](https://github.com/tootsuite/mastodon/pull/10073))
### Fixed
- Fix video player width not being updated to fit container width ([ThibG](https://github.com/tootsuite/mastodon/pull/10069))
- Fix domain filter being shown in admin page when local filter is active ([ThibG](https://github.com/tootsuite/mastodon/pull/10074))
- Fix crash when conversations have no valid participants ([ThibG](https://github.com/tootsuite/mastodon/pull/10078))
- Fix error when performing admin actions on no statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10094))
### Changed
- Change custom emojis to randomize stored file name ([hinaloe](https://github.com/tootsuite/mastodon/pull/10090))
## [2.7.2] - 2019-02-17
### Added
- Add support for IPv6 in e-mail validation ([zoc](https://github.com/tootsuite/mastodon/pull/10009))
- Add record of IP address used for signing up ([ThibG](https://github.com/tootsuite/mastodon/pull/10026))
- Add tight rate-limit for API deletions (30 per 30 minutes) ([Gargron](https://github.com/tootsuite/mastodon/pull/10042))
- Add support for embedded `Announce` objects attributed to the same actor ([ThibG](https://github.com/tootsuite/mastodon/pull/9998), [Gargron](https://github.com/tootsuite/mastodon/pull/10065))
- Add spam filter for `Create` and `Announce` activities ([Gargron](https://github.com/tootsuite/mastodon/pull/10005), [Gargron](https://github.com/tootsuite/mastodon/pull/10041), [Gargron](https://github.com/tootsuite/mastodon/pull/10062))
- Add `registrations` attribute to `GET /api/v1/instance` ([Gargron](https://github.com/tootsuite/mastodon/pull/10060))
- Add `vapid_key` to `POST /api/v1/apps` and `GET /api/v1/apps/verify_credentials` ([Gargron](https://github.com/tootsuite/mastodon/pull/10058))
### Fixed
- Fix link color and add link underlines in high-contrast theme ([Gargron](https://github.com/tootsuite/mastodon/pull/9949), [Gargron](https://github.com/tootsuite/mastodon/pull/10028))
- Fix unicode characters in URLs not being linkified ([JMendyk](https://github.com/tootsuite/mastodon/pull/8447), [hinaloe](https://github.com/tootsuite/mastodon/pull/9991))
- Fix URLs linkifier grabbing ending quotation as part of the link ([Gargron](https://github.com/tootsuite/mastodon/pull/9997))
- Fix authorized applications page design ([rinsuki](https://github.com/tootsuite/mastodon/pull/9969))
- Fix custom emojis not showing up in share page emoji picker ([rinsuki](https://github.com/tootsuite/mastodon/pull/9970))
- Fix too liberal application of whitespace in toots ([trwnh](https://github.com/tootsuite/mastodon/pull/9968))
- Fix misleading e-mail hint being displayed in admin view ([ThibG](https://github.com/tootsuite/mastodon/pull/9973))
- Fix tombstones not being cleared out ([abcang](https://github.com/tootsuite/mastodon/pull/9978))
- Fix some timeline jumps ([ThibG](https://github.com/tootsuite/mastodon/pull/9982), [ThibG](https://github.com/tootsuite/mastodon/pull/10001), [rinsuki](https://github.com/tootsuite/mastodon/pull/10046))
- Fix content warning input taking keyboard focus even when hidden ([hinaloe](https://github.com/tootsuite/mastodon/pull/10017))
- Fix hashtags select styling in default and high-contrast themes ([Gargron](https://github.com/tootsuite/mastodon/pull/10029))
- Fix style regressions on landing page ([Gargron](https://github.com/tootsuite/mastodon/pull/10030))
- Fix hashtag column not subscribing to stream on mount ([Gargron](https://github.com/tootsuite/mastodon/pull/10040))
- Fix relay enabling/disabling not resetting inbox availability status ([Gargron](https://github.com/tootsuite/mastodon/pull/10048))
- Fix mutes, blocks, domain blocks and follow requests not paginating ([Gargron](https://github.com/tootsuite/mastodon/pull/10057))
- Fix crash on public hashtag pages when streaming fails ([ThibG](https://github.com/tootsuite/mastodon/pull/10061))
### Changed
- Change icon for unlisted visibility level ([clarcharr](https://github.com/tootsuite/mastodon/pull/9952))
- Change queue of actor deletes from push to pull for non-follower recipients ([ThibG](https://github.com/tootsuite/mastodon/pull/10016))
- Change robots.txt to exclude media proxy URLs ([nightpool](https://github.com/tootsuite/mastodon/pull/10038))
- Change upload description input to allow line breaks ([BenLubar](https://github.com/tootsuite/mastodon/pull/10036))
- Change `dist/mastodon-streaming.service` to recommend running node without intermediary npm command ([nolanlawson](https://github.com/tootsuite/mastodon/pull/10032))
- Change conversations to always show names of other participants ([Gargron](https://github.com/tootsuite/mastodon/pull/10047))
- Change buttons on timeline preview to open the interaction dialog ([Gargron](https://github.com/tootsuite/mastodon/pull/10054))
- Change error graphic to hover-to-play ([Gargron](https://github.com/tootsuite/mastodon/pull/10055))
## [2.7.1] - 2019-01-28
### Fixed
- Fix SSO authentication not working due to missing agreement boolean ([Gargron](https://github.com/tootsuite/mastodon/pull/9915))
- Fix slow fallback of CopyAccountStats migration setting stats to 0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9930))
- Fix wrong command in migration error message ([angristan](https://github.com/tootsuite/mastodon/pull/9877))
- Fix initial value of volume slider in video player and handle volume changes ([ThibG](https://github.com/tootsuite/mastodon/pull/9929))
- Fix missing hotkeys for notifications ([ThibG](https://github.com/tootsuite/mastodon/pull/9927))
- Fix being able to attach unattached media created by other users ([ThibG](https://github.com/tootsuite/mastodon/pull/9921))
- Fix unrescued SSL error during link verification ([renatolond](https://github.com/tootsuite/mastodon/pull/9914))
- Fix Firefox scrollbar color regression ([trwnh](https://github.com/tootsuite/mastodon/pull/9908))
- Fix scheduled status with media immediately creating a status ([ThibG](https://github.com/tootsuite/mastodon/pull/9894))
- Fix missing strong style for landing page description ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9892))
## [2.7.0] - 2019-01-20
### Added

View File

@ -1,7 +1,7 @@
Contributing
============
Thank you for considering contributing to Mastodon 🐘
Thank you for considering contributing to Mastodon 🐘
You can contribute in the following ways:
@ -10,6 +10,8 @@ You can contribute in the following ways:
- Contributing code to Mastodon by fixing bugs or implementing features
- Improving the documentation
If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
## Bug reports
Bug reports and feature suggestions can be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected in the past using the search function. Please also use descriptive, concise titles.

View File

@ -23,7 +23,7 @@ gem 'paperclip-av-transcoder', '~> 0.6'
gem 'streamio-ffmpeg', '~> 3.0'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.5'
gem 'addressable', '~> 2.6'
gem 'bootsnap', '~> 1.3', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.6'

View File

@ -62,7 +62,7 @@ GEM
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.2)
addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0)
airbrussh (1.3.0)
sshkit (>= 1.6.1, != 1.7.0)
@ -290,8 +290,8 @@ GEM
json-ld (3.0.2)
multi_json (~> 1.12)
rdf (>= 2.2.8, < 4.0)
json-ld-preloaded (3.0.0)
json-ld (>= 2.2, < 4.0)
json-ld-preloaded (3.0.2)
json-ld (~> 3.0)
multi_json (~> 1.12)
rdf (~> 3.0)
jsonapi-renderer (0.2.0)
@ -363,7 +363,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.7.7)
oj (3.7.8)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
@ -389,7 +389,7 @@ GEM
paperclip-av-transcoder (0.6.4)
av (~> 0.9.0)
paperclip (>= 2.5.2)
parallel (1.12.1)
parallel (1.13.0)
parallel_tests (2.27.1)
parallel
parser (2.6.0.0)
@ -420,7 +420,7 @@ GEM
pry (>= 0.10.4)
public_suffix (3.0.3)
puma (3.12.0)
pundit (2.0.0)
pundit (2.0.1)
activesupport (>= 3.0.0)
raabro (1.1.6)
rack (2.0.6)
@ -513,7 +513,7 @@ GEM
rspec-mocks (3.8.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-rails (3.8.1)
rspec-rails (3.8.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
@ -525,7 +525,7 @@ GEM
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.8.0)
rubocop (0.63.0)
rubocop (0.63.1)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
@ -655,7 +655,7 @@ PLATFORMS
DEPENDENCIES
active_model_serializers (~> 0.10)
active_record_query_trace (~> 1.5)
addressable (~> 2.5)
addressable (~> 2.6)
annotate (~> 2.7)
aws-sdk-s3 (~> 1.30)
better_errors (~> 2.5)

View File

@ -21,7 +21,7 @@ Click below to **learn more** in a video:
[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE
## Navigation
## Navigation
- [Project homepage 🐘](https://joinmastodon.org)
- [Support the development via Patreon][patreon]
@ -80,13 +80,13 @@ A **Vagrant** configuration is included for development purposes.
Mastodon is **free, open source software** licensed under **AGPLv3**.
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md)
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
**IRC channel**: #mastodon on irc.freenode.net
## License
Copyright (C) 2016-2018 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md))
Copyright (C) 2016-2019 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md))
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

View File

@ -31,7 +31,7 @@ class StatusesIndex < Chewy::Index
},
}
define_type ::Status.unscoped.without_reblogs do
define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do
crutch :mentions do |collection|
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
@ -50,7 +50,7 @@ class StatusesIndex < Chewy::Index
root date_detection: false do
field :account_id, type: 'long'
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].join("\n\n") } do
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).join("\n\n") } do
field :stemmed, type: 'text', analyzer: 'content'
end

View File

@ -5,6 +5,9 @@ module Admin
before_action :set_custom_emoji, except: [:index, :new, :create]
before_action :set_filter_params
include ObfuscateFilename
obfuscate_filename [:custom_emoji, :image]
def index
authorize :custom_emoji, :index?
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])

View File

@ -38,7 +38,7 @@ module Admin
end
def filter_params
params.permit(:limited)
params.permit(:limited, :by_domain)
end
end
end

View File

@ -10,6 +10,10 @@ module Admin
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_report_path(@report)
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
redirect_to admin_report_path(@report)
end

View File

@ -6,6 +6,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
respond_to :json
def show
render json: doorkeeper_token.application, serializer: REST::StatusSerializer::ApplicationSerializer
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
end
end

View File

@ -28,6 +28,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.agreement = true
resource.current_sign_in_ip = request.remote_ip if resource.current_sign_in_ip.nil?
resource.build_account if resource.account.nil?
end

View File

@ -20,7 +20,12 @@ module Localized
if ENV['DEFAULT_LOCALE'].present?
I18n.default_locale
else
request_locale || I18n.default_locale
case request_locale
when /en\b/, nil
I18n.default_locale
else
request_locale
end
end
end

View File

@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :store_current_location
before_action :authenticate_resource_owner!
before_action :set_body_classes
include Localized
@ -15,6 +16,10 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
private
def set_body_classes
@body_classes = 'admin'
end
def store_current_location
store_location_for(:user, request.url)
end

View File

@ -6,7 +6,7 @@ module Admin::FilterHelper
INVITE_FILTER = %i(available expired).freeze
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
TAGS_FILTERS = %i(hidden).freeze
INSTANCES_FILTERS = %i(limited).freeze
INSTANCES_FILTERS = %i(limited by_domain).freeze
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS

View File

@ -3,6 +3,7 @@
module SettingsHelper
HUMAN_LOCALES = {
en: 'English',
'en-CY': 'English (Cybre)',
ar: 'العربية',
ast: 'l\'asturianu',
bg: 'Български',

View File

@ -170,7 +170,7 @@ module StreamEntriesHelper
when 'public'
fa_icon 'globe fw'
when 'unlisted'
fa_icon 'unlock-alt fw'
fa_icon 'unlock fw'
when 'private'
fa_icon 'lock fw'
when 'direct'

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg" width="384" version="1.1" height="384">
<g transform="translate(-282.71845,-76)" id="RenderLayer_LineSet">
<g id="strokes">
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path2" d="m 405.991,306.829 v 10 10 10 10 10 10 10 10 6.581 5.978 9.656 3.057" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path4" d="m 405.991,409.044 v 0 -9.656 -5.978 -10 -10 -10 -10 -10 -10 -10 -10 -6.581" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path6" d="m 370.692,127.899 v 1.329 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path8" d="m 439.906,283.073 v 0 h -10 -10 -10.303 -3.612 -10 -10 -10 -2.337 l -2.962,-2.901 v 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path10" d="m 373.654,283.073 -2.962,-2.901 v -10 -10 -10 -10 -10 -10 -1.283 -10 -10 -10 -10 -10 -9.658" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path12" d="m 370.692,127.899 h -7.383 -10 -2.899 -5.311 v 0 l -0.45,0.28 -0.37,0.387 -0.268,0.47 v 0.192 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 2.901 10 4.487 5.906 3.363 10 10 10 10 10 10 10 10 6.581 l 6.235,6.039 7.183,6.957 2.643,2.56 3.237,3.135 v 0 h 7.383 2.962 10 10 10 2.337 v 0 0 -3.057" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path14" d="m 584.095,303.466 3.074,3.363 v 10 10 10 10 10 10 10 10 6.581 6.183 9.988 10e-4 0.001 2.518 h 6.497 10 2.976 7.534 v 0 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path16" d="m 615.989,393.41 v -10 -10 -10 -10 -10 -10 -10 -10 -6.581 -3.363 -5.906 -10 -4.487 -2.901 -10 -10 -10 -10 -10 -10 -1.283 -10 -10 -10 -10 -10 -9.658 -2.701 -4.955 -10 -0.947 -10 -1.4 -0.061 l -0.299,-0.524 -0.394,-0.413 -0.019,-0.019 v 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path18" d="m 584.093,129.228 h -10 -10 -10 -10 l -10,-0.001 h -10 -5.476 -10 -10 -10 -10 -10 -10 l -10,-0.001 h -8.828 -10 -10 -10 -0.326 -3.615 l -10,0.001 h -10 l -10,0.001 h -2.205 -2.951 v 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path20" d="m 615.277,128.211 v 0 l -0.501,-0.312 v 0 0 h -8.134 -10 -2.976 -6.497 v 1.329" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path22" d="m 615.69,128.643 0.299,0.524 v 0.061 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 2.901 10 4.487 5.906 3.363 10 10 10 10 10 10 10 10 6.581 6.183 9.987 0.626 l -0.447,0.784 -0.617,0.644 -0.749,0.467" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path24" d="m 587.169,156.53 v 0 0 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 l -3.074,2.901 h -10 -10 -10 -10 -10 -10 -5.424" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path26" d="m 370.692,129.228 v -1.329 0 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path28" d="m 584.093,129.228 h 3.076" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path30" d="m 350.41,140.628 h 10 2.899 v 10 0.947 h -3.555 -9.344 v 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path32" d="m 587.169,129.228 v 10 1.4 10 0.947 4.955" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path34" d="m 350.41,151.575 v -10 -0.947" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path36" d="m 593.666,151.575 v -10 -0.947 0 h 10 2.976" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path38" d="m 606.642,140.628 v 10 0.947 h -10 -2.976 v 0 -10 -0.947" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path40" d="m 615.69,128.643 -0.394,-0.413 -0.019,-0.019 v 0 l -0.501,-0.312" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path42" d="m 615.989,393.41 v 6.183 9.987 0.626 l -0.447,0.783 -0.576,0.603 -0.041,0.042 v 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path44" d="m 614.925,411.634 v 0 l -0.749,0.467" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path46" d="m 615.69,128.643 -0.009,-0.01 -0.404,-0.422 -0.501,-0.312" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path48" d="m 614.176,412.101 0.749,-0.467 v 0 l 0.041,-0.042 0.576,-0.603 0.447,-0.783 v -0.626 -9.987 -6.183" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path50" d="m 406.223,306.613 -0.232,0.216 v 0 l 0.232,-0.216 3.38,-3.147 h 10 10 10.303 10 10 10 10 10 10 10 8.765 10 10 1.007" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path52" d="m 518.671,283.073 v 0 h -10 -2.247" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path54" d="m 615.277,128.211 0.404,0.422" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path56" d="m 383.445,273.927 h 10 10 10 10 10 6.461" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path58" d="m 576.555,273.927 2.03,-0.051 1.978,-0.302 1.707,-0.727" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path60" d="m 582.27,272.847 1.099,-1.131 0.456,-1.309 0.077,-1.342" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path62" d="m 583.902,269.065 v -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -9.834 0 -10 -7.959" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path64" d="m 583.902,141.272 -0.077,-1.344 -0.457,-1.309 -1.098,-1.13" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path66" d="m 582.27,137.489 -1.709,-0.727 -1.977,-0.301 -2.029,-0.051" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path68" d="m 576.555,136.41 h -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -3.11 l -2.03,0.051 -1.978,0.302 -1.707,0.726" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path70" d="m 377.73,137.489 -1.099,1.131 -0.456,1.309 -0.077,1.343" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path72" d="m 376.098,269.065 0.077,1.343 0.457,1.309 1.098,1.13" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path74" d="m 377.73,272.847 1.709,0.727 1.977,0.302 2.029,0.051" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path76" d="m 457.882,318.176 v 10 10 10 10 10 10 10 8.996 l -0.375,1.491 -0.542,1.03" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path78" d="m 456.965,399.693 h -10 -10 -8.726 l -0.542,-1.03 -0.375,-1.491" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path80" d="m 539.678,303.466 -0.064,-0.035 h -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10.198 l -1.889,1.029 -1.304,1.491" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path82" d="m 543.74,411.917 v -2.465 -0.024 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -2.742 l -1.686,-1.925 -2.376,-1.295" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path84" d="m 427.612,316.452 0.42,-0.797 h 10 10 8.933 l 0.542,1.03 0.375,1.491" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path86" d="m 427.322,317.607 0.29,-1.155" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path88" d="m 350.41,151.575 v -10 -0.947" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path90" d="m 370.692,127.899 v 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path92" d="m 376.098,141.272 v 10 7.954 0.005 10 10 10 10 10 10 10 10 10 10 9.834" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path94" d="m 405.991,409.044 v 3.057" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path96" d="m 406.223,305.951 v 0.662 0 0 0 10 10 10 10 10 10 10 10 10 10 2.333 0.098 2.873" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path98" d="m 427.322,397.172 v -10 -10 -10 -10 -10 -10 -10 -9.565" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path100" d="m 405.991,409.044 h 0.232" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path102" d="m 506.424,283.073 h -3.436 -10 -10 -10 -10 -6.548 -3.543 -10e-4 -10 -2.99" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path104" d="m 406.223,411.917 h 10 10 10 10 10 10 10 10 10 10 10 10 10 7.517" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path106" d="m 518.671,273.927 h 10 10 10 10 10 7.884" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path108" d="m 439.906,273.927 h 6.803 v 0 h 2.704 10 10 10 10 10 10.551 2.65 10e-4 6.056" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path110" d="m 539.678,303.466 h 10 10 10 10 4.417" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path112" d="m 587.169,156.53 v 0" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path114" d="m 543.74,409.452 10,0.029 10,0.03 10,0.03 9.999,0.029 3.43,0.01" />
<path style="fill:none;stroke:#87B97D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path116" d="m 615.989,393.41 v -10 -10 -10 -10 -10 -10 -10 -10 -6.581" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg" width="384" version="1.1" height="384">
<g transform="translate(-282.71845,-76)" id="RenderLayer_LineSet">
<g id="strokes">
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path2" d="m 405.991,306.829 v 10 10 10 10 10 10 10 10 6.581 5.978 9.656 3.057" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path4" d="m 405.991,409.044 v 0 -9.656 -5.978 -10 -10 -10 -10 -10 -10 -10 -10 -6.581" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path6" d="m 370.692,127.899 v 1.329 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path8" d="m 439.906,283.073 v 0 h -10 -10 -10.303 -3.612 -10 -10 -10 -2.337 l -2.962,-2.901 v 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path10" d="m 373.654,283.073 -2.962,-2.901 v -10 -10 -10 -10 -10 -10 -1.283 -10 -10 -10 -10 -10 -9.658" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path12" d="m 370.692,127.899 h -7.383 -10 -2.899 -5.311 v 0 l -0.45,0.28 -0.37,0.387 -0.268,0.47 v 0.192 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 2.901 10 4.487 5.906 3.363 10 10 10 10 10 10 10 10 6.581 l 6.235,6.039 7.183,6.957 2.643,2.56 3.237,3.135 v 0 h 7.383 2.962 10 10 10 2.337 v 0 0 -3.057" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path14" d="m 584.095,303.466 3.074,3.363 v 10 10 10 10 10 10 10 10 6.581 6.183 9.988 10e-4 0.001 2.518 h 6.497 10 2.976 7.534 v 0 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path16" d="m 615.989,393.41 v -10 -10 -10 -10 -10 -10 -10 -10 -6.581 -3.363 -5.906 -10 -4.487 -2.901 -10 -10 -10 -10 -10 -10 -1.283 -10 -10 -10 -10 -10 -9.658 -2.701 -4.955 -10 -0.947 -10 -1.4 -0.061 l -0.299,-0.524 -0.394,-0.413 -0.019,-0.019 v 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path18" d="m 584.093,129.228 h -10 -10 -10 -10 l -10,-0.001 h -10 -5.476 -10 -10 -10 -10 -10 -10 l -10,-0.001 h -8.828 -10 -10 -10 -0.326 -3.615 l -10,0.001 h -10 l -10,0.001 h -2.205 -2.951 v 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path20" d="m 615.277,128.211 v 0 l -0.501,-0.312 v 0 0 h -8.134 -10 -2.976 -6.497 v 1.329" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path22" d="m 615.69,128.643 0.299,0.524 v 0.061 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 2.901 10 4.487 5.906 3.363 10 10 10 10 10 10 10 10 6.581 6.183 9.987 0.626 l -0.447,0.784 -0.617,0.644 -0.749,0.467" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path24" d="m 587.169,156.53 v 0 0 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 l -3.074,2.901 h -10 -10 -10 -10 -10 -10 -5.424" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path26" d="m 370.692,129.228 v -1.329 0 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path28" d="m 584.093,129.228 h 3.076" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path30" d="m 350.41,140.628 h 10 2.899 v 10 0.947 h -3.555 -9.344 v 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path32" d="m 587.169,129.228 v 10 1.4 10 0.947 4.955" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path34" d="m 350.41,151.575 v -10 -0.947" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path36" d="m 593.666,151.575 v -10 -0.947 0 h 10 2.976" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path38" d="m 606.642,140.628 v 10 0.947 h -10 -2.976 v 0 -10 -0.947" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path40" d="m 615.69,128.643 -0.394,-0.413 -0.019,-0.019 v 0 l -0.501,-0.312" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path42" d="m 615.989,393.41 v 6.183 9.987 0.626 l -0.447,0.783 -0.576,0.603 -0.041,0.042 v 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path44" d="m 614.925,411.634 v 0 l -0.749,0.467" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path46" d="m 615.69,128.643 -0.009,-0.01 -0.404,-0.422 -0.501,-0.312" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path48" d="m 614.176,412.101 0.749,-0.467 v 0 l 0.041,-0.042 0.576,-0.603 0.447,-0.783 v -0.626 -9.987 -6.183" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path50" d="m 406.223,306.613 -0.232,0.216 v 0 l 0.232,-0.216 3.38,-3.147 h 10 10 10.303 10 10 10 10 10 10 10 8.765 10 10 1.007" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path52" d="m 518.671,283.073 v 0 h -10 -2.247" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path54" d="m 615.277,128.211 0.404,0.422" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path56" d="m 383.445,273.927 h 10 10 10 10 10 6.461" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path58" d="m 576.555,273.927 2.03,-0.051 1.978,-0.302 1.707,-0.727" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path60" d="m 582.27,272.847 1.099,-1.131 0.456,-1.309 0.077,-1.342" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path62" d="m 583.902,269.065 v -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -9.834 0 -10 -7.959" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path64" d="m 583.902,141.272 -0.077,-1.344 -0.457,-1.309 -1.098,-1.13" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path66" d="m 582.27,137.489 -1.709,-0.727 -1.977,-0.301 -2.029,-0.051" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path68" d="m 576.555,136.41 h -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -3.11 l -2.03,0.051 -1.978,0.302 -1.707,0.726" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path70" d="m 377.73,137.489 -1.099,1.131 -0.456,1.309 -0.077,1.343" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path72" d="m 376.098,269.065 0.077,1.343 0.457,1.309 1.098,1.13" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path74" d="m 377.73,272.847 1.709,0.727 1.977,0.302 2.029,0.051" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path76" d="m 457.882,318.176 v 10 10 10 10 10 10 10 8.996 l -0.375,1.491 -0.542,1.03" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path78" d="m 456.965,399.693 h -10 -10 -8.726 l -0.542,-1.03 -0.375,-1.491" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path80" d="m 539.678,303.466 -0.064,-0.035 h -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10.198 l -1.889,1.029 -1.304,1.491" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path82" d="m 543.74,411.917 v -2.465 -0.024 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -2.742 l -1.686,-1.925 -2.376,-1.295" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path84" d="m 427.612,316.452 0.42,-0.797 h 10 10 8.933 l 0.542,1.03 0.375,1.491" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path86" d="m 427.322,317.607 0.29,-1.155" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path88" d="m 350.41,151.575 v -10 -0.947" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path90" d="m 370.692,127.899 v 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path92" d="m 376.098,141.272 v 10 7.954 0.005 10 10 10 10 10 10 10 10 10 10 9.834" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path94" d="m 405.991,409.044 v 3.057" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path96" d="m 406.223,305.951 v 0.662 0 0 0 10 10 10 10 10 10 10 10 10 10 2.333 0.098 2.873" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path98" d="m 427.322,397.172 v -10 -10 -10 -10 -10 -10 -10 -9.565" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path100" d="m 405.991,409.044 h 0.232" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path102" d="m 506.424,283.073 h -3.436 -10 -10 -10 -10 -6.548 -3.543 -10e-4 -10 -2.99" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path104" d="m 406.223,411.917 h 10 10 10 10 10 10 10 10 10 10 10 10 10 7.517" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path106" d="m 518.671,273.927 h 10 10 10 10 10 7.884" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path108" d="m 439.906,273.927 h 6.803 v 0 h 2.704 10 10 10 10 10 10.551 2.65 10e-4 6.056" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path110" d="m 539.678,303.466 h 10 10 10 10 4.417" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path112" d="m 587.169,156.53 v 0" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path114" d="m 543.74,409.452 10,0.029 10,0.03 10,0.03 9.999,0.029 3.43,0.01" />
<path style="fill:none;stroke:#A9713D;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path116" d="m 615.989,393.41 v -10 -10 -10 -10 -10 -10 -10 -10 -6.581" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg" width="384" version="1.1" height="384">
<g transform="translate(-282.71845,-76)" id="RenderLayer_LineSet">
<g id="strokes">
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path2" d="m 405.991,306.829 v 10 10 10 10 10 10 10 10 6.581 5.978 9.656 3.057" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path4" d="m 405.991,409.044 v 0 -9.656 -5.978 -10 -10 -10 -10 -10 -10 -10 -10 -6.581" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path6" d="m 370.692,127.899 v 1.329 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path8" d="m 439.906,283.073 v 0 h -10 -10 -10.303 -3.612 -10 -10 -10 -2.337 l -2.962,-2.901 v 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path10" d="m 373.654,283.073 -2.962,-2.901 v -10 -10 -10 -10 -10 -10 -1.283 -10 -10 -10 -10 -10 -9.658" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path12" d="m 370.692,127.899 h -7.383 -10 -2.899 -5.311 v 0 l -0.45,0.28 -0.37,0.387 -0.268,0.47 v 0.192 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 2.901 10 4.487 5.906 3.363 10 10 10 10 10 10 10 10 6.581 l 6.235,6.039 7.183,6.957 2.643,2.56 3.237,3.135 v 0 h 7.383 2.962 10 10 10 2.337 v 0 0 -3.057" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path14" d="m 584.095,303.466 3.074,3.363 v 10 10 10 10 10 10 10 10 6.581 6.183 9.988 10e-4 0.001 2.518 h 6.497 10 2.976 7.534 v 0 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path16" d="m 615.989,393.41 v -10 -10 -10 -10 -10 -10 -10 -10 -6.581 -3.363 -5.906 -10 -4.487 -2.901 -10 -10 -10 -10 -10 -10 -1.283 -10 -10 -10 -10 -10 -9.658 -2.701 -4.955 -10 -0.947 -10 -1.4 -0.061 l -0.299,-0.524 -0.394,-0.413 -0.019,-0.019 v 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path18" d="m 584.093,129.228 h -10 -10 -10 -10 l -10,-0.001 h -10 -5.476 -10 -10 -10 -10 -10 -10 l -10,-0.001 h -8.828 -10 -10 -10 -0.326 -3.615 l -10,0.001 h -10 l -10,0.001 h -2.205 -2.951 v 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path20" d="m 615.277,128.211 v 0 l -0.501,-0.312 v 0 0 h -8.134 -10 -2.976 -6.497 v 1.329" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path22" d="m 615.69,128.643 0.299,0.524 v 0.061 10 1.4 10 0.947 4.955 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 2.901 10 4.487 5.906 3.363 10 10 10 10 10 10 10 10 6.581 6.183 9.987 0.626 l -0.447,0.784 -0.617,0.644 -0.749,0.467" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path24" d="m 587.169,156.53 v 0 0 2.701 10 10 10 10 10 9.658 10 10 10 10 10 10 1.283 l -3.074,2.901 h -10 -10 -10 -10 -10 -10 -5.424" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path26" d="m 370.692,129.228 v -1.329 0 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path28" d="m 584.093,129.228 h 3.076" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path30" d="m 350.41,140.628 h 10 2.899 v 10 0.947 h -3.555 -9.344 v 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path32" d="m 587.169,129.228 v 10 1.4 10 0.947 4.955" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path34" d="m 350.41,151.575 v -10 -0.947" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path36" d="m 593.666,151.575 v -10 -0.947 0 h 10 2.976" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path38" d="m 606.642,140.628 v 10 0.947 h -10 -2.976 v 0 -10 -0.947" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path40" d="m 615.69,128.643 -0.394,-0.413 -0.019,-0.019 v 0 l -0.501,-0.312" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path42" d="m 615.989,393.41 v 6.183 9.987 0.626 l -0.447,0.783 -0.576,0.603 -0.041,0.042 v 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path44" d="m 614.925,411.634 v 0 l -0.749,0.467" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path46" d="m 615.69,128.643 -0.009,-0.01 -0.404,-0.422 -0.501,-0.312" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path48" d="m 614.176,412.101 0.749,-0.467 v 0 l 0.041,-0.042 0.576,-0.603 0.447,-0.783 v -0.626 -9.987 -6.183" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path50" d="m 406.223,306.613 -0.232,0.216 v 0 l 0.232,-0.216 3.38,-3.147 h 10 10 10.303 10 10 10 10 10 10 10 8.765 10 10 1.007" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path52" d="m 518.671,283.073 v 0 h -10 -2.247" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path54" d="m 615.277,128.211 0.404,0.422" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path56" d="m 383.445,273.927 h 10 10 10 10 10 6.461" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path58" d="m 576.555,273.927 2.03,-0.051 1.978,-0.302 1.707,-0.727" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path60" d="m 582.27,272.847 1.099,-1.131 0.456,-1.309 0.077,-1.342" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path62" d="m 583.902,269.065 v -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -9.834 0 -10 -7.959" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path64" d="m 583.902,141.272 -0.077,-1.344 -0.457,-1.309 -1.098,-1.13" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path66" d="m 582.27,137.489 -1.709,-0.727 -1.977,-0.301 -2.029,-0.051" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path68" d="m 576.555,136.41 h -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -3.11 l -2.03,0.051 -1.978,0.302 -1.707,0.726" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path70" d="m 377.73,137.489 -1.099,1.131 -0.456,1.309 -0.077,1.343" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path72" d="m 376.098,269.065 0.077,1.343 0.457,1.309 1.098,1.13" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path74" d="m 377.73,272.847 1.709,0.727 1.977,0.302 2.029,0.051" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path76" d="m 457.882,318.176 v 10 10 10 10 10 10 10 8.996 l -0.375,1.491 -0.542,1.03" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path78" d="m 456.965,399.693 h -10 -10 -8.726 l -0.542,-1.03 -0.375,-1.491" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path80" d="m 539.678,303.466 -0.064,-0.035 h -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10.198 l -1.889,1.029 -1.304,1.491" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path82" d="m 543.74,411.917 v -2.465 -0.024 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -2.742 l -1.686,-1.925 -2.376,-1.295" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path84" d="m 427.612,316.452 0.42,-0.797 h 10 10 8.933 l 0.542,1.03 0.375,1.491" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path86" d="m 427.322,317.607 0.29,-1.155" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path88" d="m 350.41,151.575 v -10 -0.947" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path90" d="m 370.692,127.899 v 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path92" d="m 376.098,141.272 v 10 7.954 0.005 10 10 10 10 10 10 10 10 10 10 9.834" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path94" d="m 405.991,409.044 v 3.057" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path96" d="m 406.223,305.951 v 0.662 0 0 0 10 10 10 10 10 10 10 10 10 10 2.333 0.098 2.873" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path98" d="m 427.322,397.172 v -10 -10 -10 -10 -10 -10 -10 -9.565" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path100" d="m 405.991,409.044 h 0.232" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path102" d="m 506.424,283.073 h -3.436 -10 -10 -10 -10 -6.548 -3.543 -10e-4 -10 -2.99" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path104" d="m 406.223,411.917 h 10 10 10 10 10 10 10 10 10 10 10 10 10 7.517" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path106" d="m 518.671,273.927 h 10 10 10 10 10 7.884" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path108" d="m 439.906,273.927 h 6.803 v 0 h 2.704 10 10 10 10 10 10.551 2.65 10e-4 6.056" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path110" d="m 539.678,303.466 h 10 10 10 10 4.417" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path112" d="m 587.169,156.53 v 0" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path114" d="m 543.74,409.452 10,0.029 10,0.03 10,0.03 9.999,0.029 3.43,0.01" />
<path style="fill:none;stroke:#CB7590;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" id="path116" d="m 615.989,393.41 v -10 -10 -10 -10 -10 -10 -10 -10 -6.581" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

View File

@ -49,6 +49,8 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST'
export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
export const COMPOSE_DOODLE_SET = 'COMPOSE_DOODLE_SET';
export function changeCompose(text) {
return {
type: COMPOSE_CHANGE,
@ -182,6 +184,13 @@ export function submitComposeFail(error) {
};
};
export function doodleSet(options) {
return {
type: COMPOSE_DOODLE_SET,
options: options,
};
};
export function uploadCompose(files) {
return function (dispatch, getState) {
if (getState().getIn(['compose', 'media_attachments']).size > 3) {

View File

@ -88,7 +88,7 @@ class Account extends ImmutablePureComponent {
if (requested) {
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
} else if (blocking) {
buttons = <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
} else if (muting) {
let hidingNotificationsButton;
if (account.getIn(['relationship', 'muting_notifications'])) {

View File

@ -11,26 +11,36 @@ export default class DisplayName extends React.PureComponent {
};
render () {
const { account, others, localDomain } = this.props;
const displayNameHtml = { __html: account.get('display_name_html') };
const { others, localDomain } = this.props;
let suffix;
let displayName, suffix, account;
if (others && others.size > 1) {
suffix = `+${others.size}`;
displayName = others.take(2).map(a => <bdi key={a.get('id')}><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi>).reduce((prev, cur) => [prev, ', ', cur]);
if (others.size - 2 > 0) {
suffix = `+${others.size - 2}`;
}
} else {
if (others && others.size > 0) {
account = others.first();
} else {
account = this.props.account;
}
let acct = account.get('acct');
if (acct.indexOf('@') === -1 && localDomain) {
acct = `${acct}@${localDomain}`;
}
suffix = <span className='display-name__account'>@{acct}</span>;
displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>;
suffix = <span className='display-name__account'>@{acct}</span>;
}
return (
<span className='display-name'>
<bdi><strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /></bdi> {suffix}
{displayName} {suffix}
</span>
);
}

View File

@ -32,7 +32,7 @@ class Account extends ImmutablePureComponent {
</span>
<div className='domain__buttons'>
<IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} />
<IconButton active icon='unlock' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} />
</div>
</div>
</div>

View File

@ -22,6 +22,7 @@ export default class IconButton extends React.PureComponent {
animate: PropTypes.bool,
overlay: PropTypes.bool,
tabIndex: PropTypes.string,
label: PropTypes.string,
};
static defaultProps = {
@ -42,14 +43,18 @@ export default class IconButton extends React.PureComponent {
}
render () {
const style = {
let style = {
fontSize: `${this.props.size}px`,
width: `${this.props.size * 1.28571429}px`,
height: `${this.props.size * 1.28571429}px`,
lineHeight: `${this.props.size}px`,
...this.props.style,
...(this.props.active ? this.props.activeStyle : {}),
};
if (!this.props.label) {
style.width = `${this.props.size * 1.28571429}px`;
} else {
style.textAlign = 'left';
}
const {
active,
@ -104,7 +109,8 @@ export default class IconButton extends React.PureComponent {
style={style}
tabIndex={tabIndex}
>
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
{this.props.label}
</button>
)}
</Motion>

View File

@ -65,7 +65,7 @@ export default class IntersectionObserverArticle extends React.Component {
}
updateStateAfterIntersection = (prevState) => {
if (prevState.isIntersecting && !this.entry.isIntersecting) {
if (prevState.isIntersecting !== false && !this.entry.isIntersecting) {
scheduleIdleTask(this.hideIfNotIntersecting);
}
return {

View File

@ -194,6 +194,8 @@ class MediaGallery extends React.PureComponent {
height: PropTypes.number.isRequired,
onOpenMedia: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
};
static defaultProps = {
@ -202,6 +204,7 @@ class MediaGallery extends React.PureComponent {
state = {
visible: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all',
width: this.props.defaultWidth,
};
componentWillReceiveProps (nextProps) {
@ -221,6 +224,7 @@ class MediaGallery extends React.PureComponent {
handleRef = (node) => {
if (node /*&& this.isStandaloneEligible()*/) {
// offsetWidth triggers a layout, so only calculate when we need to
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
this.setState({
width: node.offsetWidth,
});
@ -233,8 +237,10 @@ class MediaGallery extends React.PureComponent {
}
render () {
const { media, intl, sensitive, height } = this.props;
const { width, visible } = this.state;
const { media, intl, sensitive, height, defaultWidth } = this.props;
const { visible } = this.state;
const width = this.state.width || defaultWidth;
let children;

View File

@ -60,6 +60,8 @@ const getUnitDelay = units => {
}
};
const fallbackFormat = new Intl.DateTimeFormat('en', shortDateFormatOptions);
export const timeAgoString = (intl, date, now, year) => {
const delta = now - date.getTime();

View File

@ -40,6 +40,7 @@ export default class ScrollableList extends PureComponent {
state = {
fullscreen: null,
cachedMediaWidth: 250, // Default media/card width using default Mastodon theme
};
intersectionObserverWrapper = new IntersectionObserverWrapper();
@ -130,6 +131,20 @@ export default class ScrollableList extends PureComponent {
this.handleScroll();
}
getScrollPosition = () => {
if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
return { height: this.node.scrollHeight, top: this.node.scrollTop };
} else {
return null;
}
}
updateScrollBottom = (snapshot) => {
const newScrollTop = this.node.scrollHeight - snapshot;
this.setScrollTop(newScrollTop);
}
getSnapshotBeforeUpdate (prevProps) {
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
@ -150,6 +165,12 @@ export default class ScrollableList extends PureComponent {
}
}
cacheMediaWidth = (width) => {
if (width && this.state.cachedMediaWidth !== width) {
this.setState({ cachedMediaWidth: width });
}
}
componentWillUnmount () {
this.clearMouseIdleTimer();
this.detachScrollListener();
@ -239,7 +260,12 @@ export default class ScrollableList extends PureComponent {
intersectionObserverWrapper={this.intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
>
{child}
{React.cloneElement(child, {
getScrollPosition: this.getScrollPosition,
updateScrollBottom: this.updateScrollBottom,
cachedMediaWidth: this.state.cachedMediaWidth,
cacheMediaWidth: this.cacheMediaWidth,
})}
</IntersectionObserverArticleContainer>
))}

View File

@ -68,6 +68,10 @@ class Status extends ImmutablePureComponent {
onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func,
showThread: PropTypes.bool,
getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number,
};
// Avoid checking props that are functions (and whose equality will always
@ -79,6 +83,43 @@ class Status extends ImmutablePureComponent {
'hidden',
];
// Track height changes we know about to compensate scrolling
componentDidMount () {
this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
}
getSnapshotBeforeUpdate () {
if (this.props.getScrollPosition) {
return this.props.getScrollPosition();
} else {
return null;
}
}
// Compensate height changes
componentDidUpdate (prevProps, prevState, snapshot) {
const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
if (doShowCard && !this.didShowCard) {
this.didShowCard = true;
if (snapshot !== null && this.props.updateScrollBottom) {
if (this.node && this.node.offsetTop < snapshot.top) {
this.props.updateScrollBottom(snapshot.height - snapshot.top);
}
}
}
}
componentWillUnmount() {
if (this.node && this.props.getScrollPosition) {
const position = this.props.getScrollPosition();
if (position !== null && this.node.offsetTop < position.top) {
requestAnimationFrame(() => {
this.props.updateScrollBottom(position.height - position.top);
});
}
}
}
handleClick = () => {
if (this.props.onClick) {
this.props.onClick();
@ -165,6 +206,10 @@ class Status extends ImmutablePureComponent {
}
}
handleRef = c => {
this.node = c;
}
render () {
let media = null;
let statusAvatar, prepend, rebloggedByText;
@ -179,7 +224,7 @@ class Status extends ImmutablePureComponent {
if (hidden) {
return (
<div>
<div ref={this.handleRef}>
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
{status.get('content')}
</div>
@ -194,7 +239,7 @@ class Status extends ImmutablePureComponent {
return (
<HotKeys handlers={minHandlers}>
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0'>
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
</div>
</HotKeys>
@ -242,11 +287,12 @@ class Status extends ImmutablePureComponent {
preview={video.get('preview_url')}
src={video.get('url')}
alt={video.get('description')}
width={239}
width={this.props.cachedMediaWidth}
height={110}
inline
sensitive={status.get('sensitive')}
onOpenVideo={this.handleOpenVideo}
cacheWidth={this.props.cacheMediaWidth}
/>
)}
</Bundle>
@ -254,7 +300,16 @@ class Status extends ImmutablePureComponent {
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />}
{Component => (
<Component
media={status.get('media_attachments')}
sensitive={status.get('sensitive')}
height={110}
onOpenMedia={this.props.onOpenMedia}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
/>
)}
</Bundle>
);
}
@ -264,11 +319,13 @@ class Status extends ImmutablePureComponent {
onOpenMedia={this.props.onOpenMedia}
card={status.get('card')}
compact
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
/>
);
}
if (otherAccounts) {
if (otherAccounts && otherAccounts.size > 0) {
statusAvatar = <AvatarComposite accounts={otherAccounts} size={48} />;
} else if (account === undefined || account === null) {
statusAvatar = <Avatar account={status.get('account')} size={48} />;
@ -290,7 +347,7 @@ class Status extends ImmutablePureComponent {
return (
<HotKeys handlers={handlers}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))} ref={this.handleRef}>
{prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
@ -308,14 +365,15 @@ class Status extends ImmutablePureComponent {
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable />
{media}
{showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && (
<button className='status__content__read-more-button' onClick={this.handleClick}>
<i className='fa fa-fw fa-sort-amount-desc status__prepend-icon' />
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
</button>
)}
{media}
<StatusActionBar status={status} account={account} {...other} />
</div>
</div>

View File

@ -77,7 +77,11 @@ class StatusActionBar extends ImmutablePureComponent {
]
handleReplyClick = () => {
this.props.onReply(this.props.status, this.context.router.history);
if (me) {
this.props.onReply(this.props.status, this.context.router.history);
} else {
this._openInteractionDialog('reply');
}
}
handleShareClick = () => {
@ -90,11 +94,23 @@ class StatusActionBar extends ImmutablePureComponent {
}
handleFavouriteClick = () => {
this.props.onFavourite(this.props.status);
if (me) {
this.props.onFavourite(this.props.status);
} else {
this._openInteractionDialog('favourite');
}
}
handleReblogClick = (e) => {
this.props.onReblog(this.props.status, e);
handleReblogClick = e => {
if (me) {
this.props.onReblog(this.props.status, e);
} else {
this._openInteractionDialog('reblog');
}
}
_openInteractionDialog = type => {
window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
}
handleDeleteClick = () => {
@ -211,9 +227,9 @@ class StatusActionBar extends ImmutablePureComponent {
return (
<div className='status__action-bar'>
<div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
<IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} />
<IconButton className='status__action-bar-button' disabled={!publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='floppy-o' onClick={this.handleFavouriteClick} />
{shareButton}
<div className='status__action-bar-dropdown'>

View File

@ -7,6 +7,7 @@ import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
import Compose from '../features/standalone/compose';
import initialState from '../initial_state';
import { fetchCustomEmojis } from '../actions/custom_emojis';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
@ -17,6 +18,8 @@ if (initialState) {
store.dispatch(hydrateStore(initialState));
}
store.dispatch(fetchCustomEmojis());
export default class TimelineContainer extends React.PureComponent {
static propTypes = {

View File

@ -132,7 +132,7 @@ class Header extends ImmutablePureComponent {
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = (
<div className='account--action-button'>
<IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
<IconButton size={26} icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
</div>
);
}

View File

@ -18,6 +18,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['user_lists', 'blocks', 'next']),
});
export default @connect(mapStateToProps)
@ -29,6 +30,7 @@ class Blocks extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
@ -41,7 +43,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, accountIds, shouldUpdateScroll } = this.props;
const { intl, accountIds, shouldUpdateScroll, hasMore } = this.props;
if (!accountIds) {
return (
@ -59,6 +61,7 @@ class Blocks extends ImmutablePureComponent {
<ScrollableList
scrollKey='blocks'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
>

View File

@ -0,0 +1,133 @@
// Package imports //
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl, defineMessages } from 'react-intl';
// Our imports //
import ComposeDropdown from './compose_dropdown';
import { uploadCompose } from '../../../actions/compose';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { openModal } from '../../../actions/modal';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const messages = defineMessages({
upload :
{ id: 'compose.attach.upload', defaultMessage: 'Upload a file' },
doodle :
{ id: 'compose.attach.doodle', defaultMessage: 'Draw something' },
attach :
{ id: 'compose.attach', defaultMessage: 'Attach...' },
});
const mapStateToProps = state => ({
// This horrible expression is copied from vanilla upload_button_container
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
resetFileKey: state.getIn(['compose', 'resetFileKey']),
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
});
const mapDispatchToProps = dispatch => ({
onSelectFile (files) {
dispatch(uploadCompose(files));
},
onOpenDoodle () {
dispatch(openModal('DOODLE', { noEsc: true }));
},
});
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
export default class ComposeAttachOptions extends ImmutablePureComponent {
static propTypes = {
intl : PropTypes.object.isRequired,
resetFileKey: PropTypes.number,
acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
disabled: PropTypes.bool,
onSelectFile: PropTypes.func.isRequired,
onOpenDoodle: PropTypes.func.isRequired,
};
handleItemClick = bt => {
if (bt === 'upload') {
this.fileElement.click();
}
if (bt === 'doodle') {
this.props.onOpenDoodle();
}
this.dropdown.setState({ open: false });
};
handleFileChange = (e) => {
if (e.target.files.length > 0) {
this.props.onSelectFile(e.target.files);
}
}
setFileRef = (c) => {
this.fileElement = c;
}
setDropdownRef = (c) => {
this.dropdown = c;
}
render () {
const { intl, resetFileKey, disabled, acceptContentTypes } = this.props;
const options = [
{ icon: 'cloud-upload', text: messages.upload, name: 'upload' },
{ icon: 'paint-brush', text: messages.doodle, name: 'doodle' },
];
const optionElems = options.map((item) => {
const hdl = () => this.handleItemClick(item.name);
return (
<div
role='button'
tabIndex='0'
key={item.name}
onClick={hdl}
className='privacy-dropdown__option'
>
<div className='privacy-dropdown__option__icon'>
<i className={`fa fa-fw fa-${item.icon}`} />
</div>
<div className='privacy-dropdown__option__content'>
<strong>{intl.formatMessage(item.text)}</strong>
</div>
</div>
);
});
return (
<div>
<ComposeDropdown
title={intl.formatMessage(messages.attach)}
icon='paperclip'
disabled={disabled}
ref={this.setDropdownRef}
>
{optionElems}
</ComposeDropdown>
<input
key={resetFileKey}
ref={this.setFileRef}
type='file'
multiple={false}
accept={acceptContentTypes.toArray().join(',')}
onChange={this.handleFileChange}
disabled={disabled}
style={{ display: 'none' }}
/>
</div>
);
}
}

View File

@ -0,0 +1,76 @@
// Package imports //
import React from 'react';
import PropTypes from 'prop-types';
// Mastodon imports //
import IconButton from '../../../components/icon_button';
const iconStyle = {
height : null,
lineHeight : '27px',
};
export default class ComposeDropdown extends React.PureComponent {
static propTypes = {
title: PropTypes.string.isRequired,
icon: PropTypes.string,
highlight: PropTypes.bool,
disabled: PropTypes.bool,
children: PropTypes.arrayOf(PropTypes.node).isRequired,
};
state = {
open: false,
};
onGlobalClick = (e) => {
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
this.setState({ open: false });
}
};
componentDidMount () {
window.addEventListener('click', this.onGlobalClick);
window.addEventListener('touchstart', this.onGlobalClick);
}
componentWillUnmount () {
window.removeEventListener('click', this.onGlobalClick);
window.removeEventListener('touchstart', this.onGlobalClick);
}
onToggleDropdown = () => {
if (this.props.disabled) return;
this.setState({ open: !this.state.open });
};
setRef = (c) => {
this.node = c;
};
render () {
const { open } = this.state;
let { highlight, title, icon, disabled } = this.props;
if (!icon) icon = 'ellipsis-h';
return (
<div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${highlight ? 'active' : ''} `}>
<div className='advanced-options-dropdown__value'>
<IconButton
className={'inverted'}
title={title}
icon={icon} active={open || highlight}
size={18}
style={iconStyle}
disabled={disabled}
onClick={this.onToggleDropdown}
/>
</div>
<div className='advanced-options-dropdown__dropdown'>
{this.props.children}
</div>
</div>
);
}
}

View File

@ -5,7 +5,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl } from 'react-intl';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
@ -17,6 +16,7 @@ import { isMobile } from '../../../is_mobile';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
import { countableText } from '../util/counter';
import ComposeAttachOptions from './attach_options';
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
@ -85,7 +85,7 @@ class ComposeForm extends ImmutablePureComponent {
const { is_submitting, is_changing_upload, is_uploading, anyMedia } = this.props;
const fulltext = [this.props.spoiler_text, countableText(this.props.text)].join('');
if (is_submitting || is_uploading || is_changing_upload || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
if (is_submitting || is_uploading || is_changing_upload || length(fulltext) > 512 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return;
}
@ -161,7 +161,7 @@ class ComposeForm extends ImmutablePureComponent {
const { intl, onPaste, showSearch, anyMedia } = this.props;
const disabled = this.props.is_submitting;
const text = [this.props.spoiler_text, countableText(this.props.text)].join('');
const disabledButton = disabled || this.props.is_uploading || this.props.is_changing_upload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
const disabledButton = disabled || this.props.is_uploading || this.props.is_changing_upload || length(text) > 512 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
let publishText = '';
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
@ -179,7 +179,7 @@ class ComposeForm extends ImmutablePureComponent {
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input' id='cw-spoiler-input' ref={this.setSpoilerText} />
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} tabIndex={this.props.spoiler ? 0 : -1} type='text' className='spoiler-input__input' id='cw-spoiler-input' ref={this.setSpoilerText} />
</label>
</div>
@ -208,12 +208,13 @@ class ComposeForm extends ImmutablePureComponent {
<div className='compose-form__buttons-wrapper'>
<div className='compose-form__buttons'>
<UploadButtonContainer />
<PrivacyDropdownContainer />
<ComposeAttachOptions />
<SensitiveButtonContainer />
<div className='compose-form__buttons-separator' />
<PrivacyDropdownContainer />
<SpoilerButtonContainer />
</div>
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
<div className='character-counter__wrapper'><CharacterCounter max={512} text={text} /></div>
</div>
<div className='compose-form__publish'>

View File

@ -357,8 +357,8 @@ class EmojiPickerDropdown extends React.PureComponent {
<div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
<img
className={classNames('emojione', { 'pulse-loading': active && loading })}
alt='🙂'
src={`${assetHost}/emoji/1f602.svg`}
alt='🤔'
src={`${assetHost}/emoji/1f914.svg`}
/>
</div>

View File

@ -214,7 +214,7 @@ class PrivacyDropdown extends React.PureComponent {
this.options = [
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
{ icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
];

View File

@ -107,9 +107,8 @@ class Upload extends ImmutablePureComponent {
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
<input
<textarea
placeholder={intl.formatMessage(messages.description)}
type='text'
value={description}
maxLength={420}
onFocus={this.handleInputFocus}

View File

@ -12,13 +12,13 @@ const DEFAULTS = [
'+1',
'grinning',
'kissing_heart',
'heart_eyes',
'vhs',
'laughing',
'stuck_out_tongue_winking_eye',
'floppy_disk',
'sweat_smile',
'joy',
'yum',
'disappointed',
'computer',
'thinking_face',
'weary',
'sob',

View File

@ -19,6 +19,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
domains: state.getIn(['domain_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
});
export default @connect(mapStateToProps)
@ -29,6 +30,7 @@ class Blocks extends ImmutablePureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
hasMore: PropTypes.bool,
domains: ImmutablePropTypes.orderedSet,
intl: PropTypes.object.isRequired,
};
@ -42,7 +44,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, domains, shouldUpdateScroll } = this.props;
const { intl, domains, shouldUpdateScroll, hasMore } = this.props;
if (!domains) {
return (
@ -60,6 +62,7 @@ class Blocks extends ImmutablePureComponent {
<ScrollableList
scrollKey='domain_blocks'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
>

View File

@ -18,6 +18,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
});
export default @connect(mapStateToProps)
@ -28,6 +29,7 @@ class FollowRequests extends ImmutablePureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
@ -41,7 +43,7 @@ class FollowRequests extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, shouldUpdateScroll, accountIds } = this.props;
const { intl, shouldUpdateScroll, accountIds, hasMore } = this.props;
if (!accountIds) {
return (
@ -59,6 +61,7 @@ class FollowRequests extends ImmutablePureComponent {
<ScrollableList
scrollKey='follow_requests'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
>

View File

@ -14,20 +14,20 @@ import { Link } from 'react-router-dom';
import NavigationBar from '../compose/components/navigation_bar';
const messages = defineMessages({
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
home_timeline: { id: 'tabs_bar.home', defaultMessage: '/timelines/home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: '~/.notifications' },
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: '/timelines/federated' },
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: '/timelines/local' },
direct: { id: 'navigation_bar.direct', defaultMessage: '~/.dms' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'edit ~/.config' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: '~/.follow-requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: '~/.florps' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: '~/.blocked' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: '~/.muted/domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: '~/.muted' },
pins: { id: 'navigation_bar.pins', defaultMessage: '~/.pinned' },
lists: { id: 'navigation_bar.lists', defaultMessage: '~/.lists' },
discover: { id: 'navigation_bar.discover', defaultMessage: 'Discover' },
personal: { id: 'navigation_bar.personal', defaultMessage: 'Personal' },
security: { id: 'navigation_bar.security', defaultMessage: 'Security' },
@ -158,11 +158,8 @@ class GettingStarted extends ImmutablePureComponent {
<ul>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
{multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this instance' /></a> · </li>
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href='/auth/sign_out' data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
</ul>
@ -171,7 +168,7 @@ class GettingStarted extends ImmutablePureComponent {
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
values={{ github: <span><a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> (v{version})</span> }}
values={{ github: <a href='https://cybre.tech/cybrespace/mastodon' rel='noopener' target='_blank'>cybrespace/mastodon</a> }}
/>
</p>
</div>

View File

@ -1,10 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
import AsyncSelect from 'react-select/lib/Async';
const messages = defineMessages({
placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' },
noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' },
});
export default @injectIntl
class ColumnSettings extends React.PureComponent {
@ -25,6 +30,7 @@ class ColumnSettings extends React.PureComponent {
tags (mode) {
let tags = this.props.settings.getIn(['tags', mode]) || [];
if (tags.toJSON) {
return tags.toJSON();
} else {
@ -32,33 +38,36 @@ class ColumnSettings extends React.PureComponent {
}
};
onSelect = (mode) => {
return (value) => {
this.props.onChange(['tags', mode], value);
};
};
onSelect = mode => value => this.props.onChange(['tags', mode], value);
onToggle = () => {
if (this.state.open && this.hasTags()) {
this.props.onChange('tags', {});
}
this.setState({ open: !this.state.open });
};
noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions);
modeSelect (mode) {
return (
<div className='column-settings__section'>
{this.modeLabel(mode)}
<div className='column-settings__row'>
<span className='column-settings__section'>
{this.modeLabel(mode)}
</span>
<AsyncSelect
isMulti
autoFocus
value={this.tags(mode)}
settings={this.props.settings}
settingPath={['tags', mode]}
onChange={this.onSelect(mode)}
loadOptions={this.props.onLoad}
classNamePrefix='column-settings__hashtag-select'
className='column-select__container'
classNamePrefix='column-select'
name='tags'
placeholder={this.props.intl.formatMessage(messages.placeholder)}
noOptionsMessage={this.noOptionsMessage}
/>
</div>
);
@ -66,11 +75,15 @@ class ColumnSettings extends React.PureComponent {
modeLabel (mode) {
switch(mode) {
case 'any': return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
case 'all': return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
case 'none': return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
case 'any':
return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
case 'all':
return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
case 'none':
return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
default:
return '';
}
return '';
};
render () {
@ -78,23 +91,21 @@ class ColumnSettings extends React.PureComponent {
<div>
<div className='column-settings__row'>
<div className='setting-toggle'>
<Toggle
id='hashtag.column_settings.tag_toggle'
onChange={this.onToggle}
checked={this.state.open}
/>
<Toggle id='hashtag.column_settings.tag_toggle' onChange={this.onToggle} checked={this.state.open} />
<span className='setting-toggle__label'>
<FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' />
</span>
</div>
</div>
{this.state.open &&
{this.state.open && (
<div className='column-settings__hashtags'>
{this.modeSelect('any')}
{this.modeSelect('all')}
{this.modeSelect('none')}
</div>
}
)}
</div>
);
}

View File

@ -41,15 +41,19 @@ class HashtagTimeline extends React.PureComponent {
title = () => {
let title = [this.props.params.id];
if (this.additionalFor('any')) {
title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
title.push(' ', <FormattedMessage key='any' id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
}
if (this.additionalFor('all')) {
title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.all' values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
title.push(' ', <FormattedMessage key='all' id='hashtag.column_header.tag_mode.all' values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
}
if (this.additionalFor('none')) {
title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
title.push(' ', <FormattedMessage key='none' id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
}
return title;
}
@ -77,9 +81,10 @@ class HashtagTimeline extends React.PureComponent {
let all = (tags.all || []).map(tag => tag.value);
let none = (tags.none || []).map(tag => tag.value);
[id, ...any].map((tag) => {
this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => {
[id, ...any].map(tag => {
this.disconnects.push(dispatch(connectHashtagStream(id, tag, status => {
let tags = status.tags.map(tag => tag.name);
return all.filter(tag => tags.includes(tag)).length === all.length &&
none.filter(tag => tags.includes(tag)).length === 0;
})));
@ -95,12 +100,14 @@ class HashtagTimeline extends React.PureComponent {
const { dispatch } = this.props;
const { id, tags } = this.props.params;
this._subscribe(dispatch, id, tags);
dispatch(expandHashtagTimeline(id, { tags }));
}
componentWillReceiveProps (nextProps) {
const { dispatch, params } = this.props;
const { id, tags } = nextProps.params;
if (id !== params.id || !isEqual(tags, params.tags)) {
this._unsubscribe();
this._subscribe(dispatch, id, tags);

View File

@ -18,6 +18,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'mutes', 'items']),
hasMore: !!state.getIn(['user_lists', 'mutes', 'next']),
});
export default @connect(mapStateToProps)
@ -28,6 +29,7 @@ class Mutes extends ImmutablePureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
@ -41,7 +43,7 @@ class Mutes extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, shouldUpdateScroll, accountIds } = this.props;
const { intl, shouldUpdateScroll, hasMore, accountIds } = this.props;
if (!accountIds) {
return (
@ -59,6 +61,7 @@ class Mutes extends ImmutablePureComponent {
<ScrollableList
scrollKey='mutes'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
>

View File

@ -29,7 +29,15 @@ class Notification extends ImmutablePureComponent {
onMoveUp: PropTypes.func.isRequired,
onMoveDown: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
status: PropTypes.option,
intl: PropTypes.object.isRequired,
getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number,
};
handleMoveUp = () => {
@ -64,14 +72,32 @@ class Notification extends ImmutablePureComponent {
onMention(notification.get('account'), this.context.router.history);
}
handleHotkeyFavourite = () => {
const { status } = this.props;
if (status) this.props.onFavourite(status);
}
handleHotkeyBoost = e => {
const { status } = this.props;
if (status) this.props.onReblog(status, e);
}
handleHotkeyToggleHidden = () => {
const { status } = this.props;
if (status) this.props.onToggleHidden(status);
}
getHandlers () {
return {
moveUp: this.handleMoveUp,
moveDown: this.handleMoveDown,
reply: this.handleMention,
favourite: this.handleHotkeyFavourite,
boost: this.handleHotkeyBoost,
mention: this.handleMention,
open: this.handleOpen,
openProfile: this.handleOpenProfile,
mention: this.handleMention,
reply: this.handleMention,
moveUp: this.handleMoveUp,
moveDown: this.handleMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
};
}
@ -106,6 +132,10 @@ class Notification extends ImmutablePureComponent {
onMoveDown={this.handleMoveDown}
onMoveUp={this.handleMoveUp}
contextType='notifications'
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
);
}
@ -118,7 +148,7 @@ class Notification extends ImmutablePureComponent {
<div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favourite', defaultMessage: '{name} favourited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<i className='fa fa-fw fa-star star-icon' />
<i className='fa fa-fw fa-floppy-o star-icon' />
</div>
<span title={notification.get('created_at')}>
@ -126,7 +156,17 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss hidden={!!this.props.hidden} />
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={!!this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
);
@ -148,7 +188,17 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss hidden={this.props.hidden} />
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
);

View File

@ -1,14 +1,31 @@
import { connect } from 'react-redux';
import { makeGetNotification } from '../../../selectors';
import { makeGetNotification, makeGetStatus } from '../../../selectors';
import Notification from '../components/notification';
import { openModal } from '../../../actions/modal';
import { mentionCompose } from '../../../actions/compose';
import {
reblog,
favourite,
unreblog,
unfavourite,
} from '../../../actions/interactions';
import {
hideStatus,
revealStatus,
} from '../../../actions/statuses';
import { boostModal } from '../../../initial_state';
const makeMapStateToProps = () => {
const getNotification = makeGetNotification();
const getStatus = makeGetStatus();
const mapStateToProps = (state, props) => ({
notification: getNotification(state, props.notification, props.accountId),
});
const mapStateToProps = (state, props) => {
const notification = getNotification(state, props.notification, props.accountId);
return {
notification: notification,
status: notification.get('status') ? getStatus(state, { id: notification.get('status') }) : null,
};
};
return mapStateToProps;
};
@ -17,6 +34,38 @@ const mapDispatchToProps = dispatch => ({
onMention: (account, router) => {
dispatch(mentionCompose(account, router));
},
onModalReblog (status) {
dispatch(reblog(status));
},
onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
}
}
},
onFavourite (status) {
if (status.get('favourited')) {
dispatch(unfavourite(status));
} else {
dispatch(favourite(status));
}
},
onToggleHidden (status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')));
} else {
dispatch(hideStatus(status.get('id')));
}
},
});
export default connect(makeMapStateToProps, mapDispatchToProps)(Notification);

View File

@ -175,7 +175,7 @@ class ActionBar extends React.PureComponent {
<div className='detailed-status__action-bar'>
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='floppy-o' onClick={this.handleFavouriteClick} /></div>
{shareButton}
<div className='detailed-status__action-bar-dropdown'>

View File

@ -60,6 +60,8 @@ export default class Card extends React.PureComponent {
maxDescription: PropTypes.number,
onOpenMedia: PropTypes.func.isRequired,
compact: PropTypes.bool,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
};
static defaultProps = {
@ -68,7 +70,7 @@ export default class Card extends React.PureComponent {
};
state = {
width: 280,
width: this.props.defaultWidth || 280,
embedded: false,
};
@ -111,6 +113,7 @@ export default class Card extends React.PureComponent {
setRef = c => {
if (c) {
if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth);
this.setState({ width: c.offsetWidth });
}
}

View File

@ -86,7 +86,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
}
render () {
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const outerStyle = { boxSizing: 'border-box' };
const { compact } = this.props;
@ -172,7 +172,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
if (this.context.router) {
favouriteLink = (
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
<i className='fa fa-star' />
<i className='fa fa-floppy-o' />
<span className='detailed-status__favorites'>
<FormattedNumber value={status.get('favourites_count')} />
</span>
@ -181,7 +181,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
} else {
favouriteLink = (
<a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
<i className='fa fa-star' />
<i className='fa fa-floppy-o' />
<span className='detailed-status__favorites'>
<FormattedNumber value={status.get('favourites_count')} />
</span>

View File

@ -0,0 +1,614 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from '../../../components/button';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Atrament from 'atrament'; // the doodling library
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { doodleSet, uploadCompose } from '../../../actions/compose';
import IconButton from '../../../components/icon_button';
import { debounce, mapValues } from 'lodash';
import classNames from 'classnames';
// palette nicked from MyPaint, CC0
const palette = [
['rgb( 0, 0, 0)', 'Black'],
['rgb( 38, 38, 38)', 'Gray 15'],
['rgb( 77, 77, 77)', 'Grey 30'],
['rgb(128, 128, 128)', 'Grey 50'],
['rgb(171, 171, 171)', 'Grey 67'],
['rgb(217, 217, 217)', 'Grey 85'],
['rgb(255, 255, 255)', 'White'],
['rgb(128, 0, 0)', 'Maroon'],
['rgb(209, 0, 0)', 'English-red'],
['rgb(255, 54, 34)', 'Tomato'],
['rgb(252, 60, 3)', 'Orange-red'],
['rgb(255, 140, 105)', 'Salmon'],
['rgb(252, 232, 32)', 'Cadium-yellow'],
['rgb(243, 253, 37)', 'Lemon yellow'],
['rgb(121, 5, 35)', 'Dark crimson'],
['rgb(169, 32, 62)', 'Deep carmine'],
['rgb(255, 140, 0)', 'Orange'],
['rgb(255, 168, 18)', 'Dark tangerine'],
['rgb(217, 144, 88)', 'Persian orange'],
['rgb(194, 178, 128)', 'Sand'],
['rgb(255, 229, 180)', 'Peach'],
['rgb(100, 54, 46)', 'Bole'],
['rgb(108, 41, 52)', 'Dark cordovan'],
['rgb(163, 65, 44)', 'Chestnut'],
['rgb(228, 136, 100)', 'Dark salmon'],
['rgb(255, 195, 143)', 'Apricot'],
['rgb(255, 219, 188)', 'Unbleached silk'],
['rgb(242, 227, 198)', 'Straw'],
['rgb( 53, 19, 13)', 'Bistre'],
['rgb( 84, 42, 14)', 'Dark chocolate'],
['rgb(102, 51, 43)', 'Burnt sienna'],
['rgb(184, 66, 0)', 'Sienna'],
['rgb(216, 153, 12)', 'Yellow ochre'],
['rgb(210, 180, 140)', 'Tan'],
['rgb(232, 204, 144)', 'Dark wheat'],
['rgb( 0, 49, 83)', 'Prussian blue'],
['rgb( 48, 69, 119)', 'Dark grey blue'],
['rgb( 0, 71, 171)', 'Cobalt blue'],
['rgb( 31, 117, 254)', 'Blue'],
['rgb(120, 180, 255)', 'Bright french blue'],
['rgb(171, 200, 255)', 'Bright steel blue'],
['rgb(208, 231, 255)', 'Ice blue'],
['rgb( 30, 51, 58)', 'Medium jungle green'],
['rgb( 47, 79, 79)', 'Dark slate grey'],
['rgb( 74, 104, 93)', 'Dark grullo green'],
['rgb( 0, 128, 128)', 'Teal'],
['rgb( 67, 170, 176)', 'Turquoise'],
['rgb(109, 174, 199)', 'Cerulean frost'],
['rgb(173, 217, 186)', 'Tiffany green'],
['rgb( 22, 34, 29)', 'Gray-asparagus'],
['rgb( 36, 48, 45)', 'Medium dark teal'],
['rgb( 74, 104, 93)', 'Xanadu'],
['rgb(119, 198, 121)', 'Mint'],
['rgb(175, 205, 182)', 'Timberwolf'],
['rgb(185, 245, 246)', 'Celeste'],
['rgb(193, 255, 234)', 'Aquamarine'],
['rgb( 29, 52, 35)', 'Cal Poly Pomona'],
['rgb( 1, 68, 33)', 'Forest green'],
['rgb( 42, 128, 0)', 'Napier green'],
['rgb(128, 128, 0)', 'Olive'],
['rgb( 65, 156, 105)', 'Sea green'],
['rgb(189, 246, 29)', 'Green-yellow'],
['rgb(231, 244, 134)', 'Bright chartreuse'],
['rgb(138, 23, 137)', 'Purple'],
['rgb( 78, 39, 138)', 'Violet'],
['rgb(193, 75, 110)', 'Dark thulian pink'],
['rgb(222, 49, 99)', 'Cerise'],
['rgb(255, 20, 147)', 'Deep pink'],
['rgb(255, 102, 204)', 'Rose pink'],
['rgb(255, 203, 219)', 'Pink'],
['rgb(255, 255, 255)', 'White'],
['rgb(229, 17, 1)', 'RGB Red'],
['rgb( 0, 255, 0)', 'RGB Green'],
['rgb( 0, 0, 255)', 'RGB Blue'],
['rgb( 0, 255, 255)', 'CMYK Cyan'],
['rgb(255, 0, 255)', 'CMYK Magenta'],
['rgb(255, 255, 0)', 'CMYK Yellow'],
];
// re-arrange to the right order for display
let palReordered = [];
for (let row = 0; row < 7; row++) {
for (let col = 0; col < 11; col++) {
palReordered.push(palette[col * 7 + row]);
}
palReordered.push(null); // null indicates a <br />
}
// Utility for converting base64 image to binary for upload
// https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f
function dataURLtoFile(dataurl, filename) {
let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
const DOODLE_SIZES = {
normal: [500, 500, 'Square 500'],
tootbanner: [702, 330, 'Tootbanner'],
s640x480: [640, 480, '640×480 - 480p'],
s800x600: [800, 600, '800×600 - SVGA'],
s720x480: [720, 405, '720x405 - 16:9'],
};
const mapStateToProps = state => ({
options: state.getIn(['compose', 'doodle']),
});
const mapDispatchToProps = dispatch => ({
/** Set options in the redux store */
setOpt: (opts) => dispatch(doodleSet(opts)),
/** Submit doodle for upload */
submit: (file) => dispatch(uploadCompose([file])),
});
/**
* Doodling dialog with drawing canvas
*
* Keyboard shortcuts:
* - Delete: Clear screen, fill with background color
* - Backspace, Ctrl+Z: Undo one step
* - Ctrl held while drawing: Use background color
* - Shift held while clicking screen: Use fill tool
*
* Palette:
* - Left mouse button: pick foreground
* - Ctrl + left mouse button: pick background
* - Right mouse button: pick background
*/
@connect(mapStateToProps, mapDispatchToProps)
export default class DoodleModal extends ImmutablePureComponent {
static propTypes = {
options: ImmutablePropTypes.map,
onClose: PropTypes.func.isRequired,
setOpt: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
};
//region Option getters/setters
/** Foreground color */
get fg () {
return this.props.options.get('fg');
}
set fg (value) {
this.props.setOpt({ fg: value });
}
/** Background color */
get bg () {
return this.props.options.get('bg');
}
set bg (value) {
this.props.setOpt({ bg: value });
}
/** Swap Fg and Bg for drawing */
get swapped () {
return this.props.options.get('swapped');
}
set swapped (value) {
this.props.setOpt({ swapped: value });
}
/** Mode - 'draw' or 'fill' */
get mode () {
return this.props.options.get('mode');
}
set mode (value) {
this.props.setOpt({ mode: value });
}
/** Base line weight */
get weight () {
return this.props.options.get('weight');
}
set weight (value) {
this.props.setOpt({ weight: value });
}
/** Drawing opacity */
get opacity () {
return this.props.options.get('opacity');
}
set opacity (value) {
this.props.setOpt({ opacity: value });
}
/** Adaptive stroke - change width with speed */
get adaptiveStroke () {
return this.props.options.get('adaptiveStroke');
}
set adaptiveStroke (value) {
this.props.setOpt({ adaptiveStroke: value });
}
/** Smoothing (for mouse drawing) */
get smoothing () {
return this.props.options.get('smoothing');
}
set smoothing (value) {
this.props.setOpt({ smoothing: value });
}
/** Size preset */
get size () {
return this.props.options.get('size');
}
set size (value) {
this.props.setOpt({ size: value });
}
//endregion
/** Key up handler */
handleKeyUp = (e) => {
if (e.target.nodeName === 'INPUT') return;
if (e.key === 'Delete') {
e.preventDefault();
this.handleClearBtn();
return;
}
if (e.key === 'Backspace' || (e.key === 'z' && (e.ctrlKey || e.metaKey))) {
e.preventDefault();
this.undo();
}
if (e.key === 'Control' || e.key === 'Meta') {
this.controlHeld = false;
this.swapped = false;
}
if (e.key === 'Shift') {
this.shiftHeld = false;
this.mode = 'draw';
}
};
/** Key down handler */
handleKeyDown = (e) => {
if (e.key === 'Control' || e.key === 'Meta') {
this.controlHeld = true;
this.swapped = true;
}
if (e.key === 'Shift') {
this.shiftHeld = true;
this.mode = 'fill';
}
};
/**
* Component installed in the DOM, do some initial set-up
*/
componentDidMount () {
this.controlHeld = false;
this.shiftHeld = false;
this.swapped = false;
window.addEventListener('keyup', this.handleKeyUp, false);
window.addEventListener('keydown', this.handleKeyDown, false);
};
/**
* Tear component down
*/
componentWillUnmount () {
window.removeEventListener('keyup', this.handleKeyUp, false);
window.removeEventListener('keydown', this.handleKeyDown, false);
if (this.sketcher) this.sketcher.destroy();
}
/**
* Set reference to the canvas element.
* This is called during component init
*
* @param elem - canvas element
*/
setCanvasRef = (elem) => {
this.canvas = elem;
if (elem) {
elem.addEventListener('dirty', () => {
this.saveUndo();
this.sketcher._dirty = false;
});
elem.addEventListener('click', () => {
// sketcher bug - does not fire dirty on fill
if (this.mode === 'fill') {
this.saveUndo();
}
});
// prevent context menu
elem.addEventListener('contextmenu', (e) => {
e.preventDefault();
});
elem.addEventListener('mousedown', (e) => {
if (e.button === 2) {
this.swapped = true;
}
});
elem.addEventListener('mouseup', (e) => {
if (e.button === 2) {
this.swapped = this.controlHeld;
}
});
this.initSketcher(elem);
this.mode = 'draw'; // Reset mode - it's confusing if left at 'fill'
}
};
/**
* Set up the sketcher instance
*
* @param canvas - canvas element. Null if we're just resizing
*/
initSketcher (canvas = null) {
const sizepreset = DOODLE_SIZES[this.size];
if (this.sketcher) this.sketcher.destroy();
this.sketcher = new Atrament(canvas || this.canvas, sizepreset[0], sizepreset[1]);
if (canvas) {
this.ctx = this.sketcher.context;
this.updateSketcherSettings();
}
this.clearScreen();
}
/**
* Done button handler
*/
onDoneButton = () => {
const dataUrl = this.sketcher.toImage();
const file = dataURLtoFile(dataUrl, 'doodle.png');
this.props.submit(file);
this.props.onClose(); // close dialog
};
/**
* Cancel button handler
*/
onCancelButton = () => {
if (this.undos.length > 1 && !confirm('Discard doodle? All changes will be lost!')) {
return;
}
this.props.onClose(); // close dialog
};
/**
* Update sketcher options based on state
*/
updateSketcherSettings () {
if (!this.sketcher) return;
if (this.oldSize !== this.size) this.initSketcher();
this.sketcher.color = (this.swapped ? this.bg : this.fg);
this.sketcher.opacity = this.opacity;
this.sketcher.weight = this.weight;
this.sketcher.mode = this.mode;
this.sketcher.smoothing = this.smoothing;
this.sketcher.adaptiveStroke = this.adaptiveStroke;
this.oldSize = this.size;
}
/**
* Fill screen with background color
*/
clearScreen = () => {
this.ctx.fillStyle = this.bg;
this.ctx.fillRect(-1, -1, this.canvas.width+2, this.canvas.height+2);
this.undos = [];
this.doSaveUndo();
};
/**
* Undo one step
*/
undo = () => {
if (this.undos.length > 1) {
this.undos.pop();
const buf = this.undos.pop();
this.sketcher.clear();
this.ctx.putImageData(buf, 0, 0);
this.doSaveUndo();
}
};
/**
* Save canvas content into the undo buffer immediately
*/
doSaveUndo = () => {
this.undos.push(this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height));
};
/**
* Called on each canvas change.
* Saves canvas content to the undo buffer after some period of inactivity.
*/
saveUndo = debounce(() => {
this.doSaveUndo();
}, 100);
/**
* Palette left click.
* Selects Fg color (or Bg, if Control/Meta is held)
*
* @param e - event
*/
onPaletteClick = (e) => {
const c = e.target.dataset.color;
if (this.controlHeld) {
this.bg = c;
} else {
this.fg = c;
}
e.target.blur();
e.preventDefault();
};
/**
* Palette right click.
* Selects Bg color
*
* @param e - event
*/
onPaletteRClick = (e) => {
this.bg = e.target.dataset.color;
e.target.blur();
e.preventDefault();
};
/**
* Handle click on the Draw mode button
*
* @param e - event
*/
setModeDraw = (e) => {
this.mode = 'draw';
e.target.blur();
};
/**
* Handle click on the Fill mode button
*
* @param e - event
*/
setModeFill = (e) => {
this.mode = 'fill';
e.target.blur();
};
/**
* Handle click on Smooth checkbox
*
* @param e - event
*/
tglSmooth = (e) => {
this.smoothing = !this.smoothing;
e.target.blur();
};
/**
* Handle click on Adaptive checkbox
*
* @param e - event
*/
tglAdaptive = (e) => {
this.adaptiveStroke = !this.adaptiveStroke;
e.target.blur();
};
/**
* Handle change of the Weight input field
*
* @param e - event
*/
setWeight = (e) => {
this.weight = +e.target.value || 1;
};
/**
* Set size - clalback from the select box
*
* @param e - event
*/
changeSize = (e) => {
let newSize = e.target.value;
if (newSize === this.oldSize) return;
if (this.undos.length > 1 && !confirm('Change size? This will erase your drawing!')) {
return;
}
this.size = newSize;
};
handleClearBtn = () => {
if (this.undos.length > 1 && !confirm('Clear screen? This will erase your drawing!')) {
return;
}
this.clearScreen();
};
/**
* Render the component
*/
render () {
this.updateSketcherSettings();
return (
<div className='modal-root__modal doodle-modal'>
<div className='doodle-modal__container'>
<canvas ref={this.setCanvasRef} />
</div>
<div className='doodle-modal__action-bar'>
<div className='doodle-toolbar'>
<Button text='Done' onClick={this.onDoneButton} />
<Button text='Cancel' onClick={this.onCancelButton} />
</div>
<div className='filler' />
<div className='doodle-toolbar with-inputs'>
<div>
<label htmlFor='dd_smoothing'>Smoothing</label>
<span className='val'>
<input type='checkbox' id='dd_smoothing' onChange={this.tglSmooth} checked={this.smoothing} />
</span>
</div>
<div>
<label htmlFor='dd_adaptive'>Adaptive</label>
<span className='val'>
<input type='checkbox' id='dd_adaptive' onChange={this.tglAdaptive} checked={this.adaptiveStroke} />
</span>
</div>
<div>
<label htmlFor='dd_weight'>Weight</label>
<span className='val'>
<input type='number' min={1} id='dd_weight' value={this.weight} onChange={this.setWeight} />
</span>
</div>
<div>
<select aria-label='Canvas size' onInput={this.changeSize} defaultValue={this.size}>
{ Object.values(mapValues(DOODLE_SIZES, (val, k) =>
<option key={k} value={k}>{val[2]}</option>
)) }
</select>
</div>
</div>
<div className='doodle-toolbar'>
<IconButton icon='pencil' title='Draw' label='Draw' onClick={this.setModeDraw} size={18} active={this.mode === 'draw'} inverted />
<IconButton icon='bath' title='Fill' label='Fill' onClick={this.setModeFill} size={18} active={this.mode === 'fill'} inverted />
<IconButton icon='undo' title='Undo' label='Undo' onClick={this.undo} size={18} inverted />
<IconButton icon='trash' title='Clear' label='Clear' onClick={this.handleClearBtn} size={18} inverted />
</div>
<div className='doodle-palette'>
{
palReordered.map((c, i) =>
c === null ?
<br key={i} /> :
<button
key={i}
style={{ backgroundColor: c[0] }}
onClick={this.onPaletteClick}
onContextMenu={this.onPaletteRClick}
data-color={c[0]}
title={c[1]}
className={classNames({
'foreground': this.fg === c[0],
'background': this.bg === c[0],
})}
/>
)
}
</div>
</div>
</div>
);
}
}

View File

@ -8,6 +8,7 @@ import ActionsModal from './actions_modal';
import MediaModal from './media_modal';
import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import DoodleModal from './doodle_modal';
import ConfirmationModal from './confirmation_modal';
import FocalPointModal from './focal_point_modal';
import {
@ -22,6 +23,7 @@ const MODAL_COMPONENTS = {
'MEDIA': () => Promise.resolve({ default: MediaModal }),
'VIDEO': () => Promise.resolve({ default: VideoModal }),
'BOOST': () => Promise.resolve({ default: BoostModal }),
'DOODLE': () => Promise.resolve({ default: DoodleModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'MUTE': MuteModal,
'REPORT': ReportModal,
@ -43,6 +45,16 @@ export default class ModalRoot extends React.PureComponent {
getSnapshotBeforeUpdate () {
return { visible: !!this.props.type };
}
state = {
revealed: false,
};
handleKeyUp = (e) => {
if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
&& !!this.props.type && !this.props.props.noEsc) {
this.props.onClose();
}
}
componentDidUpdate (prevProps, prevState, { visible }) {
if (visible) {
@ -53,7 +65,7 @@ export default class ModalRoot extends React.PureComponent {
}
renderLoading = modalId => () => {
return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
return ['MEDIA', 'VIDEO', 'BOOST', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
}
renderError = (props) => {

View File

@ -99,6 +99,7 @@ class Video extends React.PureComponent {
onCloseVideo: PropTypes.func,
detailed: PropTypes.bool,
inline: PropTypes.bool,
cacheWidth: PropTypes.func,
intl: PropTypes.object.isRequired,
};
@ -108,7 +109,7 @@ class Video extends React.PureComponent {
volume: 0.5,
paused: true,
dragging: false,
containerWidth: false,
containerWidth: this.props.width,
fullscreen: false,
hovered: false,
muted: false,
@ -128,6 +129,7 @@ class Video extends React.PureComponent {
this.player = c;
if (c) {
if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
this.setState({
containerWidth: c.offsetWidth,
});
@ -136,6 +138,9 @@ class Video extends React.PureComponent {
setVideoRef = c => {
this.video = c;
if (this.video) {
this.setState({ volume: this.video.volume, muted: this.video.muted });
}
}
setSeekRef = c => {
@ -302,6 +307,10 @@ class Video extends React.PureComponent {
}
}
handleVolumeChange = () => {
this.setState({ volume: this.video.volume, muted: this.video.muted });
}
handleOpenVideo = () => {
const { src, preview, width, height, alt } = this.props;
const media = fromJS({
@ -337,7 +346,6 @@ class Video extends React.PureComponent {
width = containerWidth;
height = containerWidth / (16/9);
playerStyle.width = width;
playerStyle.height = height;
}
@ -387,6 +395,7 @@ class Video extends React.PureComponent {
onTimeUpdate={this.handleTimeUpdate}
onLoadedData={this.handleLoadedData}
onProgress={this.handleProgress}
onVolumeChange={this.handleVolumeChange}
/>
<button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
@ -409,7 +418,7 @@ class Video extends React.PureComponent {
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onMouseEnter={this.volumeSlider} onMouseLeave={this.volumeSlider} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
<span

View File

@ -0,0 +1,293 @@
{
"account.block": "Block @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "THESE NUMBERS ARE THE STUFF WHAT YOUR SERVER KNOWS ABOUT AND THERE MIGHT BE MORE THAT IT DONT KNOW ABOUT.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "edit ~/.profile",
"account.follow": "Follow",
"account.followers": "Followers",
"account.follows": "Follows",
"account.follows_you": "Follows you",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.media": "Media",
"account.mention": "Mention @{name}",
"account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Pings",
"account.posts_with_replies": "Pings with replies",
"account.report": "Report @{name}",
"account.requested": "Awaiting approval. Click to cancel follow request",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Show boosts from @{name}",
"account.unblock": "Unblock @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Unfollow",
"account.unmute": "Unmute @{name}",
"account.unmute_notifications": "Unmute notifications from @{name}",
"account.view_full_profile": "View full profile",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",
"bundle_column_error.title": "Network error",
"bundle_modal_error.close": "Close",
"bundle_modal_error.message": "Something went wrong while loading this component.",
"bundle_modal_error.retry": "Try again",
"column.blocks": "~/.blocked",
"column.community": "/timelines/local",
"column.direct": "~/.dms",
"column.favourites": "~/.florps",
"column.follow_requests": "~/.follow-requests",
"column.home": "/timelines/home",
"column.lists": "Lists",
"column.mutes": "~/.muted",
"column.notifications": "~/.notifications",
"column.pins": "~/.pinned",
"column.public": "/timelines/federated",
"column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
"column_header.moveRight_settings": "Move column to the right",
"column_header.pin": "Pin",
"column_header.show_settings": "Show settings",
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose.attach": "Attach...",
"compose.attach.doodle": "Draw something",
"compose.attach.upload": "Upload a file",
"compose_form.hashtag_warning": "This ping won't be listed under any hashtag as it is unlisted. Only public pings can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "What is in your databanks?",
"compose_form.publish": "Ping",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Write your warning here",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.confirm": "Block",
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"doodle_button.label": "Add a drawing",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
"emoji_button.custom": "Custom",
"emoji_button.flags": "Flags",
"emoji_button.food": "Food & Drink",
"emoji_button.label": "Insert emoji",
"emoji_button.nature": "Nature",
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objects",
"emoji_button.people": "People",
"emoji_button.recent": "Frequently used",
"emoji_button.search": "Search...",
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use query to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline",
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"getting_started.appsshort": "Apps",
"getting_started.faq": "FAQ",
"getting_started.heading": "Getting started",
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
"getting_started.userguide": "User Guide",
"home.column_settings.advanced": "Advanced",
"home.column_settings.basic": "Basic",
"home.column_settings.filter_regex": "Filter out by regular expressions",
"home.column_settings.show_reblogs": "Show relays",
"home.column_settings.show_replies": "Show replies",
"home.settings": "Column settings",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "to display this legend",
"keyboard_shortcuts.mention": "to mention author",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.search": "to focus search",
"keyboard_shortcuts.toot": "to start a brand new ping",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list",
"lightbox.close": "Close",
"lightbox.next": "Next",
"lightbox.previous": "Previous",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"loading_indicator.label": "Loading...",
"media_gallery.toggle_visible": "Toggle visibility",
"missing_indicator.label": "Not found",
"missing_indicator.sublabel": "This resource could not be found",
"mute_modal.hide_notifications": "Hide notifications from this user?",
"navigation_bar.blocks": "~/.blocks",
"navigation_bar.community_timeline": "/timelines/local",
"navigation_bar.direct": "~/.dms",
"navigation_bar.edit_profile": "edit ~/.profile",
"navigation_bar.favourites": "~/.florps",
"navigation_bar.follow_requests": "~/.follow-requests",
"navigation_bar.info": "/about/more",
"navigation_bar.keyboard_shortcuts": "~/.kbd/shortcuts.conf",
"navigation_bar.lists": "~/.lists",
"navigation_bar.logout": "Jack out",
"navigation_bar.mutes": "~/.muted",
"navigation_bar.pins": "~/.pinned",
"navigation_bar.preferences": "edit ~/.config",
"navigation_bar.public_timeline": "/timelines/federated",
"notification.favourite": "{name} florped your ping",
"notification.follow": "{name} followed you",
"notification.mention": "{name} mentioned you",
"notification.reblog": "{name} relayed your ping",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.column_settings.alert": "Desktop notifications",
"notifications.column_settings.favourite": "Favourites:",
"notifications.column_settings.follow": "New followers:",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.push": "Push notifications",
"notifications.column_settings.push_meta": "This device",
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Show in column",
"notifications.column_settings.sound": "Play sound",
"onboarding.done": "Done",
"onboarding.next": "Next",
"onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
"onboarding.page_four.home": "The home timeline shows posts from people you follow.",
"onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
"onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
"onboarding.page_one.full_handle": "Your full handle",
"onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
"onboarding.page_one.welcome": "Welcome to Mastodon!",
"onboarding.page_six.admin": "Your instance's admin is {admin}.",
"onboarding.page_six.almost_done": "Almost done...",
"onboarding.page_six.appetoot": "Hang ten on the cybrewaves!",
"onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
"onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
"onboarding.page_six.guidelines": "community guidelines",
"onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
"onboarding.page_six.various_app": "mobile apps",
"onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
"onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
"onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
"onboarding.skip": "Skip",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Post to mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Post to followers only",
"privacy.private.short": "Followers-only",
"privacy.public.long": "Post to public timelines",
"privacy.public.short": "Public",
"privacy.unlisted.long": "Do not post to public timelines",
"privacy.unlisted.short": "Unlisted",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancel",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Additional comments",
"report.submit": "Submit",
"report.target": "Reporting {target}",
"search.placeholder": "Query...",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Pings",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "Peer into the data grid...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This ping cannot be relayed",
"status.delete": "Delete",
"status.embed": "Embed",
"status.favourite": "Florp",
"status.load_more": "Load more",
"status.media_hidden": "Media hidden",
"status.mention": "Mention @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",
"status.pinned": "Pinned ping",
"status.reblog": "Relay",
"status.reblogged_by": "{name} relayed",
"status.reply": "Reply",
"status.replyAll": "Reply to thread",
"status.report": "Report @{name}",
"status.sensitive_toggle": "Click to view",
"status.sensitive_warning": "Sensitive content",
"status.share": "Share",
"status.show_less": "Show less",
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"tabs_bar.federated_timeline": "/timelines/federated",
"tabs_bar.home": "/timelines/home",
"tabs_bar.local_timeline": "/timelines/local",
"tabs_bar.notifications": "~/.notifications",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Undo",
"upload_progress.label": "Uploading...",
"video.close": "Close video",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"video.fullscreen": "Full screen",
"video.hide": "Hide video",
"video.mute": "Mute sound",
"video.pause": "Pause",
"video.play": "Play",
"video.unmute": "Unmute sound",
"video_player.expand": "Expand video",
"video_player.toggle_sound": "Toggle sound",
"video_player.toggle_visible": "Toggle visibility",
"video_player.video_error": "Video could not be played"
}

View File

@ -0,0 +1,2 @@
[
]

View File

@ -28,6 +28,7 @@ import {
COMPOSE_UPLOAD_CHANGE_REQUEST,
COMPOSE_UPLOAD_CHANGE_SUCCESS,
COMPOSE_UPLOAD_CHANGE_FAIL,
COMPOSE_DOODLE_SET,
COMPOSE_RESET,
} from '../actions/compose';
import { TIMELINE_DELETE } from '../actions/timelines';
@ -62,6 +63,17 @@ const initialState = ImmutableMap({
resetFileKey: Math.floor((Math.random() * 0x10000)),
idempotencyKey: null,
tagHistory: ImmutableList(),
doodle: ImmutableMap({
fg: 'rgb( 0, 0, 0)',
bg: 'rgb(255, 255, 255)',
swapped: false,
mode: 'draw',
size: 'normal',
weight: 2,
opacity: 1,
adaptiveStroke: true,
smoothing: false,
}),
});
function statusToTextMentions(state, status) {
@ -330,6 +342,8 @@ export default function compose(state = initialState, action) {
map.set('spoiler_text', '');
}
});
case COMPOSE_DOODLE_SET:
return state.mergeIn(['doodle'], action.options);
default:
return state;
}

View File

@ -8,3 +8,5 @@ loadPolyfills().then(() => {
}).catch(e => {
console.error(e);
});
require('what-input');

View File

@ -0,0 +1,13 @@
import ready from '../mastodon/ready';
ready(() => {
const image = document.querySelector('img');
image.addEventListener('mouseenter', () => {
image.src = '/oops.gif';
});
image.addEventListener('mouseleave', () => {
image.src = '/oops.png';
});
});

View File

@ -18,6 +18,12 @@ window.addEventListener('message', e => {
id: data.id,
height: document.getElementsByTagName('html')[0].scrollHeight,
}, '*');
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(sizeBioText);
} else {
sizeBioText();
}
});
});
@ -116,6 +122,17 @@ function main() {
document.head.appendChild(scrollbarWidthStyle);
scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
}
[].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => {
const props = JSON.parse(content.getAttribute('data-props'));
ReactDOM.render(<CardContainer locale={locale} {...props} />, content);
});
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(sizeBioText);
} else {
sizeBioText();
}
});
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
@ -214,6 +231,22 @@ function main() {
console.error(err);
}
});
delegate(document, '#account_note', 'input', sizeBioText);
function sizeBioText() {
const noteCounter = document.querySelector('.note-counter');
const bioTextArea = document.querySelector('#account_note');
if (noteCounter) {
noteCounter.textContent = 413 - length(bioTextArea.value);
}
if (bioTextArea) {
bioTextArea.style.height = 'auto';
bioTextArea.style.height = (bioTextArea.scrollHeight+3) + 'px';
}
}
}
loadPolyfills().then(main).catch(error => {

View File

@ -12,3 +12,58 @@
}
}
}
.rich-formatting a,
.rich-formatting p a,
.rich-formatting li a,
.landing-page__short-description p a,
.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;
}
}
.status__content__read-more-button {
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
.getting-started__footer a {
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}

View File

@ -0,0 +1,81 @@
@import 'application';
/* Allow columns to grow wider as the screen gets
* wider, but don't ever let them get more than
* 400px (some people have a bunch of columns!) */
@media screen and (min-width: 1300px) {
.column {
flex-grow: 1 !important;
max-width: 400px;
}
.drawer {
width: 17%; /* Not part of the flex fun */
max-width: 400px;
min-width: 330px;
}
}
/* Cap the column height at 100vh (fixed an old
* bug someone encountered in safari, but which
* I've seen resurface from time to time) */
.column {
max-height:100vh;
}
/* Don't show outline around statuses if we're in
* mouse or touch mode (rather than keyboard) */
[data-whatinput="mouse"], [data-whatinput="touch"] {
.status__content:focus, .status:focus,
.status__wrapper:focus, .status__content__text:focus {
outline: none;
}
}
/* Less emphatic show more */
.status__content__read-more-button {
font-size: 14px;
color: $dark-text-color;
.status__prepend-icon {
padding-right: 4px;
}
}
/* Show a little arrowey thing after the time in a
* status to signal that you can click it to see
* a detailed view */
.status time:after,
.detailed-status__datetime span:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\00a0\00a0\f08e";
}
/* Don't display the elephant mascot (we have our
* own, thanks) */
.drawer__inner__mastodon {
display: none;
}
/* Let the compose area/drawer be short, but
* expand if necessary */
.drawer .drawer__inner {
overflow: visible;
height:inherit;
background-image: none;
}
.drawer__pager {
overflow-y:auto;
}
/* Use display: none instead of visibility:hidden
* to hide the suggested follows list on non-mobile */
@media screen and (min-width: 630px) {
.search-results .trends {
display:none;
}
}
@import 'fullwidth-media';

View File

@ -0,0 +1,943 @@
$success-green: #B64579; // Padua
$ui-base-color: #f7e8ed; // "darkest"
$ui-base-alt: #f9f2f5;
$ui-base-lighter-color: darken($ui-base-color, 40%); // Lighter darkest
$ui-secondary-color: #ead0d6; // "lightest"
$ui-primary-color: #bf5677; // "lighter"
$ui-highlight-color: #bf5677; // "vibrant"
$primary-text-color: #382b32;
$dark-text-color: #ca748f;
$secondary-text-color: #382b32;
$header-color: $ui-primary-color;
$header-text-color: #fff;
$icon-button-inactive-color: lighten(desaturate($ui-base-lighter-color, 20%), 20%);
$action-button-color: $icon-button-inactive-color;
$about-page-text: $primary-text-color;
@import 'cybre-base';
$gold-star: #dd9d08;
/* cybre-specific additions */
.column .static-content.getting-started {
background-image: url('../images/logo-cybre-light.png');
background-size:auto 50%;
background-position: 50% 75%;
background-repeat:no-repeat;
}
.ui, body {
background: $ui-base-color url('../images/background-cybre-light.png');
}
.drawer__inner__mastodon {
display: none;
}
.landing-page .header-wrapper {
background-image:url('../images/header-cybre-alt.jpg');
background-size:cover;
background-position:50% 50%;
}
.landing-page.alternative .header {
background-image:url('../images/header-cybre-colour.jpg');
background-repeat: repeat-x;
background-size:contain;
height:45vh;
width: 100%;
position:absolute;
z-index: 1;
text-align:center;
display: unset!important;
}
.landing-page.alternative .header img {
margin: auto;
max-height:45vh;
}
.landing-page.alternative .grid {
position: relative;
z-index:2;
margin-top:15vh;
}
.landing-page.alternative .landing-page__hero img {
visibility: hidden;
max-height:170px;
}
.landing-page.alternative .landing-page__forms {
height:auto;
}
.landing-page.alternative .column-1 {
display:flex;
align-items:flex-end;
}
.landing-page.alternative .column {
max-height:initial;
}
.landing-page.alternative .row__mascot {
.floats {
position:absolute;
img {
width:100%;
height:100%;
}
transition: all 0.1s linear;
animation-name: floating;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-timing-function: ease-in-out;
}
.float-1 {
width:50px;
height:50px;
bottom:60px;
left:110px;
animation-duration: 3s;
}
.float-2 {
width:130px;
height:130px;
left:85px;
bottom: -60px;
animation-duration: 3.5s;
animation-delay: 0.2s;
}
.float-3 {
width:100px;
height:100px;
right: 50;
top: -10px;
animation-duration: 4s;
animation-delay: 0.5s;
}
}
/* about.scss */
.landing-page {
h1 {
color: $about-page-text;
small {
color: lighten($about-page-text, 10%);
}
}
p, li {
color: $about-page-text;
}
.header-wrapper {
padding-top:0px;
background-size:cover;
background-position:50% 55%;
}
.header-wrapper {
.mascot {
width:500px;
bottom:-52px;
left:-120px;
}
}
.container.links {
background-color: $ui-base-color;
border-top: 5px solid $ui-primary-color;
width:100%;
max-width:100%;
padding:0px calc(50% - 400px);
a {
&:hover {
color: lighten($ui-primary-color, 10%);
}
}
}
.container.hero {
.floats {
display:none;
}
.closed-registrations-message, form {
border-top: 50px solid #5f4770;
-webkit-box-shadow: 0 0 6px rgba(0,0,0,.1);
box-shadow: 0 0 6px rgba(0,0,0,.1);
&:before {
font-size: 16px;
font-family:inherit;
line-height:inherit;
font-weight:normal;
color:white;
position:absolute;
top:-35px;
}
}
.closed-registrations-message:before {
content: "Registrations closed";
}
form:before {
content: "Register now";
}
}
#mastodon-timeline {
.column-header {
color:white;
}
}
}
.features-list__row {
.text {
color: $about-page-text;
}
}
.information-board {
.panel {
.panel-header {
color: $primary-text-color;
border-bottom: 1px solid lighten($ui-secondary-color, 4%);
a,
span {
font-weight: 400;
color: lighten($ui-primary-color, 4%);
}
}
}
}
/* components.scss */
.onboarding-modal__page {
p {
color: $primary-text-color;
}
}
.column-header {
background: $header-color;
color: $header-text-color;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
box-shadow: 0px 0px 3px rgba(0,0,0,0.3);
}
.column-header__button {
background: $header-color;
color: $header-text-color;
border-top-right-radius: 3px;
&:hover {
color: darken($ui-base-color, 10%);
}
&.active {
color: $primary-text-color;
background: darken($ui-base-color, 5%);
&:hover {
background: darken($ui-base-color, 5%);
}
}
}
.status-card, .status-card.compact {
border-color: $ui-highlight-color;
}
// selectivity -- needs to override .column-header > button
.column-header .column-header__back-button {
background: $header-color;
color:$header-text-color;
}
.column-back-button {
background: $header-color;
color:$header-text-color;
}
.column-header__collapsible-inner {
background: darken($ui-base-alt, 2%);
}
.empty-column-indicator,
.error-column {
color: darken($ui-base-lighter-color, 15%);
}
.compose-form {
.autosuggest-textarea__textarea,
.spoiler-input__input {
color: $primary-text-color;
border: 1px solid $ui-primary-color;
}
.autosuggest-textarea__textarea {
border-bottom-width:0px;
}
.compose-form__modifiers {
border: 1px solid $ui-primary-color;
border-top-width:0px;
}
.compose-form__buttons button.active:last-child {
border-radius:3px;
background: $ui-base-color;
color: $ui-primary-color;
}
.compose-form__buttons-wrapper {
background-color:$ui-primary-color;
}
.icon-button.inverted {
color:white;
&:hover {
color:$ui-secondary-color;
}
}
}
button.icon-button {
&.disabled {
}
}
.icon-button {
&.inverted {
color: darken($ui-base-lighter-color, 10%);
}
&.overlayed {
background: rgba($base-overlay-background, 0.2);
color: rgba($white, 0.7);
&:hover {
background: rgba($base-overlay-background, 0.4);
}
}
&.disabled {
color: desaturate($icon-button-inactive-color, 5%);
&:hover,
&:active,
&:focus {
color: desaturate($icon-button-inactive-color, 5%);
}
}
color: $icon-button-inactive-color;
&:hover,
&:active,
&:focus {
color: darken($icon-button-inactive-color, 5%);
}
}
.icon-button.star-icon,
.icon-button.star-icon:active {
background:transparent;
border:none;
}
.icon-button.star-icon.active {
color: $gold-star;
&:active, &:hover, &:focus {
color: $gold-star;
}
}
.text-icon-button {
color: $white;
&.active {
background: $ui-base-color;
color: $ui-primary-color;
}
&:focus, &:hover {
color: darken($ui-base-color, 3%);
}
}
.status.status-direct {
background: darken($ui-base-alt, 5%);
.icon-button.disabled {
color: lighten($ui-base-lighter-color, 10%);
}
}
.account__header, .account-card {
& > div {
background: rgba(lighten($ui-base-color, 4%), 0.6);
}
.account__header__content {
color: $primary-text-color;
}
.detailed-status__display-name .display-name strong {
color: $ui-highlight-color;
}
.icon-button {
&, &:hover {
color:desaturate($ui-base-lighter-color, 20%);
}
&.active {
&, &:hover {
color:$ui-base-lighter-color;
}
}
}
}
.account__section-headline a {
&.active {
color: $primary-text-color;
&::after {
border-bottom-color: $ui-base-alt;
}
&::after {
border-bottom-color: $ui-base-alt;
}
}
}
.privacy-dropdown.active .privacy-dropdown__value.active .icon-button {
color: $ui-primary-color;
}
.privacy-dropdown__option {
color: $primary-text-color;
strong {
color: $primary-text-color;
}
&:hover,
&.active {
color: $white;
.privacy-dropdown__option__content {
color: $white;
strong {
color: $white;
}
}
}
}
.emoji-picker-dropdown__menu {
.emoji-search-wrapper {
border-color: darken($ui-base-color, 10%);
}
.emoji-search {
background: darken($ui-base-color, 5%);
border-color: darken($ui-base-color, 10%);
}
.emoji-mart {
color: $ui-primary-color;
}
}
.search-popout {
background: $ui-base-color;
color: $ui-primary-color;
h4 {
color: $ui-primary-color;
}
em {
color: $ui-highlight-color;
}
}
.search__icon .fa.active {
opacity:1.0;
}
.search-results__hashtag {
color: darken($ui-primary-color, 10%);
&:hover {
color: lighten($ui-primary-color, 5%);
}
}
.static-content {
/*color: $primary-text-color;*/
}
#Getting-started {
background: $ui-primary-color;
border-bottom:0px;
color:white;
}
.getting-started {
p {
color: $primary-text-color;
}
a {
color: darken($ui-base-lighter-color, 10%);
}
}
.getting-started__wrapper {
flex: 0 0.5 auto;
}
.getting-started {
.column-link {
background: lighten($ui-primary-color, 5%);
color:$white;
&:hover {
background: lighten($ui-primary-color, 10%);
}
}
}
.column-link__badge {
background: saturate(darken($ui-primary-color, 5%), 5%);
}
.column-subheading {
background: darken($ui-primary-color, 5%);
color:$white;
}
.media-spoiler,
.video-player__spoiler.active {
color: $white;
&:hover {
color: darken($white, 5%);
}
}
.status {
border-bottom: 1px solid $ui-secondary-color;
}
.status__relative-time, .status__display-name {
color: darken($ui-base-color, 40%);
}
.status__content {
.status__content__spoiler-link {
background: $ui-base-lighter-color;
&:hover {
background: lighten($ui-base-lighter-color, 5%);
}
}
}
.muted .status__content p {
color: $icon-button-inactive-color;
}
.dropdown-menu__item {
& > a {
color: $primary-text-color;
&:hover, &:active, &:focus {
color: $white;
}
}
}
.dropdown--active .dropdown__content {
& > ul {
background: $ui-base-color;
box-shadow: 0 0 5px rgba($base-shadow-color, 0.2);
& > li > a {
background: $ui-base-color;
color: $primary-text-color;
&:hover {
background: $ui-highlight-color;
color: $ui-base-color;
}
}
}
}
.boost-modal,
.confirmation-modal,
.report-modal,
.actions-modal,
.mute-modal
{
color: $primary-text-color;
}
.boost-modal__action-bar,
.confirmation-modal__action-bar,
.mute-modal__action-bar,
.report-modal__action-bar,
.mute-modal__action-bar {
& > div {
color: $ui-primary-color;
}
}
.actions-modal
{
ul {
li:not(:empty) {
a {
color: $primary-text-color;
button {
}
&.active, &:hover, &:active, &:focus {
color: $white;
button {
color: $white;
}
}
}
}
}
}
.react-toggle-track {
background-color: $icon-button-inactive-color;
}
.report-modal__comment .setting-text {
color: $primary-text-color;
border-bottom-color: lighten($ui-primary-color, 10%);
&:focus, &:active {
color: $primary-text-color;
}
}
.status.light {
.status__content {
color: $primary-text-color;
}
.display-name strong {
color: $primary-text-color;
}
}
.reply-indicator__content a {
color: lighten($ui-highlight-color, 30%);
}
.status__content
{
a {
color: $ui-highlight-color;
&:hover {
.fa {
color: darken($ui-base-color, 40%);
}
}
}
}
.detailed-status__display-name {
color: $ui-base-lighter-color;
}
.drawer .drawer__inner {
overflow: visible;
height:inherit;
background:$ui-base-alt;
}
.search__icon .fa {
color: $ui-highlight-color;
}
.drawer__pager {
overflow-y:auto;
}
.drawer .drawer__header {
background: $ui-base-color;
border-radius:3px;
}
.onboarding-modal__page h1 {
background-color: darken($ui-primary-color, 5%);
}
/* forms.scss */
.block-button, .button, button {
background-color: $ui-primary-color;
color: $white;
&.button-alternative {
color: $ui-base-color;
}
&.logo-button {
color: $white;
svg path:first-child {
fill: $white;
}
}
}
.simple_form {
p.hint {
color: $primary-text-color;
}
.block-button, .button, button {
background-color: $ui-primary-color;
color: $white;
&:hover {
background-color: lighten($ui-primary-color, 5%);
}
&:active,
&:focus {
background-color: darken($ui-primary-color, 5%);
}
}
}
/* admin.scss */
.table > thead > tr > th {
border-bottom-color: $ui-secondary-color;
}
.simple_form h4 {
border-bottom: 1px solid $ui-highlight-color;
}
.admin-wrapper {
.content {
h2, p.hint, h4, h6 {
color: $primary-text-color;
}
.muted-hint {
color: $primary-text-color;
}
}
.sidebar {
.logo {
-webkit-filter: invert(100%);
filter: invert(100%);
}
ul {
ul {
a {
&.selected {
background-color: $ui-primary-color;
color: $white;
&:hover {
background-color: lighten($ui-primary-color, 10%);
}
}
}
}
a {
&.selected {
background-color: $ui-primary-color;
color: $white;
&:hover {
background-color: lighten($ui-primary-color, 10%);
}
}
}
}
}
}
.pagination .current {
color: $ui-primary-color;
}
.report-accounts__item > strong {
color: $primary-text-color;
}
.admin-wrapper .content {
& > p {
color: $primary-text-color;
}
hr {
border-color: $ui-highlight-color;
}
}
/* accounts.scss */
.card {
.name {
color: $white;
}
.counter {
.counter-number {
color: $white;
}
}
}
/* stream_entries.scss */
.activity-stream {
.entry {
}
.status.light {
.display-name {
strong {
color: $primary-text-color;
}
}
.status__content {
color: $primary-text-color;
}
}
.detailed-status.light {
.detailed-status__display-name {
.display-name {
strong {
color: $primary-text-color;
}
}
}
.status__content {
color: $primary-text-color;
}
.status-card,
.status-card__title,
.status-card__description {
color: $primary-text-color;
}
}
}
/* accounts.scss */
.card {
.name {
color: darken($ui-primary-color, 15%);
}
.counter {
.counter-number {
color: darken($ui-primary-color, 15%);
}
border-color: $ui-primary-color;
}
}
.activity-stream-tabs {
a {
color: lighten($ui-primary-color, 10%);
&.active {
color: darken($ui-primary-color, 10%);
}
}
}
/* uncategorized */
@media screen and (min-width: 1300px) {
.column {
flex-grow: 1 !important;
max-width: 400px;
}
.drawer {
width: 17%;
max-width: 400px;
min-width: 330px;
}
}
.status time:after,
.detailed-status__datetime span:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\00a0\00a0\f08e";
}
.column {
max-height:100vh;
& > .scrollable {
background-color: $ui-base-alt;
}
}
.empty-column-indicator, .error-column {
background-color: $ui-base-alt;
}
.actions .button.button-alternative {
background: $ui-highlight-color;
color: $white;
&:active,
&:focus,
&:hover {
background-color: lighten($ui-highlight-color, 4%);
}
}
.public-layout .header {
background: $ui-highlight-color;
color: $white;
}
.public-layout .public-account-header__tabs__name h1 {
color: $white;
small {
color: $white;
}
}
.public-layout .header .brand:hover,
.public-layout .header .brand:focus,
.public-layout .header .brand:active {
background: lighten($ui-highlight-color, 5%);
}
.public-layout .container:last-child {
background:$ui-highlight-color;
padding-left: 100px;
padding-right: 100px;
border-radius: 4px;
h4 {
color: white;
}
}
.modal-layout, .modal-layout__mastodon > * {
background: none;
}
.dashboard__widgets a:not(.name-tag) {
color: $primary-text-color;
}

View File

@ -0,0 +1,272 @@
$ui-base-color: #181818; // darkest
$ui-highlight-color: #1ea21e; // vibrant
$ui-secondary-color: #E4F2E4; // lightest
$ui-primary-color: #E4F2E4; // lighter
$ui-primary-color-alt: #a0b49c; // darker, for external pages
$about-page-text: lighten($ui-base-color, 50%);
@import 'cybre-base';
@keyframes floating {
from {
transform: translate(0, 0);
}
65% {
transform: translate(0, 4px);
}
to {
transform: translate(0, -0);
}
}
body, body.about-body {
background: $ui-base-color url('../images/background-cybre.png');
}
body.about-body {
// basics.scss &.about-body
background: darken($ui-base-color, 8%) url('../images/background-cybre.png');
background-position-y: 200px;
background-position-x: center;
}
.about-body .mascot {
display: none;
}
.muted {
.status__content p, .status__content a {
color: lighten($ui-base-color, 35%);
}
.status__display-name strong {
color: lighten($ui-base-color, 35%);
}
}
.compose-form__buttons button.active:last-child {
color:$ui-secondary-color;
background-color: $ui-highlight-color;
border-radius:3px;
}
.screenshot-with-signup {
min-height:300px;
}
.container.hero .closed-registrations-message .clock {
font-size: 150%;
margin: 1em auto;
}
.column .static-content.getting-started {
background-image: url('../images/logo-cybre.png'), url('../images/background-cybre.png');
background-size:auto 50%, cover;
background-position: 50% 75%, center center;
background-repeat:no-repeat, no-repeat;
}
.columns-area {
background: $ui-base-color url('../images/background-cybre.png');
}
.actions .button.button-alternative {
background: $ui-highlight-color;
color: $ui-primary-color;
&:active,
&:focus,
&:hover {
background-color: lighten($ui-highlight-color, 4%);
}
}
@media screen and (max-width: 1280px) {
.landing-page .container.links {
top: -15px;
}
}
.landing-page.alternative .header {
background-image:url('../images/header-cybre-colour.jpg');
background-repeat: repeat-x;
background-size:contain;
height:45vh;
width: 100%;
position:absolute;
z-index: 1;
text-align:center;
display: unset!important;
}
.landing-page.alternative .header img {
margin: auto;
max-height:45vh;
}
.landing-page.alternative .grid {
position: relative;
z-index:2;
margin-top:15vh;
}
.landing-page.alternative .landing-page__hero img {
visibility: hidden;
max-height:170px;
}
.landing-page.alternative .landing-page__forms {
height:auto;
}
.landing-page.alternative .column-1 {
display:flex;
align-items:flex-end;
}
.landing-page.alternative .column {
max-height:initial;
}
.landing-page.alternative .row__mascot {
.floats {
position:absolute;
img {
width:100%;
height:100%;
}
transition: all 0.1s linear;
animation-name: floating;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-timing-function: ease-in-out;
}
.float-1 {
width:50px;
height:50px;
bottom:60px;
left:110px;
animation-duration: 3s;
}
.float-2 {
width:130px;
height:130px;
left:85px;
bottom: -60px;
animation-duration: 3.5s;
animation-delay: 0.2s;
}
.float-3 {
width:100px;
height:100px;
right: 50;
top: -10px;
animation-duration: 4s;
animation-delay: 0.5s;
}
}
.activity-stream {
.status.light {
.status__header .status__meta .status__relative-time {
color: $ui-primary-color-alt;
}
.display-name span {
color: $ui-primary-color-alt;
}
.status__content {
a.status__content__spoiler-link {
background: $ui-primary-color-alt;
&:hover {
background: lighten($ui-primary-color-alt, 8%);
}
}
}
}
.detailed-status.light {
.detailed-status__display-name .display-name span {
color: $ui-primary-color-alt;
}
.status__content a.status__content__spoiler-link {
background: $ui-primary-color-alt;
&:hover {
background: lighten($ui-primary-color-alt, 8%);
}
}
.detailed-status__meta {
color: $ui-primary-color-alt;
}
}
.media-spoiler {
background: $ui-primary-color-alt;
&:hover {
background: darken($ui-primary-color-alt, 5%);
}
}
.pre-header {
color: $ui-primary-color-alt;
.status__display-name.muted strong {
color: $ui-primary-color-alt;
}
}
}
.embed .activity-stream .entry .detailed-status.light .button.button-secondary.logo-button {
color: $ui-primary-color-alt;
svg {
path:first-child {
fill: $ui-primary-color-alt;
}
}
&:active,
&:focus,
&:hover {
svg path:first-child {
fill: lighten($ui-primary-color-alt, 4%);
}
}
}
.emoji-mart-search {
background: $simple-background-color;
input {
color: $ui-primary-color-alt;
border: 1px solid $ui-primary-color-alt;
}
}
.emoji-mart-anchor {
color: $ui-primary-color-alt;
&:hover {
color: darken($ui-primary-color-alt, 8%);
}
}
.search-popout {
background: $ui-base-color;
color: $ui-primary-color;
h4 {
color: $ui-primary-color;
}
em {
color: $ui-highlight-color;
}
}

View File

@ -0,0 +1,96 @@
$doodleBg: #d9e1e8;
.doodle-modal {
@extend .boost-modal;
width: unset;
}
.doodle-modal__container {
background: $doodleBg;
text-align: center;
line-height: 0; // remove weird gap under canvas
canvas {
border: 5px solid $doodleBg;
}
}
.doodle-modal__action-bar {
@extend .boost-modal__action-bar;
.filler {
flex-grow: 1;
margin: 0;
padding: 0;
}
.doodle-toolbar {
line-height: 1;
display: flex;
flex-direction: column;
flex-grow: 0;
justify-content: space-around;
&.with-inputs {
label {
display: inline-block;
width: 70px;
text-align: right;
margin-right: 2px;
}
input[type="number"],input[type="text"] {
width: 40px;
}
span.val {
display: inline-block;
text-align: left;
width: 50px;
}
}
}
.doodle-palette {
padding-right: 0 !important;
border: 1px solid black;
line-height: .2rem;
flex-grow: 0;
background: white;
button {
appearance: none;
width: 1rem;
height: 1rem;
margin: 0; padding: 0;
text-align: center;
color: black;
text-shadow: 0 0 1px white;
cursor: pointer;
box-shadow: inset 0 0 1px rgba(white, .5);
border: 1px solid black;
outline-offset:-1px;
&.foreground {
outline: 1px dashed white;
}
&.background {
outline: 1px dashed red;
}
&.foreground.background {
outline: 1px dashed red;
border-color: white;
}
}
}
}
.compose-form__buttons-separator {
border-left: 1px solid #c3c3c3;
margin: 0 3px;
}
.compose-form__upload-button-icon {
line-height: 27px;
}

View File

@ -0,0 +1,48 @@
.detailed-status > .media-spoiler,
.status > .media-spoiler,
.status .video-player,
.media-gallery,
.status .status-card.interactive {
margin-top: 20px;
margin-left: -68px;
width: calc(100% + 80px);
}
.detailed-status > .media-spoiler,
.status > .media-spoiler,
.video-player {
max-width: none;
}
/* If there's no status text, add an extra margin on top */
.status .status__info + .media-gallery,
.status .status__info + .media-spoiler,
.status .status__info + .video-player,
.status .status__info + .status-card {
margin-top: 40px;
}
.status__video-player-video {
transform: unset;
top: unset;
}
.detailed-status .media-gallery {
margin-left: -10px;
width: calc(100% + 22px);
}
.public-layout .status {
.status__content {
min-height: 15px;
}
& > .media-spoiler,
.video-player,
.media-gallery,
.status-card {
margin-top: 20px;
width: calc(100% + 94px);
margin-left: -78px;
}
}

View File

@ -41,3 +41,34 @@
font-size: 16px;
}
}
@mixin search-popout() {
background: $simple-background-color;
border-radius: 4px;
padding: 10px 14px;
padding-bottom: 14px;
margin-top: 10px;
color: $light-text-color;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
h4 {
text-transform: uppercase;
color: $light-text-color;
font-size: 13px;
font-weight: 500;
margin-bottom: 10px;
}
li {
padding: 4px 0;
}
ul {
margin-bottom: 10px;
}
em {
font-weight: 500;
color: $inverted-text-color;
}
}

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