diff options
Diffstat (limited to 'app/javascript')
60 files changed, 2703 insertions, 2253 deletions
diff --git a/app/javascript/core/public.js b/app/javascript/core/public.js index 47c34a259..d3d80019f 100644 --- a/app/javascript/core/public.js +++ b/app/javascript/core/public.js @@ -1,6 +1,7 @@ // This file will be loaded on public pages, regardless of theme. const { delegate } = require('rails-ujs'); +const { length } = require('stringz'); delegate(document, '.webapp-btn', 'click', ({ target, button }) => { if (button !== 0) { diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js index 1add0314d..175a1758f 100644 --- a/app/javascript/core/settings.js +++ b/app/javascript/core/settings.js @@ -3,24 +3,29 @@ const { length } = require('stringz'); const { delegate } = require('rails-ujs'); -delegate(document, '.account_display_name', 'input', ({ target }) => { +delegate(document, '#account_display_name', 'input', ({ target }) => { const nameCounter = document.querySelector('.name-counter'); + const name = document.querySelector('.card .display-name strong'); if (nameCounter) { nameCounter.textContent = 30 - length(target.value); } + + if (name) { + name.innerHTML = emojify(target.value); + } }); -delegate(document, '.account_note', 'input', ({ target }) => { +delegate(document, '#account_note', 'input', ({ target }) => { const noteCounter = document.querySelector('.note-counter'); if (noteCounter) { - noteCounter.textContent = 500 - length(target.value); + noteCounter.textContent = 160 - length(target.value); } }); delegate(document, '#account_avatar', 'change', ({ target }) => { - const avatar = document.querySelector('.card.compact .avatar img'); + const avatar = document.querySelector('.card .avatar img'); const [file] = target.files || []; const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; @@ -28,9 +33,19 @@ delegate(document, '#account_avatar', 'change', ({ target }) => { }); delegate(document, '#account_header', 'change', ({ target }) => { - const header = document.querySelector('.card.compact'); + const header = document.querySelector('.card .card__img img'); const [file] = target.files || []; const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc; - header.style.backgroundImage = `url(${url})`; + header.src = url; +}); + +delegate(document, '#account_locked', 'change', ({ target }) => { + const lock = document.querySelector('.card .display-name i'); + + if (target.checked) { + lock.style.display = 'inline'; + } else { + lock.style.display = 'none'; + } }); diff --git a/app/javascript/flavours/glitch/components/relative_timestamp.js b/app/javascript/flavours/glitch/components/relative_timestamp.js index 3c8db7092..9609714a1 100644 --- a/app/javascript/flavours/glitch/components/relative_timestamp.js +++ b/app/javascript/flavours/glitch/components/relative_timestamp.js @@ -60,6 +60,32 @@ const getUnitDelay = units => { } }; +export const timeAgoString = (intl, date, now, year) => { + const delta = now - date.getTime(); + + let relativeTime; + + if (delta < 10 * SECOND) { + relativeTime = intl.formatMessage(messages.just_now); + } else if (delta < 7 * DAY) { + if (delta < MINUTE) { + relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); + } else if (delta < HOUR) { + relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); + } else if (delta < DAY) { + relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); + } else { + relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); + } + } else if (date.getFullYear() === year) { + relativeTime = intl.formatDate(date, shortDateFormatOptions); + } else { + relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); + } + + return relativeTime; +}; + @injectIntl export default class RelativeTimestamp extends React.Component { @@ -121,28 +147,8 @@ export default class RelativeTimestamp extends React.Component { render () { const { timestamp, intl, year } = this.props; - const date = new Date(timestamp); - const delta = this.state.now - date.getTime(); - - let relativeTime; - - if (delta < 10 * SECOND) { - relativeTime = intl.formatMessage(messages.just_now); - } else if (delta < 7 * DAY) { - if (delta < MINUTE) { - relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); - } else if (delta < HOUR) { - relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); - } else if (delta < DAY) { - relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); - } else { - relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); - } - } else if (date.getFullYear() === year) { - relativeTime = intl.formatDate(date, shortDateFormatOptions); - } else { - relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); - } + const date = new Date(timestamp); + const relativeTime = timeAgoString(intl, date, this.state.now, year); return ( <time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}> diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index c03c3017e..169cd3963 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -423,7 +423,7 @@ export default class Status extends ImmutablePureComponent { mediaIcon = 'video-camera'; } else { // Media type is 'image' or 'gifv' media = ( - <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} > + <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}> {Component => ( <Component media={attachments} diff --git a/app/javascript/flavours/glitch/containers/media_container.js b/app/javascript/flavours/glitch/containers/media_container.js index 0e1904132..c4b713e82 100644 --- a/app/javascript/flavours/glitch/containers/media_container.js +++ b/app/javascript/flavours/glitch/containers/media_container.js @@ -29,19 +29,19 @@ export default class MediaContainer extends PureComponent { }; handleOpenMedia = (media, index) => { - document.body.classList.add('media-standalone__body'); + document.body.classList.add('with-modals--active'); this.setState({ media, index }); } handleOpenVideo = (video, time) => { const media = ImmutableList([video]); - document.body.classList.add('media-standalone__body'); + document.body.classList.add('with-modals--active'); this.setState({ media, time }); } handleCloseMedia = () => { - document.body.classList.remove('media-standalone__body'); + document.body.classList.remove('with-modals--active'); this.setState({ media: null, index: null, time: null }); } diff --git a/app/javascript/flavours/glitch/containers/timeline_container.js b/app/javascript/flavours/glitch/containers/timeline_container.js index 56669a49a..5a1f41f7a 100644 --- a/app/javascript/flavours/glitch/containers/timeline_container.js +++ b/app/javascript/flavours/glitch/containers/timeline_container.js @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { Fragment } from 'react'; +import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import PropTypes from 'prop-types'; import configureStore from 'flavours/glitch/store/configureStore'; @@ -8,6 +9,7 @@ import { getLocale } from 'mastodon/locales'; import PublicTimeline from 'flavours/glitch/features/standalone/public_timeline'; import CommunityTimeline from 'flavours/glitch/features/standalone/community_timeline'; import HashtagTimeline from 'flavours/glitch/features/standalone/hashtag_timeline'; +import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container'; import initialState from 'flavours/glitch/util/initial_state'; const { localeData, messages } = getLocale(); @@ -47,7 +49,13 @@ export default class TimelineContainer extends React.PureComponent { return ( <IntlProvider locale={locale} messages={messages}> <Provider store={store}> - {timeline} + <Fragment> + {timeline} + {ReactDOM.createPortal( + <ModalContainer />, + document.getElementById('modal-container'), + )} + </Fragment> </Provider> </IntlProvider> ); diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js index 7e9980ef7..e54ab9a52 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js @@ -44,6 +44,18 @@ export default class ModalRoot extends React.PureComponent { onClose: PropTypes.func.isRequired, }; + getSnapshotBeforeUpdate () { + return { visible: !!this.props.type }; + } + + componentDidUpdate (prevProps, prevState, { visible }) { + if (visible) { + document.body.classList.add('with-modals--active'); + } else { + document.body.classList.remove('with-modals--active'); + } + } + renderLoading = modalId => () => { return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null; } diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss index c9c0e3081..74807bb65 100644 --- a/app/javascript/flavours/glitch/styles/about.scss +++ b/app/javascript/flavours/glitch/styles/about.scss @@ -574,6 +574,7 @@ $small-breakpoint: 960px; .avatar { width: 80px; height: 80px; + @include avatar-size(80px); margin: 0 auto; margin-bottom: 15px; @@ -582,6 +583,7 @@ $small-breakpoint: 960px; width: 80px; height: 80px; border-radius: 48px; + @include avatar-radius(); } } @@ -716,6 +718,7 @@ $small-breakpoint: 960px; &__avatar { width: 44px; height: 44px; + @include avatar-size(48px); background-size: 44px 44px; } @@ -1094,6 +1097,21 @@ $small-breakpoint: 960px; } &.tag-page { + @media screen and (max-width: $column-breakpoint) { + padding: 0; + + .container { + padding: 0; + } + + #mastodon-timeline { + display: block; + width: 100vw; + height: 100vh; + border-radius: 0; + } + } + .grid { @media screen and (min-width: $small-breakpoint) { grid-template-columns: 33% 67%; @@ -1125,24 +1143,17 @@ $small-breakpoint: 960px; @media screen and (max-width: $column-breakpoint) { .grid { + grid-gap: 0; + .column-1 { grid-column: 1; - grid-row: 2; + grid-row: 1; } .column-2 { - grid-column: 1; - grid-row: 1; + display: none; } } - - .brand { - margin: 0; - } - - .landing-page__features { - display: none; - } } } } diff --git a/app/javascript/flavours/glitch/styles/accounts.scss b/app/javascript/flavours/glitch/styles/accounts.scss index 133250822..ac1989832 100644 --- a/app/javascript/flavours/glitch/styles/accounts.scss +++ b/app/javascript/flavours/glitch/styles/accounts.scss @@ -1,243 +1,102 @@ .card { - background-color: lighten($ui-base-color, 4%); - background-size: cover; - background-position: center; - border-radius: 4px 4px 0 0; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - overflow: hidden; - position: relative; - display: flex; - - &::after { - background: rgba(darken($ui-base-color, 8%), 0.5); + & > a { display: block; - content: ""; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - z-index: 1; - } - - @media screen and (max-width: 740px) { - border-radius: 0; - box-shadow: none; - } - - .card__illustration { - padding: 60px 0; - position: relative; - flex: 1 1 auto; - display: flex; - justify-content: center; - align-items: center; - } - - .card__bio { - max-width: 260px; - flex: 1 1 auto; - display: flex; - flex-direction: column; - justify-content: space-between; - background: rgba(darken($ui-base-color, 8%), 0.8); - position: relative; - z-index: 2; - } - - &.compact { - padding: 30px 0; - border-radius: 4px; - - .avatar { - margin-bottom: 0; + text-decoration: none; + color: inherit; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - img { - object-fit: cover; - } + @media screen and (max-width: $no-gap-breakpoint) { + box-shadow: none; } - } - .name { - display: block; - font-size: 20px; - line-height: 18px * 1.5; - color: $primary-text-color; - padding: 10px 15px; - padding-bottom: 0; - font-weight: 500; - position: relative; - z-index: 2; - margin-bottom: 15px; - overflow: hidden; - text-overflow: ellipsis; - - small { - display: block; - font-size: 14px; - color: $highlight-text-color; - font-weight: 400; - overflow: hidden; - text-overflow: ellipsis; + &:hover, + &:active, + &:focus { + .card__bar { + background: lighten($ui-base-color, 8%); + } } } - .avatar { - width: 120px; - margin: 0 auto; + &__img { + height: 130px; position: relative; - z-index: 2; - @include avatar-size(120px); + background: darken($ui-base-color, 12%); + border-radius: 4px 4px 0 0; img { - width: 120px; - height: 120px; display: block; - border-radius: 120px; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - @include avatar-radius(); - @include avatar-size(120px); - } - } - - .roles { - margin-bottom: 15px; - padding: 0 15px; - } - - .details-counters { - margin-top: 30px; - display: flex; - flex-direction: row; - width: 100%; - } - - .counter { - width: 33.3%; - box-sizing: border-box; - flex: 0 0 auto; - color: $darker-text-color; - padding: 5px 10px 0; - margin-bottom: 10px; - border-right: 1px solid lighten($ui-base-color, 4%); - cursor: default; - text-align: center; - position: relative; - - a { - display: block; - } - - &:last-child { - border-right: 0; - } - - &::after { - display: block; - content: ""; - position: absolute; - bottom: -10px; - left: 0; width: 100%; - border-bottom: 4px solid $ui-primary-color; - opacity: 0.5; - transition: all 400ms ease; - } - - &.active { - &::after { - border-bottom: 4px solid $highlight-text-color; - opacity: 1; - } - } - - &:hover { - &::after { - opacity: 1; - transition-duration: 100ms; - } - } - - a { - text-decoration: none; - color: inherit; + height: 100%; + margin: 0; + object-fit: cover; + border-radius: 4px 4px 0 0; } - .counter-label { - font-size: 12px; - display: block; - margin-bottom: 5px; + @media screen and (max-width: 600px) { + height: 200px; } - .counter-number { - font-weight: 500; - font-size: 18px; - color: $primary-text-color; - font-family: 'mastodon-font-display', sans-serif; + @media screen and (max-width: $no-gap-breakpoint) { + display: none; } } - .bio { - font-size: 14px; - line-height: 18px; - padding: 0 15px; - text-align: center; - color: $secondary-text-color; - } - - @media screen and (max-width: 480px) { - display: block; + &__bar { + position: relative; + padding: 15px; + display: flex; + justify-content: flex-start; + align-items: center; + background: lighten($ui-base-color, 4%); + border-radius: 0 0 4px 4px; - .card__bio { - max-width: none; + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; } - .name, - .roles { - text-align: center; - margin-bottom: 5px; - } + .avatar { + flex: 0 0 auto; + width: 48px; + height: 48px; + @include avatar-size(48px); + padding-top: 2px; - .bio { - margin-bottom: 15px; + img { + width: 100%; + height: 100%; + display: block; + margin: 0; + border-radius: 4px; + @include avatar-radius(); + background: darken($ui-base-color, 8%); + } } - } -} -.card, -.account-grid-card { - .controls { - position: absolute; - top: 15px; - left: 15px; - z-index: 2; - - .icon-button { - color: rgba($white, 0.8); - text-decoration: none; - font-size: 13px; - line-height: 13px; - font-weight: 500; - - .fa { - font-weight: 400; - margin-right: 5px; + .display-name { + margin-left: 15px; + text-align: left; + + strong { + font-size: 15px; + color: $primary-text-color; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; } - &:hover, - &:active, - &:focus { - color: $white; + span { + display: block; + font-size: 14px; + color: $darker-text-color; + font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; } } } } -.account-grid-card .controls { - left: auto; - right: 15px; -} - .pagination { padding: 30px 0; text-align: center; @@ -314,260 +173,23 @@ } } -.accounts-grid { - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - background: darken($simple-background-color, 8%); - border-radius: 0 0 4px 4px; - padding: 20px 5px; - padding-bottom: 10px; - overflow: hidden; - display: flex; - flex-wrap: wrap; - z-index: 2; - position: relative; - - &.empty img { - position: absolute; - opacity: 0.2; - height: 200px; - left: 0; - bottom: 0; - pointer-events: none; - } - - @media screen and (max-width: 740px) { - border-radius: 0; - box-shadow: none; - } - - .account-grid-card { - box-sizing: border-box; - width: 335px; - background: $simple-background-color; - border-radius: 4px; - color: $inverted-text-color; - margin: 0 5px 10px; - position: relative; - - @media screen and (max-width: 740px) { - width: calc(100% - 10px); - } - - .account-grid-card__header { - overflow: hidden; - height: 100px; - border-radius: 4px 4px 0 0; - background-color: lighten($inverted-text-color, 4%); - background-size: cover; - background-position: center; - position: relative; - - &::after { - background: rgba(darken($ui-base-color, 8%), 0.5); - display: block; - content: ""; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - z-index: 1; - } - } - - .account-grid-card__avatar { - box-sizing: border-box; - padding: 15px; - position: absolute; - z-index: 2; - top: 100px - (40px + 2px); - left: -2px; - } - - .avatar { - width: 80px; - height: 80px; - @include avatar-size(80px); - - img { - display: block; - width: 80px; - height: 80px; - border-radius: 80px; - border: 2px solid $simple-background-color; - background: $simple-background-color; - @include avatar-radius(); - @include avatar-size(80px); - } - } - - .name { - padding: 15px; - padding-top: 10px; - padding-left: 15px + 80px + 15px; - - a { - display: block; - color: $inverted-text-color; - text-decoration: none; - text-overflow: ellipsis; - overflow: hidden; - font-weight: 500; - - &:hover { - .display_name { - text-decoration: underline; - } - } - } - } - - .display_name { - font-size: 16px; - display: block; - text-overflow: ellipsis; - overflow: hidden; - } - - .username { - color: $lighter-text-color; - font-size: 14px; - font-weight: 400; - } - - .account__header__content { - padding: 10px 15px; - padding-top: 15px; - color: $lighter-text-color; - word-wrap: break-word; - overflow: hidden; - text-overflow: ellipsis; - height: 5.5em; - position: relative; - - &::after { - display: block; - content: ""; - width: 100%; - height: 100px; - position: absolute; - bottom: 0; - background: linear-gradient(to bottom, rgba($simple-background-color, 0.01) 0%, rgba($simple-background-color, 1) 100%); - left: 0; - border-radius: 0 0 4px 4px; - pointer-events: none; - } - } - } -} - .nothing-here { - width: 100%; - display: block; + background: $ui-base-color; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); color: $light-text-color; font-size: 14px; font-weight: 500; text-align: center; - padding: 130px 0; - padding-top: 125px; - margin: 0 auto; + display: flex; + justify-content: center; + align-items: center; cursor: default; -} - -.account-card { - padding: 14px 10px; - background: $simple-background-color; border-radius: 4px; - text-align: left; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - - .detailed-status__display-name { - display: block; - overflow: hidden; - margin-bottom: 15px; - - &:last-child { - margin-bottom: 0; - } + padding: 20px; + min-height: 30vh; - & > div { - float: left; - margin-right: 10px; - width: 48px; - height: 48px; - @include avatar-size(48px); - } - - .avatar { - display: block; - border-radius: 4px; - @include avatar-radius(); - } - - .display-name { - display: block; - max-width: 100%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - cursor: default; - - strong { - font-weight: 500; - color: $ui-base-color; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - span { - font-size: 14px; - color: $light-text-color; - } - } - - &:hover { - .display-name { - strong { - text-decoration: none; - } - } - } - } - - .account__header__content { - font-size: 14px; - color: $inverted-text-color; - } -} - -.activity-stream-tabs { - background: $simple-background-color; - border-bottom: 1px solid $ui-secondary-color; - position: relative; - z-index: 2; - - a { - display: inline-block; - padding: 15px; - text-decoration: none; - color: $highlight-text-color; - text-transform: uppercase; - font-weight: 500; - - &:hover, - &:active, - &:focus { - color: lighten($highlight-text-color, 8%); - } - - &.active { - color: $inverted-text-color; - cursor: default; - } + &--under-tabs { + border-radius: 0 0 4px 4px; } } @@ -596,4 +218,56 @@ } } -@import 'metadata'; +.account__header__fields { + padding: 0; + margin: 15px -15px -15px; + border: 0 none; + border-top: 1px solid lighten($ui-base-color, 12%); + border-bottom: 1px solid lighten($ui-base-color, 12%); + font-size: 14px; + line-height: 20px; + + dl { + display: flex; + border-bottom: 1px solid lighten($ui-base-color, 12%); + } + + dt, + dd { + box-sizing: border-box; + padding: 14px; + text-align: center; + max-height: 48px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + dt { + font-weight: 500; + width: 120px; + flex: 0 0 auto; + color: $secondary-text-color; + background: rgba(darken($ui-base-color, 8%), 0.5); + } + + dd { + flex: 1 1 auto; + color: $darker-text-color; + } + + a { + color: $highlight-text-color; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + dl:last-child { + border-bottom: 0; + } +} diff --git a/app/javascript/flavours/glitch/styles/basics.scss b/app/javascript/flavours/glitch/styles/basics.scss index 8e3db2572..11c91bbc9 100644 --- a/app/javascript/flavours/glitch/styles/basics.scss +++ b/app/javascript/flavours/glitch/styles/basics.scss @@ -1,13 +1,10 @@ body { font-family: 'mastodon-font-sans-serif', sans-serif; - background: $ui-base-color; - background-size: cover; - background-attachment: fixed; + background: darken($ui-base-color, 8%); font-size: 13px; line-height: 18px; font-weight: 400; color: $primary-text-color; - padding-bottom: 20px; text-rendering: optimizelegibility; font-feature-settings: "kern"; text-size-adjust: none; @@ -35,20 +32,28 @@ body { height: 100%; padding: 0; background: $ui-base-color; + + &.with-modals--active { + overflow-y: hidden; + } } - &.about-body { - background: darken($ui-base-color, 8%); - padding-bottom: 0; + &.lighter { + background: $ui-base-color; } - &.tag-body { - background: darken($ui-base-color, 8%); - padding-bottom: 0; + &.with-modals { + overflow-x: hidden; + overflow-y: scroll; + + &--active { + overflow-y: hidden; + margin-right: 13px; + } } &.embed { - background: transparent; + background: lighten($ui-base-color, 4%); margin: 0; padding-bottom: 0; diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index de481f937..cd17bb4fa 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -442,6 +442,18 @@ background: lighten($ui-base-color, 4%); padding: 14px 10px; + &--flex { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: flex-start; + + .status__content, + .detailed-status__meta { + flex: 100%; + } + } + .status__content { font-size: 19px; line-height: 24px; diff --git a/app/javascript/flavours/glitch/styles/containers.scss b/app/javascript/flavours/glitch/styles/containers.scss index ac648c868..01c8ebbaf 100644 --- a/app/javascript/flavours/glitch/styles/containers.scss +++ b/app/javascript/flavours/glitch/styles/containers.scss @@ -60,10 +60,6 @@ } } -.media-standalone__body { - overflow: hidden; -} - .account-header { width: 400px; margin: 0 auto; @@ -87,6 +83,7 @@ .avatar { width: 40px; height: 40px; + @include avatar-size(40px); margin-right: 8px; img { @@ -95,6 +92,7 @@ display: block; margin: 0; border-radius: 4px; + @include avatar-radius(); } } @@ -118,3 +116,580 @@ margin-left: 8px; } } + +.public-layout { + @media screen and (max-width: $no-gap-breakpoint) { + padding-top: 48px; + } + + .container { + max-width: 960px; + + @media screen and (max-width: $no-gap-breakpoint) { + padding: 0; + } + } + + .header { + background: lighten($ui-base-color, 8%); + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + border-radius: 4px; + height: 48px; + margin: 10px 0; + display: flex; + align-items: stretch; + justify-content: center; + flex-wrap: nowrap; + overflow: hidden; + + @media screen and (max-width: $no-gap-breakpoint) { + position: fixed; + width: 100%; + top: 0; + left: 0; + margin: 0; + border-radius: 0; + box-shadow: none; + z-index: 110; + } + + & > div { + flex: 1 1 33.3%; + min-height: 1px; + } + + .nav-left { + display: flex; + align-items: stretch; + justify-content: flex-start; + flex-wrap: nowrap; + } + + .nav-center { + display: flex; + align-items: stretch; + justify-content: center; + flex-wrap: nowrap; + } + + .nav-right { + display: flex; + align-items: stretch; + justify-content: flex-end; + flex-wrap: nowrap; + } + + .brand { + display: block; + padding: 15px; + + img { + display: block; + height: 18px; + width: auto; + position: relative; + bottom: -2px; + + @media screen and (max-width: $no-gap-breakpoint) { + height: 20px; + } + } + + &:hover, + &:focus, + &:active { + background: lighten($ui-base-color, 12%); + } + } + + .nav-link { + display: flex; + align-items: center; + padding: 0 1rem; + font-size: 12px; + font-weight: 500; + text-decoration: none; + color: $darker-text-color; + white-space: nowrap; + text-align: center; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + color: $primary-text-color; + } + } + + .nav-button { + background: lighten($ui-base-color, 16%); + margin: 8px; + margin-left: 0; + border-radius: 4px; + + &:hover, + &:focus, + &:active { + text-decoration: none; + background: lighten($ui-base-color, 20%); + } + } + } + + $no-columns-breakpoint: 600px; + + .grid { + display: grid; + grid-gap: 10px; + grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr); + grid-auto-columns: 25%; + grid-auto-rows: max-content; + + .column-0 { + grid-row: 1; + grid-column: 1; + } + + .column-1 { + grid-row: 1; + grid-column: 2; + } + + @media screen and (max-width: $no-columns-breakpoint) { + grid-template-columns: 100%; + grid-gap: 0; + + .column-1 { + display: none; + } + } + } + + .public-account-header { + overflow: hidden; + margin-bottom: 10px; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + + &__image { + border-radius: 4px 4px 0 0; + overflow: hidden; + height: 300px; + position: relative; + background: darken($ui-base-color, 12%); + + &::after { + content: ""; + display: block; + position: absolute; + width: 100%; + height: 100%; + box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15); + top: 0; + left: 0; + } + + img { + object-fit: cover; + display: block; + width: 100%; + height: 100%; + margin: 0; + border-radius: 4px 4px 0 0; + } + + @media screen and (max-width: 600px) { + height: 200px; + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + margin-bottom: 0; + box-shadow: none; + + &__image::after { + display: none; + } + + &__image, + &__image img { + border-radius: 0; + } + } + + &__bar { + position: relative; + margin-top: -80px; + display: flex; + justify-content: flex-start; + + &::before { + content: ""; + display: block; + background: lighten($ui-base-color, 4%); + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 60px; + border-radius: 0 0 4px 4px; + z-index: -1; + } + + .avatar { + display: block; + width: 120px; + height: 120px; + @include avatar-size(120px); + padding-left: 20px - 4px; + flex: 0 0 auto; + + img { + display: block; + width: 100%; + height: 100%; + margin: 0; + border-radius: 50%; + border: 4px solid lighten($ui-base-color, 4%); + background: darken($ui-base-color, 8%); + @include avatar-radius(); + } + } + + @media screen and (max-width: 600px) { + margin-top: 0; + background: lighten($ui-base-color, 4%); + border-radius: 0 0 4px 4px; + padding: 5px; + + &::before { + display: none; + } + + .avatar { + width: 48px; + height: 48px; + @include avatar-size(48px); + padding: 7px 0; + padding-left: 10px; + + img { + border: 0; + border-radius: 4px; + @include avatar-radius(); + } + + @media screen and (max-width: 360px) { + display: none; + } + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; + } + + @media screen and (max-width: $no-columns-breakpoint) { + flex-wrap: wrap; + } + } + + &__tabs { + flex: 1 1 auto; + margin-left: 20px; + + &__name { + padding-top: 20px; + padding-bottom: 8px; + + h1 { + font-size: 20px; + line-height: 18px * 1.5; + color: $primary-text-color; + font-weight: 500; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + text-shadow: 1px 1px 1px $base-shadow-color; + + small { + display: block; + font-size: 14px; + color: $primary-text-color; + font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + @media screen and (max-width: 600px) { + margin-left: 15px; + display: flex; + justify-content: space-between; + align-items: center; + + &__name { + padding-top: 0; + padding-bottom: 0; + + h1 { + font-size: 16px; + line-height: 24px; + text-shadow: none; + + small { + color: $darker-text-color; + } + } + } + } + + &__tabs { + display: flex; + justify-content: flex-start; + align-items: stretch; + height: 58px; + + .details-counters { + display: flex; + flex-direction: row; + min-width: 300px; + } + + @media screen and (max-width: $no-columns-breakpoint) { + .details-counters { + display: none; + } + } + + .counter { + width: 33.3%; + box-sizing: border-box; + flex: 0 0 auto; + color: $darker-text-color; + padding: 10px; + border-right: 1px solid lighten($ui-base-color, 4%); + cursor: default; + text-align: center; + position: relative; + + a { + display: block; + } + + &:last-child { + border-right: 0; + } + + &::after { + display: block; + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + border-bottom: 4px solid $ui-primary-color; + opacity: 0.5; + transition: all 400ms ease; + } + + &.active { + &::after { + border-bottom: 4px solid $highlight-text-color; + opacity: 1; + } + } + + &:hover { + &::after { + opacity: 1; + transition-duration: 100ms; + } + } + + a { + text-decoration: none; + color: inherit; + } + + .counter-label { + font-size: 12px; + display: block; + } + + .counter-number { + font-weight: 500; + font-size: 18px; + margin-bottom: 5px; + color: $primary-text-color; + font-family: 'mastodon-font-display', sans-serif; + } + } + + .spacer { + flex: 1 1 auto; + height: 1px; + } + + &__buttons { + padding: 7px 8px; + } + } + } + + &__extra { + display: none; + margin-top: 4px; + + .public-account-bio { + border-radius: 0; + box-shadow: none; + background: transparent; + margin: 0 -5px; + + .account__header__fields { + border-top: 1px solid lighten($ui-base-color, 12%); + } + + .roles { + display: none; + } + } + + &__links { + margin-top: -15px; + font-size: 14px; + color: $darker-text-color; + + a { + display: inline-block; + color: $darker-text-color; + text-decoration: none; + padding: 15px; + + strong { + font-weight: 700; + color: $primary-text-color; + } + } + } + + @media screen and (max-width: $no-columns-breakpoint) { + display: block; + flex: 100%; + } + } + } + + .account__section-headline { + border-radius: 4px 4px 0 0; + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; + } + } + + .detailed-status__meta { + margin-top: 25px; + } + + .public-account-bio { + background: lighten($ui-base-color, 8%); + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + border-radius: 4px; + overflow: hidden; + margin-bottom: 10px; + + @media screen and (max-width: $no-gap-breakpoint) { + box-shadow: none; + margin-bottom: 0; + border-radius: 0; + } + + .account__header__fields { + margin: 0; + border-top: 0; + + a { + color: lighten($ui-highlight-color, 8%); + } + } + + .account__header__content { + padding: 20px; + padding-bottom: 0; + color: $primary-text-color; + } + + &__extra, + .roles { + padding: 20px; + font-size: 14px; + color: $darker-text-color; + } + + .roles { + padding-bottom: 0; + } + } + + .static-icon-button { + color: $action-button-color; + font-size: 18px; + + & > span { + font-size: 14px; + font-weight: 500; + } + } + + .card-grid { + display: flex; + flex-wrap: wrap; + min-width: 100%; + margin: 0 -5px; + + & > div { + box-sizing: border-box; + flex: 1 0 auto; + width: 300px; + padding: 0 5px; + margin-bottom: 10px; + max-width: 33.333%; + + @media screen and (max-width: 900px) { + max-width: 50%; + } + + @media screen and (max-width: 600px) { + max-width: 100%; + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + margin: 0; + border-top: 1px solid lighten($ui-base-color, 8%); + + & > div { + width: 100%; + padding: 0; + margin-bottom: 0; + border-bottom: 1px solid lighten($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + .card__bar { + background: $ui-base-color; + + &:hover, + &:active, + &:focus { + background: lighten($ui-base-color, 4%); + } + } + } + } + } +} diff --git a/app/javascript/flavours/glitch/styles/footer.scss b/app/javascript/flavours/glitch/styles/footer.scss index fe2d40c0c..4d75477e0 100644 --- a/app/javascript/flavours/glitch/styles/footer.scss +++ b/app/javascript/flavours/glitch/styles/footer.scss @@ -1,38 +1,140 @@ -.footer { - text-align: center; - margin-top: 30px; - font-size: 12px; - color: $darker-text-color; +.public-layout { + .footer { + text-align: left; + padding-top: 20px; + padding-bottom: 60px; + font-size: 12px; + color: lighten($ui-base-color, 34%); - .footer__domain { - font-weight: 500; - - a { - color: inherit; - text-decoration: none; + @media screen and (max-width: $no-gap-breakpoint) { + padding-left: 20px; + padding-right: 20px; } - } - .powered-by, - .single-user-login { - font-weight: 400; + .grid { + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr 1fr 2fr 1fr 1fr; + + .column-0 { + grid-column: 1; + grid-row: 1; + min-width: 0; + } + + .column-1 { + grid-column: 2; + grid-row: 1; + min-width: 0; + } + + .column-2 { + grid-column: 3; + grid-row: 1; + min-width: 0; + text-align: center; + + h4 a { + color: lighten($ui-base-color, 34%); + } + } + + .column-3 { + grid-column: 4; + grid-row: 1; + min-width: 0; + } + + .column-4 { + grid-column: 5; + grid-row: 1; + min-width: 0; + } + + @media screen and (max-width: 690px) { + grid-template-columns: 1fr 2fr 1fr; + + .column-0, + .column-1 { + grid-column: 1; + } + + .column-1 { + grid-row: 2; + } + + .column-2 { + grid-column: 2; + } - a { - color: inherit; - text-decoration: underline; - font-weight: 500; + .column-3, + .column-4 { + grid-column: 3; + } - &:hover { + .column-4 { + grid-row: 2; + } + } + + @media screen and (max-width: 600px) { + .column-1 { + display: block; + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + .column-0, + .column-1, + .column-3, + .column-4 { + display: none; + } + } + } + + h4 { + text-transform: uppercase; + font-weight: 700; + margin-bottom: 8px; + color: $darker-text-color; + + a { + color: inherit; text-decoration: none; } } - img { - margin: 0 4px; - position: relative; - bottom: -1px; - height: 18px; - vertical-align: top; + ul a { + text-decoration: none; + color: lighten($ui-base-color, 34%); + + &:hover, + &:active, + &:focus { + text-decoration: underline; + } + } + + .brand { + svg { + display: block; + height: 36px; + width: auto; + margin: 0 auto; + + path { + fill: lighten($ui-base-color, 34%); + } + } + + &:hover, + &:focus, + &:active { + svg path { + fill: lighten($ui-base-color, 38%); + } + } } } } diff --git a/app/javascript/flavours/glitch/styles/index.scss b/app/javascript/flavours/glitch/styles/index.scss index e25a0ddd6..8e3ff43e3 100644 --- a/app/javascript/flavours/glitch/styles/index.scss +++ b/app/javascript/flavours/glitch/styles/index.scss @@ -11,7 +11,7 @@ @import 'modal'; @import 'footer'; @import 'compact_header'; -@import 'landing_strip'; +@import 'widgets'; @import 'forms'; @import 'accounts'; @import 'stream_entries'; diff --git a/app/javascript/flavours/glitch/styles/landing_strip.scss b/app/javascript/flavours/glitch/styles/landing_strip.scss deleted file mode 100644 index 86614b89b..000000000 --- a/app/javascript/flavours/glitch/styles/landing_strip.scss +++ /dev/null @@ -1,111 +0,0 @@ -.landing-strip, -.memoriam-strip { - background: rgba(darken($ui-base-color, 7%), 0.8); - color: $darker-text-color; - font-weight: 400; - padding: 14px; - border-radius: 4px; - margin-bottom: 20px; - display: flex; - align-items: center; - - strong, - a { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - a { - color: inherit; - text-decoration: underline; - } - - .logo { - width: 30px; - height: 30px; - flex: 0 0 auto; - margin-right: 15px; - } - - @media screen and (max-width: 740px) { - margin-bottom: 0; - } -} - -.memoriam-strip { - background: rgba($base-shadow-color, 0.7); -} - -.moved-strip { - padding: 14px; - border-radius: 4px; - background: rgba(darken($ui-base-color, 7%), 0.8); - color: $secondary-text-color; - font-weight: 400; - margin-bottom: 20px; - - strong, - a { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - a { - color: inherit; - text-decoration: underline; - - &.mention { - text-decoration: none; - - span { - text-decoration: none; - } - - &:focus, - &:hover, - &:active { - text-decoration: none; - - span { - text-decoration: underline; - } - } - } - } - - &__message { - margin-bottom: 15px; - - .fa { - margin-right: 5px; - color: $darker-text-color; - } - } - - &__card { - .detailed-status__display-avatar { - position: relative; - cursor: pointer; - } - - .detailed-status__display-name { - margin-bottom: 0; - text-decoration: none; - - span { - color: $highlight-text-color; - font-weight: 400; - } - } - } -} diff --git a/app/javascript/flavours/glitch/styles/stream_entries.scss b/app/javascript/flavours/glitch/styles/stream_entries.scss index a26859ba1..0094f672c 100644 --- a/app/javascript/flavours/glitch/styles/stream_entries.scss +++ b/app/javascript/flavours/glitch/styles/stream_entries.scss @@ -1,368 +1,185 @@ .activity-stream { - clear: both; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + border-radius: 4px; + overflow: hidden; + margin-bottom: 10px; + + @media screen and (max-width: $no-gap-breakpoint) { + margin-bottom: 0; + border-radius: 0; + box-shadow: none; + } + + &--headless { + border-radius: 0; + margin: 0; + box-shadow: none; + + .detailed-status, + .status { + border-radius: 0 !important; + } + } div[data-component] { width: 100%; } .entry { - background: $simple-background-color; + background: $ui-base-color; - .detailed-status.light, - .status.light, - .more.light { - border-bottom: 1px solid $ui-secondary-color; + .detailed-status, + .status, + .load-more { animation: none; } &:last-child { - &, - .detailed-status.light, - .status.light { + .detailed-status, + .status { border-bottom: 0; border-radius: 0 0 4px 4px; } } &:first-child { - &, - .detailed-status.light, - .status.light { + .detailed-status, + .status { border-radius: 4px 4px 0 0; } &:last-child { - &, - .detailed-status.light, - .status.light { + .detailed-status, + .status { border-radius: 4px; } } } @media screen and (max-width: 740px) { - &, - .detailed-status.light, - .status.light { + .detailed-status, + .status { border-radius: 0 !important; } } } +} - &.with-header { - .entry { - &:first-child { - &, - .detailed-status.light, - .status.light { - border-radius: 0; - } - - &:last-child { - &, - .detailed-status.light, - .status.light { - border-radius: 0 0 4px 4px; - } - } - } - } - } - - .media-gallery__gifv__label { - bottom: 9px; - } - - .status.light { - padding: 14px 14px 14px (48px + 14px * 2); - position: relative; - min-height: 48px; - cursor: default; - - .status__header { - font-size: 15px; - - .status__meta { - float: right; - font-size: 14px; - - .status__relative-time { - color: $lighter-text-color; - } - } - } - - .status__display-name { - display: block; - max-width: 100%; - padding-right: 25px; - color: $inverted-text-color; - } - - .status__avatar { - position: absolute; - left: 14px; - top: 14px; - width: 48px; - height: 48px; - @include avatar-size(48px); - - & > div { - width: 48px; - height: 48px; - @include avatar-size(48px); - } - - img { - display: block; - border-radius: 4px; - @include avatar-radius(); - } - } - - .display-name { - display: block; - max-width: 100%; - //overflow: hidden; - //white-space: nowrap; - //text-overflow: ellipsis; - - strong { - font-weight: 500; - color: $inverted-text-color; +.button.logo-button { + flex: 0 auto; + font-size: 14px; + background: $ui-highlight-color; + color: $primary-text-color; + text-transform: none; + line-height: 36px; + height: auto; + padding: 3px 15px; + border: 0; - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } + svg { + width: 20px; + height: auto; + vertical-align: middle; + margin-right: 5px; - span { - font-size: 14px; - color: $light-text-color; - } + path:first-child { + fill: $primary-text-color; } - .status__content { - color: $inverted-text-color; - - a { - color: $highlight-text-color; - } - - a.status__content__spoiler-link { - color: $primary-text-color; - background: $ui-base-color; - - &:hover { - background: lighten($ui-base-color, 8%); - } - } + path:last-child { + fill: $ui-highlight-color; } } - .detailed-status.light { - padding: 14px; - background: $simple-background-color; - cursor: default; - - .detailed-status__display-name { - display: block; - overflow: hidden; - margin-bottom: 15px; + &:active, + &:focus, + &:hover { + background: lighten($ui-highlight-color, 10%); - & > div { - float: left; - margin-right: 10px; - } - - .display-name { - display: block; - max-width: 100%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - strong { - font-weight: 500; - color: $inverted-text-color; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - span { - font-size: 14px; - color: $light-text-color; - } - } - } - - .avatar { - width: 48px; - height: 48px; - @include avatar-size(48px); - - img { - display: block; - border-radius: 4px; - @include avatar-radius(); - } - } - - .status__content { - color: $inverted-text-color; - - a { - color: $highlight-text-color; - } - - a.status__content__spoiler-link { - color: $primary-text-color; - background: $ui-base-color; - - &:hover { - background: lighten($ui-base-color, 8%); - } - } - } - - .detailed-status__meta { - margin-top: 15px; - color: $light-text-color; - font-size: 14px; - line-height: 18px; - - a { - color: inherit; - } - - span > span { - font-weight: 500; - font-size: 12px; - margin-left: 6px; - display: inline-block; - } - } - - .status-card { - border-color: lighten($ui-secondary-color, 4%); - color: $lighter-text-color; - - &:hover { - background: lighten($ui-secondary-color, 4%); - } - } - - .status-card__title, - .status-card__description { - color: $inverted-text-color; + svg path:last-child { + fill: lighten($ui-highlight-color, 10%); } + } - .status-card__image { - background: $ui-secondary-color; + @media screen and (max-width: $no-gap-breakpoint) { + svg { + display: none; } } +} - .media-spoiler { - background: $ui-base-color; - color: $darker-text-color; +.embed, +.public-layout { + .detailed-status { + padding: 15px; } - .pre-header { - padding: 14px 0; - padding-left: (48px + 14px * 2); - padding-bottom: 0; - margin-bottom: -4px; - color: $light-text-color; - font-size: 14px; - position: relative; + .status { + padding: 15px 15px 15px (48px + 15px * 2); + min-height: 48px + 2px; - .pre-header__icon { - position: absolute; - left: (48px + 14px * 2 - 30px); + &__avatar { + left: 15px; + top: 17px; } - .status__display-name.muted strong { - color: $light-text-color; + &__content { + padding-top: 5px; } - } - .open-in-web-link { - text-decoration: none; - - &:hover { - text-decoration: underline; + &__prepend { + margin-left: 48px + 15px * 2; + padding-top: 15px; } - } - .more { - color: $darker-text-color; - display: block; - padding: 14px; - text-align: center; - - &:not(:hover) { - text-decoration: none; + &__prepend-icon-wrapper { + left: -32px; } - } -} -.embed { - .activity-stream { - box-shadow: none; + .media-gallery, + &__action-bar, + .video-player { + margin-top: 10px; + } } } -.entry { - - .detailed-status.light { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - align-items: flex-start; +// Styling from upstream's WebUI, as public pages use the same layout +.embed, +.public-layout { + .status { + .status__info .status__display-name { + display: block; + max-width: 100%; + padding-right: 25px; + } - .detailed-status__display-name { - flex: 1; - margin: 0 5px 15px 0; + .status__info { + font-size: 15px; + display: initial; } - .button.button-secondary.logo-button { - flex: 0 auto; + .status__relative-time { + color: $dark-text-color; + float: right; font-size: 14px; + width: auto; + margin: initial; + padding: initial; + } - svg { - width: 20px; - height: auto; - vertical-align: middle; - margin-right: 5px; - - path:first-child { - fill: $ui-primary-color; - } - - path:last-child { - fill: $simple-background-color; - } - } - - &:active, - &:focus, - &:hover { - svg path:first-child { - fill: lighten($ui-primary-color, 4%); - } - } + .status__info .status__display-name { + display: block; + max-width: 100%; + padding-right: 25px; + margin: initial; } - .status__content, - .detailed-status__meta { - flex: 100%; + .status__avatar { + height: 48px; + position: absolute; + width: 48px; + margin: initial; } } } diff --git a/app/javascript/flavours/glitch/styles/variables.scss b/app/javascript/flavours/glitch/styles/variables.scss index bde808fe2..715ecf98f 100644 --- a/app/javascript/flavours/glitch/styles/variables.scss +++ b/app/javascript/flavours/glitch/styles/variables.scss @@ -49,6 +49,8 @@ $media-modal-media-max-width: 100%; // put margins on top and bottom of image to avoid the screen covered by image. $media-modal-media-max-height: 80%; +$no-gap-breakpoint: 415px; + // Avatar border size (8% default, 100% for rounded avatars) $ui-avatar-border-size: 8%; diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss new file mode 100644 index 000000000..d37a6f458 --- /dev/null +++ b/app/javascript/flavours/glitch/styles/widgets.scss @@ -0,0 +1,161 @@ +.hero-widget { + margin-bottom: 10px; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + + &__img { + width: 100%; + height: 167px; + position: relative; + overflow: hidden; + border-radius: 4px 4px 0 0; + background: $base-shadow-color; + + img { + object-fit: cover; + display: block; + width: 100%; + height: 100%; + margin: 0; + border-radius: 4px 4px 0 0; + } + } + + &__text { + background: $ui-base-color; + padding: 20px; + border-radius: 0 0 4px 4px; + font-size: 15px; + color: $darker-text-color; + line-height: 20px; + word-wrap: break-word; + font-weight: 400; + + .emojione { + width: 20px; + height: 20px; + margin: -3px 0 0; + } + + p { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + } + + em { + display: inline; + margin: 0; + padding: 0; + font-weight: 700; + background: transparent; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: lighten($darker-text-color, 10%); + } + + a { + color: $secondary-text-color; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + display: none; + } +} + +.moved-account-widget { + padding: 15px; + padding-bottom: 20px; + border-radius: 4px; + background: $ui-base-color; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + color: $secondary-text-color; + font-weight: 400; + margin-bottom: 10px; + + strong, + a { + font-weight: 500; + + @each $lang in $cjk-langs { + &:lang(#{$lang}) { + font-weight: 700; + } + } + } + + a { + color: inherit; + text-decoration: underline; + + &.mention { + text-decoration: none; + + span { + text-decoration: none; + } + + &:focus, + &:hover, + &:active { + text-decoration: none; + + span { + text-decoration: underline; + } + } + } + } + + &__message { + margin-bottom: 15px; + + .fa { + margin-right: 5px; + color: $darker-text-color; + } + } + + &__card { + .detailed-status__display-avatar { + position: relative; + cursor: pointer; + } + + .detailed-status__display-name { + margin-bottom: 0; + text-decoration: none; + + span { + font-weight: 400; + } + } + } +} + +.memoriam-widget { + padding: 20px; + border-radius: 4px; + background: $base-shadow-color; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + font-size: 14px; + color: $darker-text-color; + margin-bottom: 10px; +} + +.moved-account-widget, +.memoriam-widget { + @media screen and (max-width: $no-gap-breakpoint) { + margin-bottom: 0; + box-shadow: none; + border-radius: 0; + } +} diff --git a/app/javascript/mastodon/components/relative_timestamp.js b/app/javascript/mastodon/components/relative_timestamp.js index 3c8db7092..9609714a1 100644 --- a/app/javascript/mastodon/components/relative_timestamp.js +++ b/app/javascript/mastodon/components/relative_timestamp.js @@ -60,6 +60,32 @@ const getUnitDelay = units => { } }; +export const timeAgoString = (intl, date, now, year) => { + const delta = now - date.getTime(); + + let relativeTime; + + if (delta < 10 * SECOND) { + relativeTime = intl.formatMessage(messages.just_now); + } else if (delta < 7 * DAY) { + if (delta < MINUTE) { + relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); + } else if (delta < HOUR) { + relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); + } else if (delta < DAY) { + relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); + } else { + relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); + } + } else if (date.getFullYear() === year) { + relativeTime = intl.formatDate(date, shortDateFormatOptions); + } else { + relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); + } + + return relativeTime; +}; + @injectIntl export default class RelativeTimestamp extends React.Component { @@ -121,28 +147,8 @@ export default class RelativeTimestamp extends React.Component { render () { const { timestamp, intl, year } = this.props; - const date = new Date(timestamp); - const delta = this.state.now - date.getTime(); - - let relativeTime; - - if (delta < 10 * SECOND) { - relativeTime = intl.formatMessage(messages.just_now); - } else if (delta < 7 * DAY) { - if (delta < MINUTE) { - relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); - } else if (delta < HOUR) { - relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); - } else if (delta < DAY) { - relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); - } else { - relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); - } - } else if (date.getFullYear() === year) { - relativeTime = intl.formatDate(date, shortDateFormatOptions); - } else { - relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); - } + const date = new Date(timestamp); + const relativeTime = timeAgoString(intl, date, this.state.now, year); return ( <time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}> diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 68c9eef54..37f21fb44 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -1,12 +1,12 @@ import { debounce } from 'lodash'; import React from 'react'; +import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import LoadGap from './load_gap'; import ScrollableList from './scrollable_list'; -import { FormattedMessage } from 'react-intl'; export default class StatusList extends ImmutablePureComponent { @@ -71,7 +71,7 @@ export default class StatusList extends ImmutablePureComponent { } render () { - const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other } = this.props; + const { statusIds, featuredStatusIds, shouldUpdateScroll, onLoadMore, timelineId, ...other } = this.props; const { isLoading, isPartial } = other; if (isPartial) { @@ -122,7 +122,7 @@ export default class StatusList extends ImmutablePureComponent { } return ( - <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}> + <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} shouldUpdateScroll={shouldUpdateScroll} ref={this.setRef}> {scrollableContent} </ScrollableList> ); diff --git a/app/javascript/mastodon/containers/media_container.js b/app/javascript/mastodon/containers/media_container.js index 1700fba05..43bb39403 100644 --- a/app/javascript/mastodon/containers/media_container.js +++ b/app/javascript/mastodon/containers/media_container.js @@ -29,19 +29,19 @@ export default class MediaContainer extends PureComponent { }; handleOpenMedia = (media, index) => { - document.body.classList.add('media-standalone__body'); + document.body.classList.add('with-modals--active'); this.setState({ media, index }); } handleOpenVideo = (video, time) => { const media = ImmutableList([video]); - document.body.classList.add('media-standalone__body'); + document.body.classList.add('with-modals--active'); this.setState({ media, time }); } handleCloseMedia = () => { - document.body.classList.remove('media-standalone__body'); + document.body.classList.remove('with-modals--active'); this.setState({ media: null, index: null, time: null }); } diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index 69726a416..e3f2d0f55 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -142,17 +142,17 @@ export default class ActionBar extends React.PureComponent { <div className='account__action-bar'> <div className='account__action-bar-links'> <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}> - <span><FormattedMessage id='account.posts' defaultMessage='Toots' /></span> + <FormattedMessage id='account.posts' defaultMessage='Toots' /> <strong>{shortNumberFormat(account.get('statuses_count'))}</strong> </Link> <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}> - <span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span> + <FormattedMessage id='account.follows' defaultMessage='Follows' /> <strong>{shortNumberFormat(account.get('following_count'))}</strong> </Link> <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}> - <span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span> + <FormattedMessage id='account.followers' defaultMessage='Followers' /> <strong>{shortNumberFormat(account.get('followers_count'))}</strong> </Link> </div> diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 5f564d3a9..a6c464aff 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -23,6 +23,7 @@ const mapStateToProps = (state, props) => ({ class LoadMoreMedia extends ImmutablePureComponent { static propTypes = { + shouldUpdateScroll: PropTypes.func, maxId: PropTypes.string, onLoadMore: PropTypes.func.isRequired, }; @@ -90,7 +91,7 @@ export default class AccountGallery extends ImmutablePureComponent { } render () { - const { medias, isLoading, hasMore } = this.props; + const { medias, shouldUpdateScroll, isLoading, hasMore } = this.props; let loadOlder = null; @@ -110,7 +111,7 @@ export default class AccountGallery extends ImmutablePureComponent { <Column> <ColumnBackButton /> - <ScrollContainer scrollKey='account_gallery'> + <ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable' onScroll={this.handleScroll}> <HeaderContainer accountId={this.props.params.accountId} /> diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index d329bac5c..934513cd7 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -29,6 +29,7 @@ export default class AccountTimeline extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, statusIds: ImmutablePropTypes.list, featuredStatusIds: ImmutablePropTypes.list, isLoading: PropTypes.bool, @@ -61,7 +62,7 @@ export default class AccountTimeline extends ImmutablePureComponent { } render () { - const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props; + const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore } = this.props; if (!statusIds && isLoading) { return ( @@ -83,6 +84,7 @@ export default class AccountTimeline extends ImmutablePureComponent { isLoading={isLoading} hasMore={hasMore} onLoadMore={this.handleLoadMore} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js index 14a512ae8..0b88e50ae 100644 --- a/app/javascript/mastodon/features/blocks/index.js +++ b/app/javascript/mastodon/features/blocks/index.js @@ -1,5 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import LoadingIndicator from '../../components/loading_indicator'; @@ -8,8 +10,6 @@ import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountContainer from '../../containers/account_container'; import { fetchBlocks, expandBlocks } from '../../actions/blocks'; -import { defineMessages, injectIntl } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ heading: { id: 'column.blocks', defaultMessage: 'Blocked users' }, @@ -26,6 +26,7 @@ export default class Blocks extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, }; @@ -43,7 +44,7 @@ export default class Blocks extends ImmutablePureComponent { } render () { - const { intl, accountIds } = this.props; + const { intl, accountIds, shouldUpdateScroll } = this.props; if (!accountIds) { return ( @@ -56,7 +57,7 @@ export default class Blocks extends ImmutablePureComponent { return ( <Column icon='ban' heading={intl.formatMessage(messages.heading)}> <ColumnBackButtonSlim /> - <ScrollContainer scrollKey='blocks'> + <ScrollContainer scrollKey='blocks' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable' onScroll={this.handleScroll}> {accountIds.map(id => <AccountContainer key={id} id={id} /> diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index eb9ad97a2..1cd5cf157 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -39,6 +39,7 @@ export default class CommunityTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, columnId: PropTypes.string, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, @@ -100,7 +101,7 @@ export default class CommunityTimeline extends React.PureComponent { } render () { - const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; + const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; return ( @@ -124,6 +125,7 @@ export default class CommunityTimeline extends React.PureComponent { timelineId={`community${onlyMedia ? ':media' : ''}`} onLoadMore={this.handleLoadMore} emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js index 63dc41d9e..2181c75b6 100644 --- a/app/javascript/mastodon/features/direct_timeline/index.js +++ b/app/javascript/mastodon/features/direct_timeline/index.js @@ -23,6 +23,7 @@ export default class DirectTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, columnId: PropTypes.string, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, @@ -71,7 +72,7 @@ export default class DirectTimeline extends React.PureComponent { } render () { - const { intl, hasUnread, columnId, multiColumn } = this.props; + const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props; const pinned = !!columnId; return ( @@ -93,6 +94,7 @@ export default class DirectTimeline extends React.PureComponent { timelineId='direct' onLoadMore={this.handleLoadMore} emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js index b8a942d6c..e4e2b5239 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.js +++ b/app/javascript/mastodon/features/domain_blocks/index.js @@ -1,15 +1,15 @@ import React from 'react'; import { connect } from 'react-redux'; -import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import DomainContainer from '../../containers/domain_container'; import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; -import { defineMessages, injectIntl } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { debounce } from 'lodash'; import ScrollableList from '../../components/scrollable_list'; const messages = defineMessages({ @@ -28,6 +28,7 @@ export default class Blocks extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, domains: ImmutablePropTypes.orderedSet, intl: PropTypes.object.isRequired, }; @@ -41,7 +42,7 @@ export default class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, domains } = this.props; + const { intl, domains, shouldUpdateScroll } = this.props; if (!domains) { return ( @@ -54,7 +55,7 @@ export default class Blocks extends ImmutablePureComponent { return ( <Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}> <ColumnBackButtonSlim /> - <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore}> + <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore} shouldUpdateScroll={shouldUpdateScroll}> {domains.map(domain => <DomainContainer key={domain} domain={domain} /> )} diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index 6f1c863b4..3973ed3cb 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -27,6 +27,7 @@ export default class Favourites extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, statusIds: ImmutablePropTypes.list.isRequired, intl: PropTypes.object.isRequired, columnId: PropTypes.string, @@ -67,7 +68,7 @@ export default class Favourites extends ImmutablePureComponent { }, 300, { leading: true }) render () { - const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; + const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const pinned = !!columnId; return ( @@ -90,6 +91,7 @@ export default class Favourites extends ImmutablePureComponent { hasMore={hasMore} isLoading={isLoading} onLoadMore={this.handleLoadMore} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js index 6f113beb4..40fe6c9a8 100644 --- a/app/javascript/mastodon/features/favourites/index.js +++ b/app/javascript/mastodon/features/favourites/index.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; @@ -8,7 +9,6 @@ import { ScrollContainer } from 'react-router-scroll-4'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import ColumnBackButton from '../../components/column_back_button'; -import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]), @@ -20,6 +20,7 @@ export default class Favourites extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, }; @@ -34,7 +35,7 @@ export default class Favourites extends ImmutablePureComponent { } render () { - const { accountIds } = this.props; + const { shouldUpdateScroll, accountIds } = this.props; if (!accountIds) { return ( @@ -48,7 +49,7 @@ export default class Favourites extends ImmutablePureComponent { <Column> <ColumnBackButton /> - <ScrollContainer scrollKey='favourites'> + <ScrollContainer scrollKey='favourites' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable'> {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} </div> diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index eae821f92..53a394cbc 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -1,5 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; @@ -8,8 +10,6 @@ import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountAuthorizeContainer from './containers/account_authorize_container'; import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; -import { defineMessages, injectIntl } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }, @@ -26,6 +26,7 @@ export default class FollowRequests extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, }; @@ -43,7 +44,7 @@ export default class FollowRequests extends ImmutablePureComponent { } render () { - const { intl, accountIds } = this.props; + const { intl, shouldUpdateScroll, accountIds } = this.props; if (!accountIds) { return ( @@ -57,7 +58,7 @@ export default class FollowRequests extends ImmutablePureComponent { <Column icon='users' heading={intl.formatMessage(messages.heading)}> <ColumnBackButtonSlim /> - <ScrollContainer scrollKey='follow_requests'> + <ScrollContainer scrollKey='follow_requests' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable' onScroll={this.handleScroll}> {accountIds.map(id => <AccountAuthorizeContainer key={id} id={id} /> diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js index 919a89332..5bb8fdd6a 100644 --- a/app/javascript/mastodon/features/followers/index.js +++ b/app/javascript/mastodon/features/followers/index.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; @@ -14,7 +15,6 @@ import Column from '../ui/components/column'; import HeaderContainer from '../account_timeline/containers/header_container'; import LoadMore from '../../components/load_more'; import ColumnBackButton from '../../components/column_back_button'; -import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']), @@ -27,6 +27,7 @@ export default class Followers extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, }; @@ -57,7 +58,7 @@ export default class Followers extends ImmutablePureComponent { } render () { - const { accountIds, hasMore } = this.props; + const { shouldUpdateScroll, accountIds, hasMore } = this.props; let loadMore = null; @@ -77,7 +78,7 @@ export default class Followers extends ImmutablePureComponent { <Column> <ColumnBackButton /> - <ScrollContainer scrollKey='followers'> + <ScrollContainer scrollKey='followers' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable' onScroll={this.handleScroll}> <div className='followers'> <HeaderContainer accountId={this.props.params.accountId} hideTabs /> diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js index 5719259d1..97b0a8964 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; @@ -14,7 +15,6 @@ import Column from '../ui/components/column'; import HeaderContainer from '../account_timeline/containers/header_container'; import LoadMore from '../../components/load_more'; import ColumnBackButton from '../../components/column_back_button'; -import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']), @@ -27,6 +27,7 @@ export default class Following extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, hasMore: PropTypes.bool, }; @@ -57,7 +58,7 @@ export default class Following extends ImmutablePureComponent { } render () { - const { accountIds, hasMore } = this.props; + const { shouldUpdateScroll, accountIds, hasMore } = this.props; let loadMore = null; @@ -77,7 +78,7 @@ export default class Following extends ImmutablePureComponent { <Column> <ColumnBackButton /> - <ScrollContainer scrollKey='following'> + <ScrollContainer scrollKey='following' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable' onScroll={this.handleScroll}> <div className='following'> <HeaderContainer accountId={this.props.params.accountId} hideTabs /> diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index 374615ac7..15fca9ab4 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -20,6 +20,7 @@ export default class HashtagTimeline extends React.PureComponent { params: PropTypes.object.isRequired, columnId: PropTypes.string, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, }; @@ -83,7 +84,7 @@ export default class HashtagTimeline extends React.PureComponent { } render () { - const { hasUnread, columnId, multiColumn } = this.props; + const { shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props; const { id } = this.props.params; const pinned = !!columnId; @@ -107,6 +108,7 @@ export default class HashtagTimeline extends React.PureComponent { timelineId={`hashtag:${id}`} onLoadMore={this.handleLoadMore} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index db6bbdec1..4e6853c5b 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -25,6 +25,7 @@ export default class HomeTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, isPartial: PropTypes.bool, @@ -93,7 +94,7 @@ export default class HomeTimeline extends React.PureComponent { } render () { - const { intl, hasUnread, columnId, multiColumn } = this.props; + const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props; const pinned = !!columnId; return ( @@ -117,6 +118,7 @@ export default class HomeTimeline extends React.PureComponent { onLoadMore={this.handleLoadMore} timelineId='home' emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js index f08e77b7a..5c40fb758 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -35,6 +35,7 @@ export default class ListTimeline extends React.PureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, columnId: PropTypes.string, hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, @@ -112,7 +113,7 @@ export default class ListTimeline extends React.PureComponent { } render () { - const { hasUnread, columnId, multiColumn, list } = this.props; + const { shouldUpdateScroll, hasUnread, columnId, multiColumn, list } = this.props; const { id } = this.props.params; const pinned = !!columnId; const title = list ? list.get('title') : id; @@ -166,6 +167,7 @@ export default class ListTimeline extends React.PureComponent { timelineId={`list:${id}`} onLoadMore={this.handleLoadMore} emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js index bb351ece2..66fd3796d 100644 --- a/app/javascript/mastodon/features/mutes/index.js +++ b/app/javascript/mastodon/features/mutes/index.js @@ -1,5 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; @@ -8,8 +10,6 @@ import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountContainer from '../../containers/account_container'; import { fetchMutes, expandMutes } from '../../actions/mutes'; -import { defineMessages, injectIntl } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ heading: { id: 'column.mutes', defaultMessage: 'Muted users' }, @@ -26,6 +26,7 @@ export default class Mutes extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, }; @@ -43,7 +44,7 @@ export default class Mutes extends ImmutablePureComponent { } render () { - const { intl, accountIds } = this.props; + const { intl, shouldUpdateScroll, accountIds } = this.props; if (!accountIds) { return ( @@ -56,7 +57,7 @@ export default class Mutes extends ImmutablePureComponent { return ( <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}> <ColumnBackButtonSlim /> - <ScrollContainer scrollKey='mutes'> + <ScrollContainer scrollKey='mutes' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable mutes' onScroll={this.handleScroll}> {accountIds.map(id => <AccountContainer key={id} id={id} /> diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.js index b4a6c1e52..c6eb689d2 100644 --- a/app/javascript/mastodon/features/pinned_statuses/index.js +++ b/app/javascript/mastodon/features/pinned_statuses/index.js @@ -24,6 +24,7 @@ export default class PinnedStatuses extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, statusIds: ImmutablePropTypes.list.isRequired, intl: PropTypes.object.isRequired, hasMore: PropTypes.bool.isRequired, @@ -42,7 +43,7 @@ export default class PinnedStatuses extends ImmutablePureComponent { } render () { - const { intl, statusIds, hasMore } = this.props; + const { intl, shouldUpdateScroll, statusIds, hasMore } = this.props; return ( <Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}> @@ -51,6 +52,7 @@ export default class PinnedStatuses extends ImmutablePureComponent { statusIds={statusIds} scrollKey='pinned_statuses' hasMore={hasMore} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 2d5bb3baf..5f7ac5fc7 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -39,6 +39,7 @@ export default class PublicTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, intl: PropTypes.object.isRequired, columnId: PropTypes.string, multiColumn: PropTypes.bool, @@ -107,7 +108,7 @@ export default class PublicTimeline extends React.PureComponent { } render () { - const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props; + const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; return ( @@ -131,6 +132,7 @@ export default class PublicTimeline extends React.PureComponent { trackScroll={!pinned} scrollKey={`public_timeline-${columnId}`} emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} + shouldUpdateScroll={shouldUpdateScroll} /> </Column> ); diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js index 579d6aaa0..367739636 100644 --- a/app/javascript/mastodon/features/reblogs/index.js +++ b/app/javascript/mastodon/features/reblogs/index.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; @@ -8,7 +9,6 @@ import { ScrollContainer } from 'react-router-scroll-4'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import ColumnBackButton from '../../components/column_back_button'; -import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]), @@ -20,6 +20,7 @@ export default class Reblogs extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, + shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, }; @@ -34,7 +35,7 @@ export default class Reblogs extends ImmutablePureComponent { } render () { - const { accountIds } = this.props; + const { shouldUpdateScroll, accountIds } = this.props; if (!accountIds) { return ( @@ -48,7 +49,7 @@ export default class Reblogs extends ImmutablePureComponent { <Column> <ColumnBackButton /> - <ScrollContainer scrollKey='reblogs'> + <ScrollContainer scrollKey='reblogs' shouldUpdateScroll={shouldUpdateScroll}> <div className='scrollable reblogs'> {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} </div> diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index d7b50786c..89387ca43 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -42,7 +42,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; import { boostModal, deleteModal } from '../../initial_state'; -import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen'; +import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, @@ -370,7 +370,7 @@ export default class Status extends ImmutablePureComponent { render () { let ancestors, descendants; - const { status, ancestorsIds, descendantsIds, intl } = this.props; + const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl } = this.props; const { fullscreen } = this.state; if (status === null) { @@ -410,7 +410,7 @@ export default class Status extends ImmutablePureComponent { )} /> - <ScrollContainer scrollKey='thread'> + <ScrollContainer scrollKey='thread' shouldUpdateScroll={shouldUpdateScroll}> <div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}> {ancestors} diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index 12db95326..83b9e1b50 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -16,7 +16,7 @@ const messages = defineMessages({ next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); -const previewState = 'previewMediaModal'; +export const previewState = 'previewMediaModal'; @injectIntl export default class MediaModal extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index a334318ce..d8e034554 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -41,14 +41,15 @@ export default class ModalRoot extends React.PureComponent { }; getSnapshotBeforeUpdate () { - const visible = !!this.props.type; - return { - overflowY: visible ? 'hidden' : null, - }; + return { visible: !!this.props.type }; } - componentDidUpdate (prevProps, prevState, { overflowY }) { - document.body.style.overflowY = overflowY; + componentDidUpdate (prevProps, prevState, { visible }) { + if (visible) { + document.body.classList.add('with-modals--active'); + } else { + document.body.classList.remove('with-modals--active'); + } } renderLoading = modalId => () => { diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 56a856230..67484fc63 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -1,12 +1,14 @@ import classNames from 'classnames'; import React from 'react'; -import NotificationsContainer from './containers/notifications_container'; +import { HotKeys } from 'react-hotkeys'; +import { defineMessages, injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import { Redirect, withRouter } from 'react-router-dom'; import PropTypes from 'prop-types'; +import NotificationsContainer from './containers/notifications_container'; import LoadingBarContainer from './containers/loading_bar_container'; import TabsBar from './components/tabs_bar'; import ModalContainer from './containers/modal_container'; -import { connect } from 'react-redux'; -import { Redirect, withRouter } from 'react-router-dom'; import { isMobile } from '../../is_mobile'; import { debounce } from 'lodash'; import { uploadCompose, resetCompose } from '../../actions/compose'; @@ -44,9 +46,8 @@ import { PinnedStatuses, Lists, } from './util/async-components'; -import { HotKeys } from 'react-hotkeys'; import { me } from '../../initial_state'; -import { defineMessages, injectIntl } from 'react-intl'; +import { previewState } from './components/media_modal'; // Dummy import, to make sure that <Status /> ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. @@ -117,6 +118,10 @@ class SwitchingColumnsArea extends React.PureComponent { window.removeEventListener('resize', this.handleResize); } + shouldUpdateScroll (_, { location }) { + return location.state !== previewState; + } + handleResize = debounce(() => { // The cached heights are no longer accurate, invalidate this.props.onLayoutChange(); @@ -141,36 +146,36 @@ class SwitchingColumnsArea extends React.PureComponent { {redirect} <WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> - <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} /> - <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} /> - <WrappedRoute path='/timelines/public/media' component={PublicTimeline} content={children} componentParams={{ onlyMedia: true }} /> - <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} /> - <WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ onlyMedia: true }} /> - <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} /> - <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} /> - <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} /> - - <WrappedRoute path='/notifications' component={Notifications} content={children} /> - <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} /> - <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} /> + <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/timelines/public/media' component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, onlyMedia: true }} /> + <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, onlyMedia: true }} /> + <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + + <WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/search' component={Compose} content={children} componentParams={{ isSearchPage: true }} /> <WrappedRoute path='/statuses/new' component={Compose} content={children} /> - <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} /> - <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} /> - <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} /> - - <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} /> - <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} /> - <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} /> - <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} /> - <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} /> - - <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} /> - <WrappedRoute path='/blocks' component={Blocks} content={children} /> - <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} /> - <WrappedRoute path='/mutes' component={Mutes} content={children} /> + <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + + <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} /> + <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + + <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> + <WrappedRoute path='/mutes' component={Mutes} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> <WrappedRoute path='/lists' component={Lists} content={children} /> <WrappedRoute component={GenericNotFound} content={children} /> diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index e0dd0ab23..d276c4651 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -1,17 +1,17 @@ { "account.badges.bot": "Robot", - "account.block": "Blokovat @{name}", + "account.block": "Zablokovat uživatele @{name}", "account.block_domain": "Skrýt vše z {domain}", "account.blocked": "Blokován/a", "account.direct": "Přímá zpráva pro uživatele @{name}", "account.disclaimer_full": "Níže uvedené informace nemusejí zcela odrážet profil uživatele.", "account.domain_blocked": "Doména skryta", - "account.edit_profile": "Uprav profil", + "account.edit_profile": "Upravit profil", "account.follow": "Sleduj", "account.followers": "Sledovatelé", "account.follows": "Sleduje", "account.follows_you": "Sleduje vás", - "account.hide_reblogs": "Skrýt povýšení od uživatele @{name}", + "account.hide_reblogs": "Skrýt boosty od uživatele @{name}", "account.media": "Média", "account.mention": "Zmínit uživatele @{name}", "account.moved_to": "{name} se přesunul/a na:", @@ -23,7 +23,7 @@ "account.report": "Nahlásit uživatele @{name}", "account.requested": "Požadavek čeká na schválení. Kliknutím zrušíte požadavek o sledování", "account.share": "Sdílet profil uživatele @{name}", - "account.show_reblogs": "Zobrazit povýšení od uživatele @{name}", + "account.show_reblogs": "Zobrazit boosty od uživatele @{name}", "account.unblock": "Odblokovat uživatele @{name}", "account.unblock_domain": "Odkrýt doménu {domain}", "account.unfollow": "Přestat sledovat", @@ -64,7 +64,7 @@ "compose_form.direct_message_warning_learn_more": "Zjistit více", "compose_form.hashtag_warning": "Tento toot nebude zobrazen pod žádným hashtagem, neboť je neuvedený. Pouze veřejné tooty mohou být vyhledány podle hashtagu.", "compose_form.lock_disclaimer": "Váš účet není {locked}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledovatele.", - "compose_form.lock_disclaimer.lock": "zamknutý", + "compose_form.lock_disclaimer.lock": "zamčený", "compose_form.placeholder": "Co máte na mysli?", "compose_form.publish": "Tootnout", "compose_form.publish_loud": "{publish}!", @@ -85,225 +85,225 @@ "confirmations.mute.confirm": "Ignorovat", "confirmations.mute.message": "Jste si jistý/á, že chcete ignorovat uživatele {name}?", "confirmations.redraft.confirm": "Vymazat a přepsat", - "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.", - "confirmations.unfollow.confirm": "Unfollow", - "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", - "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 search 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.developers": "Developers", + "confirmations.redraft.message": "Jste si jistý/á, že chcete vymazat a přepsat tento status? Ztratíte všechny jeho odpovědi, boosty a oblíbení.", + "confirmations.unfollow.confirm": "Přestat sledovat", + "confirmations.unfollow.message": "jste si jistý/á, že chcete přestat sledovat uživatele {name}?", + "embed.instructions": "Pro přidání statusu na vaši webovou stránku zkopírujte níže uvedený kód.", + "embed.preview": "Takhle to bude vypadat:", + "emoji_button.activity": "Aktivita", + "emoji_button.custom": "Vlastní", + "emoji_button.flags": "Vlajky", + "emoji_button.food": "Jídla a nápoje", + "emoji_button.label": "Vložit emoji", + "emoji_button.nature": "Příroda", + "emoji_button.not_found": "Žádné emoji!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "Předměty", + "emoji_button.people": "Lidé", + "emoji_button.recent": "Často používané", + "emoji_button.search": "Hledat...", + "emoji_button.search_results": "Výsledky hledání", + "emoji_button.symbols": "Symboly", + "emoji_button.travel": "Cestování a místa", + "empty_column.community": "Místní časová osa je prázdná. Napište něco veřejně a rozhýbejte to tu!", + "empty_column.direct": "Ještě nemáte žádné přímé zprávy. Pokud nějakou pošlete nebo dostanete, zobrazí se zde.", + "empty_column.hashtag": "Pod tímto hashtagem ještě nic není.", + "empty_column.home": "Vaše domovská časová osa je prázdná! Začněte navštívením {public} nebo použijte hledání a seznamte se s dalšími uživateli.", + "empty_column.home.public_timeline": "veřejné časové osy", + "empty_column.list": "V tomto seznamu ještě nic není. Pokud budou členové tohoto seznamu psát nové statusy, objeví se zde.", + "empty_column.notifications": "Ještě nemáte žádná oznámení. Začněte konverzaci komunikováním s ostatními.", + "empty_column.public": "Tady nic není! Napište něco veřejně, nebo manuálně začněte sledovat uživatele z jiných instancí, aby tu něco přibylo", + "follow_request.authorize": "Autorizovat", + "follow_request.reject": "Odmítnout", + "getting_started.developers": "Vývojáři", "getting_started.documentation": "Documentation", - "getting_started.find_friends": "Find friends from Twitter", - "getting_started.heading": "Getting started", - "getting_started.invite": "Invite people", - "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", - "getting_started.security": "Security", - "getting_started.terms": "Terms of service", - "home.column_settings.basic": "Basic", - "home.column_settings.show_reblogs": "Show boosts", - "home.column_settings.show_replies": "Show replies", - "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", + "getting_started.find_friends": "Najděte si přátele z Twitteru", + "getting_started.heading": "Začínáme", + "getting_started.invite": "Pozvat lidi", + "getting_started.open_source_notice": "Mastodon je otevřený software. Na GitHubu k němu můžete přispět nebo nahlásit chyby: {github}.", + "getting_started.security": "Zabezpečení", + "getting_started.terms": "Podmínky používání", + "home.column_settings.basic": "Základní", + "home.column_settings.show_reblogs": "Zobrazit boosty", + "home.column_settings.show_replies": "Zobrazit odpovědi", + "keyboard_shortcuts.back": "k návratu zpět", + "keyboard_shortcuts.boost": "k boostnutí", + "keyboard_shortcuts.column": "k zaměření na status v jednom ze sloupců", + "keyboard_shortcuts.compose": "k zaměření na psací prostor", + "keyboard_shortcuts.description": "Popis", + "keyboard_shortcuts.down": "k přesunutí dolů v seznamu", + "keyboard_shortcuts.enter": "k otevření statusu", + "keyboard_shortcuts.favourite": "k oblíbení", + "keyboard_shortcuts.heading": "Klávesové zkratky", + "keyboard_shortcuts.hotkey": "Horká klávesa", + "keyboard_shortcuts.legend": "k zobrazení této legendy", + "keyboard_shortcuts.mention": "ke zmínění autora", "keyboard_shortcuts.profile": "to open author's profile", - "keyboard_shortcuts.reply": "to reply", - "keyboard_shortcuts.search": "to focus search", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", - "keyboard_shortcuts.toot": "to start a brand new toot", - "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": "Blocked users", - "navigation_bar.community_timeline": "Local timeline", - "navigation_bar.direct": "Direct messages", - "navigation_bar.discover": "Discover", - "navigation_bar.domain_blocks": "Hidden domains", - "navigation_bar.edit_profile": "Edit profile", - "navigation_bar.favourites": "Favourites", - "navigation_bar.filters": "Muted words", - "navigation_bar.follow_requests": "Follow requests", - "navigation_bar.info": "About this instance", - "navigation_bar.keyboard_shortcuts": "Hotkeys", - "navigation_bar.lists": "Lists", - "navigation_bar.logout": "Logout", - "navigation_bar.mutes": "Muted users", - "navigation_bar.personal": "Personal", - "navigation_bar.pins": "Pinned toots", - "navigation_bar.preferences": "Preferences", - "navigation_bar.public_timeline": "Federated timeline", - "navigation_bar.security": "Security", - "notification.favourite": "{name} favourited your status", - "notification.follow": "{name} followed you", - "notification.mention": "{name} mentioned you", - "notification.reblog": "{name} boosted your status", - "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", - "notifications.group": "{count} notifications", - "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": "Bon Appetoot!", - "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", + "keyboard_shortcuts.reply": "k odpovězení", + "keyboard_shortcuts.search": "k zaměření na vyhledávání", + "keyboard_shortcuts.toggle_hidden": "k zobrazení/skrytí textu za CW", + "keyboard_shortcuts.toot": "k napsání úplně nového tootu", + "keyboard_shortcuts.unfocus": "ke zrušení soustředění na psací prostor/hledání", + "keyboard_shortcuts.up": "k posunutí nahoru v seznamu", + "lightbox.close": "Zavřít", + "lightbox.next": "Další", + "lightbox.previous": "Předchozí", + "lists.account.add": "Přidat do seznamu", + "lists.account.remove": "Odebrat ze seznamu", + "lists.delete": "Smazat seznam", + "lists.edit": "Upravit seznam", + "lists.new.create": "Přidat seznam", + "lists.new.title_placeholder": "Název nového seznamu", + "lists.search": "Hledejte mezi uživateli, které sledujete", + "lists.subheading": "Vaše seznamy", + "loading_indicator.label": "Načítám...", + "media_gallery.toggle_visible": "Přepínat viditelnost", + "missing_indicator.label": "Nenalezeno", + "missing_indicator.sublabel": "Tento zdroj se nepodažilo najít", + "mute_modal.hide_notifications": "Skrýt oznámení před tímto uživatelem?", + "navigation_bar.blocks": "Blokovaní uživatelé", + "navigation_bar.community_timeline": "Místní časová osa", + "navigation_bar.direct": "Přímé zprávy", + "navigation_bar.discover": "Objevujte", + "navigation_bar.domain_blocks": "Skryté domény", + "navigation_bar.edit_profile": "Upravit profil", + "navigation_bar.favourites": "Oblíbené", + "navigation_bar.filters": "Skrytá slova", + "navigation_bar.follow_requests": "Žádosti o sledování", + "navigation_bar.info": "O této instanci", + "navigation_bar.keyboard_shortcuts": "Klávesové zkratky", + "navigation_bar.lists": "Seznamy", + "navigation_bar.logout": "Odhlásit se", + "navigation_bar.mutes": "Ignorovaní uživatelé", + "navigation_bar.personal": "Osobní", + "navigation_bar.pins": "Připnuté tooty", + "navigation_bar.preferences": "Předvolby", + "navigation_bar.public_timeline": "Federovaná časová osa", + "navigation_bar.security": "Zabezpečení", + "notification.favourite": "{name} označil/a váš status jako oblíbený", + "notification.follow": "{name} vás začal/a sledovat", + "notification.mention": "{name} vás zmínil/a", + "notification.reblog": "{name} vám boostnul/a status", + "notifications.clear": "Vymazat oznámení", + "notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?", + "notifications.column_settings.alert": "Desktopová oznámení", + "notifications.column_settings.favourite": "Oblíbené:", + "notifications.column_settings.follow": "Noví sledovatelé:", + "notifications.column_settings.mention": "Zmínky:", + "notifications.column_settings.push": "Push oznámení", + "notifications.column_settings.push_meta": "Toto zařízení", + "notifications.column_settings.reblog": "Boosty:", + "notifications.column_settings.show": "Zobrazit ve sloupci", + "notifications.column_settings.sound": "Přehrát zvuk", + "notifications.group": "{count} oznámení", + "onboarding.done": "Hotovo", + "onboarding.next": "Další", + "onboarding.page_five.public_timelines": "Místní časová osa zobrazuje veřejné příspěvky od všech lidí na {domain}. Federovaná časová osa zobrazuje veřejné příspěvky ode všech, které lidé na {domain} sledují. Toto jsou veřejné časové osy, výborný způsob, jak objevovat nové lidi.", + "onboarding.page_four.home": "Domovská časová osa zobrazuje příspěvky od lidí, které sledujete.", + "onboarding.page_four.notifications": "Sloupec oznámení se zobrazí, když s vámi někdo bude komunikovat.", + "onboarding.page_one.federation": "Mastodon je síť nezávislých serverů, jejichž propojením vzniká jedna velká sociální síť. Těmto serverům říkáme instance.", + "onboarding.page_one.full_handle": "Vaše celá adresa profilu", + "onboarding.page_one.handle_hint": "Tohle je, co byste řekl/a svým přátelům, aby hledali.", + "onboarding.page_one.welcome": "Vítejte na Mastodonu!", + "onboarding.page_six.admin": "Administrátorem vaší instance je {admin}.", + "onboarding.page_six.almost_done": "Skoro hotovo...", + "onboarding.page_six.appetoot": "Bon appetoot!", + "onboarding.page_six.apps_available": "Jsou dostupné {apps} pro iOS, Android a jiné platformy.", + "onboarding.page_six.github": "Mastodon je svobodný a otevřený software. Na {github} můžete nahlásit chyby, požádat o nové funkce, nebo přispívat ke kódu.", + "onboarding.page_six.guidelines": "komunitní pravidla", + "onboarding.page_six.read_guidelines": "Prosím přečtěte si {guidelines} {domain}!", + "onboarding.page_six.various_app": "mobilní aplikace", + "onboarding.page_three.profile": "Upravte si svůj profil a změňte si svůj avatar, popis profilu a zobrazované jméno. V nastaveních najdete i další možnosti.", + "onboarding.page_three.search": "Pomocí vyhledávacího řádku najděte lidi a podívejte se na hashtagy jako {illustration} a {introductions}. Chcete-li najít někoho, kdo není na této instanci, použijte jeho celou adresu profilu.", + "onboarding.page_two.compose": "Příspěvky pište z pole na komponování. Ikonami níže můžete nahrávat obrázky, změnit nastavení soukromí a přidat varování o obsahu.", + "onboarding.skip": "Přeskočit", + "privacy.change": "Změnit viditelnost statusu", + "privacy.direct.long": "Odeslat pouze zmíněným uživatelům", + "privacy.direct.short": "Přímé", + "privacy.private.long": "Odeslat pouze sledovatelům", + "privacy.private.short": "Pouze pro sledovatele", + "privacy.public.long": "Odeslat na veřejné časové osy", + "privacy.public.short": "Veřejné", "privacy.unlisted.long": "Do not show in public timelines", - "privacy.unlisted.short": "Unlisted", - "regeneration_indicator.label": "Loading…", - "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "privacy.unlisted.short": "Nezobrazované", + "regeneration_indicator.label": "Načítám…", + "regeneration_indicator.sublabel": "Váš domovský proud se připravuje!", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", - "relative_time.just_now": "now", + "relative_time.just_now": "teď", "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": "Report {target}", - "search.placeholder": "Search", - "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.", + "reply_indicator.cancel": "Zrušit", + "report.forward": "Přeposlat k {target}", + "report.forward_hint": "Tento účet je z jiného serveru. Chcete na něj také poslat anonymizovanou kopii?", + "report.hint": "Toto nahlášení bude zasláno moderátorům vaší instance. Níže můžete uvést, proč tento účet nahlašujete:", + "report.placeholder": "Další komentáře", + "report.submit": "Odeslat", + "report.target": "Nahlásit {target}", + "search.placeholder": "Hledat", + "search_popout.search_format": "Pokročilé vyhledávání", + "search_popout.tips.full_text": "Jednoduchý textový výpis statusů, které jste napsal/a, oblíbil/a si, povýšil/a, nebo v nich byl/a zmíněn/a, včetně odpovídajících přezdívek, jmen a hashtagů.", "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": "Toots", - "search_results.total": "{count, number} {count, plural, one {result} other {results}}", - "standalone.public_title": "A look inside...", - "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", - "status.cannot_reblog": "This post cannot be boosted", + "search_popout.tips.text": "Jednoduchý textový výpis odpovídajících jmen, přezdívek a hashtagů", + "search_popout.tips.user": "uživatel", + "search_results.accounts": "Lidé", + "search_results.hashtags": "Hashtagy", + "search_results.statuses": "Tooty", + "search_results.total": "{count, number} {count, plural, one {výsledek} other {výsledků}}", + "standalone.public_title": "Nahlédnout dovnitř...", + "status.block": "Zablokovat uživatele @{name}", + "status.cancel_reblog_private": "Zrušit boost", + "status.cannot_reblog": "Tento příspěvek nemůže být boostnutý", "status.delete": "Delete", - "status.direct": "Direct message @{name}", - "status.embed": "Embed", - "status.favourite": "Favourite", - "status.filtered": "Filtered", - "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 toot", - "status.reblog": "Boost", - "status.reblog_private": "Boost to original audience", - "status.reblogged_by": "{name} boosted", - "status.redraft": "Delete & re-draft", - "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": "Federated", - "tabs_bar.home": "Home", - "tabs_bar.local_timeline": "Local", - "tabs_bar.notifications": "Notifications", - "tabs_bar.search": "Search", - "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking", - "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": "Delete", - "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" + "status.direct": "Poslat přímou zprávu uživateli @{name}", + "status.embed": "Vložit", + "status.favourite": "Oblíbit", + "status.filtered": "Filtrováno", + "status.load_more": "Zobrazit více", + "status.media_hidden": "Média skryta", + "status.mention": "Zmínit uživatele @{name}", + "status.more": "Více", + "status.mute": "Ignorovat uživatele @{name}", + "status.mute_conversation": "Ignorovat konverzaci", + "status.open": "Otevřít tento status", + "status.pin": "Připnout na profil", + "status.pinned": "Připnutý toot", + "status.reblog": "Boostnout", + "status.reblog_private": "Boostnout původnímu publiku", + "status.reblogged_by": "{name} boostnul/a", + "status.redraft": "Vymazat a přepsat", + "status.reply": "Odpovědět", + "status.replyAll": "Odpovědět na vlákno", + "status.report": "Nahlásit uživatele @{name}", + "status.sensitive_toggle": "Klikněte pro zobrazení", + "status.sensitive_warning": "Citlivý obsah", + "status.share": "Sdílet", + "status.show_less": "Zobrazit méně", + "status.show_less_all": "Zobrazit méně pro všechny", + "status.show_more": "Zobrazit více", + "status.show_more_all": "Zobrazit více pro všechny", + "status.unmute_conversation": "Přestat ignorovat konverzaci", + "status.unpin": "Odepnout z profilu", + "tabs_bar.federated_timeline": "Federovaná", + "tabs_bar.home": "Domů", + "tabs_bar.local_timeline": "Místní", + "tabs_bar.notifications": "Oznámení", + "tabs_bar.search": "Hledat", + "trends.count_by_accounts": "{count} {rawCount, plural, one {člověk} other {lidí}} diskutuje", + "ui.beforeunload": "Váš koncept se ztratí, pokud Mastodon opustíte.", + "upload_area.title": "Přetažením nahrajete", + "upload_button.label": "Přidat média", + "upload_form.description": "Popis pro zrakově postižené", + "upload_form.focus": "Vystřihnout", + "upload_form.undo": "Smazat", + "upload_progress.label": "Nahrávám...", + "video.close": "Zavřít video", + "video.exit_fullscreen": "Ukončit celou obrazovku", + "video.expand": "Otevřít video", + "video.fullscreen": "Celá obrazovka", + "video.hide": "Skrýt video", + "video.mute": "Vypnout zvuk", + "video.pause": "Pauza", + "video.play": "Přehrát", + "video.unmute": "Zapnout zvuk" } diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 206ebeefd..9e630520d 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -167,7 +167,7 @@ "navigation_bar.domain_blocks": "숨겨진 도메인", "navigation_bar.edit_profile": "프로필 편집", "navigation_bar.favourites": "즐겨찾기", - "navigation_bar.filters": "Muted words", + "navigation_bar.filters": "뮤트", "navigation_bar.follow_requests": "팔로우 요청", "navigation_bar.info": "이 인스턴스에 대해서", "navigation_bar.keyboard_shortcuts": "단축키", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index fa56e52fb..69db735dd 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -129,11 +129,11 @@ "keyboard_shortcuts.boost": "para compartilhar", "keyboard_shortcuts.column": "Focar um status em uma das colunas", "keyboard_shortcuts.compose": "para focar a área de redação", - "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.description": "Descrição", "keyboard_shortcuts.down": "para mover para baixo na lista", - "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.enter": "para expandir um status", "keyboard_shortcuts.favourite": "para adicionar aos favoritos", - "keyboard_shortcuts.heading": "Keyboard Shortcuts", + "keyboard_shortcuts.heading": "Atalhos de teclado", "keyboard_shortcuts.hotkey": "Atalho", "keyboard_shortcuts.legend": "para mostrar essa legenda", "keyboard_shortcuts.mention": "para mencionar o autor", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json index 48a648622..635647355 100644 --- a/app/javascript/mastodon/locales/te.json +++ b/app/javascript/mastodon/locales/te.json @@ -65,7 +65,7 @@ "compose_form.hashtag_warning": "ఈ టూట్ అన్లిస్టెడ్ కాబట్టి ఏ హాష్ ట్యాగ్ క్రిందకూ రాదు. పబ్లిక్ టూట్ లను మాత్రమే హాష్ ట్యాగ్ ద్వారా శోధించవచ్చు.", "compose_form.lock_disclaimer": "మీ ఖాతా {locked} చేయబడలేదు. ఎవరైనా మిమ్మల్ని అనుసరించి మీ అనుచరులకు-మాత్రమే పోస్ట్లను వీక్షించవచ్చు.", "compose_form.lock_disclaimer.lock": "బిగించబడినది", - "compose_form.placeholder": "మీ మనస్సులో ఏమి ఉంది?", + "compose_form.placeholder": "మీ మనస్సులో ఏముంది?", "compose_form.publish": "టూట్", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "మీడియా సున్నితమైనదిగా గుర్తించబడింది", @@ -115,7 +115,7 @@ "follow_request.authorize": "అనుమతించు", "follow_request.reject": "తిరస్కరించు", "getting_started.developers": "డెవలపర్లు", - "getting_started.documentation": "Documentation", + "getting_started.documentation": "డాక్యుమెంటేషన్", "getting_started.find_friends": "ట్విట్టర్ నుండి స్నేహితులను కనుగొనండి", "getting_started.heading": "మొదలుపెడదాం", "getting_started.invite": "వ్యక్తులను ఆహ్వానించండి", @@ -167,7 +167,7 @@ "navigation_bar.domain_blocks": "దాచిన డొమైన్లు", "navigation_bar.edit_profile": "ప్రొఫైల్ని సవరించండి", "navigation_bar.favourites": "ఇష్టపడినవి", - "navigation_bar.filters": "Muted words", + "navigation_bar.filters": "మ్యూట్ చేయబడిన పదాలు", "navigation_bar.follow_requests": "అనుసరించడానికి అభ్యర్ధనలు", "navigation_bar.info": "ఈ దృష్టాంతం గురించి", "navigation_bar.keyboard_shortcuts": "హాట్ కీలు", @@ -258,7 +258,7 @@ "status.direct": "@{name}కు నేరుగా సందేశం పంపు", "status.embed": "ఎంబెడ్", "status.favourite": "ఇష్టపడు", - "status.filtered": "Filtered", + "status.filtered": "వడకట్టబడిన", "status.load_more": "మరిన్ని లోడ్ చేయి", "status.media_hidden": "మీడియా దాచబడింది", "status.mention": "@{name}ను ప్రస్తావించు", diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 0d37c34c8..3a1ca1a7b 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -5,14 +5,16 @@ import { start } from '../mastodon/common'; start(); function main() { - const IntlRelativeFormat = require('intl-relativeformat').default; + const { length } = require('stringz'); + const IntlMessageFormat = require('intl-messageformat').default; + const { timeAgoString } = require('../mastodon/components/relative_timestamp'); + const { delegate } = require('rails-ujs'); const emojify = require('../mastodon/features/emoji/emoji').default; const { getLocale } = require('../mastodon/locales'); - const { localeData } = getLocale(); + const { messages } = getLocale(); const React = require('react'); const ReactDOM = require('react-dom'); - - localeData.forEach(IntlRelativeFormat.__addLocaleData); + const Rellax = require('rellax'); ready(() => { const locale = document.documentElement.lang; @@ -25,8 +27,6 @@ function main() { minute: 'numeric', }); - const relativeFormat = new IntlRelativeFormat(locale); - [].forEach.call(document.querySelectorAll('.emojify'), (content) => { content.innerHTML = emojify(content.innerHTML); }); @@ -41,12 +41,16 @@ function main() { [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => { const datetime = new Date(content.getAttribute('datetime')); + const now = new Date(); content.title = dateTimeFormat.format(datetime); - content.textContent = relativeFormat.format(datetime); + content.textContent = timeAgoString({ + formatMessage: ({ id, defaultMessage }, values) => (new IntlMessageFormat(messages[id] || defaultMessage, locale)).format(values), + formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date), + }, datetime, now, datetime.getFullYear()); }); - [].forEach.call(document.querySelectorAll('.logo-button'), (content) => { + [].forEach.call(document.querySelectorAll('.modal-button'), (content) => { content.addEventListener('click', (e) => { e.preventDefault(); window.open(e.target.href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); @@ -64,6 +68,8 @@ function main() { }) .catch(error => console.error(error)); } + + new Rellax('.parallax', { speed: -1 }); }); } diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss index 7b3b10dfe..0990a4f25 100644 --- a/app/javascript/styles/application.scss +++ b/app/javascript/styles/application.scss @@ -10,7 +10,7 @@ @import 'mastodon/lists'; @import 'mastodon/footer'; @import 'mastodon/compact_header'; -@import 'mastodon/landing_strip'; +@import 'mastodon/widgets'; @import 'mastodon/forms'; @import 'mastodon/accounts'; @import 'mastodon/stream_entries'; diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index fefb03407..b9544bb33 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -1115,6 +1115,21 @@ $small-breakpoint: 960px; } &.tag-page { + @media screen and (max-width: $column-breakpoint) { + padding: 0; + + .container { + padding: 0; + } + + #mastodon-timeline { + display: block; + width: 100vw; + height: 100vh; + border-radius: 0; + } + } + .grid { @media screen and (min-width: $small-breakpoint) { grid-template-columns: 33% 67%; @@ -1146,24 +1161,17 @@ $small-breakpoint: 960px; @media screen and (max-width: $column-breakpoint) { .grid { + grid-gap: 0; + .column-1 { grid-column: 1; - grid-row: 2; + grid-row: 1; } .column-2 { - grid-column: 1; - grid-row: 1; + display: none; } } - - .brand { - margin: 0; - } - - .landing-page__features { - display: none; - } } } } diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index b4612b063..c27bc0df3 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -1,243 +1,100 @@ .card { - background-color: $base-shadow-color; - background-size: cover; - background-position: center; - border-radius: 4px 4px 0 0; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - overflow: hidden; - position: relative; - display: flex; - - &::after { - background: rgba(darken($ui-base-color, 8%), 0.5); + & > a { display: block; - content: ""; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - z-index: 1; - } - - @media screen and (max-width: 740px) { - border-radius: 0; - box-shadow: none; - } - - .card__illustration { - padding: 60px 0; - position: relative; - flex: 1 1 auto; - display: flex; - justify-content: center; - align-items: center; - } - - .card__bio { - max-width: 260px; - flex: 1 1 auto; - display: flex; - flex-direction: column; - justify-content: space-between; - background: rgba(darken($ui-base-color, 8%), 0.8); - position: relative; - z-index: 2; - } - - &.compact { - padding: 30px 0; - border-radius: 4px; - - .avatar { - margin-bottom: 0; + text-decoration: none; + color: inherit; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - img { - object-fit: cover; - } + @media screen and (max-width: $no-gap-breakpoint) { + box-shadow: none; } - } - - .name { - display: block; - font-size: 20px; - line-height: 18px * 1.5; - color: $primary-text-color; - padding: 10px 15px; - padding-bottom: 0; - font-weight: 500; - position: relative; - z-index: 2; - margin-bottom: 30px; - overflow: hidden; - text-overflow: ellipsis; - small { - display: block; - font-size: 14px; - color: $highlight-text-color; - font-weight: 400; - overflow: hidden; - text-overflow: ellipsis; - - .fa { - margin-left: 3px; + &:hover, + &:active, + &:focus { + .card__bar { + background: lighten($ui-base-color, 8%); } } } - .avatar { - width: 120px; - margin: 0 auto; + &__img { + height: 130px; position: relative; - z-index: 2; + background: darken($ui-base-color, 12%); + border-radius: 4px 4px 0 0; img { - width: 120px; - height: 120px; - display: block; - border-radius: 120px; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - } - } - - .roles { - margin-bottom: 30px; - padding: 0 15px; - } - - .details-counters { - margin-top: 30px; - display: flex; - flex-direction: row; - width: 100%; - } - - .counter { - width: 33.3%; - box-sizing: border-box; - flex: 0 0 auto; - color: $darker-text-color; - padding: 5px 10px 0; - margin-bottom: 10px; - border-right: 1px solid lighten($ui-base-color, 4%); - cursor: default; - text-align: center; - position: relative; - - a { - display: block; - } - - &:last-child { - border-right: 0; - } - - &::after { display: block; - content: ""; - position: absolute; - bottom: -10px; - left: 0; width: 100%; - border-bottom: 4px solid $ui-primary-color; - opacity: 0.5; - transition: all 400ms ease; - } - - &.active { - &::after { - border-bottom: 4px solid $highlight-text-color; - opacity: 1; - } - } - - &:hover { - &::after { - opacity: 1; - transition-duration: 100ms; - } + height: 100%; + margin: 0; + object-fit: cover; + border-radius: 4px 4px 0 0; } - a { - text-decoration: none; - color: inherit; + @media screen and (max-width: 600px) { + height: 200px; } - .counter-label { - font-size: 12px; - display: block; - margin-bottom: 5px; - } - - .counter-number { - font-weight: 500; - font-size: 18px; - color: $primary-text-color; - font-family: 'mastodon-font-display', sans-serif; + @media screen and (max-width: $no-gap-breakpoint) { + display: none; } } - .bio { - font-size: 14px; - line-height: 18px; - padding: 0 15px; - color: $secondary-text-color; - } - - @media screen and (max-width: 480px) { - display: block; + &__bar { + position: relative; + padding: 15px; + display: flex; + justify-content: flex-start; + align-items: center; + background: lighten($ui-base-color, 4%); + border-radius: 0 0 4px 4px; - .card__bio { - max-width: none; + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; } - .name, - .roles { - text-align: center; - margin-bottom: 15px; - } + .avatar { + flex: 0 0 auto; + width: 48px; + height: 48px; + padding-top: 2px; - .bio { - margin-bottom: 15px; + img { + width: 100%; + height: 100%; + display: block; + margin: 0; + border-radius: 4px; + background: darken($ui-base-color, 8%); + } } - } -} -.card, -.account-grid-card { - .controls { - position: absolute; - top: 15px; - left: 15px; - z-index: 2; - - .icon-button { - color: rgba($white, 0.8); - text-decoration: none; - font-size: 13px; - line-height: 13px; - font-weight: 500; - - .fa { - font-weight: 400; - margin-right: 5px; + .display-name { + margin-left: 15px; + text-align: left; + + strong { + font-size: 15px; + color: $primary-text-color; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; } - &:hover, - &:active, - &:focus { - color: $white; + span { + display: block; + font-size: 14px; + color: $darker-text-color; + font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; } } } } -.account-grid-card .controls { - left: auto; - right: 15px; -} - .pagination { padding: 30px 0; text-align: center; @@ -314,289 +171,23 @@ } } -.accounts-grid { - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - background: darken($simple-background-color, 8%); - border-radius: 0 0 4px 4px; - padding: 20px 5px; - padding-bottom: 10px; - overflow: hidden; - display: flex; - flex-wrap: wrap; - z-index: 2; - position: relative; - - &.empty img { - position: absolute; - opacity: 0.2; - height: 200px; - left: 0; - bottom: 0; - pointer-events: none; - } - - @media screen and (max-width: 740px) { - border-radius: 0; - box-shadow: none; - } - - .account-grid-card { - box-sizing: border-box; - width: 335px; - background: $simple-background-color; - border-radius: 4px; - color: $inverted-text-color; - margin: 0 5px 10px; - position: relative; - - @media screen and (max-width: 740px) { - width: calc(100% - 10px); - } - - .account-grid-card__header { - overflow: hidden; - height: 100px; - border-radius: 4px 4px 0 0; - background-color: lighten($inverted-text-color, 4%); - background-size: cover; - background-position: center; - position: relative; - - &::after { - background: rgba(darken($ui-base-color, 8%), 0.5); - display: block; - content: ""; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - z-index: 1; - } - } - - .account-grid-card__avatar { - box-sizing: border-box; - padding: 15px; - position: absolute; - z-index: 2; - top: 100px - (40px + 2px); - left: -2px; - } - - .avatar { - width: 80px; - height: 80px; - - img { - display: block; - width: 80px; - height: 80px; - border-radius: 80px; - border: 2px solid $simple-background-color; - background: $simple-background-color; - } - } - - .name { - padding: 15px; - padding-top: 10px; - padding-left: 15px + 80px + 15px; - - a { - display: block; - color: $inverted-text-color; - text-decoration: none; - text-overflow: ellipsis; - overflow: hidden; - font-weight: 500; - - &:hover { - .display_name { - text-decoration: underline; - } - } - } - } - - .display_name { - font-size: 16px; - display: block; - text-overflow: ellipsis; - overflow: hidden; - } - - .username { - color: $lighter-text-color; - font-size: 14px; - font-weight: 400; - } - - .account__header__content { - padding: 10px 15px; - padding-top: 15px; - color: $lighter-text-color; - word-wrap: break-word; - overflow: hidden; - text-overflow: ellipsis; - height: 5.5em; - position: relative; - - &::after { - display: block; - content: ""; - width: 100%; - height: 100px; - position: absolute; - bottom: 0; - background: linear-gradient(to bottom, rgba($simple-background-color, 0.01) 0%, rgba($simple-background-color, 1) 100%); - left: 0; - border-radius: 0 0 4px 4px; - pointer-events: none; - } - } - } -} - .nothing-here { - width: 100%; - display: block; + background: $ui-base-color; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); color: $light-text-color; font-size: 14px; font-weight: 500; text-align: center; - padding: 130px 0; - padding-top: 125px; - margin: 0 auto; + display: flex; + justify-content: center; + align-items: center; cursor: default; -} - -.account-card { border-radius: 4px; - text-align: left; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - background: $simple-background-color; - - &__header { - background: $base-shadow-color; - background-size: cover; - background-position: center center; - height: 90px; - border-radius: 4px 4px 0 0; - } + padding: 20px; + min-height: 30vh; - & > .detailed-status__display-name { - display: block; - overflow: hidden; - display: flex; - align-items: center; - padding: 10px; - - &:last-child { - margin-bottom: 0; - } - - & > div:first-child { - flex: 0 0 auto; - margin-right: 10px; - width: 48px; - height: 48px; - } - - .avatar { - display: block; - border-radius: 4px; - margin: 0; - } - - .display-name { - flex: 1 0 auto; - display: block; - max-width: 100%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - cursor: default; - - & > .detailed-status__display-name { - margin-bottom: 0; - } - - strong { - font-weight: 500; - color: $ui-base-color; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - span { - font-size: 14px; - color: $light-text-color; - } - } - - &:hover { - .display-name { - strong { - text-decoration: none; - } - } - } - } - - .counter { - box-sizing: border-box; - flex: 0 0 auto; - color: $light-text-color; - padding: 0 10px; - cursor: default; - text-align: center; - position: relative; - line-height: 24px; - - .counter-label { - font-size: 12px; - display: block; - text-transform: uppercase; - } - - .counter-number { - font-weight: 500; - font-size: 16px; - color: $inverted-text-color; - font-family: 'mastodon-font-display', sans-serif; - } - } -} - -.activity-stream-tabs { - background: $simple-background-color; - border-bottom: 1px solid $ui-secondary-color; - position: relative; - z-index: 2; - - a { - display: inline-block; - padding: 15px; - text-decoration: none; - color: $highlight-text-color; - text-transform: uppercase; - font-weight: 500; - - &:hover, - &:active, - &:focus { - color: lighten($highlight-text-color, 8%); - } - - &.active { - color: $inverted-text-color; - cursor: default; - } + &--under-tabs { + border-radius: 0 0 4px 4px; } } @@ -629,14 +220,14 @@ padding: 0; margin: 15px -15px -15px; border: 0 none; - border-top: 1px solid lighten($ui-base-color, 4%); - border-bottom: 1px solid lighten($ui-base-color, 4%); + border-top: 1px solid lighten($ui-base-color, 12%); + border-bottom: 1px solid lighten($ui-base-color, 12%); font-size: 14px; line-height: 20px; dl { display: flex; - border-bottom: 1px solid lighten($ui-base-color, 4%); + border-bottom: 1px solid lighten($ui-base-color, 12%); } dt, diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index c52e069be..7a6a1c490 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -1,13 +1,10 @@ body { font-family: 'mastodon-font-sans-serif', sans-serif; - background: $ui-base-color; - background-size: cover; - background-attachment: fixed; + background: darken($ui-base-color, 8%); font-size: 13px; line-height: 18px; font-weight: 400; color: $primary-text-color; - padding-bottom: 20px; text-rendering: optimizelegibility; font-feature-settings: "kern"; text-size-adjust: none; @@ -35,16 +32,24 @@ body { height: 100%; padding: 0; background: $ui-base-color; + + &.with-modals--active { + overflow-y: hidden; + } } - &.about-body { - background: darken($ui-base-color, 8%); - padding-bottom: 0; + &.lighter { + background: $ui-base-color; } - &.tag-body { - background: darken($ui-base-color, 8%); - padding-bottom: 0; + &.with-modals { + overflow-x: hidden; + overflow-y: scroll; + + &--active { + overflow-y: hidden; + margin-right: 13px; + } } &.player { @@ -52,7 +57,7 @@ body { } &.embed { - background: transparent; + background: lighten($ui-base-color, 4%); margin: 0; padding-bottom: 0; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 48460d760..8067b80bb 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -946,6 +946,18 @@ background: lighten($ui-base-color, 4%); padding: 14px 10px; + &--flex { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: flex-start; + + .status__content, + .detailed-status__meta { + flex: 100%; + } + } + .status__content { font-size: 19px; line-height: 24px; @@ -1224,7 +1236,6 @@ a .account__avatar { } .account__action-bar-dropdown { - flex: 0 1 calc(50% - 140px); padding: 10px; .icon-button { @@ -1256,9 +1267,9 @@ a .account__avatar { .account__action-bar__tab { text-decoration: none; overflow: hidden; - flex: 0 1 80px; + flex: 0 1 100%; border-right: 1px solid lighten($ui-base-color, 8%); - padding: 10px 5px; + padding: 10px 0; & > span { display: block; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index ac648c868..7b339277f 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -60,10 +60,6 @@ } } -.media-standalone__body { - overflow: hidden; -} - .account-header { width: 400px; margin: 0 auto; @@ -118,3 +114,576 @@ margin-left: 8px; } } + +.public-layout { + @media screen and (max-width: $no-gap-breakpoint) { + padding-top: 48px; + } + + .container { + max-width: 960px; + + @media screen and (max-width: $no-gap-breakpoint) { + padding: 0; + } + } + + .header { + background: lighten($ui-base-color, 8%); + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + border-radius: 4px; + height: 48px; + margin: 10px 0; + display: flex; + align-items: stretch; + justify-content: center; + flex-wrap: nowrap; + overflow: hidden; + + @media screen and (max-width: $no-gap-breakpoint) { + position: fixed; + width: 100%; + top: 0; + left: 0; + margin: 0; + border-radius: 0; + box-shadow: none; + z-index: 110; + } + + & > div { + flex: 1 1 33.3%; + min-height: 1px; + } + + .nav-left { + display: flex; + align-items: stretch; + justify-content: flex-start; + flex-wrap: nowrap; + } + + .nav-center { + display: flex; + align-items: stretch; + justify-content: center; + flex-wrap: nowrap; + } + + .nav-right { + display: flex; + align-items: stretch; + justify-content: flex-end; + flex-wrap: nowrap; + } + + .brand { + display: block; + padding: 15px; + + img { + display: block; + height: 18px; + width: auto; + position: relative; + bottom: -2px; + + @media screen and (max-width: $no-gap-breakpoint) { + height: 20px; + } + } + + &:hover, + &:focus, + &:active { + background: lighten($ui-base-color, 12%); + } + } + + .nav-link { + display: flex; + align-items: center; + padding: 0 1rem; + font-size: 12px; + font-weight: 500; + text-decoration: none; + color: $darker-text-color; + white-space: nowrap; + text-align: center; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + color: $primary-text-color; + } + } + + .nav-button { + background: lighten($ui-base-color, 16%); + margin: 8px; + margin-left: 0; + border-radius: 4px; + + &:hover, + &:focus, + &:active { + text-decoration: none; + background: lighten($ui-base-color, 20%); + } + } + } + + $no-columns-breakpoint: 600px; + + .grid { + display: grid; + grid-gap: 10px; + grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr); + grid-auto-columns: 25%; + grid-auto-rows: max-content; + + .column-0 { + grid-row: 1; + grid-column: 1; + } + + .column-1 { + grid-row: 1; + grid-column: 2; + } + + @media screen and (max-width: $no-columns-breakpoint) { + grid-template-columns: 100%; + grid-gap: 0; + + .column-1 { + display: none; + } + } + } + + .public-account-header { + overflow: hidden; + margin-bottom: 10px; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + + &__image { + border-radius: 4px 4px 0 0; + overflow: hidden; + height: 300px; + position: relative; + background: darken($ui-base-color, 12%); + + &::after { + content: ""; + display: block; + position: absolute; + width: 100%; + height: 100%; + box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15); + top: 0; + left: 0; + } + + img { + object-fit: cover; + display: block; + width: 100%; + height: 100%; + margin: 0; + border-radius: 4px 4px 0 0; + } + + @media screen and (max-width: 600px) { + height: 200px; + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + margin-bottom: 0; + box-shadow: none; + + &__image::after { + display: none; + } + + &__image, + &__image img { + border-radius: 0; + } + } + + &__bar { + position: relative; + margin-top: -80px; + display: flex; + justify-content: flex-start; + + &::before { + content: ""; + display: block; + background: lighten($ui-base-color, 4%); + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 60px; + border-radius: 0 0 4px 4px; + z-index: -1; + } + + .avatar { + display: block; + width: 120px; + height: 120px; + padding-left: 20px - 4px; + flex: 0 0 auto; + + img { + display: block; + width: 100%; + height: 100%; + margin: 0; + border-radius: 50%; + border: 4px solid lighten($ui-base-color, 4%); + background: darken($ui-base-color, 8%); + } + } + + @media screen and (max-width: 600px) { + margin-top: 0; + background: lighten($ui-base-color, 4%); + border-radius: 0 0 4px 4px; + padding: 5px; + + &::before { + display: none; + } + + .avatar { + width: 48px; + height: 48px; + padding: 7px 0; + padding-left: 10px; + + img { + border: 0; + border-radius: 4px; + } + + @media screen and (max-width: 360px) { + display: none; + } + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; + } + + @media screen and (max-width: $no-columns-breakpoint) { + flex-wrap: wrap; + } + } + + &__tabs { + flex: 1 1 auto; + margin-left: 20px; + + &__name { + padding-top: 20px; + padding-bottom: 8px; + + h1 { + font-size: 20px; + line-height: 18px * 1.5; + color: $primary-text-color; + font-weight: 500; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + text-shadow: 1px 1px 1px $base-shadow-color; + + small { + display: block; + font-size: 14px; + color: $primary-text-color; + font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + @media screen and (max-width: 600px) { + margin-left: 15px; + display: flex; + justify-content: space-between; + align-items: center; + + &__name { + padding-top: 0; + padding-bottom: 0; + + h1 { + font-size: 16px; + line-height: 24px; + text-shadow: none; + + small { + color: $darker-text-color; + } + } + } + } + + &__tabs { + display: flex; + justify-content: flex-start; + align-items: stretch; + height: 58px; + + .details-counters { + display: flex; + flex-direction: row; + min-width: 300px; + } + + @media screen and (max-width: $no-columns-breakpoint) { + .details-counters { + display: none; + } + } + + .counter { + width: 33.3%; + box-sizing: border-box; + flex: 0 0 auto; + color: $darker-text-color; + padding: 10px; + border-right: 1px solid lighten($ui-base-color, 4%); + cursor: default; + text-align: center; + position: relative; + + a { + display: block; + } + + &:last-child { + border-right: 0; + } + + &::after { + display: block; + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + border-bottom: 4px solid $ui-primary-color; + opacity: 0.5; + transition: all 400ms ease; + } + + &.active { + &::after { + border-bottom: 4px solid $highlight-text-color; + opacity: 1; + } + } + + &:hover { + &::after { + opacity: 1; + transition-duration: 100ms; + } + } + + a { + text-decoration: none; + color: inherit; + } + + .counter-label { + font-size: 12px; + display: block; + } + + .counter-number { + font-weight: 500; + font-size: 18px; + margin-bottom: 5px; + color: $primary-text-color; + font-family: 'mastodon-font-display', sans-serif; + } + } + + .spacer { + flex: 1 1 auto; + height: 1px; + } + + &__buttons { + padding: 7px 8px; + } + } + } + + &__extra { + display: none; + margin-top: 4px; + + .public-account-bio { + border-radius: 0; + box-shadow: none; + background: transparent; + margin: 0 -5px; + + .account__header__fields { + border-top: 1px solid lighten($ui-base-color, 12%); + } + + .roles { + display: none; + } + } + + &__links { + margin-top: -15px; + font-size: 14px; + color: $darker-text-color; + + a { + display: inline-block; + color: $darker-text-color; + text-decoration: none; + padding: 15px; + + strong { + font-weight: 700; + color: $primary-text-color; + } + } + } + + @media screen and (max-width: $no-columns-breakpoint) { + display: block; + flex: 100%; + } + } + } + + .account__section-headline { + border-radius: 4px 4px 0 0; + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; + } + } + + .detailed-status__meta { + margin-top: 25px; + } + + .public-account-bio { + background: lighten($ui-base-color, 8%); + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + border-radius: 4px; + overflow: hidden; + margin-bottom: 10px; + + @media screen and (max-width: $no-gap-breakpoint) { + box-shadow: none; + margin-bottom: 0; + border-radius: 0; + } + + .account__header__fields { + margin: 0; + border-top: 0; + + a { + color: lighten($ui-highlight-color, 8%); + } + } + + .account__header__content { + padding: 20px; + padding-bottom: 0; + color: $primary-text-color; + } + + &__extra, + .roles { + padding: 20px; + font-size: 14px; + color: $darker-text-color; + } + + .roles { + padding-bottom: 0; + } + } + + .static-icon-button { + color: $action-button-color; + font-size: 18px; + + & > span { + font-size: 14px; + font-weight: 500; + } + } + + .card-grid { + display: flex; + flex-wrap: wrap; + min-width: 100%; + margin: 0 -5px; + + & > div { + box-sizing: border-box; + flex: 1 0 auto; + width: 300px; + padding: 0 5px; + margin-bottom: 10px; + max-width: 33.333%; + + @media screen and (max-width: 900px) { + max-width: 50%; + } + + @media screen and (max-width: 600px) { + max-width: 100%; + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + margin: 0; + border-top: 1px solid lighten($ui-base-color, 8%); + + & > div { + width: 100%; + padding: 0; + margin-bottom: 0; + border-bottom: 1px solid lighten($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + .card__bar { + background: $ui-base-color; + + &:hover, + &:active, + &:focus { + background: lighten($ui-base-color, 4%); + } + } + } + } + } +} diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss index 81eb1ce2d..4d75477e0 100644 --- a/app/javascript/styles/mastodon/footer.scss +++ b/app/javascript/styles/mastodon/footer.scss @@ -1,39 +1,140 @@ -.footer { - text-align: center; - margin-top: 30px; - padding-bottom: 60px; - font-size: 12px; - color: $darker-text-color; - - .footer__domain { - font-weight: 500; - - a { - color: inherit; - text-decoration: none; +.public-layout { + .footer { + text-align: left; + padding-top: 20px; + padding-bottom: 60px; + font-size: 12px; + color: lighten($ui-base-color, 34%); + + @media screen and (max-width: $no-gap-breakpoint) { + padding-left: 20px; + padding-right: 20px; } - } - .powered-by, - .single-user-login { - font-weight: 400; + .grid { + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr 1fr 2fr 1fr 1fr; + + .column-0 { + grid-column: 1; + grid-row: 1; + min-width: 0; + } + + .column-1 { + grid-column: 2; + grid-row: 1; + min-width: 0; + } + + .column-2 { + grid-column: 3; + grid-row: 1; + min-width: 0; + text-align: center; + + h4 a { + color: lighten($ui-base-color, 34%); + } + } + + .column-3 { + grid-column: 4; + grid-row: 1; + min-width: 0; + } + + .column-4 { + grid-column: 5; + grid-row: 1; + min-width: 0; + } + + @media screen and (max-width: 690px) { + grid-template-columns: 1fr 2fr 1fr; + + .column-0, + .column-1 { + grid-column: 1; + } + + .column-1 { + grid-row: 2; + } + + .column-2 { + grid-column: 2; + } - a { - color: inherit; - text-decoration: underline; - font-weight: 500; + .column-3, + .column-4 { + grid-column: 3; + } - &:hover { + .column-4 { + grid-row: 2; + } + } + + @media screen and (max-width: 600px) { + .column-1 { + display: block; + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + .column-0, + .column-1, + .column-3, + .column-4 { + display: none; + } + } + } + + h4 { + text-transform: uppercase; + font-weight: 700; + margin-bottom: 8px; + color: $darker-text-color; + + a { + color: inherit; text-decoration: none; } } - img { - margin: 0 4px; - position: relative; - bottom: -1px; - height: 18px; - vertical-align: top; + ul a { + text-decoration: none; + color: lighten($ui-base-color, 34%); + + &:hover, + &:active, + &:focus { + text-decoration: underline; + } + } + + .brand { + svg { + display: block; + height: 36px; + width: auto; + margin: 0 auto; + + path { + fill: lighten($ui-base-color, 34%); + } + } + + &:hover, + &:focus, + &:active { + svg path { + fill: lighten($ui-base-color, 38%); + } + } } } } diff --git a/app/javascript/styles/mastodon/landing_strip.scss b/app/javascript/styles/mastodon/landing_strip.scss deleted file mode 100644 index 86614b89b..000000000 --- a/app/javascript/styles/mastodon/landing_strip.scss +++ /dev/null @@ -1,111 +0,0 @@ -.landing-strip, -.memoriam-strip { - background: rgba(darken($ui-base-color, 7%), 0.8); - color: $darker-text-color; - font-weight: 400; - padding: 14px; - border-radius: 4px; - margin-bottom: 20px; - display: flex; - align-items: center; - - strong, - a { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - a { - color: inherit; - text-decoration: underline; - } - - .logo { - width: 30px; - height: 30px; - flex: 0 0 auto; - margin-right: 15px; - } - - @media screen and (max-width: 740px) { - margin-bottom: 0; - } -} - -.memoriam-strip { - background: rgba($base-shadow-color, 0.7); -} - -.moved-strip { - padding: 14px; - border-radius: 4px; - background: rgba(darken($ui-base-color, 7%), 0.8); - color: $secondary-text-color; - font-weight: 400; - margin-bottom: 20px; - - strong, - a { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - a { - color: inherit; - text-decoration: underline; - - &.mention { - text-decoration: none; - - span { - text-decoration: none; - } - - &:focus, - &:hover, - &:active { - text-decoration: none; - - span { - text-decoration: underline; - } - } - } - } - - &__message { - margin-bottom: 15px; - - .fa { - margin-right: 5px; - color: $darker-text-color; - } - } - - &__card { - .detailed-status__display-avatar { - position: relative; - cursor: pointer; - } - - .detailed-status__display-name { - margin-bottom: 0; - text-decoration: none; - - span { - color: $highlight-text-color; - font-weight: 400; - } - } - } -} diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss index f4d6e237f..9e2aa720c 100644 --- a/app/javascript/styles/mastodon/stream_entries.scss +++ b/app/javascript/styles/mastodon/stream_entries.scss @@ -1,367 +1,145 @@ .activity-stream { - clear: both; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + border-radius: 4px; + overflow: hidden; + margin-bottom: 10px; + + @media screen and (max-width: $no-gap-breakpoint) { + margin-bottom: 0; + border-radius: 0; + box-shadow: none; + } + + &--headless { + border-radius: 0; + margin: 0; + box-shadow: none; + + .detailed-status, + .status { + border-radius: 0 !important; + } + } div[data-component] { width: 100%; } .entry { - background: $simple-background-color; + background: $ui-base-color; - .detailed-status.light, - .status.light, - .more.light { - border-bottom: 1px solid $ui-secondary-color; + .detailed-status, + .status, + .load-more { animation: none; } &:last-child { - &, - .detailed-status.light, - .status.light { + .detailed-status, + .status { border-bottom: 0; border-radius: 0 0 4px 4px; } } &:first-child { - &, - .detailed-status.light, - .status.light { + .detailed-status, + .status { border-radius: 4px 4px 0 0; } &:last-child { - &, - .detailed-status.light, - .status.light { + .detailed-status, + .status { border-radius: 4px; } } } @media screen and (max-width: 740px) { - &, - .detailed-status.light, - .status.light { + .detailed-status, + .status { border-radius: 0 !important; } } } +} - &.with-header { - .entry { - &:first-child { - &, - .detailed-status.light, - .status.light { - border-radius: 0; - } - - &:last-child { - &, - .detailed-status.light, - .status.light { - border-radius: 0 0 4px 4px; - } - } - } - } - } - - .media-gallery__gifv__label { - bottom: 9px; - } - - .status.light { - padding: 14px 14px 14px (48px + 14px * 2); - position: relative; - min-height: 48px; - cursor: default; - - .status__header { - font-size: 15px; - - .status__meta { - float: right; - font-size: 14px; - - .status__relative-time { - color: $lighter-text-color; - } - } - } - - .status__display-name { - display: block; - max-width: 100%; - padding-right: 25px; - color: $inverted-text-color; - } - - .status__avatar { - position: absolute; - left: 14px; - top: 14px; - width: 48px; - height: 48px; - - & > div { - width: 48px; - height: 48px; - } - - img { - display: block; - border-radius: 4px; - } - } - - .display-name { - display: block; - max-width: 100%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - strong { - font-weight: 500; - color: $inverted-text-color; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - span { - font-size: 14px; - color: $light-text-color; - } - } - - .status__content { - color: $inverted-text-color; - - a { - color: $highlight-text-color; - } - - a.status__content__spoiler-link { - color: $primary-text-color; - background: $ui-base-color; - - &:hover { - background: lighten($ui-base-color, 8%); - } - } - } - } - - .detailed-status.light { - padding: 14px; - background: $simple-background-color; - cursor: default; - - .detailed-status__display-name { - display: block; - overflow: hidden; - margin-bottom: 15px; - - & > div { - float: left; - margin-right: 10px; - } - - .display-name { - display: block; - max-width: 100%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - strong { - font-weight: 500; - color: $inverted-text-color; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - span { - font-size: 14px; - color: $light-text-color; - } - } - } - - .avatar { - width: 48px; - height: 48px; - - img { - display: block; - border-radius: 4px; - } - } - - .status__content { - color: $inverted-text-color; - - a { - color: $highlight-text-color; - } - - a.status__content__spoiler-link { - color: $primary-text-color; - background: $ui-base-color; - - &:hover { - background: lighten($ui-base-color, 8%); - } - } - } - - .detailed-status__meta { - margin-top: 15px; - color: $light-text-color; - font-size: 14px; - line-height: 18px; - - a { - color: inherit; - } - - span > span { - font-weight: 500; - font-size: 12px; - margin-left: 6px; - display: inline-block; - } - } - - .status-card { - border-color: lighten($ui-secondary-color, 4%); - color: $lighter-text-color; - - &:hover { - background: lighten($ui-secondary-color, 4%); - } - } - - .status-card__title, - .status-card__description { - color: $inverted-text-color; - } - - .status-card__image { - background: $ui-secondary-color; - } - } - - .media-spoiler { - background: $ui-base-color; - color: $darker-text-color; - } +.button.logo-button { + flex: 0 auto; + font-size: 14px; + background: $ui-highlight-color; + color: $primary-text-color; + text-transform: none; + line-height: 36px; + height: auto; + padding: 3px 15px; + border: 0; - .pre-header { - padding: 14px 0; - padding-left: (48px + 14px * 2); - padding-bottom: 0; - margin-bottom: -4px; - color: $light-text-color; - font-size: 14px; - position: relative; + svg { + width: 20px; + height: auto; + vertical-align: middle; + margin-right: 5px; - .pre-header__icon { - position: absolute; - left: (48px + 14px * 2 - 30px); + path:first-child { + fill: $primary-text-color; } - .status__display-name.muted strong { - color: $light-text-color; + path:last-child { + fill: $ui-highlight-color; } } - .open-in-web-link { - text-decoration: none; + &:active, + &:focus, + &:hover { + background: lighten($ui-highlight-color, 10%); - &:hover { - text-decoration: underline; + svg path:last-child { + fill: lighten($ui-highlight-color, 10%); } } - .more { - color: $darker-text-color; - display: block; - padding: 14px; - text-align: center; - - &:not(:hover) { - text-decoration: none; + @media screen and (max-width: $no-gap-breakpoint) { + svg { + display: none; } } } -.embed { - .activity-stream { - box-shadow: none; +.embed, +.public-layout { + .detailed-status { + padding: 15px; } -} -.entry { - .detailed-status.light { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - align-items: flex-start; + .status { + padding: 15px 15px 15px (48px + 15px * 2); + min-height: 48px + 2px; - .detailed-status__display-name { - flex: 1; - margin: 0 5px 15px 0; + &__avatar { + left: 15px; + top: 17px; } - .button.button-secondary.logo-button { - flex: 0 auto; - font-size: 14px; - background: $ui-highlight-color; - color: $primary-text-color; - border: 0; - - svg { - width: 20px; - height: auto; - vertical-align: middle; - margin-right: 5px; - - path:first-child { - fill: $primary-text-color; - } - - path:last-child { - fill: $ui-highlight-color; - } - } + &__content { + padding-top: 5px; + } - &:active, - &:focus, - &:hover { - background: lighten($ui-highlight-color, 10%); + &__prepend { + margin-left: 48px + 15px * 2; + padding-top: 15px; + } - svg path:last-child { - fill: lighten($ui-highlight-color, 10%); - } - } + &__prepend-icon-wrapper { + left: -32px; } - .status__content, - .detailed-status__meta { - flex: 100%; + .media-gallery, + &__action-bar, + .video-player { + margin-top: 10px; } } } diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index 40aeb4afc..009f0a3c9 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -46,3 +46,5 @@ $cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW; $media-modal-media-max-width: 100%; // put margins on top and bottom of image to avoid the screen covered by image. $media-modal-media-max-height: 80%; + +$no-gap-breakpoint: 415px; diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss new file mode 100644 index 000000000..d37a6f458 --- /dev/null +++ b/app/javascript/styles/mastodon/widgets.scss @@ -0,0 +1,161 @@ +.hero-widget { + margin-bottom: 10px; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + + &__img { + width: 100%; + height: 167px; + position: relative; + overflow: hidden; + border-radius: 4px 4px 0 0; + background: $base-shadow-color; + + img { + object-fit: cover; + display: block; + width: 100%; + height: 100%; + margin: 0; + border-radius: 4px 4px 0 0; + } + } + + &__text { + background: $ui-base-color; + padding: 20px; + border-radius: 0 0 4px 4px; + font-size: 15px; + color: $darker-text-color; + line-height: 20px; + word-wrap: break-word; + font-weight: 400; + + .emojione { + width: 20px; + height: 20px; + margin: -3px 0 0; + } + + p { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + } + + em { + display: inline; + margin: 0; + padding: 0; + font-weight: 700; + background: transparent; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: lighten($darker-text-color, 10%); + } + + a { + color: $secondary-text-color; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + display: none; + } +} + +.moved-account-widget { + padding: 15px; + padding-bottom: 20px; + border-radius: 4px; + background: $ui-base-color; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + color: $secondary-text-color; + font-weight: 400; + margin-bottom: 10px; + + strong, + a { + font-weight: 500; + + @each $lang in $cjk-langs { + &:lang(#{$lang}) { + font-weight: 700; + } + } + } + + a { + color: inherit; + text-decoration: underline; + + &.mention { + text-decoration: none; + + span { + text-decoration: none; + } + + &:focus, + &:hover, + &:active { + text-decoration: none; + + span { + text-decoration: underline; + } + } + } + } + + &__message { + margin-bottom: 15px; + + .fa { + margin-right: 5px; + color: $darker-text-color; + } + } + + &__card { + .detailed-status__display-avatar { + position: relative; + cursor: pointer; + } + + .detailed-status__display-name { + margin-bottom: 0; + text-decoration: none; + + span { + font-weight: 400; + } + } + } +} + +.memoriam-widget { + padding: 20px; + border-radius: 4px; + background: $base-shadow-color; + box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + font-size: 14px; + color: $darker-text-color; + margin-bottom: 10px; +} + +.moved-account-widget, +.memoriam-widget { + @media screen and (max-width: $no-gap-breakpoint) { + margin-bottom: 0; + box-shadow: none; + border-radius: 0; + } +} |