From 2587fcdd27613e52d620e3c93bb089932be48db0 Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 25 May 2018 03:03:22 +0200 Subject: Use .star-icon instead of hardcoding color in detailed statuses (fixes #7610) (#7613) --- app/javascript/mastodon/features/status/components/action_bar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index bb9b75505..9162e1326 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -155,7 +155,7 @@ export default class ActionBar extends React.PureComponent {
-
+
{shareButton}
-- cgit From cdbdf7f98bb45439908cb23d0dfae228d59b3c1f Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 25 May 2018 14:26:45 +0200 Subject: Ignore multiple occurrences of a hashtag within a status (fixes #7585) (#7606) --- app/lib/activitypub/activity/create.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index ab1d63cd4..010ed1bb5 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -80,7 +80,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity hashtag = tag['name'].gsub(/\A#/, '').mb_chars.downcase hashtag = Tag.where(name: hashtag).first_or_initialize(name: hashtag) - status.tags << hashtag + status.tags << hashtag unless status.tags.include?(hashtag) rescue ActiveRecord::RecordInvalid nil end -- cgit From 6042403621243b0492992a89342f61c8859a021f Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Fri, 25 May 2018 23:48:17 +0900 Subject: Fix color of /about/more link anchors (#7618) --- app/javascript/styles/mastodon/about.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index c9c0e3081..77728995d 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -396,7 +396,7 @@ $small-breakpoint: 960px; display: flex; justify-content: center; align-items: center; - color: $ui-primary-color; + color: $darker-text-color; text-decoration: none; padding: 12px 16px; line-height: 32px; -- cgit From 7ea91dcbb3054f3dcad59f0ce4f72e140bbdaaa1 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Sat, 26 May 2018 01:36:26 +0900 Subject: More polished light theme (#7620) * Revert "Remove pointer to light theme until it is more polished (#7594)" This reverts commit f8cf85db3b3a4541a0add4b985a9bf0fbe8f2aa0. * True up the hierarchical structure of mastodon-light theme * Fix mastodon-light theme --- app/javascript/styles/mastodon-light.scss | 228 +-------------------- app/javascript/styles/mastodon-light/diff.scss | 157 ++++++++++++++ .../styles/mastodon-light/variables.scss | 38 ++++ config/themes.yml | 1 + 4 files changed, 198 insertions(+), 226 deletions(-) create mode 100644 app/javascript/styles/mastodon-light/diff.scss create mode 100644 app/javascript/styles/mastodon-light/variables.scss (limited to 'app') diff --git a/app/javascript/styles/mastodon-light.scss b/app/javascript/styles/mastodon-light.scss index ce9452ff1..756a12d86 100644 --- a/app/javascript/styles/mastodon-light.scss +++ b/app/javascript/styles/mastodon-light.scss @@ -1,227 +1,3 @@ -// Set variables -$ui-base-color: #d9e1e8; -$ui-base-lighter-color: darken($ui-base-color, 57%); -$ui-highlight-color: #2b90d9; -$ui-primary-color: darken($ui-highlight-color, 28%); -$ui-secondary-color: #282c37; - -$primary-text-color: black; -$base-overlay-background: $ui-base-color; - -$login-button-color: white; -$account-background-color: white; - -// Import defaults +@import 'mastodon-light/variables'; @import 'application'; - -// Change the color of the log in button -.button { - &.button-alternative-2 { - color: $login-button-color; - } -} - -// Change columns' default background colors -.column { - > .scrollable { - background: lighten($ui-base-color, 13%); - } -} - -.drawer__inner { - background: $ui-base-color; -} - -.drawer__inner__mastodon { - background: $ui-base-color url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto; -} - -// Change the default appearance of the content warning button -.status__content, -.reply-indicator__content { - - .status__content__spoiler-link { - - background: darken($ui-base-color, 30%); - - &:hover { - background: darken($ui-base-color, 35%); - text-decoration: none; - } - - } - -} - -// Change the default appearance of the action buttons -.icon-button { - - &:hover, - &:active, - &:focus { - color: darken($ui-base-color, 40%); - transition: color 200ms ease-out; - } - - &.disabled { - color: darken($ui-base-color, 30%); - } - -} - -.status { - &.status-direct { - .icon-button.disabled { - color: darken($ui-base-color, 30%); - } - } -} - -button.icon-button i.fa-retweet { - &:hover { - background-image: url("data:image/svg+xml;utf8,"); - } -} - -button.icon-button.disabled i.fa-retweet { - background-image: url("data:image/svg+xml;utf8,"); -} - -// Change the colors used in the dropdown menu -.dropdown-menu { - background: $ui-base-color; -} - -.dropdown-menu__arrow { - - &.left { - border-left-color: $ui-base-color; - } - - &.top { - border-top-color: $ui-base-color; - } - - &.bottom { - border-bottom-color: $ui-base-color; - } - - &.right { - border-right-color: $ui-base-color; - } - -} - -.dropdown-menu__item { - a { - background: $ui-base-color; - color: $ui-secondary-color; - } -} - -// Change the default color of several parts of the compose form -.compose-form { - - .compose-form__warning { - color: lighten($ui-secondary-color, 65%); - } - - strong { - color: lighten($ui-secondary-color, 65%); - } - - .autosuggest-textarea__textarea, - .spoiler-input__input { - - color: darken($ui-base-color, 80%); - - &::placeholder { - color: darken($ui-base-color, 70%); - } - - } - - .compose-form__buttons-wrapper { - background: darken($ui-base-color, 10%); - } - - .privacy-dropdown__option { - color: $ui-primary-color; - } - - .privacy-dropdown__option__content { - - strong { - color: $ui-primary-color; - } - - } - -} - -// Change the default color used for the text in an empty column or on the error column -.empty-column-indicator, -.error-column { - color: darken($ui-base-color, 60%); -} - -// Change the default colors used on some parts of the profile pages -.activity-stream-tabs { - - background: $account-background-color; - - a { - &.active { - color: $ui-primary-color; - } - } - -} - -.activity-stream { - - .entry { - background: $account-background-color; - } - - .status.light { - - .status__content { - color: $primary-text-color; - } - - .display-name { - strong { - color: $primary-text-color; - } - } - - } - -} - -.accounts-grid { - .account-grid-card { - - .controls { - .icon-button { - color: $ui-secondary-color; - } - } - - .name { - a { - color: $primary-text-color; - } - } - - .username { - color: $ui-secondary-color; - } - - .account__header__content { - color: $primary-text-color; - } - - } -} +@import 'mastodon-light/diff'; diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss new file mode 100644 index 000000000..42c790bac --- /dev/null +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -0,0 +1,157 @@ +// Notes! +// Sass color functions, "darken" and "lighten" are automatically replaced. + +// Change the colors of button texts +.button { + color: $white; + + &.button-alternative-2 { + color: $white; + } +} + +// Change default background colors of columns +.column { + > .scrollable { + background: $white; + } +} + +.drawer__inner { + background: $ui-base-color; +} + +.drawer__inner__mastodon { + background: $ui-base-color url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto; +} + +// Change the colors used in the dropdown menu +.dropdown-menu { + background: $ui-base-color; +} + +.dropdown-menu__arrow { + &.left { + border-left-color: $ui-base-color; + } + + &.top { + border-top-color: $ui-base-color; + } + + &.bottom { + border-bottom-color: $ui-base-color; + } + + &.right { + border-right-color: $ui-base-color; + } +} + +.dropdown-menu__item { + a { + background: $ui-base-color; + color: $ui-secondary-color; + } +} + +// Change the text colors on inverted background +.privacy-dropdown__option.active .privacy-dropdown__option__content, +.privacy-dropdown__option.active .privacy-dropdown__option__content strong, +.privacy-dropdown__option:hover .privacy-dropdown__option__content, +.privacy-dropdown__option:hover .privacy-dropdown__option__content strong, +.dropdown-menu__item a:active, +.dropdown-menu__item a:focus, +.dropdown-menu__item a:hover, +.actions-modal ul li:not(:empty) a.active, +.actions-modal ul li:not(:empty) a.active button, +.actions-modal ul li:not(:empty) a:active, +.actions-modal ul li:not(:empty) a:active button, +.actions-modal ul li:not(:empty) a:focus, +.actions-modal ul li:not(:empty) a:focus button, +.actions-modal ul li:not(:empty) a:hover, +.actions-modal ul li:not(:empty) a:hover button, +.admin-wrapper .sidebar ul ul a.selected, +.simple_form .block-button, +.simple_form .button, +.simple_form button { + color: $white; +} + +// Change the background colors of modals +.actions-modal, +.boost-modal, +.confirmation-modal, +.mute-modal, +.report-modal { + background: $ui-secondary-color; +} + +.boost-modal__action-bar, +.confirmation-modal__action-bar, +.mute-modal__action-bar { + background: darken($ui-secondary-color, 6%); +} + +.react-toggle-track { + background: $ui-base-color; +} + +// Change the default color used for the text in an empty column or on the error column +.empty-column-indicator, +.error-column { + color: $primary-text-color; +} + +// Change the default colors used on some parts of the profile pages +.activity-stream-tabs { + background: $account-background-color; + + a { + &.active { + color: $ui-primary-color; + } + } +} + +.activity-stream { + .entry { + background: $account-background-color; + } + + .status.light { + .status__content { + color: $primary-text-color; + } + + .display-name { + strong { + color: $primary-text-color; + } + } + } +} + +.accounts-grid { + .account-grid-card { + .controls { + .icon-button { + color: $ui-secondary-color; + } + } + + .name { + a { + color: $primary-text-color; + } + } + + .username { + color: $ui-secondary-color; + } + + .account__header__content { + color: $primary-text-color; + } + } +} diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss new file mode 100644 index 000000000..4be454e66 --- /dev/null +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -0,0 +1,38 @@ +// Dependent colors +$black: #000000; +$white: #ffffff; + +$classic-base-color: #282c37; +$classic-primary-color: #9baec8; +$classic-secondary-color: #d9e1e8; +$classic-highlight-color: #2b90d9; + +// Differences +$base-overlay-background: $white; + +$ui-base-color: $classic-secondary-color !default; +$ui-base-lighter-color: #b0c0cf; +$ui-primary-color: #9bcbed; +$ui-secondary-color: $classic-base-color !default; +$ui-highlight-color: #2b5fd9; + +$primary-text-color: $black !default; +$darker-text-color: $classic-base-color !default; +$dark-text-color: #444b5d; +$action-button-color: #606984; + +$inverted-text-color: $black !default; +$lighter-text-color: $classic-base-color !default; +$light-text-color: #444b5d; + +//Newly added colors +$account-background-color: $white; + +//Invert darkened and lightened colors +@function darken($color, $amount) { + @return hsl(hue($color), saturation($color), lightness($color) + $amount); +} + +@function lighten($color, $amount) { + @return hsl(hue($color), saturation($color), lightness($color) - $amount); +} diff --git a/config/themes.yml b/config/themes.yml index f0bb1e6f7..9c21c9459 100644 --- a/config/themes.yml +++ b/config/themes.yml @@ -1,2 +1,3 @@ default: styles/application.scss contrast: styles/contrast.scss +mastodon-light: styles/mastodon-light.scss -- cgit From 8182b615183b83fb6651fb23912abda3cc4ccf03 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 26 May 2018 01:36:46 +0900 Subject: Enable media timeline (#7598) --- .../mastodon/features/community_timeline/index.js | 24 ++++++++++------------ .../mastodon/features/public_timeline/index.js | 24 ++++++++++------------ 2 files changed, 22 insertions(+), 26 deletions(-) (limited to 'app') diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 58b8a8b73..f9ee835bc 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -8,7 +8,7 @@ import ColumnHeader from '../../components/column_header'; import { expandCommunityTimeline } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn, changeColumnParams } from '../../actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; -// import SectionHeadline from './components/section_headline'; +import SectionHeadline from './components/section_headline'; import { connectCommunityStream } from '../../actions/streaming'; const messages = defineMessages({ @@ -100,17 +100,15 @@ export default class CommunityTimeline extends React.PureComponent { const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; - // pending - // - // const headline = ( - // - // ); + const headline = ( + + ); return ( @@ -128,7 +126,7 @@ export default class CommunityTimeline extends React.PureComponent { - // ); + const headline = ( + + ); return ( @@ -128,7 +126,7 @@ export default class PublicTimeline extends React.PureComponent { Date: Sat, 26 May 2018 01:46:28 +0900 Subject: Introduce flat layout to contexts reducer (#7150) This allows to filter out replies in threads even if contexts of those replies are not fetched. --- app/javascript/mastodon/actions/timelines.js | 20 ----- app/javascript/mastodon/features/status/index.js | 47 ++++++++++-- app/javascript/mastodon/reducers/contexts.js | 94 +++++++++++++++--------- 3 files changed, 101 insertions(+), 60 deletions(-) (limited to 'app') diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 8f54dfd8a..11a199db6 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -13,21 +13,9 @@ export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; -export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; - export function updateTimeline(timeline, status) { return (dispatch, getState) => { const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : []; - const parents = []; - - if (status.in_reply_to_id) { - let parent = getState().getIn(['statuses', status.in_reply_to_id]); - - while (parent && parent.get('in_reply_to_id')) { - parents.push(parent.get('id')); - parent = getState().getIn(['statuses', parent.get('in_reply_to_id')]); - } - } dispatch(importFetchedStatus(status)); @@ -37,14 +25,6 @@ export function updateTimeline(timeline, status) { status, references, }); - - if (parents.length > 0) { - dispatch({ - type: TIMELINE_CONTEXT_UPDATE, - status, - references: parents, - }); - } }; }; diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index d5af2a459..2e53dfa7e 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -1,3 +1,4 @@ +import Immutable from 'immutable'; import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; @@ -54,11 +55,47 @@ const messages = defineMessages({ const makeMapStateToProps = () => { const getStatus = makeGetStatus(); - const mapStateToProps = (state, props) => ({ - status: getStatus(state, props.params.statusId), - ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]), - descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]), - }); + const mapStateToProps = (state, props) => { + const status = getStatus(state, props.params.statusId); + let ancestorsIds = Immutable.List(); + let descendantsIds = Immutable.List(); + + if (status) { + ancestorsIds = ancestorsIds.withMutations(mutable => { + function addAncestor(id) { + if (id) { + const inReplyTo = state.getIn(['contexts', 'inReplyTos', id]); + + mutable.unshift(id); + addAncestor(inReplyTo); + } + } + + addAncestor(status.get('in_reply_to_id')); + }); + + descendantsIds = descendantsIds.withMutations(mutable => { + function addDescendantOf(id) { + const replies = state.getIn(['contexts', 'replies', id]); + + if (replies) { + replies.forEach(reply => { + mutable.push(reply); + addDescendantOf(reply); + }); + } + } + + addDescendantOf(status.get('id')); + }); + } + + return { + status, + ancestorsIds, + descendantsIds, + }; + }; return mapStateToProps; }; diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index ebd01e532..53e70b58e 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -3,38 +3,62 @@ import { ACCOUNT_MUTE_SUCCESS, } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; -import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from '../actions/timelines'; +import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; const initialState = ImmutableMap({ - ancestors: ImmutableMap(), - descendants: ImmutableMap(), + inReplyTos: ImmutableMap(), + replies: ImmutableMap(), }); -const normalizeContext = (state, id, ancestors, descendants) => { - const ancestorsIds = ImmutableList(ancestors.map(ancestor => ancestor.id)); - const descendantsIds = ImmutableList(descendants.map(descendant => descendant.id)); +const normalizeContext = (immutableState, id, ancestors, descendants) => immutableState.withMutations(state => { + state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => { + state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => { + function addReply({ id, in_reply_to_id }) { + if (in_reply_to_id) { + const siblings = replies.get(in_reply_to_id, ImmutableList()); - return state.withMutations(map => { - map.setIn(['ancestors', id], ancestorsIds); - map.setIn(['descendants', id], descendantsIds); - }); -}; + if (!siblings.includes(id)) { + const index = siblings.findLastIndex(sibling => sibling.id < id); + replies.set(in_reply_to_id, siblings.insert(index + 1, id)); + } + + inReplyTos.set(id, in_reply_to_id); + } + } + + if (ancestors[0]) { + addReply({ id, in_reply_to_id: ancestors[0].id }); + } + + if (descendants[0]) { + addReply({ id: descendants[0].id, in_reply_to_id: id }); + } + + [ancestors, descendants].forEach(statuses => statuses.forEach(addReply)); + })); + })); +}); const deleteFromContexts = (immutableState, ids) => immutableState.withMutations(state => { - state.update('ancestors', immutableAncestors => immutableAncestors.withMutations(ancestors => { - state.update('descendants', immutableDescendants => immutableDescendants.withMutations(descendants => { + state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => { + state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => { ids.forEach(id => { - descendants.get(id, ImmutableList()).forEach(descendantId => { - ancestors.update(descendantId, ImmutableList(), list => list.filterNot(itemId => itemId === id)); - }); + const inReplyToIdOfId = inReplyTos.get(id); + const repliesOfId = replies.get(id); + const siblings = replies.get(inReplyToIdOfId); - ancestors.get(id, ImmutableList()).forEach(ancestorId => { - descendants.update(ancestorId, ImmutableList(), list => list.filterNot(itemId => itemId === id)); - }); + if (siblings) { + replies.set(inReplyToIdOfId, siblings.filterNot(sibling => sibling === id)); + } + + + if (repliesOfId) { + repliesOfId.forEach(reply => inReplyTos.delete(reply)); + } - descendants.delete(id); - ancestors.delete(id); + inReplyTos.delete(id); + replies.delete(id); }); })); })); @@ -48,23 +72,23 @@ const filterContexts = (state, relationship, statuses) => { return deleteFromContexts(state, ownedStatusIds); }; -const updateContext = (state, status, references) => { - return state.update('descendants', map => { - references.forEach(parentId => { - map = map.update(parentId, ImmutableList(), list => { - if (list.includes(status.id)) { - return list; - } +const updateContext = (state, status) => { + if (status.in_reply_to_id) { + return state.withMutations(mutable => { + const replies = mutable.getIn(['replies', status.in_reply_to_id], ImmutableList()); - return list.push(status.id); - }); + mutable.setIn(['inReplyTos', status.id], status.in_reply_to_id); + + if (!replies.includes(status.id)) { + mutable.setIn(['replies', status.id], replies.push(status.id)); + } }); + } - return map; - }); + return state; }; -export default function contexts(state = initialState, action) { +export default function replies(state = initialState, action) { switch(action.type) { case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: @@ -73,8 +97,8 @@ export default function contexts(state = initialState, action) { return normalizeContext(state, action.id, action.ancestors, action.descendants); case TIMELINE_DELETE: return deleteFromContexts(state, [action.id]); - case TIMELINE_CONTEXT_UPDATE: - return updateContext(state, action.status, action.references); + case TIMELINE_UPDATE: + return updateContext(state, action.status); default: return state; } -- cgit