diff options
author | kibigo! <marrus-sh@users.noreply.github.com> | 2017-07-12 02:03:17 -0700 |
---|---|---|
committer | kibigo! <marrus-sh@users.noreply.github.com> | 2017-07-12 02:03:17 -0700 |
commit | 79d898ae0ad8c0e66bd63ec3e0904e9e5e7894e8 (patch) | |
tree | ee8d832ed2f11e9afe62daf0e586a86004eb8d98 /app/javascript | |
parent | bcf7ee48e94cd2e4d2de28e8854e7f0e2b5cad1f (diff) | |
parent | 056b5ed72f6d980bceeb49eb249b8365fe8fce66 (diff) |
Merge upstream!! #64 <3 <3
Diffstat (limited to 'app/javascript')
104 files changed, 2069 insertions, 575 deletions
diff --git a/app/javascript/fonts/montserrat/Montserrat-Medium.ttf b/app/javascript/fonts/montserrat/Montserrat-Medium.ttf new file mode 100644 index 000000000..88d70b89c --- /dev/null +++ b/app/javascript/fonts/montserrat/Montserrat-Medium.ttf Binary files differdiff --git a/app/javascript/images/cloud2.png b/app/javascript/images/cloud2.png new file mode 100644 index 000000000..f325ca6de --- /dev/null +++ b/app/javascript/images/cloud2.png Binary files differdiff --git a/app/javascript/images/cloud3.png b/app/javascript/images/cloud3.png new file mode 100644 index 000000000..ab194d0b8 --- /dev/null +++ b/app/javascript/images/cloud3.png Binary files differdiff --git a/app/javascript/images/cloud4.png b/app/javascript/images/cloud4.png new file mode 100644 index 000000000..98323f5a2 --- /dev/null +++ b/app/javascript/images/cloud4.png Binary files differdiff --git a/app/javascript/images/elephant-fren.png b/app/javascript/images/elephant-fren.png new file mode 100644 index 000000000..3b64edf08 --- /dev/null +++ b/app/javascript/images/elephant-fren.png Binary files differdiff --git a/app/javascript/images/logo.svg b/app/javascript/images/logo.svg index c233db842..16cb3a944 100644 --- a/app/javascript/images/logo.svg +++ b/app/javascript/images/logo.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><path d="M500 0a500 500 0 0 0-353.553 146.447 500 500 0 1 0 707.106 707.106A500 500 0 0 0 500 0zm-.059 280.05h107.12c-19.071 13.424-26.187 51.016-27.12 73.843V562.05c0 44.32-35.68 80-80 80s-80-35.68-80-80v-202c0-44.32 35.68-80 80-80zm-.441 52c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zm-279.059 7.9c44.32 0 80 35.68 80 80v206.157c.933 22.827 8.049 60.42 27.12 73.842H220.44c-44.32 0-80-35.68-80-80v-200c0-44.32 35.68-80 80-80zm559.12 0c44.32 0 80 35.68 80 80v200c0 44.32-35.68 80-80 80H672.44c19.071-13.424 26.187-51.016 27.12-73.843V419.95c0-44.32 35.68-80 80-80zM220 392c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm560 0c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm-280.5 40.05c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zM220 491.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zM499.5 532c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zM220 591.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28z" fill="#189efc"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><path d="M500 0a500 500 0 0 0-353.553 146.447 500 500 0 1 0 707.106 707.106A500 500 0 0 0 500 0zm-.059 280.05h107.12c-19.071 13.424-26.187 51.016-27.12 73.843V562.05c0 44.32-35.68 80-80 80s-80-35.68-80-80v-202c0-44.32 35.68-80 80-80zm-.441 52c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zm-279.059 7.9c44.32 0 80 35.68 80 80v206.157c.933 22.827 8.049 60.42 27.12 73.842H220.44c-44.32 0-80-35.68-80-80v-200c0-44.32 35.68-80 80-80zm559.12 0c44.32 0 80 35.68 80 80v200c0 44.32-35.68 80-80 80H672.44c19.071-13.424 26.187-51.016 27.12-73.843V419.95c0-44.32 35.68-80 80-80zM220 392c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm560 0c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm-280.5 40.05c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zM220 491.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zM499.5 532c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zM220 591.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28z" fill="#fff"/></svg> diff --git a/app/javascript/mastodon/actions/bundles.js b/app/javascript/mastodon/actions/bundles.js new file mode 100644 index 000000000..ecc9c8f7d --- /dev/null +++ b/app/javascript/mastodon/actions/bundles.js @@ -0,0 +1,25 @@ +export const BUNDLE_FETCH_REQUEST = 'BUNDLE_FETCH_REQUEST'; +export const BUNDLE_FETCH_SUCCESS = 'BUNDLE_FETCH_SUCCESS'; +export const BUNDLE_FETCH_FAIL = 'BUNDLE_FETCH_FAIL'; + +export function fetchBundleRequest(skipLoading) { + return { + type: BUNDLE_FETCH_REQUEST, + skipLoading, + }; +} + +export function fetchBundleSuccess(skipLoading) { + return { + type: BUNDLE_FETCH_SUCCESS, + skipLoading, + }; +} + +export function fetchBundleFail(error, skipLoading) { + return { + type: BUNDLE_FETCH_FAIL, + error, + skipLoading, + }; +} diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index cda636139..c7d248122 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -1,5 +1,5 @@ import api, { getLinks } from '../api'; -import Immutable from 'immutable'; +import { List as ImmutableList } from 'immutable'; import IntlMessageFormat from 'intl-messageformat'; import { fetchRelationships } from './accounts'; import { defineMessages } from 'react-intl'; @@ -124,7 +124,7 @@ export function refreshNotificationsFail(error, skipLoading) { export function expandNotifications() { return (dispatch, getState) => { - const items = getState().getIn(['notifications', 'items'], Immutable.List()); + const items = getState().getIn(['notifications', 'items'], ImmutableList()); if (getState().getIn(['notifications', 'isLoading']) || items.size === 0) { return; diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js index 601cea001..0597d265e 100644 --- a/app/javascript/mastodon/actions/store.js +++ b/app/javascript/mastodon/actions/store.js @@ -1,10 +1,11 @@ -import Immutable from 'immutable'; +import { Iterable, fromJS } from 'immutable'; export const STORE_HYDRATE = 'STORE_HYDRATE'; +export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; const convertState = rawState => - Immutable.fromJS(rawState, (k, v) => - Immutable.Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x => + fromJS(rawState, (k, v) => + Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x => Number.isNaN(x * 1) ? x : x * 1)); export function hydrateStore(rawState) { diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index cb4410eba..dd14cb1cd 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -1,5 +1,5 @@ import api, { getLinks } from '../api'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE'; @@ -66,13 +66,13 @@ export function refreshTimelineRequest(timeline, skipLoading) { export function refreshTimeline(timelineId, path, params = {}) { return function (dispatch, getState) { - const timeline = getState().getIn(['timelines', timelineId], Immutable.Map()); + const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); if (timeline.get('isLoading') || timeline.get('online')) { return; } - const ids = timeline.get('items', Immutable.List()); + const ids = timeline.get('items', ImmutableList()); const newestId = ids.size > 0 ? ids.first() : null; let skipLoading = timeline.get('loaded'); @@ -111,8 +111,8 @@ export function refreshTimelineFail(timeline, error, skipLoading) { export function expandTimeline(timelineId, path, params = {}) { return (dispatch, getState) => { - const timeline = getState().getIn(['timelines', timelineId], Immutable.Map()); - const ids = timeline.get('items', Immutable.List()); + const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); + const ids = timeline.get('items', ImmutableList()); if (timeline.get('isLoading') || ids.size === 0) { return; diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index 027d01767..e9f041be6 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -10,7 +10,7 @@ export default class ColumnHeader extends React.PureComponent { }; static propTypes = { - title: PropTypes.string.isRequired, + title: PropTypes.node.isRequired, icon: PropTypes.string.isRequired, active: PropTypes.bool, multiColumn: PropTypes.bool, diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 12e1b44fa..98323b069 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -14,6 +14,7 @@ export default class DropdownMenu extends React.PureComponent { size: PropTypes.number.isRequired, direction: PropTypes.string, ariaLabel: PropTypes.string, + disabled: PropTypes.bool, }; static defaultProps = { @@ -68,9 +69,19 @@ export default class DropdownMenu extends React.PureComponent { } render () { - const { icon, items, size, direction, ariaLabel } = this.props; - const { expanded } = this.state; + const { icon, items, size, direction, ariaLabel, disabled } = this.props; + const { expanded } = this.state; const directionClass = (direction === 'left') ? 'dropdown__left' : 'dropdown__right'; + const iconStyle = { fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }; + const iconClassname = `fa fa-fw fa-${icon} dropdown__icon`; + + if (disabled) { + return ( + <div className='icon-button disabled' style={iconStyle} aria-label={ariaLabel}> + <i className={iconClassname} aria-hidden /> + </div> + ); + } const dropdownItems = expanded && ( <ul className='dropdown__content-list'> @@ -80,8 +91,8 @@ export default class DropdownMenu extends React.PureComponent { return ( <Dropdown ref={this.setRef} onShow={this.handleShow} onHide={this.handleHide}> - <DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }} aria-label={ariaLabel}> - <i className={`fa fa-fw fa-${icon} dropdown__icon`} aria-hidden /> + <DropdownTrigger className='icon-button' style={iconStyle} aria-label={ariaLabel}> + <i className={iconClassname} aria-hidden /> </DropdownTrigger> <DropdownContent className={directionClass}> diff --git a/app/javascript/mastodon/components/permalink.js b/app/javascript/mastodon/components/permalink.js index 0b7d0a65a..d726d37a2 100644 --- a/app/javascript/mastodon/components/permalink.js +++ b/app/javascript/mastodon/components/permalink.js @@ -15,7 +15,7 @@ export default class Permalink extends React.PureComponent { }; handleClick = (e) => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { + if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.context.router.history.push(this.props.to); } @@ -25,7 +25,7 @@ export default class Permalink extends React.PureComponent { const { href, children, className, ...other } = this.props; return ( - <a href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}> + <a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}> {children} </a> ); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 398f7d243..8287375c4 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -22,14 +22,15 @@ import { getLocale } from '../locales'; const { localeData, messages } = getLocale(); addLocaleData(localeData); -const store = configureStore(); +export const store = configureStore(); const initialState = JSON.parse(document.getElementById('initial-state').textContent); try { initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings')); } catch (e) { initialState.local_settings = {}; } -store.dispatch(hydrateStore(initialState)); +const hydrateAction = hydrateStore(initialState); +store.dispatch(hydrateAction); export default class Mastodon extends React.PureComponent { diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js new file mode 100644 index 000000000..6b545ef09 --- /dev/null +++ b/app/javascript/mastodon/containers/timeline_container.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import PropTypes from 'prop-types'; +import configureStore from '../store/configureStore'; +import { hydrateStore } from '../actions/store'; +import { IntlProvider, addLocaleData } from 'react-intl'; +import { getLocale } from '../locales'; +import PublicTimeline from '../features/standalone/public_timeline'; + +const { localeData, messages } = getLocale(); +addLocaleData(localeData); + +const store = configureStore(); +const initialStateContainer = document.getElementById('initial-state'); + +if (initialStateContainer !== null) { + const initialState = JSON.parse(initialStateContainer.textContent); + store.dispatch(hydrateStore(initialState)); +} + +export default class TimelineContainer extends React.PureComponent { + + static propTypes = { + locale: PropTypes.string.isRequired, + }; + + render () { + const { locale } = this.props; + + return ( + <IntlProvider locale={locale} messages={messages}> + <Provider store={store}> + <PublicTimeline /> + </Provider> + </IntlProvider> + ); + } + +} diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js index 01d01fb72..7043d5f3a 100644 --- a/app/javascript/mastodon/emoji.js +++ b/app/javascript/mastodon/emoji.js @@ -1,35 +1,55 @@ import emojione from 'emojione'; - -const toImage = str => shortnameToImage(unicodeToImage(str)); - -const unicodeToImage = str => { - const mappedUnicode = emojione.mapUnicodeToShort(); - - return str.replace(emojione.regUnicode, unicodeChar => { - if (typeof unicodeChar === 'undefined' || unicodeChar === '' || !(unicodeChar in emojione.jsEscapeMap)) { - return unicodeChar; +import Trie from 'substring-trie'; + +const mappedUnicode = emojione.mapUnicodeToShort(); +const trie = new Trie(Object.keys(emojione.jsEscapeMap)); + +function emojify(str) { + // This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.) + // and replacing valid shortnames like :smile: and :wink: as well as unicode strings + // that _aren't_ within tags with an <img> version. + // The goal is to be the same as an emojione.regShortNames/regUnicode replacement, but faster. + let i = -1; + let insideTag = false; + let insideShortname = false; + let shortnameStartIndex = -1; + let match; + while (++i < str.length) { + const char = str.charAt(i); + if (insideShortname && char === ':') { + const shortname = str.substring(shortnameStartIndex, i + 1); + if (shortname in emojione.emojioneList) { + const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1]; + const alt = emojione.convert(unicode.toUpperCase()); + const replacement = `<img draggable="false" class="emojione" alt="${alt}" title="${shortname}" src="/emoji/${unicode}.svg" />`; + str = str.substring(0, shortnameStartIndex) + replacement + str.substring(i + 1); + i += (replacement.length - shortname.length - 1); // jump ahead the length we've added to the string + } else { + i--; // stray colon, try again + } + insideShortname = false; + } else if (insideTag && char === '>') { + insideTag = false; + } else if (char === '<') { + insideTag = true; + insideShortname = false; + } else if (!insideTag && char === ':') { + insideShortname = true; + shortnameStartIndex = i; + } else if (!insideTag && (match = trie.search(str.substring(i)))) { + const unicodeStr = match; + if (unicodeStr in emojione.jsEscapeMap) { + const unicode = emojione.jsEscapeMap[unicodeStr]; + const short = mappedUnicode[unicode]; + const filename = emojione.emojioneList[short].fname; + const alt = emojione.convert(unicode.toUpperCase()); + const replacement = `<img draggable="false" class="emojione" alt="${alt}" title="${short}" src="/emoji/${filename}.svg" />`; + str = str.substring(0, i) + replacement + str.substring(i + unicodeStr.length); + i += (replacement.length - unicodeStr.length); // jump ahead the length we've added to the string + } } - - const unicode = emojione.jsEscapeMap[unicodeChar]; - const short = mappedUnicode[unicode]; - const filename = emojione.emojioneList[short].fname; - const alt = emojione.convert(unicode.toUpperCase()); - - return `<img draggable="false" class="emojione" alt="${alt}" title="${short}" src="/emoji/${filename}.svg" />`; - }); -}; - -const shortnameToImage = str => str.replace(emojione.regShortNames, shortname => { - if (typeof shortname === 'undefined' || shortname === '' || !(shortname in emojione.emojioneList)) { - return shortname; } + return str; +} - const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1]; - const alt = emojione.convert(unicode.toUpperCase()); - - return `<img draggable="false" class="emojione" alt="${alt}" title="${shortname}" src="/emoji/${unicode}.svg" />`; -}); - -export default function emojify(text) { - return toImage(text); -}; +export default emojify; diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index 955d0000e..3c8b63114 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -9,11 +9,11 @@ import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; import HeaderContainer from './containers/header_container'; import ColumnBackButton from '../../components/column_back_button'; -import Immutable from 'immutable'; +import { List as ImmutableList } from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; const mapStateToProps = (state, props) => ({ - statusIds: state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'items'], Immutable.List()), + statusIds: state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'items'], ImmutableList()), isLoading: state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'isLoading']), hasMore: !!state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'next']), me: state.getIn(['meta', 'me']), diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index c83dbb63e..83c66a5d5 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -2,6 +2,7 @@ import React from 'react'; import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; +import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, @@ -50,7 +51,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { this.setState({ active: true }); if (!EmojiPicker) { this.setState({ loading: true }); - import(/* webpackChunkName: "emojione_picker" */ 'emojione-picker').then(TheEmojiPicker => { + EmojiPickerAsync().then(TheEmojiPicker => { EmojiPicker = TheEmojiPicker.default; this.setState({ loading: false }); }).catch(() => { diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index 3ec205f2c..69bead689 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -2,6 +2,7 @@ import React from 'react'; import ComposeFormContainer from './containers/compose_form_container'; import NavigationContainer from './containers/navigation_container'; import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import { mountCompose, unmountCompose } from '../../actions/compose'; import { openModal } from '../../actions/modal'; @@ -15,6 +16,8 @@ import SearchResultsContainer from './containers/search_results_container'; const messages = defineMessages({ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, + home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, + notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, @@ -22,6 +25,7 @@ const messages = defineMessages({ }); const mapStateToProps = state => ({ + columns: state.getIn(['settings', 'columns']), showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), }); @@ -31,6 +35,7 @@ export default class Compose extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + columns: ImmutablePropTypes.list.isRequired, multiColumn: PropTypes.bool, showSearch: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -60,11 +65,22 @@ export default class Compose extends React.PureComponent { let header = ''; if (multiColumn) { + const { columns } = this.props; header = ( <div className='drawer__header'> <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role='img' aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link> - <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role='img' aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link> - <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role='img' aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link> + {!columns.some(column => column.get('id') === 'HOME') && ( + <Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)}><i role='img' className='fa fa-fw fa-home' aria-label={intl.formatMessage(messages.home_timeline)} /></Link> + )} + {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && ( + <Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)}><i role='img' className='fa fa-fw fa-bell' aria-label={intl.formatMessage(messages.notifications)} /></Link> + )} + {!columns.some(column => column.get('id') === 'COMMUNITY') && ( + <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role='img' aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link> + )} + {!columns.some(column => column.get('id') === 'PUBLIC') && ( + <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role='img' aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link> + )} <a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)}><i role='img' aria-label={intl.formatMessage(messages.settings)} className='fa fa-fw fa-cogs' /></a> <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role='img' aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a> </div> diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index d4e1555b2..fb8f0d3cc 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -11,7 +11,7 @@ import { ScrollContainer } from 'react-router-scroll'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; import { createSelector } from 'reselect'; -import Immutable from 'immutable'; +import { List as ImmutableList } from 'immutable'; import LoadMore from '../../components/load_more'; import { debounce } from 'lodash'; @@ -20,7 +20,7 @@ const messages = defineMessages({ }); const getNotifications = createSelector([ - state => Immutable.List(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()), + state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()), state => state.getIn(['notifications', 'items']), ], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type')))); @@ -122,7 +122,7 @@ export default class Notifications extends React.PureComponent { let unread = ''; let scrollContainer = ''; - if (!isLoading && notifications.size > 0 && hasMore) { + if (!isLoading && hasMore) { loadMore = <LoadMore onClick={this.handleLoadMore} />; } @@ -132,7 +132,7 @@ export default class Notifications extends React.PureComponent { if (isLoading && this.scrollableArea) { scrollableArea = this.scrollableArea; - } else if (notifications.size > 0) { + } else if (notifications.size > 0 || hasMore) { scrollableArea = ( <div className='scrollable' onScroll={this.handleScroll} ref={this.setRef}> {unread} diff --git a/app/javascript/mastodon/features/report/containers/status_check_box_container.js b/app/javascript/mastodon/features/report/containers/status_check_box_container.js index 8997718a2..48cd0319b 100644 --- a/app/javascript/mastodon/features/report/containers/status_check_box_container.js +++ b/app/javascript/mastodon/features/report/containers/status_check_box_container.js @@ -1,11 +1,11 @@ import { connect } from 'react-redux'; import StatusCheckBox from '../components/status_check_box'; import { toggleStatusReport } from '../../../actions/reports'; -import Immutable from 'immutable'; +import { Set as ImmutableSet } from 'immutable'; const mapStateToProps = (state, { id }) => ({ status: state.getIn(['statuses', id]), - checked: state.getIn(['reports', 'new', 'status_ids'], Immutable.Set()).includes(id), + checked: state.getIn(['reports', 'new', 'status_ids'], ImmutableSet()).includes(id), }); const mapDispatchToProps = (dispatch, { id }) => ({ diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js new file mode 100644 index 000000000..de4b5320a --- /dev/null +++ b/app/javascript/mastodon/features/standalone/public_timeline/index.js @@ -0,0 +1,76 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import StatusListContainer from '../../ui/containers/status_list_container'; +import { + refreshPublicTimeline, + expandPublicTimeline, +} from '../../../actions/timelines'; +import Column from '../../../components/column'; +import ColumnHeader from '../../../components/column_header'; +import { defineMessages, injectIntl } from 'react-intl'; + +const messages = defineMessages({ + title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' }, +}); + +@connect() +@injectIntl +export default class PublicTimeline extends React.PureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleHeaderClick = () => { + this.column.scrollTop(); + } + + setRef = c => { + this.column = c; + } + + componentDidMount () { + const { dispatch } = this.props; + + dispatch(refreshPublicTimeline()); + + this.polling = setInterval(() => { + dispatch(refreshPublicTimeline()); + }, 3000); + } + + componentWillUnmount () { + if (typeof this.polling !== 'undefined') { + clearInterval(this.polling); + this.polling = null; + } + } + + handleLoadMore = () => { + this.props.dispatch(expandPublicTimeline()); + } + + render () { + const { intl } = this.props; + + return ( + <Column ref={this.setRef}> + <ColumnHeader + icon='globe' + title={intl.formatMessage(messages.title)} + onClick={this.handleHeaderClick} + /> + + <StatusListContainer + timelineId='public' + loadMore={this.handleLoadMore} + scrollKey='standalone_public_timeline' + trackScroll={false} + /> + </Column> + ); + } + +} diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.js new file mode 100644 index 000000000..72798f690 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/bundle.js @@ -0,0 +1,101 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const emptyComponent = () => null; +const noop = () => { }; + +class Bundle extends React.Component { + + static propTypes = { + fetchComponent: PropTypes.func.isRequired, + loading: PropTypes.func, + error: PropTypes.func, + children: PropTypes.func.isRequired, + renderDelay: PropTypes.number, + onFetch: PropTypes.func, + onFetchSuccess: PropTypes.func, + onFetchFail: PropTypes.func, + } + + static defaultProps = { + loading: emptyComponent, + error: emptyComponent, + renderDelay: 0, + onFetch: noop, + onFetchSuccess: noop, + onFetchFail: noop, + } + + static cache = {} + + state = { + mod: undefined, + forceRender: false, + } + + componentWillMount() { + this.load(this.props); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.fetchComponent !== this.props.fetchComponent) { + this.load(nextProps); + } + } + + componentWillUnmount () { + if (this.timeout) { + clearTimeout(this.timeout); + } + } + + load = (props) => { + const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props; + + this.setState({ mod: undefined }); + onFetch(); + + if (renderDelay !== 0) { + this.timestamp = new Date(); + this.timeout = setTimeout(() => this.setState({ forceRender: true }), renderDelay); + } + + if (Bundle.cache[fetchComponent.name]) { + const mod = Bundle.cache[fetchComponent.name]; + + this.setState({ mod: mod.default }); + onFetchSuccess(); + return Promise.resolve(); + } + + return fetchComponent() + .then((mod) => { + Bundle.cache[fetchComponent.name] = mod; + this.setState({ mod: mod.default }); + onFetchSuccess(); + }) + .catch((error) => { + this.setState({ mod: null }); + onFetchFail(error); + }); + } + + render() { + const { loading: Loading, error: Error, children, renderDelay } = this.props; + const { mod, forceRender } = this.state; + const elapsed = this.timestamp ? (new Date() - this.timestamp) : renderDelay; + + if (mod === undefined) { + return (elapsed >= renderDelay || forceRender) ? <Loading /> : null; + } + + if (mod === null) { + return <Error onRetry={this.load} />; + } + + return children(mod); + } + +} + +export default Bundle; diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.js b/app/javascript/mastodon/features/ui/components/bundle_column_error.js new file mode 100644 index 000000000..cd124746a --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl } from 'react-intl'; + +import Column from './column'; +import ColumnHeader from './column_header'; +import ColumnBackButtonSlim from '../../../components/column_back_button_slim'; +import IconButton from '../../../components/icon_button'; + +const messages = defineMessages({ + title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' }, + body: { id: 'bundle_column_error.body', defaultMessage: 'Something went wrong while loading this component.' }, + retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' }, +}); + +class BundleColumnError extends React.Component { + + static propTypes = { + onRetry: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + } + + handleRetry = () => { + this.props.onRetry(); + } + + render () { + const { intl: { formatMessage } } = this.props; + + return ( + <Column> + <ColumnHeader icon='exclamation-circle' type={formatMessage(messages.title)} /> + <ColumnBackButtonSlim /> + <div className='error-column'> + <IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} /> + {formatMessage(messages.body)} + </div> + </Column> + ); + } + +} + +export default injectIntl(BundleColumnError); diff --git a/app/javascript/mastodon/features/ui/components/bundle_modal_error.js b/app/javascript/mastodon/features/ui/components/bundle_modal_error.js new file mode 100644 index 000000000..928bfe1f7 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/bundle_modal_error.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl } from 'react-intl'; + +import IconButton from '../../../components/icon_button'; + +const messages = defineMessages({ + error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this component.' }, + retry: { id: 'bundle_modal_error.retry', defaultMessage: 'Try again' }, + close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' }, +}); + +class BundleModalError extends React.Component { + + static propTypes = { + onRetry: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + } + + handleRetry = () => { + this.props.onRetry(); + } + + render () { + const { onClose, intl: { formatMessage } } = this.props; + + // Keep the markup in sync with <ModalLoading /> + // (make sure they have the same dimensions) + return ( + <div className='modal-root__modal error-modal'> + <div className='error-modal__body'> + <IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} /> + {formatMessage(messages.error)} + </div> + + <div className='error-modal__footer'> + <div> + <button + onClick={onClose} + className='error-modal__nav onboarding-modal__skip' + > + {formatMessage(messages.close)} + </button> + </div> + </div> + </div> + ); + } + +} + +export default injectIntl(BundleModalError); diff --git a/app/javascript/mastodon/features/ui/components/column_loading.js b/app/javascript/mastodon/features/ui/components/column_loading.js new file mode 100644 index 000000000..7ecfaf77a --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/column_loading.js @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import Column from '../../../components/column'; +import ColumnHeader from '../../../components/column_header'; + +const ColumnLoading = ({ title = '', icon = ' ' }) => ( + <Column> + <ColumnHeader icon={icon} title={title} multiColumn={false} /> + <div className='scrollable' /> + </Column> +); + +ColumnLoading.propTypes = { + title: PropTypes.node, + icon: PropTypes.string, +}; + +export default ColumnLoading; diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 3c3e9425d..cbc185a7d 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -2,14 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import ReactSwipeable from 'react-swipeable'; -import HomeTimeline from '../../home_timeline'; -import Notifications from '../../notifications'; -import PublicTimeline from '../../public_timeline'; -import CommunityTimeline from '../../community_timeline'; -import HashtagTimeline from '../../hashtag_timeline'; -import Compose from '../../compose'; -import { getPreviousLink, getNextLink } from './tabs_bar'; + +import ReactSwipeableViews from 'react-swipeable-views'; +import { links, getIndex, getLink } from './tabs_bar'; + +import BundleContainer from '../containers/bundle_container'; +import ColumnLoading from './column_loading'; +import BundleColumnError from './bundle_column_error'; +import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline } from '../../ui/util/async-components'; const componentMap = { 'COMPOSE': Compose, @@ -32,39 +32,61 @@ export default class ColumnsArea extends ImmutablePureComponent { children: PropTypes.node, }; - handleRightSwipe = () => { - const previousLink = getPreviousLink(this.context.router.history.location.pathname); + handleSwipe = (index) => { + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { + this.context.router.history.push(getLink(index)); + }); + }); + } - if (previousLink) { - this.context.router.history.push(previousLink); - } + renderView = (link, index) => { + const columnIndex = getIndex(this.context.router.history.location.pathname); + const title = link.props.children[1] && React.cloneElement(link.props.children[1]); + const icon = (link.props.children[0] || link.props.children).props.className.split(' ')[2].split('-')[1]; + + const view = (index === columnIndex) ? + React.cloneElement(this.props.children) : + <ColumnLoading title={title} icon={icon} />; + + return ( + <div className='columns-area' key={index}> + {view} + </div> + ); } - handleLeftSwipe = () => { - const previousLink = getNextLink(this.context.router.history.location.pathname); + renderLoading = () => { + return <ColumnLoading />; + } - if (previousLink) { - this.context.router.history.push(previousLink); - } - }; + renderError = (props) => { + return <BundleColumnError {...props} />; + } render () { const { columns, children, singleColumn } = this.props; + const columnIndex = getIndex(this.context.router.history.location.pathname); + if (singleColumn) { - return ( - <ReactSwipeable onSwipedLeft={this.handleLeftSwipe} onSwipedRight={this.handleRightSwipe} className='columns-area'> - {children} - </ReactSwipeable> - ); + return columnIndex !== -1 ? ( + <ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} animateTransitions={false} style={{ height: '100%' }}> + {links.map(this.renderView)} + </ReactSwipeableViews> + ) : <div className='columns-area'>{children}</div>; } return ( <div className='columns-area'> {columns.map(column => { - const SpecificComponent = componentMap[column.get('id')]; const params = column.get('params', null) === null ? null : column.get('params').toJS(); - return <SpecificComponent key={column.get('uuid')} columnId={column.get('uuid')} params={params} multiColumn />; + + return ( + <BundleContainer key={column.get('uuid')} fetchComponent={componentMap[column.get('id')]} loading={this.renderLoading} error={this.renderError}> + {SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn />} + </BundleContainer> + ); })} {React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))} diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js index 52c3a898b..aad594380 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.js +++ b/app/javascript/mastodon/features/ui/components/image_loader.js @@ -8,12 +8,14 @@ export default class ImageLoader extends React.PureComponent { alt: PropTypes.string, src: PropTypes.string.isRequired, previewSrc: PropTypes.string.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, + width: PropTypes.number, + height: PropTypes.number, } static defaultProps = { alt: '', + width: null, + height: null, }; state = { @@ -46,8 +48,8 @@ export default class ImageLoader extends React.PureComponent { this.setState({ loading: true, error: false }); Promise.all([ this.loadPreviewCanvas(props), - this.loadOriginalImage(props), - ]) + this.hasSize() && this.loadOriginalImage(props), + ].filter(Boolean)) .then(() => { this.setState({ loading: false, error: false }); this.clearPreviewCanvas(); @@ -106,6 +108,11 @@ export default class ImageLoader extends React.PureComponent { this.removers = []; } + hasSize () { + const { width, height } = this.props; + return typeof width === 'number' && typeof height === 'number'; + } + setCanvasRef = c => { this.canvas = c; } @@ -116,6 +123,7 @@ export default class ImageLoader extends React.PureComponent { const className = classNames('image-loader', { 'image-loader--loading': loading, + 'image-loader--amorphous': !this.hasSize(), }); return ( @@ -125,6 +133,7 @@ export default class ImageLoader extends React.PureComponent { width={width} height={height} ref={this.setCanvasRef} + style={{ opacity: loading ? 1 : 0 }} /> {!loading && ( diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index 8bb81ca01..d869fffa6 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactSwipeable from 'react-swipeable'; +import ReactSwipeableViews from 'react-swipeable-views'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import ExtendedVideoPlayer from '../../../components/extended_video_player'; @@ -26,12 +26,16 @@ export default class MediaModal extends ImmutablePureComponent { index: null, }; + handleSwipe = (index) => { + this.setState({ index: (index) % this.props.media.size }); + } + handleNextClick = () => { this.setState({ index: (this.getIndex() + 1) % this.props.media.size }); } handlePrevClick = () => { - this.setState({ index: (this.getIndex() - 1) % this.props.media.size }); + this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size }); } handleKeyUp = (e) => { @@ -74,7 +78,12 @@ export default class MediaModal extends ImmutablePureComponent { } if (attachment.get('type') === 'image') { - content = <ImageLoader previewSrc={attachment.get('preview_url')} src={url} width={attachment.getIn(['meta', 'original', 'width'])} height={attachment.getIn(['meta', 'original', 'height'])} />; + content = media.map((image) => { + const width = image.getIn(['meta', 'original', 'width']) || null; + const height = image.getIn(['meta', 'original', 'height']) || null; + + return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} key={image.get('preview_url')} />; + }).toArray(); } else if (attachment.get('type') === 'gifv') { content = <ExtendedVideoPlayer src={url} muted controls={false} />; } @@ -85,9 +94,9 @@ export default class MediaModal extends ImmutablePureComponent { <div className='media-modal__content'> <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} /> - <ReactSwipeable onSwipedRight={this.handlePrevClick} onSwipedLeft={this.handleNextClick}> + <ReactSwipeableViews onChangeIndex={this.handleSwipe} index={index} animateHeight> {content} - </ReactSwipeable> + </ReactSwipeableViews> </div> {rightNav} diff --git a/app/javascript/mastodon/features/ui/components/modal_loading.js b/app/javascript/mastodon/features/ui/components/modal_loading.js new file mode 100644 index 000000000..f403ca4c9 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/modal_loading.js @@ -0,0 +1,20 @@ +import React from 'react'; + +import LoadingIndicator from '../../../components/loading_indicator'; + +// Keep the markup in sync with <BundleModalError /> +// (make sure they have the same dimensions) +const ModalLoading = () => ( + <div className='modal-root__modal error-modal'> + <div className='error-modal__body'> + <LoadingIndicator /> + </div> + <div className='error-modal__footer'> + <div> + <button className='error-modal__nav onboarding-modal__skip' /> + </div> + </div> + </div> +); + +export default ModalLoading; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index 3777c1bf6..de4f44ce6 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -1,14 +1,19 @@ import React from 'react'; import PropTypes from 'prop-types'; -import MediaModal from './media_modal'; -import OnboardingModal from './onboarding_modal'; -import VideoModal from './video_modal'; -import BoostModal from './boost_modal'; -import ConfirmationModal from './confirmation_modal'; -import ReportModal from './report_modal'; -import SettingsContainer from '../../../../glitch/containers/settings'; import TransitionMotion from 'react-motion/lib/TransitionMotion'; import spring from 'react-motion/lib/spring'; +import BundleContainer from '../containers/bundle_container'; +import BundleModalError from './bundle_modal_error'; +import ModalLoading from './modal_loading'; +import { + MediaModal, + OnboardingModal, + VideoModal, + BoostModal, + ConfirmationModal, + ReportModal, + SettingsModal, +} from '../../../features/ui/util/async-components'; const MODAL_COMPONENTS = { 'MEDIA': MediaModal, @@ -17,7 +22,7 @@ const MODAL_COMPONENTS = { 'BOOST': BoostModal, 'CONFIRM': ConfirmationModal, 'REPORT': ReportModal, - 'SETTINGS': SettingsContainer, + 'SETTINGS': SettingsModal, }; export default class ModalRoot extends React.PureComponent { @@ -51,6 +56,22 @@ export default class ModalRoot extends React.PureComponent { return { opacity: spring(0), scale: spring(0.98) }; } + renderModal = (SpecificComponent) => { + const { props, onClose } = this.props; + + return <SpecificComponent {...props} onClose={onClose} />; + } + + renderLoading = () => { + return <ModalLoading />; + } + + renderError = (props) => { + const { onClose } = this.props; + + return <BundleModalError {...props} onClose={onClose} />; + } + render () { const { type, props, onClose } = this.props; const visible = !!type; @@ -72,18 +93,14 @@ export default class ModalRoot extends React.PureComponent { > {interpolatedStyles => <div className='modal-root'> - {interpolatedStyles.map(({ key, data: { type, props }, style }) => { - const SpecificComponent = MODAL_COMPONENTS[type]; - - return ( - <div key={key} style={{ pointerEvents: visible ? 'auto' : 'none' }}> - <div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} /> - <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}> - <SpecificComponent {...props} onClose={onClose} /> - </div> + {interpolatedStyles.map(({ key, data: { type }, style }) => ( + <div key={key} style={{ pointerEvents: visible ? 'auto' : 'none' }}> + <div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} /> + <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}> + <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading} error={this.renderError} renderDelay={200}>{this.renderModal}</BundleContainer> </div> - ); - })} + </div> + ))} </div> } </TransitionMotion> diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js index dab5e47ea..1b1cb00da 100644 --- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js +++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js @@ -3,16 +3,14 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ReactSwipeable from 'react-swipeable'; +import ReactSwipeableViews from 'react-swipeable-views'; import classNames from 'classnames'; import Permalink from '../../../components/permalink'; -import TransitionMotion from 'react-motion/lib/TransitionMotion'; -import spring from 'react-motion/lib/spring'; import ComposeForm from '../../compose/components/compose_form'; import Search from '../../compose/components/search'; import NavigationBar from '../../compose/components/navigation_bar'; import ColumnHeader from './column_header'; -import Immutable from 'immutable'; +import { List as ImmutableList } from 'immutable'; const noop = () => { }; @@ -50,7 +48,7 @@ const PageTwo = ({ me }) => ( </div> <ComposeForm text='Awoo! #introductions' - suggestions={Immutable.List()} + suggestions={ImmutableList()} mentionedDomains={[]} spoiler={false} onChange={noop} @@ -227,6 +225,10 @@ export default class OnboardingModal extends React.PureComponent { })); } + handleSwipe = (index) => { + this.setState({ currentIndex: index }); + } + handleKeyUp = ({ key }) => { switch (key) { case 'ArrowLeft': @@ -263,30 +265,18 @@ export default class OnboardingModal extends React.PureComponent { </button> ); - const styles = pages.map((data, i) => ({ - key: `page-${i}`, - data, - style: { - opacity: spring(i === currentIndex ? 1 : 0), - }, - })); - return ( <div className='modal-root__modal onboarding-modal'> - <TransitionMotion styles={styles}> - {interpolatedStyles => ( - <ReactSwipeable onSwipedRight={this.handlePrev} onSwipedLeft={this.handleNext} className='onboarding-modal__pager'> - {interpolatedStyles.map(({ key, data, style }, i) => { - const className = classNames('onboarding-modal__page__wrapper', { - 'onboarding-modal__page__wrapper--active': i === currentIndex, - }); - return ( - <div key={key} style={style} className={className}>{data}</div> - ); - })} - </ReactSwipeable> - )} - </TransitionMotion> + <ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='onboarding-modal__pager'> + {pages.map((page, i) => { + const className = classNames('onboarding-modal__page__wrapper', { + 'onboarding-modal__page__wrapper--active': i === currentIndex, + }); + return ( + <div key={i} className={className}>{page}</div> + ); + })} + </ReactSwipeableViews> <div className='onboarding-modal__paginator'> <div> diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js index c989d2c9b..b5dfa422e 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.js +++ b/app/javascript/mastodon/features/ui/components/report_modal.js @@ -7,7 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { makeGetAccount } from '../../../selectors'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import StatusCheckBox from '../../report/containers/status_check_box_container'; -import Immutable from 'immutable'; +import { OrderedSet } from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Button from '../../../components/button'; @@ -26,7 +26,7 @@ const makeMapStateToProps = () => { isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']), account: getAccount(state, accountId), comment: state.getIn(['reports', 'new', 'comment']), - statusIds: Immutable.OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])), + statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])), }; }; diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index c2e6c88b5..b4153ff45 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -2,7 +2,7 @@ import React from 'react'; import NavLink from 'react-router-dom/NavLink'; import { FormattedMessage } from 'react-intl'; -const links = [ +export const links = [ <NavLink className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>, <NavLink className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>, <NavLink className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>, @@ -13,25 +13,13 @@ const links = [ <NavLink className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></NavLink>, ]; -export function getPreviousLink (path) { - const index = links.findIndex(link => link.props.to === path); - - if (index > 0) { - return links[index - 1].props.to; - } - - return null; -}; - -export function getNextLink (path) { - const index = links.findIndex(link => link.props.to === path); - - if (index !== -1 && index < links.length - 1) { - return links[index + 1].props.to; - } +export function getIndex (path) { + return links.findIndex(link => link.props.to === path); +} - return null; -}; +export function getLink (index) { + return links[index].props.to; +} export default class TabsBar extends React.Component { diff --git a/app/javascript/mastodon/features/ui/containers/bundle_container.js b/app/javascript/mastodon/features/ui/containers/bundle_container.js new file mode 100644 index 000000000..7e3f0c3a6 --- /dev/null +++ b/app/javascript/mastodon/features/ui/containers/bundle_container.js @@ -0,0 +1,19 @@ +import { connect } from 'react-redux'; + +import Bundle from '../components/bundle'; + +import { fetchBundleRequest, fetchBundleSuccess, fetchBundleFail } from '../../../actions/bundles'; + +const mapDispatchToProps = dispatch => ({ + onFetch () { + dispatch(fetchBundleRequest()); + }, + onFetchSuccess () { + dispatch(fetchBundleSuccess()); + }, + onFetchFail (error) { + dispatch(fetchBundleFail(error)); + }, +}); + +export default connect(null, mapDispatchToProps)(Bundle); diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js index 45ad6209b..1b2e1056a 100644 --- a/app/javascript/mastodon/features/ui/containers/status_list_container.js +++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js @@ -1,13 +1,13 @@ import { connect } from 'react-redux'; import StatusList from '../../../components/status_list'; import { scrollTopTimeline } from '../../../actions/timelines'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { debounce } from 'lodash'; const makeGetStatusIds = () => createSelector([ - (state, { type }) => state.getIn(['settings', type], Immutable.Map()), - (state, { type }) => state.getIn(['timelines', type, 'items'], Immutable.List()), + (state, { type }) => state.getIn(['settings', type], ImmutableMap()), + (state, { type }) => state.getIn(['timelines', type, 'items'], ImmutableList()), (state) => state.get('statuses'), (state) => state.getIn(['meta', 'me']), ], (columnSettings, statusIds, statuses, me) => statusIds.filter(id => { diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 824963a53..5a0398eb4 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -1,6 +1,5 @@ import React from 'react'; -import Switch from 'react-router-dom/Switch'; -import Route from 'react-router-dom/Route'; +import classNames from 'classnames'; import Redirect from 'react-router-dom/Redirect'; import NotificationsContainer from './containers/notifications_container'; import PropTypes from 'prop-types'; @@ -13,66 +12,37 @@ import { debounce } from 'lodash'; import { uploadCompose } from '../../actions/compose'; import { refreshHomeTimeline } from '../../actions/timelines'; import { refreshNotifications } from '../../actions/notifications'; +import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; import UploadArea from './components/upload_area'; import ColumnsAreaContainer from './containers/columns_area_container'; -import Status from '../../features/status'; -import GettingStarted from '../../features/getting_started'; -import PublicTimeline from '../../features/public_timeline'; -import CommunityTimeline from '../../features/community_timeline'; -import AccountTimeline from '../../features/account_timeline'; -import AccountGallery from '../../features/account_gallery'; -import HomeTimeline from '../../features/home_timeline'; -import Compose from '../../features/compose'; -import Followers from '../../features/followers'; -import Following from '../../features/following'; -import Reblogs from '../../features/reblogs'; -import Favourites from '../../features/favourites'; -import HashtagTimeline from '../../features/hashtag_timeline'; -import Notifications from '../../features/notifications'; -import FollowRequests from '../../features/follow_requests'; -import GenericNotFound from '../../features/generic_not_found'; -import FavouritedStatuses from '../../features/favourited_statuses'; -import Blocks from '../../features/blocks'; -import Mutes from '../../features/mutes'; - -// Small wrapper to pass multiColumn to the route components -const WrappedSwitch = ({ multiColumn, children }) => ( - <Switch> - {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))} - </Switch> -); - -WrappedSwitch.propTypes = { - multiColumn: PropTypes.bool, - children: PropTypes.node, -}; - -// Small Wraper to extract the params from the route and pass -// them to the rendered component, together with the content to -// be rendered inside (the children) -class WrappedRoute extends React.Component { - - static propTypes = { - component: PropTypes.func.isRequired, - content: PropTypes.node, - multiColumn: PropTypes.bool, - } - - renderComponent = ({ match: { params } }) => { - const { component: Component, content, multiColumn } = this.props; - - return <Component params={params} multiColumn={multiColumn}>{content}</Component>; - } - - render () { - const { component: Component, content, ...rest } = this.props; - - return <Route {...rest} render={this.renderComponent} />; - } - -} +import { + Compose, + Status, + GettingStarted, + PublicTimeline, + CommunityTimeline, + AccountTimeline, + AccountGallery, + HomeTimeline, + Followers, + Following, + Reblogs, + Favourites, + HashtagTimeline, + Notifications, + FollowRequests, + GenericNotFound, + FavouritedStatuses, + Blocks, + Mutes, +} from './util/async-components'; + +// Dummy import, to make sure that <Status /> ends up in the application bundle. +// Without this it ends up in ~8 very commonly used bundles. +import '../../../glitch/components/status'; const mapStateToProps = state => ({ + systemFontUi: state.getIn(['meta', 'system_font_ui']), layout: state.getIn(['local_settings', 'layout']), isWide: state.getIn(['local_settings', 'stretch']), }); @@ -85,6 +55,7 @@ export default class UI extends React.PureComponent { children: PropTypes.node, layout: PropTypes.string, isWide: PropTypes.bool, + systemFontUi: PropTypes.bool, }; state = { @@ -194,8 +165,13 @@ export default class UI extends React.PureComponent { } }; + const className = classNames('ui', columnsClass(layout), { + 'wide': isWide, + 'system-font': this.props.systemFontUi, + }); + return ( - <div className={'ui ' + columnsClass(layout) + (isWide ? ' wide' : '')} ref={this.setRef}> + <div className={className} ref={this.setRef}> <TabsBar /> <ColumnsAreaContainer singleColumn={isMobile(width, layout)}> <WrappedSwitch> diff --git a/app/javascript/mastodon/features/ui/util/get_rect_from_entry.js b/app/javascript/mastodon/features/ui/util/get_rect_from_entry.js new file mode 100644 index 000000000..c266cd7dc --- /dev/null +++ b/app/javascript/mastodon/features/ui/util/get_rect_from_entry.js @@ -0,0 +1,21 @@ + +// Get the bounding client rect from an IntersectionObserver entry. +// This is to work around a bug in Chrome: https://crbug.com/737228 + +let hasBoundingRectBug; + +function getRectFromEntry(entry) { + if (typeof hasBoundingRectBug !== 'boolean') { + const boundingRect = entry.target.getBoundingClientRect(); + const observerRect = entry.boundingClientRect; + hasBoundingRectBug = boundingRect.height !== observerRect.height || + boundingRect.top !== observerRect.top || + boundingRect.width !== observerRect.width || + boundingRect.bottom !== observerRect.bottom || + boundingRect.left !== observerRect.left || + boundingRect.right !== observerRect.right; + } + return hasBoundingRectBug ? entry.target.getBoundingClientRect() : entry.boundingClientRect; +} + +export default getRectFromEntry; diff --git a/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js b/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js index 0e959f9ae..2b24c6583 100644 --- a/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js +++ b/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js @@ -37,9 +37,18 @@ class IntersectionObserverWrapper { } } + unobserve (id, node) { + if (this.observer) { + delete this.callbacks[id]; + this.observer.unobserve(node); + } + } + disconnect () { if (this.observer) { + this.callbacks = {}; this.observer.disconnect(); + this.observer = null; } } diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js new file mode 100644 index 000000000..ede578e56 --- /dev/null +++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js @@ -0,0 +1,57 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Switch from 'react-router-dom/Switch'; +import Route from 'react-router-dom/Route'; + +import ColumnLoading from '../components/column_loading'; +import BundleColumnError from '../components/bundle_column_error'; +import BundleContainer from '../containers/bundle_container'; + +// Small wrapper to pass multiColumn to the route components +export const WrappedSwitch = ({ multiColumn, children }) => ( + <Switch> + {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))} + </Switch> +); + +WrappedSwitch.propTypes = { + multiColumn: PropTypes.bool, + children: PropTypes.node, +}; + +// Small Wraper to extract the params from the route and pass +// them to the rendered component, together with the content to +// be rendered inside (the children) +export class WrappedRoute extends React.Component { + + static propTypes = { + component: PropTypes.func.isRequired, + content: PropTypes.node, + multiColumn: PropTypes.bool, + } + + renderComponent = ({ match }) => { + const { component, content, multiColumn } = this.props; + + return ( + <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}> + {Component => <Component params={match.params} multiColumn={multiColumn}>{content}</Component>} + </BundleContainer> + ); + } + + renderLoading = () => { + return <ColumnLoading />; + } + + renderError = (props) => { + return <BundleColumnError {...props} />; + } + + render () { + const { component: Component, content, ...rest } = this.props; + + return <Route {...rest} render={this.renderComponent} />; + } + +} diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index c13bc73d3..6992e7e0f 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -18,6 +18,12 @@ "account.unfollow": "إلغاء المتابعة", "account.unmute": "إلغاء الكتم عن @{name}", "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "الحسابات المحجوبة", "column.community": "الخيط العام المحلي", "column.favourites": "المفضلة", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "لا تقم بإدراجه على الخيوط العامة", "privacy.unlisted.short": "غير مدرج", "reply_indicator.cancel": "إلغاء", - "report.heading": "تقرير جديد", "report.placeholder": "تعليقات إضافية", "report.submit": "إرسال", "report.target": "إبلاغ", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 3b6f228c6..7a56e1446 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -18,6 +18,12 @@ "account.unfollow": "Не следвай", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", "column.favourites": "Favourites", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Do not show in public timelines", "privacy.unlisted.short": "Unlisted", "reply_indicator.cancel": "Отказ", - "report.heading": "New report", "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 8e8c95d56..b2673915a 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -18,6 +18,12 @@ "account.unfollow": "Deixar de seguir", "account.unmute": "Treure silenci de @{name}", "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Usuaris bloquejats", "column.community": "Línia de temps local", "column.favourites": "Favorits", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "No publicar en línies de temps públiques", "privacy.unlisted.short": "No llistat", "reply_indicator.cancel": "Cancel·lar", - "report.heading": "Nou informe", "report.placeholder": "Comentaris addicionals", "report.submit": "Enviar", "report.target": "Informes", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 55499c0a3..4b62403c3 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -18,6 +18,12 @@ "account.unfollow": "Entfolgen", "account.unmute": "@{name} nicht mehr stummschalten", "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blockierte Benutzer", "column.community": "Lokale Zeitleiste", "column.favourites": "Favoriten", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen", "privacy.unlisted.short": "Nicht gelistet", "reply_indicator.cancel": "Abbrechen", - "report.heading": "Neue Meldung", "report.placeholder": "Zusätzliche Kommentare", "report.submit": "Absenden", "report.target": "Melden", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index a5ff686a0..36d82ec1a 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -644,6 +644,14 @@ "id": "getting_started.heading" }, { + "defaultMessage": "Home", + "id": "tabs_bar.home" + }, + { + "defaultMessage": "Notifications", + "id": "tabs_bar.notifications" + }, + { "defaultMessage": "Federated timeline", "id": "navigation_bar.public_timeline" }, @@ -959,27 +967,6 @@ { "descriptors": [ { - "defaultMessage": "New report", - "id": "report.heading" - }, - { - "defaultMessage": "Additional comments", - "id": "report.placeholder" - }, - { - "defaultMessage": "Submit", - "id": "report.submit" - }, - { - "defaultMessage": "Reporting", - "id": "report.target" - } - ], - "path": "app/javascript/mastodon/features/report/index.json" - }, - { - "descriptors": [ - { "defaultMessage": "Delete", "id": "status.delete" }, @@ -1039,6 +1026,40 @@ { "descriptors": [ { + "defaultMessage": "Network error", + "id": "bundle_column_error.title" + }, + { + "defaultMessage": "Something went wrong while loading this component.", + "id": "bundle_column_error.body" + }, + { + "defaultMessage": "Try again", + "id": "bundle_column_error.retry" + } + ], + "path": "app/javascript/mastodon/features/ui/components/bundle_column_error.json" + }, + { + "descriptors": [ + { + "defaultMessage": "Something went wrong while loading this component.", + "id": "bundle_modal_error.message" + }, + { + "defaultMessage": "Try again", + "id": "bundle_modal_error.retry" + }, + { + "defaultMessage": "Close", + "id": "bundle_modal_error.close" + } + ], + "path": "app/javascript/mastodon/features/ui/components/bundle_modal_error.json" + }, + { + "descriptors": [ + { "defaultMessage": "Cancel", "id": "confirmation_modal.cancel" } diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index b286ed2d3..cf29e38da 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -18,6 +18,12 @@ "account.unfollow": "Unfollow", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", "column.favourites": "Favourites", @@ -141,7 +147,6 @@ "privacy.unlisted.long": "Do not post to public timelines", "privacy.unlisted.short": "Unlisted", "reply_indicator.cancel": "Cancel", - "report.heading": "Report {target}", "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting {target}", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 330fe831d..2648a6840 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -18,6 +18,12 @@ "account.unfollow": "Malsekvi", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Loka tempolinio", "column.favourites": "Favourites", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Do not show in public timelines", "privacy.unlisted.short": "Unlisted", "reply_indicator.cancel": "Rezigni", - "report.heading": "New report", "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 6469aa6f2..c42930380 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -18,6 +18,12 @@ "account.unfollow": "Dejar de seguir", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Usuarios bloqueados", "column.community": "Historia local", "column.favourites": "Favoritos", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "No mostrar en la historia federada", "privacy.unlisted.short": "Sin federar", "reply_indicator.cancel": "Cancelar", - "report.heading": "New report", "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 3835caab1..c9f1888b5 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -18,6 +18,12 @@ "account.unfollow": "پایان پیگیری", "account.unmute": "باصدا کردن @{name}", "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "کاربران مسدودشده", "column.community": "نوشتههای محلی", "column.favourites": "پسندیدهها", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "عمومی، ولی فهرست نکن", "privacy.unlisted.short": "فهرستنشده", "reply_indicator.cancel": "لغو", - "report.heading": "گزارش تازه", "report.placeholder": "توضیح اضافه", "report.submit": "بفرست", "report.target": "گزارشدادن", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index dae911799..b836d2f5d 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -18,6 +18,12 @@ "account.unfollow": "Lopeta seuraaminen", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Paikallinen aikajana", "column.favourites": "Favourites", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Do not show in public timelines", "privacy.unlisted.short": "Unlisted", "reply_indicator.cancel": "Peruuta", - "report.heading": "New report", "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 1a69235c8..eaa01638c 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -7,7 +7,7 @@ "account.followers": "Abonné⋅e⋅s", "account.follows": "Abonnements", "account.follows_you": "Vous suit", - "account.media": "Media", + "account.media": "Média", "account.mention": "Mentionner", "account.mute": "Masquer", "account.posts": "Statuts", @@ -18,6 +18,12 @@ "account.unfollow": "Ne plus suivre", "account.unmute": "Ne plus masquer", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Comptes bloqués", "column.community": "Fil public local", "column.favourites": "Favoris", @@ -31,10 +37,10 @@ "column_header.unpin": "Retirer", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Paramètres", - "compose_form.lock_disclaimer": "Votre compte n'est pas {locked}. Tout le monde peut vous suivre et voir vos pouets restreints.", + "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets restreints.", "compose_form.lock_disclaimer.lock": "verrouillé", - "compose_form.placeholder": "Qu’avez-vous en tête ?", - "compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.", + "compose_form.placeholder": "Qu’avez-vous en tête ?", + "compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.", "compose_form.publish": "Pouet ", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive": "Marquer le média comme délicat", @@ -42,13 +48,13 @@ "compose_form.spoiler_placeholder": "Avertissement", "confirmation_modal.cancel": "Annuler", "confirmations.block.confirm": "Bloquer", - "confirmations.block.message": "Confirmez vous le blocage de {name} ?", + "confirmations.block.message": "Confirmez vous le blocage de {name} ?", "confirmations.delete.confirm": "Supprimer", - "confirmations.delete.message": "Confirmez vous la suppression de ce pouet ?", + "confirmations.delete.message": "Confirmez vous la suppression de ce pouet ?", "confirmations.domain_block.confirm": "Masquer le domaine entier", - "confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou silenciations ciblés sont suffisants et préférables.", + "confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou silenciations ciblés sont suffisants et préférables.", "confirmations.mute.confirm": "Silencer", - "confirmations.mute.message": "Confirmez vous la silenciation {name} ?", + "confirmations.mute.message": "Confirmez vous la silenciation {name} ?", "emoji_button.activity": "Activités", "emoji_button.flags": "Drapeaux", "emoji_button.food": "Boire et manger", @@ -59,20 +65,20 @@ "emoji_button.search": "Recherche…", "emoji_button.symbols": "Symboles", "emoji_button.travel": "Lieux et voyages", - "empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !", + "empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !", "empty_column.hashtag": "Il n’y a encore aucun contenu relatif à ce hashtag", - "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateurs⋅trices.", + "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateur⋅ice⋅s.", "empty_column.home.inactivity": "Votre accueil est vide. Si vous ne vous êtes pas connecté⋅e depuis un moment, il se remplira automatiquement très bientôt.", "empty_column.home.public_timeline": "le fil public", - "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateurs⋅trices pour débuter la conversation.", - "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateurs⋅trices d’autres instances pour remplir le fil public.", + "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateur⋅ice⋅s pour débuter la conversation.", + "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice⋅s d’autres instances pour remplir le fil public.", "follow_request.authorize": "Autoriser", "follow_request.reject": "Rejeter", "getting_started.appsshort": "Applications", "getting_started.faq": "FAQ", "getting_started.heading": "Pour commencer", "getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.", - "getting_started.userguide": "Guide d'utilisation", + "getting_started.userguide": "Guide d’utilisation", "home.column_settings.advanced": "Avancé", "home.column_settings.basic": "Basique", "home.column_settings.filter_regex": "Filtrer avec une expression rationnelle", @@ -93,37 +99,37 @@ "navigation_bar.mutes": "Comptes silencés", "navigation_bar.preferences": "Préférences", "navigation_bar.public_timeline": "Fil public global", - "notification.favourite": "{name} a ajouté à ses favoris :", + "notification.favourite": "{name} a ajouté à ses favoris :", "notification.follow": "{name} vous suit.", - "notification.mention": "{name} vous a mentionné⋅e :", - "notification.reblog": "{name} a partagé votre statut :", + "notification.mention": "{name} vous a mentionné⋅e :", + "notification.reblog": "{name} a partagé votre statut :", "notifications.clear": "Nettoyer", - "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?", + "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?", "notifications.column_settings.alert": "Notifications locales", - "notifications.column_settings.favourite": "Favoris :", - "notifications.column_settings.follow": "Nouveaux⋅elles abonn⋅é⋅s :", - "notifications.column_settings.mention": "Mentions :", - "notifications.column_settings.reblog": "Partages :", + "notifications.column_settings.favourite": "Favoris :", + "notifications.column_settings.follow": "Nouveaux⋅elles abonn⋅é⋅s :", + "notifications.column_settings.mention": "Mentions :", + "notifications.column_settings.reblog": "Partages :", "notifications.column_settings.show": "Afficher dans la colonne", "notifications.column_settings.sound": "Émettre un son", "onboarding.done": "Effectué", "onboarding.next": "Suivant", - "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateurs⋅trices suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateurs⋅trices de {domain}.", - "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te⋅s les utilisateurs⋅trices que vous suivez", + "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateur⋅ice⋅s suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateur⋅ice⋅s de {domain}.", + "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te⋅s les utilisateur⋅ice⋅s que vous suivez", "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu’un interagit avec vous", "onboarding.page_one.federation": "Mastodon est un réseau social qui appartient à tou⋅te⋅s.", - "onboarding.page_one.handle": "Vous êtes sur {domain}, une des nombreuses instances indépendantes de Mastodon. Votre nom d’utilisateur⋅trice complet est {handle}", - "onboarding.page_one.welcome": "Bienvenue sur Mastodon !", + "onboarding.page_one.handle": "Vous êtes sur {domain}, une des nombreuses instances indépendantes de Mastodon. Votre nom d’utilisateur⋅ice complet est {handle}", + "onboarding.page_one.welcome": "Bienvenue sur Mastodon !", "onboarding.page_six.admin": "L’administrateur⋅trice de votre instance est {admin}", "onboarding.page_six.almost_done": "Nous y sommes presque…", "onboarding.page_six.appetoot": "Bon Appetoot!", "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon Appetoot!", "onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.", "onboarding.page_six.guidelines": "règles de la communauté", - "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !", + "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !", "onboarding.page_six.various_app": "applications mobiles", "onboarding.page_three.profile": "Modifiez votre profil pour changer votre avatar, votre description ainsi que votre nom. Vous y trouverez également d’autres préférences.", - "onboarding.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateurs⋅trices et regarder des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu’un qui n’est pas sur cette instance, utilisez son nom d’utilisateur⋅trice complet.", + "onboarding.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateur⋅ice⋅s et regarder des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu’un qui n’est pas sur cette instance, utilisez son nom d’utilisateur⋅ice complet.", "onboarding.page_two.compose": "Écrivez depuis la colonne de composition. Vous pouvez ajouter des images, changer les réglages de confidentialité, et ajouter des avertissements de contenu (Content Warning) grâce aux icônes en dessous.", "onboarding.skip": "Passer", "privacy.change": "Ajuster la confidentialité du message", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Ne pas afficher dans les fils publics", "privacy.unlisted.short": "Non-listé", "reply_indicator.cancel": "Annuler", - "report.heading": "Nouveau signalement", "report.placeholder": "Commentaires additionnels", "report.submit": "Envoyer", "report.target": "Signalement", @@ -151,7 +156,7 @@ "status.mute_conversation": "Masquer la conversation", "status.open": "Déplier ce statut", "status.reblog": "Partager", - "status.reblogged_by": "{name} a partagé :", + "status.reblogged_by": "{name} a partagé :", "status.reply": "Répondre", "status.replyAll": "Répondre au fil", "status.report": "Signaler @{name}", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index db3d00394..98c7ea021 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -18,6 +18,12 @@ "account.unfollow": "הפסקת מעקב", "account.unmute": "הפסקת השתקת @{name}", "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "חסימות", "column.community": "ציר זמן מקומי", "column.favourites": "חיבובים", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "לא יופיע בפידים הציבוריים המשותפים", "privacy.unlisted.short": "לא לפיד הכללי", "reply_indicator.cancel": "ביטול", - "report.heading": "דווח חדש", "report.placeholder": "הערות נוספות", "report.submit": "שליחה", "report.target": "דיווח", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index f85eb8a3f..fdf5c11c0 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -18,6 +18,12 @@ "account.unfollow": "Prestani slijediti", "account.unmute": "Poništi utišavanje @{name}", "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blokirani korisnici", "column.community": "Lokalni timeline", "column.favourites": "Favoriti", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Ne prikazuj u javnim timelineovima", "privacy.unlisted.short": "Unlisted", "reply_indicator.cancel": "Otkaži", - "report.heading": "Nova prijava", "report.placeholder": "Dodatni komentari", "report.submit": "Pošalji", "report.target": "Prijavljivanje", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 350410c4b..baf762c8d 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -18,6 +18,12 @@ "account.unfollow": "Követés abbahagyása", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", "column.favourites": "Favourites", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Do not show in public timelines", "privacy.unlisted.short": "Unlisted", "reply_indicator.cancel": "Mégsem", - "report.heading": "New report", "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 6e9bc5ba9..6f6d688e9 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -18,6 +18,12 @@ "account.unfollow": "Berhenti mengikuti", "account.unmute": "Berhenti membisukan @{name}", "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Pengguna diblokir", "column.community": "Linimasa Lokal", "column.favourites": "Favorit", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Tidak ditampilkan di linimasa publik", "privacy.unlisted.short": "Tak Terdaftar", "reply_indicator.cancel": "Batal", - "report.heading": "Laporan baru", "report.placeholder": "Komentar tambahan", "report.submit": "Kirim", "report.target": "Melaporkan", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 005dd4f56..25e0adc8a 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -18,6 +18,12 @@ "account.unfollow": "Ne plus sequar", "account.unmute": "Ne plus celar @{name}", "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blokusita uzeri", "column.community": "Lokala tempolineo", "column.favourites": "Favorati", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Ne montrar en publika tempolinei", "privacy.unlisted.short": "Ne enlistigota", "reply_indicator.cancel": "Nihiligar", - "report.heading": "Nova denunco", "report.placeholder": "Plusa komenti", "report.submit": "Sendar", "report.target": "Denuncante", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 4a5b218e8..4881b0f08 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -18,6 +18,12 @@ "account.unfollow": "Non seguire", "account.unmute": "Non silenziare @{name}", "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Utenti bloccati", "column.community": "Timeline locale", "column.favourites": "Apprezzati", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Non mostrare sulla timeline pubblica", "privacy.unlisted.short": "Non elencato", "reply_indicator.cancel": "Annulla", - "report.heading": "Nuova segnalazione", "report.placeholder": "Commenti aggiuntivi", "report.submit": "Invia", "report.target": "Invio la segnalazione", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index cb8074b5d..f62072852 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -18,6 +18,12 @@ "account.unfollow": "フォロー解除", "account.unmute": "ミュート解除", "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。", + "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。", + "bundle_column_error.retry": "再試行", + "bundle_column_error.title": "ネットワークエラー", + "bundle_modal_error.close": "閉じる", + "bundle_modal_error.message": "コンポーネントの読み込み中に問題が発生しました。", + "bundle_modal_error.retry": "再試行", "column.blocks": "ブロックしたユーザー", "column.community": "ローカルタイムライン", "column.favourites": "お気に入り", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "公開TLで表示しない", "privacy.unlisted.short": "未収載", "reply_indicator.cancel": "キャンセル", - "report.heading": "新規通報", "report.placeholder": "コメント", "report.submit": "通報する", "report.target": "問題のユーザー", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json new file mode 100644 index 000000000..5e1aaac85 --- /dev/null +++ b/app/javascript/mastodon/locales/ko.json @@ -0,0 +1,181 @@ +{ + "account.block": "차단", + "account.block_domain": "{domain} 전체를 숨김", + "account.disclaimer": "이 사용자는 다른 인스턴스에 소속되어 있으므로, 수치가 정확하지 않을 수도 있습니다.", + "account.edit_profile": "프로필 편집", + "account.follow": "팔로우", + "account.followers": "팔로워", + "account.follows": "팔로우", + "account.follows_you": "날 팔로우합니다", + "account.media": "미디어", + "account.mention": "답장", + "account.mute": "뮤트", + "account.posts": "포스트", + "account.report": "신고", + "account.requested": "승인 대기 중", + "account.unblock": "차단 해제", + "account.unblock_domain": "{domain} 숨김 해제", + "account.unfollow": "팔로우 해제", + "account.unmute": "뮤트 해제", + "boost_modal.combo": "다음부터 {combo}를 누르면 이 과정을 건너뛸 수 있습니다.", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", + "column.blocks": "차단 중인 사용자", + "column.community": "로컬 타임라인", + "column.favourites": "즐겨찾기", + "column.follow_requests": "팔로우 요청", + "column.home": "홈", + "column.mutes": "뮤트 중인 사용자", + "column.notifications": "알림", + "column.public": "연합 타임라인", + "column_back_button.label": "돌아가기", + "column_header.pin": "고정하기", + "column_header.unpin": "고정 해제", + "column_subheading.navigation": "내비게이션", + "column_subheading.settings": "설정", + "compose_form.lock_disclaimer": "이 계정은 {locked}로 설정 되어 있지 않습니다. 누구나 이 계정을 팔로우 할 수 있으며, 팔로워 공개의 포스팅을 볼 수 있습니다.", + "compose_form.lock_disclaimer.lock": "비공개", + "compose_form.placeholder": "지금 무엇을 하고 있나요?", + "compose_form.privacy_disclaimer": "이 계정의 비공개 포스트는 멘션된 사용자가 소속된 {domains}으로 전송됩니다. {domainsCount, plural, one {이 서버를} other {이 서버들을}} 신뢰할 수 있습니까? 포스팅의 프라이버시 보호는 Mastodon 서버에서만 유효합니다. {domains}가 Mastodon 인스턴스가 아닐 경우, 이 투고가 사적인 것으로 취급되지 않은 채 부스트 되거나 원하지 않는 사용자에게 보여질 가능성이 있습니다.", + "compose_form.publish": "Toot", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive": "이 미디어를 민감한 미디어로 취급", + "compose_form.spoiler": "텍스트 숨기기", + "compose_form.spoiler_placeholder": "경고", + "confirmation_modal.cancel": "취소", + "confirmations.block.confirm": "차단", + "confirmations.block.message": "정말로 {name}를 차단하시겠습니까?", + "confirmations.delete.confirm": "삭제", + "confirmations.delete.message": "정말로 삭제하시겠습니까?", + "confirmations.domain_block.confirm": "도메인 전체를 숨김", + "confirmations.domain_block.message": "정말로 {domain} 전체를 숨기시겠습니까? 대부분의 경우 개별 차단이나 뮤트로 충분합니다.", + "confirmations.mute.confirm": "뮤트", + "confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?", + "emoji_button.activity": "활동", + "emoji_button.flags": "국기", + "emoji_button.food": "음식", + "emoji_button.label": "emoji를 추가", + "emoji_button.nature": "자연", + "emoji_button.objects": "물건", + "emoji_button.people": "사람들", + "emoji_button.search": "검색...", + "emoji_button.symbols": "기호", + "emoji_button.travel": "여행과 장소", + "empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!", + "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.", + "empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.", + "empty_column.home.inactivity": "홈 피드에 아무 것도 없습니다. 한동안 활동하지 않은 경우 곧 원래대로 돌아올 것입니다.", + "empty_column.home.public_timeline": "연합 타임라인", + "empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요!", + "empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 인스턴스 유저를 팔로우 해서 가득 채워보세요!", + "follow_request.authorize": "허가", + "follow_request.reject": "거부", + "getting_started.appsshort": "어플리케이션", + "getting_started.faq": "자주 있는 질문", + "getting_started.heading": "시작", + "getting_started.open_source_notice": "Mastodon은 오픈 소스 소프트웨어입니다. 누구나 GitHub({github})에서 개발에 참여하거나, 문제를 보고할 수 있습니다.", + "getting_started.userguide": "사용자 가이드", + "home.column_settings.advanced": "고급 사용자용", + "home.column_settings.basic": "기본 설정", + "home.column_settings.filter_regex": "정규 표현식으로 필터링", + "home.column_settings.show_reblogs": "부스트 표시", + "home.column_settings.show_replies": "답글 표시", + "home.settings": "컬럼 설정", + "lightbox.close": "닫기", + "loading_indicator.label": "불러오는 중...", + "media_gallery.toggle_visible": "표시 전환", + "missing_indicator.label": "찾을 수 없습니다", + "navigation_bar.blocks": "차단한 사용자", + "navigation_bar.community_timeline": "로컬 타임라인", + "navigation_bar.edit_profile": "프로필 편집", + "navigation_bar.favourites": "즐겨찾기", + "navigation_bar.follow_requests": "팔로우 요청", + "navigation_bar.info": "이 인스턴스에 대해서", + "navigation_bar.logout": "로그아웃", + "navigation_bar.mutes": "뮤트 중인 사용자", + "navigation_bar.preferences": "사용자 설정", + "navigation_bar.public_timeline": "연합 타임라인", + "notification.favourite": "{name}님이 즐겨찾기 했습니다", + "notification.follow": "{name}님이 나를 팔로우 했습니다", + "notification.mention": "{name}님이 답글을 보냈습니다", + "notification.reblog": "{name}님이 부스트 했습니다", + "notifications.clear": "알림 지우기", + "notifications.clear_confirmation": "정말로 알림을 삭제하시겠습니까?", + "notifications.column_settings.alert": "데스크탑 알림", + "notifications.column_settings.favourite": "즐겨찾기", + "notifications.column_settings.follow": "새 팔로워", + "notifications.column_settings.mention": "답글", + "notifications.column_settings.reblog": "부스트", + "notifications.column_settings.show": "컬럼에 표시", + "notifications.column_settings.sound": "효과음 재생", + "onboarding.done": "완료", + "onboarding.next": "다음", + "onboarding.page_five.public_timelines": "연합 타임라인에서는 {domain}의 사람들이 팔로우 중인 Mastodon 전체 인스턴스의 공개 포스트를 표시합니다. 로컬 타임라인에서는 {domain} 만의 공개 포스트를 표시합니다.", + "onboarding.page_four.home": "홈 타임라인에서는 내가 팔로우 중인 사람들의 포스트를 표시합니다.", + "onboarding.page_four.notifications": "알림에서는 다른 사람들과의 연결을 표시합니다.", + "onboarding.page_one.federation": "Mastodon은 누구나 참가할 수 있는 SNS입니다.", + "onboarding.page_one.handle": "여러분은 지금 수많은 Mastodon 인스턴스 중 하나인 {domain}에 있습니다. 당신의 유저 이름은 {handle} 입니다.", + "onboarding.page_one.welcome": "Mastodon에 어서 오세요!", + "onboarding.page_six.admin": "이 인스턴스의 관리자는 {admin}입니다.", + "onboarding.page_six.almost_done": "이상입니다.", + "onboarding.page_six.appetoot": "Bon Appetoot!", + "onboarding.page_six.apps_available": "iOS、Android 또는 다른 플랫폼에서 사용할 수 있는 {apps}이 있습니다.", + "onboarding.page_six.github": "Mastodon는 오픈 소스 소프트웨어입니다. 버그 보고나 기능 추가 요청, 기여는 {github}에서 할 수 있습니다.", + "onboarding.page_six.guidelines": "커뮤니티 가이드라인", + "onboarding.page_six.read_guidelines": "{guidelines}을 확인하는 것을 잊지 마세요.", + "onboarding.page_six.various_app": "다양한 모바일 어플리케이션", + "onboarding.page_three.profile": "[프로필 편집] 에서 자기 소개나 이름을 변경할 수 있습니다. 또한 다른 설정도 변경할 수 있습니다.", + "onboarding.page_three.search": "검색 바에서 {illustration} 나 {introductions} 와 같이 특정 해시태그가 달린 포스트를 보거나, 사용자를 찾을 수 있습니다.", + "onboarding.page_two.compose": "이 폼에서 포스팅 할 수 있습니다. 이미지나 공개 범위 설정, 스포일러 경고 설정은 아래 아이콘으로 설정할 수 있습니다.", + "onboarding.skip": "건너뛰기", + "privacy.change": "포스트의 프라이버시 설정을 변경", + "privacy.direct.long": "멘션한 사용자에게만 공개", + "privacy.direct.short": "다이렉트", + "privacy.private.long": "팔로워에게만 공개", + "privacy.private.short": "비공개", + "privacy.public.long": "공개 타임라인에 표시", + "privacy.public.short": "공개", + "privacy.unlisted.long": "공개 타임라인에 표시하지 않음", + "privacy.unlisted.short": "Unlisted", + "reply_indicator.cancel": "취소", + "report.placeholder": "코멘트", + "report.submit": "신고하기", + "report.target": "문제가 된 사용자", + "search.placeholder": "검색", + "search_results.total": "{count, number}건의 결과", + "status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다", + "status.delete": "삭제", + "status.favourite": "즐겨찾기", + "status.load_more": "더 보기", + "status.media_hidden": "미디어 숨겨짐", + "status.mention": "답장", + "status.mute_conversation": "이 대화를 뮤트", + "status.open": "상세 정보 표시", + "status.reblog": "부스트", + "status.reblogged_by": "{name}님이 부스트 했습니다", + "status.reply": "답장", + "status.replyAll": "전원에게 답장", + "status.report": "신고", + "status.sensitive_toggle": "클릭해서 표시하기", + "status.sensitive_warning": "민감한 미디어", + "status.show_less": "숨기기", + "status.show_more": "더 보기", + "status.unmute_conversation": "이 대화의 뮤트 해제하기", + "tabs_bar.compose": "포스트", + "tabs_bar.federated_timeline": "연합", + "tabs_bar.home": "홈", + "tabs_bar.local_timeline": "로컬", + "tabs_bar.notifications": "알림", + "upload_area.title": "드래그 & 드롭으로 업로드", + "upload_button.label": "미디어 추가", + "upload_form.undo": "재시도", + "upload_progress.label": "업로드 중...", + "video_player.expand": "동영상 자세히 보기", + "video_player.toggle_sound": "소리 토글하기", + "video_player.toggle_visible": "표시 전환", + "video_player.video_error": "동영상 재생에 실패했습니다" +} diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 38ca6518a..479d157f3 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -10,7 +10,7 @@ "account.media": "Media", "account.mention": "Vermeld @{name}", "account.mute": "Negeer @{name}", - "account.posts": "Berichten", + "account.posts": "Toots", "account.report": "Rapporteer @{name}", "account.requested": "Wacht op goedkeuring", "account.unblock": "Deblokkeer @{name}", @@ -18,11 +18,17 @@ "account.unfollow": "Ontvolgen", "account.unmute": "@{name} niet meer negeren", "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Geblokkeerde gebruikers", "column.community": "Lokale tijdlijn", "column.favourites": "Favorieten", "column.follow_requests": "Volgverzoeken", - "column.home": "Jouw tijdlijn", + "column.home": "Start", "column.mutes": "Genegeerde gebruikers", "column.notifications": "Meldingen", "column.public": "Globale tijdlijn", @@ -62,7 +68,7 @@ "empty_column.community": "De lokale tijdlijn is leeg. Toot iets in het openbaar om de bal aan het rollen te krijgen!", "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.", "empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.", - "empty_column.home.inactivity": "Jouw tijdlijn is leeg. Wanneer je een tijdje inactief bent geweest wordt deze snel opnieuw aangemaakt.", + "empty_column.home.inactivity": "Deze tijdlijn is leeg. Wanneer je een tijdje inactief bent geweest wordt deze snel opnieuw aangemaakt.", "empty_column.home.public_timeline": "de globale tijdlijn", "empty_column.notifications": "Je hebt nog geen meldingen. Heb interactie met andere mensen om het gesprek aan te gaan.", "empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere Mastodon-servers om het te vullen.", @@ -109,7 +115,7 @@ "onboarding.done": "Klaar", "onboarding.next": "Volgende", "onboarding.page_five.public_timelines": "De lokale tijdlijn toont openbare toots van iedereen op {domain}. De globale tijdlijn toont openbare toots van iedereen die door gebruikers van {domain} worden gevolgd, dus ook mensen van andere Mastodon-servers. Dit zijn de openbare tijdlijnen en vormen een uitstekende manier om nieuwe mensen te ontdekken.", - "onboarding.page_four.home": "Jouw tijdlijn laat toots zien van mensen die jij volgt.", + "onboarding.page_four.home": "Deze tijdlijn laat toots zien van mensen die jij volgt.", "onboarding.page_four.notifications": "De kolom met meldingen toont alle interacties die je met andere Mastodon-gebruikers hebt.", "onboarding.page_one.federation": "Mastodon is een netwerk van onafhankelijke servers die samen een groot sociaal netwerk vormen.", "onboarding.page_one.handle": "Je bevindt je nu op {domain}, dus is jouw volledige Mastodon-adres {handle}", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Niet op openbare tijdlijnen tonen", "privacy.unlisted.short": "Minder openbaar", "reply_indicator.cancel": "Annuleren", - "report.heading": "Rapporteren", "report.placeholder": "Extra opmerkingen", "report.submit": "Verzenden", "report.target": "Rapporteren van", @@ -162,7 +167,7 @@ "status.unmute_conversation": "Conversatie niet meer negeren", "tabs_bar.compose": "Schrijven", "tabs_bar.federated_timeline": "Globaal", - "tabs_bar.home": "Jouw tijdlijn", + "tabs_bar.home": "Start", "tabs_bar.local_timeline": "Lokaal", "tabs_bar.notifications": "Meldingen", "upload_area.title": "Hierin slepen om te uploaden", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index a3c956279..4bbf14938 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -18,6 +18,12 @@ "account.unfollow": "Avfølg", "account.unmute": "Avdemp @{name}", "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blokkerte brukere", "column.community": "Lokal tidslinje", "column.favourites": "Likt", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer", "privacy.unlisted.short": "Uoppført", "reply_indicator.cancel": "Avbryt", - "report.heading": "Ny rapport", "report.placeholder": "Tilleggskommentarer", "report.submit": "Send inn", "report.target": "Rapporterer", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index a2a82ae9f..2c119ef41 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -18,6 +18,12 @@ "account.unfollow": "Quitar de sègre", "account.unmute": "Quitar de rescondre @{name}", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Personas blocadas", "column.community": "Flux d’actualitat public local", "column.favourites": "Favorits", @@ -34,7 +40,7 @@ "compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.", "compose_form.lock_disclaimer.lock": "clavat", "compose_form.placeholder": "A de qué pensatz ?", - "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste{domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias a Mastodons. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists", + "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste {domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias de Mastodon. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists", "compose_form.publish": "Tut", "compose_form.publish_loud": "{publish} !", "compose_form.sensitive": "Marcar lo mèdia coma sensible", @@ -51,7 +57,7 @@ "confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?", "emoji_button.activity": "Activitat", "emoji_button.flags": "Drapèus", - "emoji_button.food": "Manjar e beure", + "emoji_button.food": "Beure e manjar", "emoji_button.label": "Inserir un emoji", "emoji_button.nature": "Natura", "emoji_button.objects": "Objèctes", @@ -136,10 +142,9 @@ "privacy.unlisted.long": "Mostrar pas dins los fluxes publics", "privacy.unlisted.short": "Pas-listat", "reply_indicator.cancel": "Anullar", - "report.heading": "Nòu senhalament", "report.placeholder": "Comentaris addicionals", - "report.submit": "Mandat", - "report.target": "Senhalament", + "report.submit": "Mandar", + "report.target": "Senhalar {target}", "search.placeholder": "Recercar", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index bf425501f..ac63ec40f 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -6,7 +6,7 @@ "account.follow": "Obserwuj", "account.followers": "Obserwujący", "account.follows": "Obserwacje", - "account.follows_you": "Obserwują cię", + "account.follows_you": "Obserwuje cię", "account.media": "Media", "account.mention": "Wspomnij o @{name}", "account.mute": "Wycisz @{name}", @@ -18,6 +18,12 @@ "account.unfollow": "Przestań obserwować", "account.unmute": "Cofnij wyciszenie @{name}", "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem", + "bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.", + "bundle_column_error.retry": "Spróbuj ponownie", + "bundle_column_error.title": "Błąd sieci", + "bundle_modal_error.close": "Zamknij", + "bundle_modal_error.message": "Coś poszło nie tak podczas ładowania tego składnika.", + "bundle_modal_error.retry": "Spróbuj ponownie", "column.blocks": "Zablokowani użytkownicy", "column.community": "Lokalna oś czasu", "column.favourites": "Ulubione", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Niewidoczne na publicznych osiach czasu", "privacy.unlisted.short": "Niewidoczne", "reply_indicator.cancel": "Anuluj", - "report.heading": "Zgłoś {target}", "report.placeholder": "Dodatkowe komentarze", "report.submit": "Wyślij", "report.target": "Zgłaszanie {target}", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 5e5834a0e..b199a39ce 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -18,6 +18,12 @@ "account.unfollow": "Deixar de seguir", "account.unmute": "Não silenciar @{name}", "boost_modal.combo": "Pode clicar {combo} para não voltar a ver", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Utilizadores Bloqueados", "column.community": "Local", "column.favourites": "Favoritos", @@ -72,7 +78,6 @@ "getting_started.faq": "FAQ", "getting_started.heading": "Primeiros passos", "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}.", - "getting_started.support": "{faq} • {userguide} • {apps}", "getting_started.userguide": "User Guide", "home.column_settings.advanced": "Avançado", "home.column_settings.basic": "Básico", @@ -107,7 +112,6 @@ "notifications.column_settings.reblog": "Partilhas:", "notifications.column_settings.show": "Mostrar nas colunas", "notifications.column_settings.sound": "Reproduzir som", - "notifications.settings": "Parâmetros da listagem de Notificações", "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.", @@ -138,7 +142,6 @@ "privacy.unlisted.long": "Não publicar nos feeds públicos", "privacy.unlisted.short": "Não listar", "reply_indicator.cancel": "Cancelar", - "report.heading": "Nova denúncia", "report.placeholder": "Comentários adicionais", "report.submit": "Enviar", "report.target": "Denunciar", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 4ebfe0c60..b199a39ce 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -18,6 +18,12 @@ "account.unfollow": "Deixar de seguir", "account.unmute": "Não silenciar @{name}", "boost_modal.combo": "Pode clicar {combo} para não voltar a ver", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Utilizadores Bloqueados", "column.community": "Local", "column.favourites": "Favoritos", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Não publicar nos feeds públicos", "privacy.unlisted.short": "Não listar", "reply_indicator.cancel": "Cancelar", - "report.heading": "Nova denúncia", "report.placeholder": "Comentários adicionais", "report.submit": "Enviar", "report.target": "Denunciar", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index f561f0151..f9f48a48d 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -18,6 +18,12 @@ "account.unfollow": "Отписаться", "account.unmute": "Снять глушение", "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Список блокировки", "column.community": "Локальная лента", "column.favourites": "Понравившееся", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Не показывать в лентах", "privacy.unlisted.short": "Скрытый", "reply_indicator.cancel": "Отмена", - "report.heading": "Новая жалоба", "report.placeholder": "Комментарий", "report.submit": "Отправить", "report.target": "Жалуемся на", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 608d911e9..8a39beacb 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -18,6 +18,12 @@ "account.unfollow": "Unfollow", "account.unmute": "Unmute @{name}", "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Blocked users", "column.community": "Local timeline", "column.favourites": "Favourites", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Do not post to public timelines", "privacy.unlisted.short": "Unlisted", "reply_indicator.cancel": "Cancel", - "report.heading": "New report", "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 7512971f5..203e4a09e 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -18,6 +18,12 @@ "account.unfollow": "Takipten vazgeç", "account.unmute": "Sesi aç @{name}", "boost_modal.combo": "Bir dahaki sefere {combo} tuşuna basabilirsiniz", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Engellenen kullanıcılar", "column.community": "Yerel zaman tüneli", "column.favourites": "Favoriler", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Herkese açık zaman tüneline gönderme", "privacy.unlisted.short": "Listelenmemiş", "reply_indicator.cancel": "İptal", - "report.heading": "Yeni rapor", "report.placeholder": "Ek yorumlar", "report.submit": "Gönder", "report.target": "Raporlama", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index a117c854b..c0f4a8dbb 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -18,6 +18,12 @@ "account.unfollow": "Відписатися", "account.unmute": "Зняти глушення", "boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "Заблоковані користувачі", "column.community": "Локальна стрічка", "column.favourites": "Вподобане", @@ -136,7 +142,6 @@ "privacy.unlisted.long": "Не показувати у публічних стрічках", "privacy.unlisted.short": "Прихований", "reply_indicator.cancel": "Відмінити", - "report.heading": "Нова скарга", "report.placeholder": "Додаткові коментарі", "report.submit": "Відправити", "report.target": "Скаржимося на", diff --git a/app/javascript/mastodon/locales/whitelist_ko.json b/app/javascript/mastodon/locales/whitelist_ko.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/app/javascript/mastodon/locales/whitelist_ko.json @@ -0,0 +1,2 @@ +[ +] diff --git a/app/javascript/mastodon/locales/whitelist_zh-HK.json b/app/javascript/mastodon/locales/whitelist_zh-HK.json index d5fddc3bc..0d4f101c7 100644 --- a/app/javascript/mastodon/locales/whitelist_zh-HK.json +++ b/app/javascript/mastodon/locales/whitelist_zh-HK.json @@ -1,3 +1,2 @@ [ - "getting_started.support" ] diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 68648f2dd..998e1c8da 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -18,6 +18,12 @@ "account.unfollow": "取消关注", "account.unmute": "取消 @{name} 的静音", "boost_modal.combo": "如你想在下次路过时显示,请按{combo},", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "屏蔽用户", "column.community": "本站时间轴", "column.favourites": "赞过的嘟文", @@ -72,7 +78,6 @@ "getting_started.faq": "FAQ", "getting_started.heading": "开始使用", "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。", - "getting_started.support": "{faq} • {userguide} • {apps}", "getting_started.userguide": "User Guide", "home.column_settings.advanced": "高端", "home.column_settings.basic": "基本", @@ -107,7 +112,6 @@ "notifications.column_settings.reblog": "你的嘟文被转嘟:", "notifications.column_settings.show": "在通知栏显示", "notifications.column_settings.sound": "播放音效", - "notifications.settings": "字段设置", "onboarding.done": "出发!", "onboarding.next": "下一步", "onboarding.page_five.public_timelines": "本站时间轴显示来自 {domain} 的所有人的公共嘟文。 跨站公共时间轴显示 {domain} 上的各位关注的来自所有Mastodon服务器实例上的人发表的公共嘟文。这些就是寻人好去处的公共时间轴啦。", @@ -138,7 +142,6 @@ "privacy.unlisted.long": "公开,但不在公共时间轴显示", "privacy.unlisted.short": "公开", "reply_indicator.cancel": "取消", - "report.heading": "举报", "report.placeholder": "额外消息", "report.submit": "提交", "report.target": "Reporting", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index a9844a625..1079d5429 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -18,6 +18,12 @@ "account.unfollow": "取消關注", "account.unmute": "取消 @{name} 的靜音", "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "封鎖用戶", "column.community": "本站時間軸", "column.favourites": "喜歡的文章", @@ -72,7 +78,6 @@ "getting_started.faq": "常見問題", "getting_started.heading": "開始使用", "getting_started.open_source_notice": "Mastodon(萬象)是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。", - "getting_started.support": "{faq} • {userguide} • {apps}", "getting_started.userguide": "使用指南", "home.column_settings.advanced": "進階", "home.column_settings.basic": "基本", @@ -107,7 +112,6 @@ "notifications.column_settings.reblog": "轉推你的文章:", "notifications.column_settings.show": "在通知欄顯示", "notifications.column_settings.sound": "播放音效", - "notifications.settings": "欄位設定", "onboarding.done": "開始使用", "onboarding.next": "繼續", "onboarding.page_five.public_timelines": "「本站時間軸」顯示在 {domain} 各用戶的公開文章。「跨站時間軸」顯示在 {domain} 各人關注的所有用戶(包括其他服務站)的公開文章。這些都是「公共時間軸」,是認識新朋友的好地方。", @@ -138,7 +142,6 @@ "privacy.unlisted.long": "公開,但不在公共時間軸顯示", "privacy.unlisted.short": "公開", "reply_indicator.cancel": "取消", - "report.heading": "舉報", "report.placeholder": "額外訊息", "report.submit": "提交", "report.target": "舉報", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 5497becf0..6240b8879 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -18,6 +18,12 @@ "account.unfollow": "取消關注", "account.unmute": "不再消音 @{name}", "boost_modal.combo": "下次你可以按 {combo} 來跳過", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", "column.blocks": "封鎖的使用者", "column.community": "本地時間軸", "column.favourites": "最愛", @@ -72,7 +78,6 @@ "getting_started.faq": "FAQ", "getting_started.heading": "馬上開始", "getting_started.open_source_notice": "Mastodon 是開源軟體。你可以在 GitHub {github} 上做出貢獻或是回報問題。", - "getting_started.support": "{faq} • {userguide} • {apps}", "getting_started.userguide": "使用者指南", "home.column_settings.advanced": "進階", "home.column_settings.basic": "基本", @@ -107,7 +112,6 @@ "notifications.column_settings.reblog": "轉推:", "notifications.column_settings.show": "顯示在欄位中", "notifications.column_settings.sound": "播放音效", - "notifications.settings": "欄位設定", "onboarding.done": "完成", "onboarding.next": "下一步", "onboarding.page_five.public_timelines": "本地時間軸顯示 {domain} 上所有人的公開貼文。聯盟時間軸顯示 {domain} 上所有人關注的公開貼文。這就是公開時間軸,發現新朋友的好地方。", @@ -138,7 +142,6 @@ "privacy.unlisted.long": "不要貼到公開時間軸", "privacy.unlisted.short": "不列出來", "reply_indicator.cancel": "取消", - "report.heading": "新的通報", "report.placeholder": "更多訊息", "report.submit": "送出", "report.target": "通報中", diff --git a/app/javascript/mastodon/main.js b/app/javascript/mastodon/main.js index f14b6a825..90c2c5da2 100644 --- a/app/javascript/mastodon/main.js +++ b/app/javascript/mastodon/main.js @@ -1,9 +1,5 @@ const perf = require('./performance'); -// import default stylesheet with variables -require('font-awesome/css/font-awesome.css'); -require('mastodon-application-style'); - function onDomContentLoaded(callback) { if (document.readyState !== 'loading') { callback(); @@ -20,6 +16,14 @@ function main() { require.context('../images/', true); + if (window.history && history.replaceState) { + const { pathname, search, hash } = window.location; + const path = pathname + search + hash; + if (!(/^\/web[$/]/).test(path)) { + history.replaceState(null, document.title, `/web${path}`); + } + } + onDomContentLoaded(() => { const mountNode = document.getElementById('mastodon'); const props = JSON.parse(mountNode.getAttribute('data-props')); diff --git a/app/javascript/mastodon/reducers/accounts.js b/app/javascript/mastodon/reducers/accounts.js index 7b7074317..4d7c3adc9 100644 --- a/app/javascript/mastodon/reducers/accounts.js +++ b/app/javascript/mastodon/reducers/accounts.js @@ -44,7 +44,7 @@ import { FAVOURITED_STATUSES_EXPAND_SUCCESS, } from '../actions/favourites'; import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, fromJS } from 'immutable'; const normalizeAccount = (state, account) => { account = { ...account }; @@ -53,7 +53,7 @@ const normalizeAccount = (state, account) => { delete account.following_count; delete account.statuses_count; - return state.set(account.id, Immutable.fromJS(account)); + return state.set(account.id, fromJS(account)); }; const normalizeAccounts = (state, accounts) => { @@ -82,7 +82,7 @@ const normalizeAccountsFromStatuses = (state, statuses) => { return state; }; -const initialState = Immutable.Map(); +const initialState = ImmutableMap(); export default function accounts(state = initialState, action) { switch(action.type) { diff --git a/app/javascript/mastodon/reducers/accounts_counters.js b/app/javascript/mastodon/reducers/accounts_counters.js index eb8a4f83d..4423e1b50 100644 --- a/app/javascript/mastodon/reducers/accounts_counters.js +++ b/app/javascript/mastodon/reducers/accounts_counters.js @@ -46,9 +46,9 @@ import { FAVOURITED_STATUSES_EXPAND_SUCCESS, } from '../actions/favourites'; import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, fromJS } from 'immutable'; -const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS({ +const normalizeAccount = (state, account) => state.set(account.id, fromJS({ followers_count: account.followers_count, following_count: account.following_count, statuses_count: account.statuses_count, @@ -80,12 +80,12 @@ const normalizeAccountsFromStatuses = (state, statuses) => { return state; }; -const initialState = Immutable.Map(); +const initialState = ImmutableMap(); export default function accountsCounters(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: - return state.merge(action.state.get('accounts').map(item => Immutable.fromJS({ + return state.merge(action.state.get('accounts').map(item => fromJS({ followers_count: item.get('followers_count'), following_count: item.get('following_count'), statuses_count: item.get('statuses_count'), diff --git a/app/javascript/mastodon/reducers/alerts.js b/app/javascript/mastodon/reducers/alerts.js index aaea9775f..089d920c3 100644 --- a/app/javascript/mastodon/reducers/alerts.js +++ b/app/javascript/mastodon/reducers/alerts.js @@ -3,14 +3,14 @@ import { ALERT_DISMISS, ALERT_CLEAR, } from '../actions/alerts'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -const initialState = Immutable.List([]); +const initialState = ImmutableList([]); export default function alerts(state = initialState, action) { switch(action.type) { case ALERT_SHOW: - return state.push(Immutable.Map({ + return state.push(ImmutableMap({ key: state.size > 0 ? state.last().get('key') + 1 : 0, title: action.title, message: action.message, diff --git a/app/javascript/mastodon/reducers/cards.js b/app/javascript/mastodon/reducers/cards.js index 3c9395011..4d86b0d7e 100644 --- a/app/javascript/mastodon/reducers/cards.js +++ b/app/javascript/mastodon/reducers/cards.js @@ -1,13 +1,13 @@ import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, fromJS } from 'immutable'; -const initialState = Immutable.Map(); +const initialState = ImmutableMap(); export default function cards(state = initialState, action) { switch(action.type) { case STATUS_CARD_FETCH_SUCCESS: - return state.set(action.id, Immutable.fromJS(action.card)); + return state.set(action.id, fromJS(action.card)); default: return state; } diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 21d801f2a..6ac7b4b4a 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -25,12 +25,12 @@ import { } from '../actions/compose'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; import uuid from '../uuid'; -const initialState = Immutable.Map({ +const initialState = ImmutableMap({ mounted: false, - advanced_options: Immutable.Map({ + advanced_options: ImmutableMap({ do_not_federate: false, }), sensitive: false, @@ -44,20 +44,21 @@ const initialState = Immutable.Map({ is_submitting: false, is_uploading: false, progress: 0, - media_attachments: Immutable.List(), + media_attachments: ImmutableList(), suggestion_token: null, - suggestions: Immutable.List(), + suggestions: ImmutableList(), me: null, - default_advanced_options: Immutable.Map({ + default_advanced_options: ImmutableMap({ do_not_federate: false, }), default_privacy: 'public', + default_sensitive: false, resetFileKey: Math.floor((Math.random() * 0x10000)), idempotencyKey: null, }); function statusToTextMentions(state, status) { - let set = Immutable.OrderedSet([]); + let set = ImmutableOrderedSet([]); let me = state.get('me'); if (status.getIn(['account', 'id']) !== me) { @@ -83,6 +84,8 @@ function clearAll(state) { }; function appendMedia(state, media) { + const prevSize = state.get('media_attachments').size; + return state.withMutations(map => { map.update('media_attachments', list => list.push(media)); map.set('is_uploading', false); @@ -90,6 +93,10 @@ function appendMedia(state, media) { map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`); map.set('focusDate', new Date()); map.set('idempotencyKey', uuid()); + + if (prevSize === 0 && state.get('default_sensitive')) { + map.set('sensitive', true); + } }); }; @@ -112,7 +119,7 @@ const insertSuggestion = (state, position, token, completion) => { return state.withMutations(map => { map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`); map.set('suggestion_token', null); - map.update('suggestions', Immutable.List(), list => list.clear()); + map.update('suggestions', ImmutableList(), list => list.clear()); map.set('focusDate', new Date()); map.set('idempotencyKey', uuid()); }); @@ -180,7 +187,7 @@ export default function compose(state = initialState, action) { map.set('in_reply_to', action.status.get('id')); map.set('text', statusToTextMentions(state, action.status)); map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); - map.set('advanced_options', new Immutable.Map({ + map.set('advanced_options', new ImmutableMap({ do_not_federate: /👁\ufe0f?<\/p>$/.test(action.status.get('content')), })); map.set('focusDate', new Date()); @@ -216,7 +223,7 @@ export default function compose(state = initialState, action) { map.set('is_uploading', true); }); case COMPOSE_UPLOAD_SUCCESS: - return appendMedia(state, Immutable.fromJS(action.media)); + return appendMedia(state, fromJS(action.media)); case COMPOSE_UPLOAD_FAIL: return state.set('is_uploading', false); case COMPOSE_UPLOAD_UNDO: @@ -229,9 +236,9 @@ export default function compose(state = initialState, action) { .set('focusDate', new Date()) .set('idempotencyKey', uuid()); case COMPOSE_SUGGESTIONS_CLEAR: - return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null); + return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null); case COMPOSE_SUGGESTIONS_READY: - return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id))).set('suggestion_token', action.token); + return state.set('suggestions', ImmutableList(action.accounts.map(item => item.id))).set('suggestion_token', action.token); case COMPOSE_SUGGESTION_SELECT: return insertSuggestion(state, action.position, action.token, action.completion); case TIMELINE_DELETE: diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index 8a24f5f7a..9bfc09aa7 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -1,10 +1,10 @@ import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; import { TIMELINE_DELETE } from '../actions/timelines'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; -const initialState = Immutable.Map({ - ancestors: Immutable.Map(), - descendants: Immutable.Map(), +const initialState = ImmutableMap({ + ancestors: ImmutableMap(), + descendants: ImmutableMap(), }); const normalizeContext = (state, id, ancestors, descendants) => { @@ -18,12 +18,12 @@ const normalizeContext = (state, id, ancestors, descendants) => { }; const deleteFromContexts = (state, id) => { - state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => { - state = state.updateIn(['ancestors', descendantId], Immutable.List(), list => list.filterNot(itemId => itemId === id)); + state.getIn(['descendants', id], ImmutableList()).forEach(descendantId => { + state = state.updateIn(['ancestors', descendantId], ImmutableList(), list => list.filterNot(itemId => itemId === id)); }); - state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => { - state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id)); + state.getIn(['ancestors', id], ImmutableList()).forEach(ancestorId => { + state = state.updateIn(['descendants', ancestorId], ImmutableList(), list => list.filterNot(itemId => itemId === id)); }); state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]); @@ -34,7 +34,7 @@ const deleteFromContexts = (state, id) => { export default function contexts(state = initialState, action) { switch(action.type) { case CONTEXT_FETCH_SUCCESS: - return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants)); + return normalizeContext(state, action.id, fromJS(action.ancestors), fromJS(action.descendants)); case TIMELINE_DELETE: return deleteFromContexts(state, action.id); default: diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index a3f5cb58e..35f30f601 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -1,7 +1,6 @@ import { combineReducers } from 'redux-immutable'; import timelines from './timelines'; import meta from './meta'; -import compose from './compose'; import alerts from './alerts'; import { loadingBarReducer } from 'react-redux-loading-bar'; import modal from './modal'; @@ -9,21 +8,21 @@ import user_lists from './user_lists'; import accounts from './accounts'; import accounts_counters from './accounts_counters'; import statuses from './statuses'; -import media_attachments from './media_attachments'; import relationships from './relationships'; -import search from './search'; -import notifications from './notifications'; import settings from './settings'; import local_settings from '../../glitch/reducers/local_settings'; import status_lists from './status_lists'; import cards from './cards'; import reports from './reports'; import contexts from './contexts'; +import compose from './compose'; +import search from './search'; +import media_attachments from './media_attachments'; +import notifications from './notifications'; -export default combineReducers({ +const reducers = { timelines, meta, - compose, alerts, loadingBar: loadingBarReducer, modal, @@ -31,14 +30,17 @@ export default combineReducers({ status_lists, accounts, accounts_counters, - media_attachments, statuses, relationships, - search, - notifications, settings, local_settings, cards, reports, contexts, -}); + compose, + search, + media_attachments, + notifications, +}; + +export default combineReducers(reducers); diff --git a/app/javascript/mastodon/reducers/media_attachments.js b/app/javascript/mastodon/reducers/media_attachments.js index 85bea4f0b..24119f628 100644 --- a/app/javascript/mastodon/reducers/media_attachments.js +++ b/app/javascript/mastodon/reducers/media_attachments.js @@ -1,7 +1,7 @@ import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; -const initialState = Immutable.Map({ +const initialState = ImmutableMap({ accept_content_types: [], }); diff --git a/app/javascript/mastodon/reducers/meta.js b/app/javascript/mastodon/reducers/meta.js index 1551228ec..119ef9d8f 100644 --- a/app/javascript/mastodon/reducers/meta.js +++ b/app/javascript/mastodon/reducers/meta.js @@ -1,7 +1,7 @@ import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; -const initialState = Immutable.Map({ +const initialState = ImmutableMap({ streaming_api_base_url: null, access_token: null, me: null, diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 0c1cf5b0f..0063d24e4 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -11,10 +11,10 @@ import { } from '../actions/notifications'; import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts'; import { TIMELINE_DELETE } from '../actions/timelines'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -const initialState = Immutable.Map({ - items: Immutable.List(), +const initialState = ImmutableMap({ + items: ImmutableList(), next: null, top: true, unread: 0, @@ -22,7 +22,7 @@ const initialState = Immutable.Map({ isLoading: true, }); -const notificationToMap = notification => Immutable.Map({ +const notificationToMap = notification => ImmutableMap({ id: notification.id, type: notification.type, account: notification.account.id, @@ -46,7 +46,7 @@ const normalizeNotification = (state, notification) => { }; const normalizeNotifications = (state, notifications, next) => { - let items = Immutable.List(); + let items = ImmutableList(); const loaded = state.get('loaded'); notifications.forEach((n, i) => { @@ -64,7 +64,7 @@ const normalizeNotifications = (state, notifications, next) => { }; const appendNormalizedNotifications = (state, notifications, next) => { - let items = Immutable.List(); + let items = ImmutableList(); notifications.forEach((n, i) => { items = items.set(i, notificationToMap(n)); @@ -110,7 +110,7 @@ export default function notifications(state = initialState, action) { case ACCOUNT_BLOCK_SUCCESS: return filterNotifications(state, action.relationship); case NOTIFICATIONS_CLEAR: - return state.set('items', Immutable.List()).set('next', null); + return state.set('items', ImmutableList()).set('next', null); case TIMELINE_DELETE: return deleteByStatus(state, action.id); default: diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js index b6607860c..c7b04a668 100644 --- a/app/javascript/mastodon/reducers/relationships.js +++ b/app/javascript/mastodon/reducers/relationships.js @@ -11,9 +11,9 @@ import { DOMAIN_BLOCK_SUCCESS, DOMAIN_UNBLOCK_SUCCESS, } from '../actions/domain_blocks'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, fromJS } from 'immutable'; -const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(relationship)); +const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship)); const normalizeRelationships = (state, relationships) => { relationships.forEach(relationship => { @@ -23,7 +23,7 @@ const normalizeRelationships = (state, relationships) => { return state; }; -const initialState = Immutable.Map(); +const initialState = ImmutableMap(); export default function relationships(state = initialState, action) { switch(action.type) { diff --git a/app/javascript/mastodon/reducers/reports.js b/app/javascript/mastodon/reducers/reports.js index ad35eaa05..283c5b6f5 100644 --- a/app/javascript/mastodon/reducers/reports.js +++ b/app/javascript/mastodon/reducers/reports.js @@ -7,13 +7,13 @@ import { REPORT_STATUS_TOGGLE, REPORT_COMMENT_CHANGE, } from '../actions/reports'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; -const initialState = Immutable.Map({ - new: Immutable.Map({ +const initialState = ImmutableMap({ + new: ImmutableMap({ isSubmitting: false, account_id: null, - status_ids: Immutable.Set(), + status_ids: ImmutableSet(), comment: '', }), }); @@ -26,14 +26,14 @@ export default function reports(state = initialState, action) { map.setIn(['new', 'account_id'], action.account.get('id')); if (state.getIn(['new', 'account_id']) !== action.account.get('id')) { - map.setIn(['new', 'status_ids'], action.status ? Immutable.Set([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : Immutable.Set()); + map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet()); map.setIn(['new', 'comment'], ''); } else { - map.updateIn(['new', 'status_ids'], Immutable.Set(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id')))); + map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id')))); } }); case REPORT_STATUS_TOGGLE: - return state.updateIn(['new', 'status_ids'], Immutable.Set(), set => { + return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => { if (action.checked) { return set.add(action.statusId); } @@ -50,7 +50,7 @@ export default function reports(state = initialState, action) { case REPORT_SUBMIT_SUCCESS: return state.withMutations(map => { map.setIn(['new', 'account_id'], null); - map.setIn(['new', 'status_ids'], Immutable.Set()); + map.setIn(['new', 'status_ids'], ImmutableSet()); map.setIn(['new', 'comment'], ''); map.setIn(['new', 'isSubmitting'], false); }); diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index 0a3adac05..08d90e4e8 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -5,13 +5,13 @@ import { SEARCH_SHOW, } from '../actions/search'; import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -const initialState = Immutable.Map({ +const initialState = ImmutableMap({ value: '', submitted: false, hidden: false, - results: Immutable.Map(), + results: ImmutableMap(), }); export default function search(state = initialState, action) { @@ -21,7 +21,7 @@ export default function search(state = initialState, action) { case SEARCH_CLEAR: return state.withMutations(map => { map.set('value', ''); - map.set('results', Immutable.Map()); + map.set('results', ImmutableMap()); map.set('submitted', false); map.set('hidden', false); }); @@ -31,10 +31,10 @@ export default function search(state = initialState, action) { case COMPOSE_MENTION: return state.set('hidden', true); case SEARCH_FETCH_SUCCESS: - return state.set('results', Immutable.Map({ - accounts: Immutable.List(action.results.accounts.map(item => item.id)), - statuses: Immutable.List(action.results.statuses.map(item => item.id)), - hashtags: Immutable.List(action.results.hashtags), + return state.set('results', ImmutableMap({ + accounts: ImmutableList(action.results.accounts.map(item => item.id)), + statuses: ImmutableList(action.results.statuses.map(item => item.id)), + hashtags: ImmutableList(action.results.hashtags), })).set('submitted', true); default: return state; diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 9a15a1fe3..1bdee7356 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -1,40 +1,40 @@ import { SETTING_CHANGE } from '../actions/settings'; import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from '../actions/columns'; import { STORE_HYDRATE } from '../actions/store'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, fromJS } from 'immutable'; import uuid from '../uuid'; -const initialState = Immutable.Map({ +const initialState = ImmutableMap({ onboarded: false, layout: 'auto', - home: Immutable.Map({ - shows: Immutable.Map({ + home: ImmutableMap({ + shows: ImmutableMap({ reblog: true, reply: true, }), - regex: Immutable.Map({ + regex: ImmutableMap({ body: '', }), }), - notifications: Immutable.Map({ - alerts: Immutable.Map({ + notifications: ImmutableMap({ + alerts: ImmutableMap({ follow: true, favourite: true, reblog: true, mention: true, }), - shows: Immutable.Map({ + shows: ImmutableMap({ follow: true, favourite: true, reblog: true, mention: true, }), - sounds: Immutable.Map({ + sounds: ImmutableMap({ follow: true, favourite: true, reblog: true, @@ -42,20 +42,20 @@ const initialState = Immutable.Map({ }), }), - community: Immutable.Map({ - regex: Immutable.Map({ + community: ImmutableMap({ + regex: ImmutableMap({ body: '', }), }), - public: Immutable.Map({ - regex: Immutable.Map({ + public: ImmutableMap({ + regex: ImmutableMap({ body: '', }), }), }); -const defaultColumns = Immutable.fromJS([ +const defaultColumns = fromJS([ { id: 'COMPOSE', uuid: uuid(), params: {} }, { id: 'HOME', uuid: uuid(), params: {} }, { id: 'NOTIFICATIONS', uuid: uuid(), params: {} }, @@ -83,7 +83,7 @@ export default function settings(state = initialState, action) { case SETTING_CHANGE: return state.setIn(action.key, action.value); case COLUMN_ADD: - return state.update('columns', list => list.push(Immutable.fromJS({ id: action.id, uuid: uuid(), params: action.params }))); + return state.update('columns', list => list.push(fromJS({ id: action.id, uuid: uuid(), params: action.params }))); case COLUMN_REMOVE: return state.update('columns', list => list.filterNot(item => item.get('uuid') === action.uuid)); case COLUMN_MOVE: diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js index 7d00f6d30..bbc973302 100644 --- a/app/javascript/mastodon/reducers/status_lists.js +++ b/app/javascript/mastodon/reducers/status_lists.js @@ -2,13 +2,13 @@ import { FAVOURITED_STATUSES_FETCH_SUCCESS, FAVOURITED_STATUSES_EXPAND_SUCCESS, } from '../actions/favourites'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -const initialState = Immutable.Map({ - favourites: Immutable.Map({ +const initialState = ImmutableMap({ + favourites: ImmutableMap({ next: null, loaded: false, - items: Immutable.List(), + items: ImmutableList(), }), }); @@ -16,7 +16,7 @@ const normalizeList = (state, listType, statuses, next) => { return state.update(listType, listMap => listMap.withMutations(map => { map.set('next', next); map.set('loaded', true); - map.set('items', Immutable.List(statuses.map(item => item.id))); + map.set('items', ImmutableList(statuses.map(item => item.id))); })); }; diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 691135ff7..b1b1d0988 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -33,7 +33,7 @@ import { FAVOURITED_STATUSES_EXPAND_SUCCESS, } from '../actions/favourites'; import { SEARCH_FETCH_SUCCESS } from '../actions/search'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, fromJS } from 'immutable'; const normalizeStatus = (state, status) => { if (!status) { @@ -51,7 +51,7 @@ const normalizeStatus = (state, status) => { const searchContent = [status.spoiler_text, status.content].join(' ').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n'); normalStatus.search_index = new DOMParser().parseFromString(searchContent, 'text/html').documentElement.textContent; - return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus))); + return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus))); }; const normalizeStatuses = (state, statuses) => { @@ -82,7 +82,7 @@ const filterStatuses = (state, relationship) => { return state; }; -const initialState = Immutable.Map(); +const initialState = ImmutableMap(); export default function statuses(state = initialState, action) { switch(action.type) { diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 1b738a16a..065e89f96 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -15,25 +15,25 @@ import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS, } from '../actions/accounts'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; -const initialState = Immutable.Map(); +const initialState = ImmutableMap(); -const initialTimeline = Immutable.Map({ +const initialTimeline = ImmutableMap({ unread: 0, online: false, top: true, loaded: false, isLoading: false, next: false, - items: Immutable.List(), + items: ImmutableList(), }); const normalizeTimeline = (state, timeline, statuses, next) => { - const ids = Immutable.List(statuses.map(status => status.get('id'))); + const ids = ImmutableList(statuses.map(status => status.get('id'))); const wasLoaded = state.getIn([timeline, 'loaded']); const hadNext = state.getIn([timeline, 'next']); - const oldIds = state.getIn([timeline, 'items'], Immutable.List()); + const oldIds = state.getIn([timeline, 'items'], ImmutableList()); return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { mMap.set('loaded', true); @@ -44,8 +44,8 @@ const normalizeTimeline = (state, timeline, statuses, next) => { }; const appendNormalizedTimeline = (state, timeline, statuses, next) => { - const ids = Immutable.List(statuses.map(status => status.get('id'))); - const oldIds = state.getIn([timeline, 'items'], Immutable.List()); + const ids = ImmutableList(statuses.map(status => status.get('id'))); + const oldIds = state.getIn([timeline, 'items'], ImmutableList()); return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { mMap.set('isLoading', false); @@ -56,7 +56,7 @@ const appendNormalizedTimeline = (state, timeline, statuses, next) => { const updateTimeline = (state, timeline, status, references) => { const top = state.getIn([timeline, 'top']); - const ids = state.getIn([timeline, 'items'], Immutable.List()); + const ids = state.getIn([timeline, 'items'], ImmutableList()); const includesId = ids.includes(status.get('id')); const unread = state.getIn([timeline, 'unread'], 0); @@ -124,11 +124,11 @@ export default function timelines(state = initialState, action) { case TIMELINE_EXPAND_FAIL: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false)); case TIMELINE_REFRESH_SUCCESS: - return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next); + return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next); case TIMELINE_EXPAND_SUCCESS: - return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next); + return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next); case TIMELINE_UPDATE: - return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references); + return updateTimeline(state, action.timeline, fromJS(action.status), action.references); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); case ACCOUNT_BLOCK_SUCCESS: diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index 83bf1be1b..8db18c5dc 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -20,22 +20,22 @@ import { MUTES_FETCH_SUCCESS, MUTES_EXPAND_SUCCESS, } from '../actions/mutes'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -const initialState = Immutable.Map({ - followers: Immutable.Map(), - following: Immutable.Map(), - reblogged_by: Immutable.Map(), - favourited_by: Immutable.Map(), - follow_requests: Immutable.Map(), - blocks: Immutable.Map(), - mutes: Immutable.Map(), +const initialState = ImmutableMap({ + followers: ImmutableMap(), + following: ImmutableMap(), + reblogged_by: ImmutableMap(), + favourited_by: ImmutableMap(), + follow_requests: ImmutableMap(), + blocks: ImmutableMap(), + mutes: ImmutableMap(), }); const normalizeList = (state, type, id, accounts, next) => { - return state.setIn([type, id], Immutable.Map({ + return state.setIn([type, id], ImmutableMap({ next, - items: Immutable.List(accounts.map(item => item.id)), + items: ImmutableList(accounts.map(item => item.id)), })); }; @@ -56,22 +56,22 @@ export default function userLists(state = initialState, action) { case FOLLOWING_EXPAND_SUCCESS: return appendToList(state, 'following', action.id, action.accounts, action.next); case REBLOGS_FETCH_SUCCESS: - return state.setIn(['reblogged_by', action.id], Immutable.List(action.accounts.map(item => item.id))); + return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id))); case FAVOURITES_FETCH_SUCCESS: - return state.setIn(['favourited_by', action.id], Immutable.List(action.accounts.map(item => item.id))); + return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id))); case FOLLOW_REQUESTS_FETCH_SUCCESS: - return state.setIn(['follow_requests', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next); + return state.setIn(['follow_requests', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next); case FOLLOW_REQUESTS_EXPAND_SUCCESS: return state.updateIn(['follow_requests', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next); case FOLLOW_REQUEST_AUTHORIZE_SUCCESS: case FOLLOW_REQUEST_REJECT_SUCCESS: return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id)); case BLOCKS_FETCH_SUCCESS: - return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next); + return state.setIn(['blocks', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next); case BLOCKS_EXPAND_SUCCESS: return state.updateIn(['blocks', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next); case MUTES_FETCH_SUCCESS: - return state.setIn(['mutes', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next); + return state.setIn(['mutes', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next); case MUTES_EXPAND_SUCCESS: return state.updateIn(['mutes', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next); default: diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 07d9a2629..d26d1b727 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -1,5 +1,5 @@ import { createSelector } from 'reselect'; -import Immutable from 'immutable'; +import { List as ImmutableList } from 'immutable'; const getAccountBase = (state, id) => state.getIn(['accounts', id], null); const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); @@ -73,10 +73,10 @@ export const makeGetNotification = () => { }; export const getAccountGallery = createSelector([ - (state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], Immutable.List()), + (state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()), state => state.get('statuses'), ], (statusIds, statuses) => { - let medias = Immutable.List(); + let medias = ImmutableList(); statusIds.forEach(statusId => { const status = statuses.get(statusId); diff --git a/app/javascript/packs/common.js b/app/javascript/packs/common.js index 9d63d8f98..a0cb91ae4 100644 --- a/app/javascript/packs/common.js +++ b/app/javascript/packs/common.js @@ -1,2 +1,7 @@ import { start } from 'rails-ujs'; + +// import default stylesheet with variables +require('font-awesome/css/font-awesome.css'); +require('mastodon-application-style'); + start(); diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 8a3ae0b3c..06cc1b53a 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -5,6 +5,9 @@ import emojify from '../mastodon/emoji'; import { getLocale } from '../mastodon/locales'; import loadPolyfills from '../mastodon/load_polyfills'; import { processBio } from '../glitch/util/bio_metadata'; +import TimelineContainer from '../mastodon/containers/timeline_container'; +import React from 'react'; +import ReactDOM from 'react-dom'; require.context('../images/', true); @@ -37,6 +40,13 @@ function loaded() { const datetime = new Date(content.getAttribute('datetime')); content.textContent = relativeFormat.format(datetime);; }); + + const mountNode = document.getElementById('mastodon-timeline'); + + if (mountNode !== null) { + const props = JSON.parse(mountNode.getAttribute('data-props')); + ReactDOM.render(<TimelineContainer {...props} />, mountNode); + } } function main() { @@ -54,8 +64,12 @@ function main() { } }); - delegate(document, '.media-spoiler', 'click', ({ target }) => { - target.style.display = 'none'; + delegate(document, '.activity-stream .media-spoiler-wrapper .media-spoiler', 'click', function() { + this.parentNode.classList.add('media-spoiler-wrapper__visible'); + }); + + delegate(document, '.activity-stream .media-spoiler-wrapper .spoiler-button', 'click', function() { + this.parentNode.classList.remove('media-spoiler-wrapper__visible'); }); delegate(document, '.webapp-btn', 'click', ({ target, button }) => { diff --git a/app/javascript/styles/about.scss b/app/javascript/styles/about.scss index 7145d0092..5716163be 100644 --- a/app/javascript/styles/about.scss +++ b/app/javascript/styles/about.scss @@ -116,10 +116,6 @@ .wrapper { padding: 20px; } - - .features-list { - display: block; - } } } @@ -299,80 +295,438 @@ } } -.features-list { +.features-list__row { display: flex; - margin-bottom: 20px; + padding: 10px 0; + justify-content: space-between; + + &:first-child { + padding-top: 0; + } - .features-list__column { - flex: 1 1 0; + .visual { + flex: 0 0 auto; + display: flex; + align-items: center; + margin-left: 15px; - ul { - list-style: none; + .fa { + display: block; + color: $ui-primary-color; + font-size: 48px; } + } - li { - margin: 0; + .text { + font-size: 16px; + line-height: 30px; + color: lighten($ui-base-color, 26%); + + h6 { + font-weight: 500; + color: $ui-primary-color; } } } -.screenshot-with-signup { - display: flex; - margin-bottom: 20px; - - .mascot { - flex: 1 1 auto; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; +.landing-page { + .header-wrapper { + padding-top: 15px; + background: $ui-base-color; + background: linear-gradient(150deg, lighten($ui-base-color, 8%), $ui-base-color); + position: relative; - img { - display: block; + .mascot-container { + max-width: 800px; margin: 0 auto; - max-width: 100%; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + } + + .mascot { + position: absolute; + bottom: -14px; + width: auto; height: auto; + left: 60px; + z-index: 3; + } + } + + p, + li { + font: inherit; + font-weight: inherit; + margin-bottom: 0; + } + + .header { + line-height: 30px; + overflow: hidden; + + .container { + display: flex; + justify-content: space-between; + } + + .hero { + margin-top: 50px; + align-items: center; + position: relative; + + .floats { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + + img { + position: absolute; + transition: all 0.1s linear; + animation-name: floating; + animation-duration: 1.7s; + animation-iteration-count: infinite; + animation-direction: alternate; + animation-timing-function: linear; + z-index: 2; + } + + .float-1 { + height: 170px; + right: -120px; + bottom: 0; + } + + .float-2 { + height: 100px; + right: 210px; + bottom: 0; + animation-delay: 0.2s; + } + + .float-3 { + height: 140px; + right: 110px; + top: -30px; + animation-delay: 0.1s; + } + } + + .simple_form, + .closed-registrations-message { + background: darken($ui-base-color, 4%); + width: 280px; + padding: 15px 20px; + border-radius: 4px 4px 0 0; + line-height: initial; + position: relative; + z-index: 4; + + .actions { + margin-bottom: 0; + + button, + .button, + .block-button { + margin-bottom: 0; + } + } + } + + .heading { + position: relative; + z-index: 4; + padding-bottom: 150px; + } + + .closed-registrations-message { + min-height: 330px; + display: flex; + flex-direction: column; + justify-content: space-between; + } + } + + ul { + list-style: none; + margin: 0; + + li { + display: inline-block; + vertical-align: bottom; + margin: 0; + + &:first-child a { + padding-left: 0; + } + + &:last-child a { + padding-right: 0; + } + } + } + + .links { + position: relative; + z-index: 4; + + a { + display: flex; + justify-content: center; + align-items: center; + color: $ui-primary-color; + text-decoration: none; + padding: 12px 16px; + line-height: 32px; + font-family: 'mastodon-font-display', sans-serif; + font-weight: 500; + font-size: 14px; + + &:hover { + color: $ui-secondary-color; + } + } + + .brand { + a { + padding-left: 0; + color: $white; + } + + img { + width: 32px; + height: 32px; + margin-right: 10px; + } + } + } + } + + .container { + width: 100%; + box-sizing: border-box; + max-width: 800px; + margin: 0 auto; + } + + .wrapper { + max-width: 800px; + margin: 0 auto; + padding: 0; + } + + .learn-more-cta { + background: darken($ui-base-color, 4%); + padding: 50px 0; + } + + h3 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $ui-primary-color; + } + + p { + font-size: 16px; + line-height: 30px; + color: lighten($ui-base-color, 26%); + } + + .features { + padding: 50px 0; + + .container { + display: flex; } } - .simple_form, - .closed-registrations-message { - width: 300px; + #mastodon-timeline { + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + font-family: 'mastodon-font-sans-serif', sans-serif; + font-size: 13px; + line-height: 18px; + font-weight: 400; + color: $primary-text-color; + width: 330px; + margin-right: 30px; flex: 0 0 auto; - background: rgba(darken($ui-base-color, 7%), 0.5); - padding: 14px; - border-radius: 4px; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.4); + background: $ui-base-color; + overflow: hidden; + box-shadow: 0 0 6px rgba($black, 0.1); - .actions { - margin-bottom: 0; + .column { + padding: 0; + border-radius: 4px; + overflow: hidden; + height: 100%; } - .info { - text-align: center; + .scrollable { + height: 400px; + } + + p { + font-size: inherit; + line-height: inherit; + font-weight: inherit; + color: $primary-text-color; a { color: $ui-secondary-color; + text-decoration: none; } } } - @media screen and (max-width: 625px) { - .mascot { + .about-mastodon { + max-width: 675px; + + p { + margin-bottom: 20px; + } + + .features-list { + margin-top: 20px; + } + } + + em { + display: inline; + margin: 0; + padding: 0; + font-weight: 500; + background: transparent; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: $ui-primary-color; + } + + h1 { + font-family: 'mastodon-font-display', sans-serif; + font-size: 26px; + line-height: 30px; + margin-bottom: 0; + font-weight: 500; + color: $ui-secondary-color; + + small { + font-family: 'mastodon-font-sans-serif', sans-serif; + display: block; + font-size: 18px; + font-weight: 400; + color: lighten($ui-base-color, 26%); + } + } + + .footer-links { + padding-bottom: 50px; + text-align: right; + color: lighten($ui-base-color, 26%); + + p { + font-size: 14px; + } + + a { + color: inherit; + text-decoration: underline; + } + } + + @media screen and (max-width: 800px) { + .container { + padding: 0 20px; + } + + .header-wrapper .mascot { + left: 20px; + } + } + + @media screen and (max-width: 689px) { + .header-wrapper .mascot { display: none; } + } - .simple_form, - .closed-registrations-message { - flex: auto; + @media screen and (max-width: 675px) { + .header-wrapper { + padding-top: 0; + } + + .header .container, + .features .container { + display: block; + } + + .links { + padding-top: 15px; + background: darken($ui-base-color, 4%); + } + + .header { + padding-top: 0; + + .hero { + margin-top: 30px; + padding: 0; + + .heading { + padding-bottom: 20px; + } + } + + .floats { + display: none; + } + + .heading, + .nav { + text-align: center; + } + + .heading h1 { + padding: 30px 0; + } + + .hero { + .simple_form, + .closed-registrations-message { + background: darken($ui-base-color, 8%); + width: 100%; + border-radius: 0; + box-sizing: border-box; + } + } + } + + #mastodon-timeline { + height: 70vh; + width: 100%; + margin-bottom: 50px; } } } -.closed-registrations-message { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; +@keyframes floating { + from { + transform: translate(0, 0); + } + + 65% { + transform: translate(0, 4px); + } + + to { + transform: translate(0, -0); + } } diff --git a/app/javascript/styles/basics.scss b/app/javascript/styles/basics.scss index 70a5be367..182ea36a4 100644 --- a/app/javascript/styles/basics.scss +++ b/app/javascript/styles/basics.scss @@ -1,6 +1,6 @@ body { font-family: 'mastodon-font-sans-serif', sans-serif; - background: $ui-base-color url('../images/background-photo.jpg'); + background: $ui-base-color; background-size: cover; background-attachment: fixed; font-size: 13px; @@ -11,6 +11,8 @@ body { text-rendering: optimizelegibility; font-feature-settings: "kern"; text-size-adjust: none; + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-tap-highlight-color: transparent; &.app-body { position: fixed; @@ -20,6 +22,11 @@ body { background: $ui-base-color; } + &.about-body { + background: darken($ui-base-color, 8%); + padding-bottom: 0; + } + &.embed { background: transparent; margin: 0; @@ -61,3 +68,18 @@ button { align-items: center; justify-content: center; } + +.system-font { + // system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+) + // -apple-system => Safari <11 specific + // BlinkMacSystemFont => Chrome <56 on macOS specific + // Segoe UI => Windows 7/8/10 + // Oxygen => KDE + // Ubuntu => Unity/Ubuntu + // Cantarell => GNOME + // Fira Sans => Firefox OS + // Droid Sans => Older Androids (<4.0) + // Helvetica Neue => Older macOS <10.11 + // mastodon-font-sans-serif => web-font (Roboto) fallback and newer Androids (>=4.0) + font-family: system-ui, -apple-system,BlinkMacSystemFont, "Segoe UI","Oxygen", "Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",mastodon-font-sans-serif, sans-serif; +} diff --git a/app/javascript/styles/boost.scss b/app/javascript/styles/boost.scss index 9cad7a4f5..e44df2ea4 100644 --- a/app/javascript/styles/boost.scss +++ b/app/javascript/styles/boost.scss @@ -1,12 +1,15 @@ -@function url-friendly-colour($colour) { - @return '%23' + str-slice('#{$colour}', 2, -1) +@function hex-color($color) { + @if type-of($color) == 'color' { + $color: str-slice(ie-hex-str($color), 4); + } + @return '%23' + unquote($color) } button.icon-button i.fa-retweet { - background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour(lighten($ui-base-color, 26%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour($ui-highlight-color)}' stroke-width='0'/></svg>"); + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{hex-color(lighten($ui-base-color, 26%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{hex-color($ui-highlight-color)}' stroke-width='0'/></svg>"); &:hover { - background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour(lighten($ui-base-color, 33%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{url-friendly-colour($ui-highlight-color)}' stroke-width='0'/></svg>"); + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{hex-color(lighten($ui-base-color, 33%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{hex-color($ui-highlight-color)}' stroke-width='0'/></svg>"); } } @@ -23,3 +26,11 @@ button.icon-button.disabled i.fa-retweet { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{lighten($ui-base-color, 16%)}' stroke-width='0'/></svg>"); } } + +// Mastodon gave us this one, but I'm not sure if it's better. - @kibi@glitch.social + +/* +button.icon-button.disabled i.fa-retweet { + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='209'><path d='M4.97 3.16c-.1.03-.17.1-.22.18L.8 8.24c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77L5.5 3.35c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.02-2.4.02H7.1l2.32 2.85.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{hex-color(lighten($ui-base-color, 13%))}' stroke-width='0'/><path d='M7.78 19.66c-.24.02-.44.25-.44.5v2.46h-.06c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v4.47c0 4.26-.56 3.62 3.65 3.62H8.5l-1.3-1.06c-.1-.08-.18-.2-.2-.3-.02-.17.06-.35.2-.45l1.33-1.1H7.28c-.44 0-.72-.3-.72-.7v-4.48c0-.44.28-.72.72-.72h.06v2.5c0 .38.54.63.82.38l4.9-3.93c.25-.18.25-.6 0-.78l-4.9-3.92c-.1-.1-.24-.14-.38-.12zm9.34 2.93c-.54-.02-1.3.02-2.4.02h-1.25l1.3 1.07c.1.07.18.2.2.33.02.16-.06.3-.2.4l-1.33 1.1h1.28c.42 0 .72.28.72.72v4.47c0 .42-.3.72-.72.72h-.1v-2.47c0-.3-.3-.53-.6-.47-.07 0-.14.05-.2.1l-4.9 3.93c-.26.18-.26.6 0 .78l4.9 3.92c.27.25.82 0 .8-.38v-2.5h.1c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.15.4-3.62-1.25-3.66zM10.34 38.66c-.24.02-.44.25-.43.5v2.47H7.3c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.47c0 3.66-.23 3.7 2.34 3.66l-1.34-1.1c-.1-.08-.18-.2-.2-.3 0-.17.07-.35.2-.45l1.96-1.6c-.03-.06-.04-.13-.04-.2v-4.48c0-.44.28-.72.72-.72H9.9v2.5c0 .36.5.6.8.38l4.93-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.08-.23-.13-.36-.12zm5.63 2.93l1.34 1.1c.1.07.18.2.2.33.02.16-.03.3-.16.4l-1.96 1.6c.02.07.06.13.06.22v4.47c0 .42-.3.72-.72.72h-2.66v-2.47c0-.3-.3-.53-.6-.47-.06.02-.12.05-.18.1l-4.94 3.93c-.24.18-.24.6 0 .78l4.94 3.92c.28.22.78-.02.78-.38v-2.5h2.66c4.27 0 3.65.67 3.65-3.62v-4.47c0-3.66.34-3.7-2.4-3.66zM13.06 57.66c-.23.03-.4.26-.4.5v2.47H7.28c-1.08 0-1.86-.04-2.4-.04-1.64 0-1.25.43-1.25 3.65v4.87l2.93-2.37v-2.5c0-.44.28-.72.72-.72h5.38v2.5c0 .36.5.6.78.38l4.94-3.93c.24-.18.24-.6 0-.78l-4.94-3.92c-.1-.1-.24-.14-.38-.12zm5.3 6.15l-2.92 2.4v2.52c0 .42-.3.72-.72.72h-5.4v-2.47c0-.3-.32-.53-.6-.47-.07.02-.13.05-.2.1L3.6 70.52c-.25.18-.25.6 0 .78l4.93 3.92c.28.22.78-.02.78-.38v-2.5h5.42c4.27 0 3.65.67 3.65-3.62v-4.47-.44zM19.25 78.8c-.1.03-.2.1-.28.17l-.9.9c-.44-.3-1.36-.25-3.35-.25H7.28c-1.08 0-1.86-.03-2.4-.03-1.64 0-1.25.43-1.25 3.65v.7l2.93.3v-1c0-.44.28-.72.72-.72h7.44c.2 0 .37.08.5.2l-1.8 1.8c-.25.26-.08.76.27.8l6.27.7c.28.03.56-.25.53-.53l-.7-6.25c0-.27-.3-.48-.55-.44zm-17.2 6.1c-.2.07-.36.3-.33.54l.7 6.25c.02.36.58.55.83.27l.8-.8c.02 0 .04-.02.04 0 .46.24 1.37.17 3.18.17h7.44c4.27 0 3.65.67 3.65-3.62v-.75l-2.93-.3v1.05c0 .42-.3.72-.72.72H7.28c-.15 0-.3-.03-.4-.1L8.8 86.4c.3-.24.1-.8-.27-.84l-6.28-.65h-.2zM4.88 98.6c-1.33 0-1.34.48-1.3 2.3l1.14-1.37c.08-.1.22-.17.34-.2.16 0 .34.08.44.2l1.66 2.03c.04 0 .07-.03.12-.03h7.44c.34 0 .57.2.65.5h-2.43c-.34.05-.53.52-.3.78l3.92 4.95c.18.24.6.24.78 0l3.94-4.94c.22-.27-.02-.76-.37-.77H18.4c.02-3.9.6-3.4-3.66-3.4H7.28c-1.08 0-1.86-.04-2.4-.04zm.15 2.46c-.1.03-.2.1-.28.2l-3.94 4.9c-.2.28.03.77.4.78H3.6c-.02 3.94-.45 3.4 3.66 3.4h7.44c3.65 0 3.74.3 3.7-2.25l-1.1 1.34c-.1.1-.2.17-.32.2-.16 0-.34-.08-.44-.2l-1.65-2.03c-.06.02-.1.04-.18.04H7.28c-.35 0-.57-.2-.66-.5h2.44c.37 0 .63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.23-.47-.2zM4.88 117.6c-1.16 0-1.3.3-1.3 1.56l1.14-1.38c.08-.1.22-.14.34-.16.16 0 .34.04.44.16l2.22 2.75h7c.42 0 .72.28.72.72v.53h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-.53c0-4.2.72-3.63-3.66-3.63H7.28c-1.08 0-1.86-.03-2.4-.03zm.1 1.74c-.1.03-.17.1-.23.16L.8 124.44c-.2.28.03.77.4.78H3.6v.5c0 4.26-.55 3.62 3.66 3.62h7.44c1.03 0 1.74.02 2.28 0-.16.02-.34-.03-.44-.15l-2.22-2.76H7.28c-.44 0-.72-.3-.72-.72v-.5h2.5c.37.02.63-.5.4-.78L5.5 119.5c-.12-.15-.34-.22-.53-.16zm12.02 10c1.2-.02 1.4-.25 1.4-1.53l-1.1 1.36c-.07.1-.17.17-.3.18zM5.94 136.6l2.37 2.93h6.42c.42 0 .72.28.72.72v1.25h-2.6c-.3.1-.43.54-.2.78l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.25c0-4.2.72-3.63-3.66-3.63H7.28c-.6 0-.92-.02-1.34-.03zm-1.72.06c-.4.08-.54.3-.6.75l.6-.74zm.84.93c-.12 0-.24.08-.3.18l-3.95 4.9c-.24.3 0 .83.4.82H3.6v1.22c0 4.26-.55 3.62 3.66 3.62h7.44c.63 0 .97.02 1.4.03l-2.37-2.93H7.28c-.44 0-.72-.3-.72-.72v-1.22h2.5c.4.04.67-.53.4-.8l-3.96-4.92c-.1-.13-.27-.2-.44-.2zm13.28 10.03l-.56.7c.36-.07.5-.3.56-.7zM17.13 155.6c-.55-.02-1.32.03-2.4.03h-8.2l2.38 2.9h5.82c.42 0 .72.28.72.72v1.97H12.9c-.32.06-.48.52-.28.78l3.94 4.94c.2.23.6.22.78-.03l3.94-4.9c.22-.28-.02-.77-.37-.78H18.4v-1.97c0-3.15.4-3.62-1.25-3.66zm-12.1.28c-.1.02-.2.1-.28.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v1.96c0 4.26-.55 3.62 3.66 3.62h8.24l-2.36-2.9H7.28c-.44 0-.72-.3-.72-.72v-1.97h2.5c.37.02.63-.5.4-.78l-3.96-4.9c-.1-.15-.3-.22-.47-.2zM5.13 174.5c-.15 0-.3.07-.38.2L.8 179.6c-.24.27 0 .82.4.8H3.6v2.32c0 4.26-.55 3.62 3.66 3.62h7.94l-2.35-2.9h-5.6c-.43 0-.7-.3-.7-.72v-2.3h2.5c.38.03.66-.54.4-.83l-3.97-4.9c-.1-.13-.23-.2-.38-.2zm12 .1c-.55-.02-1.32.03-2.4.03H6.83l2.35 2.9h5.52c.42 0 .72.28.72.72v2.34h-2.6c-.3.1-.43.53-.2.78l3.92 4.9c.18.24.6.24.78 0l3.94-4.9c.22-.3-.02-.78-.37-.8H18.4v-2.33c0-3.15.4-3.62-1.25-3.66zM4.97 193.16c-.1.03-.17.1-.22.18l-3.94 4.9c-.2.3.03.78.4.8H3.6v2.68c0 4.26-.55 3.62 3.66 3.62h7.66l-2.3-2.84c-.03-.02-.03-.04-.05-.06H7.27c-.44 0-.72-.3-.72-.72v-2.7h2.5c.37.03.63-.48.4-.77l-3.96-4.9c-.12-.17-.34-.25-.53-.2zm12.16.43c-.55-.02-1.32.03-2.4.03H7.1l2.32 2.84.03.06h5.25c.42 0 .72.28.72.72v2.7h-2.5c-.36.02-.56.54-.3.8l3.92 4.9c.18.25.6.25.78 0l3.94-4.9c.26-.28 0-.83-.37-.8H18.4v-2.7c0-3.15.4-3.62-1.25-3.66z' fill='#{hex-color($ui-highlight-color)}' stroke-width='0'/></svg>"); +} +*/ diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 49d3c9873..6cca3666a 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -42,8 +42,38 @@ cursor: default; } + &.button-alternative { + font-size: 16px; + line-height: 36px; + height: auto; + color: $ui-base-color; + background: $ui-primary-color; + text-transform: none; + padding: 4px 16px; + + &:active, + &:focus, + &:hover { + background-color: lighten($ui-primary-color, 4%); + } + } + &.button-secondary { - // + font-size: 16px; + line-height: 36px; + height: auto; + color: $ui-primary-color; + text-transform: none; + background: transparent; + padding: 3px 15px; + border: 1px solid $ui-primary-color; + + &:active, + &:focus, + &:hover { + border-color: lighten($ui-primary-color, 4%); + color: lighten($ui-primary-color, 4%); + } } &.button--block { @@ -1327,6 +1357,20 @@ height: 100%; background-image: none; } + + &.image-loader--amorphous { + position: static; + + .image-loader__preview-canvas { + display: none; + } + + .image-loader__img { + position: static; + width: auto; + height: auto; + } + } } .navigation-bar { @@ -1463,6 +1507,23 @@ .columns-area { padding: 0; } + + .react-swipeable-view-container .columns-area { + height: calc(100% - 20px) !important; + } +} + +.react-swipeable-view-container { + &, + .columns-area, + .drawer, + .column { + height: 100%; + } +} + +.react-swipeable-view-container > * { + height: 100%; } .column { @@ -1510,8 +1571,7 @@ .drawer__tab { display: block; flex: 1 1 auto; - padding: 15px; - padding-bottom: 13px; + padding: 15px 5px 13px; color: $ui-primary-color; text-decoration: none; text-align: center; @@ -2537,7 +2597,8 @@ button.icon-button.active i.fa-retweet { vertical-align: middle; } -.empty-column-indicator { +.empty-column-indicator, +.error-column { color: lighten($ui-base-color, 20%); background: $ui-base-color; text-align: center; @@ -2563,6 +2624,10 @@ button.icon-button.active i.fa-retweet { } } +.error-column { + flex-direction: column; +} + @keyframes pulse { 0% { opacity: 1; @@ -3206,7 +3271,7 @@ button.icon-button.active i.fa-retweet { video { max-width: 80vw; max-height: 80vh; - width: auto; + width: 100%; height: auto; } @@ -3214,6 +3279,7 @@ button.icon-button.active i.fa-retweet { canvas { display: block; background: url('../images/void.png') repeat; + object-fit: contain; } } @@ -3224,7 +3290,8 @@ button.icon-button.active i.fa-retweet { z-index: 100; } -.onboarding-modal { +.onboarding-modal, +.error-modal { background: $ui-secondary-color; color: $ui-base-color; border-radius: 8px; @@ -3238,6 +3305,26 @@ button.icon-button.active i.fa-retweet { width: 80vw; max-width: 520px; max-height: 420px; + + .react-swipeable-view-container > div { + width: 100%; + height: 100%; + box-sizing: border-box; + padding: 25px; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + display: flex; + user-select: text; + } +} + +.error-modal__body { + height: 80vh; + width: 80vw; + max-width: 520px; + max-height: 420px; position: relative; & > div { @@ -3258,6 +3345,14 @@ button.icon-button.active i.fa-retweet { } } +.error-modal__body { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; +} + @media screen and (max-width: 550px) { .onboarding-modal { width: 100%; @@ -3274,7 +3369,8 @@ button.icon-button.active i.fa-retweet { } } -.onboarding-modal__paginator { +.onboarding-modal__paginator, +.error-modal__footer { flex: 0 0 auto; background: darken($ui-secondary-color, 8%); display: flex; @@ -3284,7 +3380,8 @@ button.icon-button.active i.fa-retweet { min-width: 33px; } - .onboarding-modal__nav { + .onboarding-modal__nav, + .error-modal__nav { color: darken($ui-secondary-color, 34%); background-color: transparent; border: 0; @@ -3307,6 +3404,10 @@ button.icon-button.active i.fa-retweet { } } +.error-modal__footer { + justify-content: center; +} + .onboarding-modal__dots { flex: 1 1 auto; display: flex; @@ -3676,6 +3777,7 @@ button.icon-button.active i.fa-retweet { .report-modal__statuses { min-height: 20vh; + max-height: 40vh; overflow-y: auto; overflow-x: hidden; } @@ -3831,8 +3933,7 @@ button.icon-button.active i.fa-retweet { .media-gallery__item-thumbnail { cursor: zoom-in; - display: flex; - align-items: center; + display: block; text-decoration: none; width: 100%; height: 100%; diff --git a/app/javascript/styles/containers.scss b/app/javascript/styles/containers.scss index 68f73e0c0..44d4c1118 100644 --- a/app/javascript/styles/containers.scss +++ b/app/javascript/styles/containers.scss @@ -10,52 +10,36 @@ } .logo-container { - max-width: 400px; margin: 100px auto; - margin-bottom: 0; - cursor: default; + margin-bottom: 50px; @media screen and (max-width: 360px) { margin: 30px auto; } h1 { - display: block; - text-align: center; - color: $primary-text-color; - font-size: 48px; - font-weight: 500; + display: flex; + justify-content: center; + align-items: center; img { - display: block; - margin: 20px auto; - width: 180px; - height: 180px; + width: 32px; + height: 32px; + margin-right: 10px; } a { - color: inherit; + display: flex; + justify-content: center; + align-items: center; + color: $primary-text-color; text-decoration: none; outline: 0; - - img { - opacity: 0.8; - transition: opacity 0.8s ease; - } - - &:hover { - img { - opacity: 1; - transition-duration: 0.2s; - } - } - } - - small { - display: block; - font-size: 12px; - font-weight: 400; - font-family: 'mastodon-font-monospace', monospace; + padding: 12px 16px; + line-height: 32px; + font-family: 'mastodon-font-display', sans-serif; + font-weight: 500; + font-size: 14px; } } } diff --git a/app/javascript/styles/fonts/montserrat.scss b/app/javascript/styles/fonts/montserrat.scss index e4012ab02..206f1865e 100644 --- a/app/javascript/styles/fonts/montserrat.scss +++ b/app/javascript/styles/fonts/montserrat.scss @@ -7,3 +7,11 @@ font-weight: 400; font-style: normal; } + +@font-face { + font-family: 'mastodon-font-display'; + src: local('Montserrat'), + url('../fonts/montserrat/Montserrat-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss index 7a181f36b..e1de36d55 100644 --- a/app/javascript/styles/forms.scss +++ b/app/javascript/styles/forms.scss @@ -24,6 +24,20 @@ code { p.hint { margin-bottom: 15px; + color: lighten($ui-base-color, 32%); + + &.subtle-hint { + text-align: center; + font-size: 12px; + line-height: 18px; + margin-top: 15px; + margin-bottom: 0; + color: lighten($ui-base-color, 26%); + + a { + color: $ui-primary-color; + } + } } strong { @@ -43,10 +57,7 @@ code { } } - .input.file, - .input.select, - .input.radio_buttons, - .input.check_boxes { + .input.with_label { padding: 15px 0; margin-bottom: 0; @@ -57,6 +68,44 @@ code { display: block; padding-top: 5px; } + + &.boolean { + padding: initial; + margin-bottom: initial; + + .label_input > label { + font-family: inherit; + font-size: 14px; + color: $primary-text-color; + display: block; + width: auto; + } + + label.checkbox { + position: relative; + padding-left: 25px; + flex: 1 1 auto; + } + } + } + + .input.with_block_label { + & > label { + font-family: inherit; + font-size: 16px; + color: $primary-text-color; + display: block; + padding-top: 5px; + } + + .hint { + margin-bottom: 15px; + } + + li { + float: left; + width: 50%; + } } .fields-group { @@ -92,7 +141,7 @@ code { input[type=checkbox] { position: absolute; left: 0; - top: 1px; + top: 5px; margin: 0; } @@ -102,6 +151,29 @@ code { } } + .check_boxes { + .checkbox { + label { + font-family: inherit; + font-size: 14px; + color: $primary-text-color; + display: block; + width: auto; + position: relative; + padding-top: 5px; + padding-left: 25px; + flex: 1 1 auto; + } + + input[type=checkbox] { + position: absolute; + left: 0; + top: 5px; + margin: 0; + } + } + } + input[type=text], input[type=number], input[type=email], @@ -197,8 +269,6 @@ code { &:active, &:focus { - position: relative; - top: 1px; background-color: darken($ui-highlight-color, 5%); } @@ -219,6 +289,27 @@ code { select { font-size: 16px; } + + .input-with-append { + position: relative; + + .input input { + padding-right: 127px; + } + + .append { + position: absolute; + right: 0; + top: 0; + padding: 7px 4px; + padding-bottom: 9px; + font-size: 16px; + color: lighten($ui-base-color, 26%); + font-family: inherit; + pointer-events: none; + cursor: default; + } + } } .flash-message { @@ -240,7 +331,7 @@ code { text-align: center; a { - color: $primary-text-color; + color: $ui-primary-color; text-decoration: none; &:hover { @@ -357,21 +448,11 @@ code { } } -.user_filtered_languages { - & > label { - font-family: inherit; - font-size: 16px; - color: $primary-text-color; - display: block; - padding-top: 5px; - } - - .hint { - margin-bottom: 15px; - } +.post-follow-actions { + text-align: center; + color: $ui-primary-color; - li { - float: left; - width: 50%; + div { + margin-bottom: 4px; } } diff --git a/app/javascript/styles/stream_entries.scss b/app/javascript/styles/stream_entries.scss index 8ecf31fd5..a9111d7c9 100644 --- a/app/javascript/styles/stream_entries.scss +++ b/app/javascript/styles/stream_entries.scss @@ -326,6 +326,18 @@ } } + .media-spoiler-wrapper { + &.media-spoiler-wrapper__visible { + .media-spoiler { + display: none; + } + + .spoiler-button { + display: block; + } + } + } + .pre-header { padding: 14px 0; padding-left: (48px + 14px * 2); |