Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `app/javascript/styles/mastodon/modal.scss`: For some reason we changed the file loading path in glitch-soc, but now upstream has completely changed how the logo is loaded. Applied upstream changes.
This commit is contained in:
@ -2,17 +2,16 @@
|
||||
|
||||
class Api::Web::SettingsController < Api::Web::BaseController
|
||||
before_action :require_user!
|
||||
before_action :set_setting
|
||||
|
||||
def update
|
||||
setting.data = params[:data]
|
||||
setting.save!
|
||||
|
||||
@setting.update!(data: params[:data])
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setting
|
||||
@_setting ||= ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user)
|
||||
def set_setting
|
||||
@setting = ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user)
|
||||
end
|
||||
end
|
||||
|
@ -44,7 +44,7 @@ class ApplicationController < ActionController::Base
|
||||
private
|
||||
|
||||
def https_enabled?
|
||||
Rails.env.production? && !request.path.start_with?('/health')
|
||||
Rails.env.production? && !request.path.start_with?('/health') && !request.headers["Host"].ends_with?(".onion")
|
||||
end
|
||||
|
||||
def authorized_fetch_mode?
|
||||
|
@ -13,7 +13,7 @@ class InstanceActorsController < ApplicationController
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(-99)
|
||||
@account = Account.representative
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
|
13
app/helpers/mascot_helper.rb
Normal file
13
app/helpers/mascot_helper.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module MascotHelper
|
||||
def mascot_url
|
||||
full_asset_url(instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def instance_presenter
|
||||
@instance_presenter ||= InstancePresenter.new
|
||||
end
|
||||
end
|
29
app/javascript/mastodon/actions/boosts.js
Normal file
29
app/javascript/mastodon/actions/boosts.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { openModal } from './modal';
|
||||
|
||||
export const BOOSTS_INIT_MODAL = 'BOOSTS_INIT_MODAL';
|
||||
export const BOOSTS_CHANGE_PRIVACY = 'BOOSTS_CHANGE_PRIVACY';
|
||||
|
||||
export function initBoostModal(props) {
|
||||
return (dispatch, getState) => {
|
||||
const default_privacy = getState().getIn(['compose', 'default_privacy']);
|
||||
|
||||
const privacy = props.status.get('visibility') === 'private' ? 'private' : default_privacy;
|
||||
|
||||
dispatch({
|
||||
type: BOOSTS_INIT_MODAL,
|
||||
privacy
|
||||
});
|
||||
|
||||
dispatch(openModal('BOOST', props));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function changeBoostPrivacy(privacy) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: BOOSTS_CHANGE_PRIVACY,
|
||||
privacy,
|
||||
});
|
||||
};
|
||||
}
|
@ -41,11 +41,11 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
|
||||
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
|
||||
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';
|
||||
|
||||
export function reblog(status) {
|
||||
export function reblog(status, visibility) {
|
||||
return function (dispatch, getState) {
|
||||
dispatch(reblogRequest(status));
|
||||
|
||||
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) {
|
||||
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) {
|
||||
// The reblog API method returns a new status wrapped around the original. In this case we are only
|
||||
// interested in how the original is modified, hence passing it skipping the wrapper
|
||||
dispatch(importFetchedStatus(response.data.reblog));
|
||||
|
@ -177,7 +177,6 @@ export default class Dropdown extends React.PureComponent {
|
||||
disabled: PropTypes.bool,
|
||||
status: ImmutablePropTypes.map,
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
dropdownPlacement: PropTypes.string,
|
||||
|
@ -223,10 +223,11 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
render () {
|
||||
const { status, relationship, intl, withDismiss, scrollKey } = this.props;
|
||||
|
||||
const mutingConversation = status.get('muted');
|
||||
const anonymousAccess = !me;
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const mutingConversation = status.get('muted');
|
||||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
|
||||
let menu = [];
|
||||
|
||||
@ -237,19 +238,22 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||
menu.push(null);
|
||||
|
||||
if (status.getIn(['account', 'id']) === me || withDismiss) {
|
||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||
|
||||
if (writtenByMe && publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
|
||||
if (writtenByMe || withDismiss) {
|
||||
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (status.getIn(['account', 'id']) === me) {
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
}
|
||||
|
||||
if (writtenByMe) {
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
|
||||
} else {
|
||||
|
@ -6,7 +6,6 @@ import DropdownMenu from '../components/dropdown_menu';
|
||||
import { isUserTouching } from '../is_mobile';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
||||
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
|
||||
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
|
||||
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
|
||||
|
@ -35,6 +35,7 @@ import {
|
||||
} from '../actions/domain_blocks';
|
||||
import { initMuteModal } from '../actions/mutes';
|
||||
import { initBlockModal } from '../actions/blocks';
|
||||
import { initBoostModal } from '../actions/boosts';
|
||||
import { initReport } from '../actions/reports';
|
||||
import { openModal } from '../actions/modal';
|
||||
import { deployPictureInPicture } from '../actions/picture_in_picture';
|
||||
@ -82,11 +83,11 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
});
|
||||
},
|
||||
|
||||
onModalReblog (status) {
|
||||
onModalReblog (status, privacy) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
} else {
|
||||
dispatch(reblog(status));
|
||||
dispatch(reblog(status, privacy));
|
||||
}
|
||||
},
|
||||
|
||||
@ -94,7 +95,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
if ((e && e.shiftKey) || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -127,7 +127,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
||||
// It should not be transformed when mounting because the resulting
|
||||
// size will be used to determine the coordinate of the menu by
|
||||
// react-overlays
|
||||
<div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null, zIndex: 2 }} role='listbox' ref={this.setRef}>
|
||||
<div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
|
||||
{items.map(item => (
|
||||
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
|
||||
<div className='privacy-dropdown__option__icon'>
|
||||
@ -153,11 +153,12 @@ class PrivacyDropdown extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
noDirect: PropTypes.bool,
|
||||
container: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@ -167,7 +168,7 @@ class PrivacyDropdown extends React.PureComponent {
|
||||
};
|
||||
|
||||
handleToggle = ({ target }) => {
|
||||
if (this.props.isUserTouching()) {
|
||||
if (this.props.isUserTouching && this.props.isUserTouching()) {
|
||||
if (this.state.open) {
|
||||
this.props.onModalClose();
|
||||
} else {
|
||||
@ -236,12 +237,17 @@ class PrivacyDropdown extends React.PureComponent {
|
||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
];
|
||||
|
||||
if (!this.props.noDirect) {
|
||||
this.options.push(
|
||||
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, intl } = this.props;
|
||||
const { value, container, intl } = this.props;
|
||||
const { open, placement } = this.state;
|
||||
|
||||
const valueOption = this.options.find(item => item.value === value);
|
||||
@ -264,7 +270,7 @@ class PrivacyDropdown extends React.PureComponent {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Overlay show={open} placement={placement} target={this}>
|
||||
<Overlay show={open} placement={placement} target={this} container={container}>
|
||||
<PrivacyDropdownMenu
|
||||
items={this.options}
|
||||
value={value}
|
||||
|
@ -5,7 +5,6 @@ import { openModal, closeModal } from '../../../actions/modal';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isModalOpen: state.get('modal').modalType === 'ACTIONS',
|
||||
value: state.getIn(['compose', 'privacy']),
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { makeGetNotification, makeGetStatus } from '../../../selectors';
|
||||
import Notification from '../components/notification';
|
||||
import { initBoostModal } from '../../../actions/boosts';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
import { mentionCompose } from '../../../actions/compose';
|
||||
import {
|
||||
@ -35,8 +36,8 @@ const mapDispatchToProps = dispatch => ({
|
||||
dispatch(mentionCompose(account, router));
|
||||
},
|
||||
|
||||
onModalReblog (status) {
|
||||
dispatch(reblog(status));
|
||||
onModalReblog (status, privacy) {
|
||||
dispatch(reblog(status, privacy));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
@ -46,7 +47,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -10,6 +10,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { replyCompose } from 'mastodon/actions/compose';
|
||||
import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
|
||||
import { makeGetStatus } from 'mastodon/selectors';
|
||||
import { initBoostModal } from 'mastodon/actions/boosts';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -89,9 +90,9 @@ class Footer extends ImmutablePureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
_performReblog = () => {
|
||||
const { dispatch, status } = this.props;
|
||||
dispatch(reblog(status));
|
||||
_performReblog = (status, privacy) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(reblog(status, privacy));
|
||||
}
|
||||
|
||||
handleReblogClick = e => {
|
||||
@ -100,9 +101,9 @@ class Footer extends ImmutablePureComponent {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
} else if ((e && e.shiftKey) || !boostModal) {
|
||||
this._performReblog();
|
||||
this._performReblog(status);
|
||||
} else {
|
||||
dispatch(openModal('BOOST', { status, onReblog: this._performReblog }));
|
||||
dispatch(initBoostModal({ status, onReblog: this._performReblog }));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -187,9 +187,10 @@ class ActionBar extends React.PureComponent {
|
||||
render () {
|
||||
const { status, relationship, intl } = this.props;
|
||||
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const mutingConversation = status.get('muted');
|
||||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
|
||||
let menu = [];
|
||||
|
||||
@ -199,12 +200,12 @@ class ActionBar extends React.PureComponent {
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (me === status.getIn(['account', 'id'])) {
|
||||
if (writtenByMe) {
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
} from '../../../actions/statuses';
|
||||
import { initMuteModal } from '../../../actions/mutes';
|
||||
import { initBlockModal } from '../../../actions/blocks';
|
||||
import { initBoostModal } from '../../../actions/boosts';
|
||||
import { initReport } from '../../../actions/reports';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
@ -68,8 +69,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
});
|
||||
},
|
||||
|
||||
onModalReblog (status) {
|
||||
dispatch(reblog(status));
|
||||
onModalReblog (status, privacy) {
|
||||
dispatch(reblog(status, privacy));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
@ -79,7 +80,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -42,6 +42,7 @@ import {
|
||||
} from '../../actions/domain_blocks';
|
||||
import { initMuteModal } from '../../actions/mutes';
|
||||
import { initBlockModal } from '../../actions/blocks';
|
||||
import { initBoostModal } from '../../actions/boosts';
|
||||
import { initReport } from '../../actions/reports';
|
||||
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
@ -234,8 +235,8 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleModalReblog = (status) => {
|
||||
this.props.dispatch(reblog(status));
|
||||
handleModalReblog = (status, privacy) => {
|
||||
this.props.dispatch(reblog(status, privacy));
|
||||
}
|
||||
|
||||
handleReblogClick = (status, e) => {
|
||||
@ -245,7 +246,7 @@ class Status extends ImmutablePureComponent {
|
||||
if ((e && e.shiftKey) || !boostModal) {
|
||||
this.handleModalReblog(status);
|
||||
} else {
|
||||
this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
|
||||
this.props.dispatch(initBoostModal({ status, onReblog: this.handleModalReblog }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
@ -10,7 +11,9 @@ import DisplayName from '../../../components/display_name';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
|
||||
import classNames from 'classnames';
|
||||
import { changeBoostPrivacy } from 'mastodon/actions/boosts';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
@ -21,7 +24,22 @@ const messages = defineMessages({
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
privacy: state.getIn(['boosts', 'new', 'privacy']),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onChangeBoostPrivacy(value) {
|
||||
dispatch(changeBoostPrivacy(value));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class BoostModal extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
@ -32,6 +50,8 @@ class BoostModal extends ImmutablePureComponent {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onChangeBoostPrivacy: PropTypes.func.isRequired,
|
||||
privacy: PropTypes.string.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@ -40,7 +60,7 @@ class BoostModal extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
handleReblog = () => {
|
||||
this.props.onReblog(this.props.status);
|
||||
this.props.onReblog(this.props.status, this.props.privacy);
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
@ -52,12 +72,16 @@ class BoostModal extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
_findContainer = () => {
|
||||
return document.getElementsByClassName('modal-root__container')[0];
|
||||
};
|
||||
|
||||
setRef = (c) => {
|
||||
this.button = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
const { status, privacy, intl } = this.props;
|
||||
const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
|
||||
|
||||
const visibilityIconInfo = {
|
||||
@ -102,6 +126,14 @@ class BoostModal extends ImmutablePureComponent {
|
||||
|
||||
<div className='boost-modal__action-bar'>
|
||||
<div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} /></div>
|
||||
{status.get('visibility') !== 'private' && !status.get('reblogged') && (
|
||||
<PrivacyDropdown
|
||||
noDirect
|
||||
value={privacy}
|
||||
container={this._findContainer}
|
||||
onChange={this.props.onChangeBoostPrivacy}
|
||||
/>
|
||||
)}
|
||||
<Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
25
app/javascript/mastodon/reducers/boosts.js
Normal file
25
app/javascript/mastodon/reducers/boosts.js
Normal file
@ -0,0 +1,25 @@
|
||||
import Immutable from 'immutable';
|
||||
|
||||
import {
|
||||
BOOSTS_INIT_MODAL,
|
||||
BOOSTS_CHANGE_PRIVACY,
|
||||
} from 'mastodon/actions/boosts';
|
||||
|
||||
const initialState = Immutable.Map({
|
||||
new: Immutable.Map({
|
||||
privacy: 'public',
|
||||
}),
|
||||
});
|
||||
|
||||
export default function mutes(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case BOOSTS_INIT_MODAL:
|
||||
return state.withMutations((state) => {
|
||||
state.setIn(['new', 'privacy'], action.privacy);
|
||||
});
|
||||
case BOOSTS_CHANGE_PRIVACY:
|
||||
return state.setIn(['new', 'privacy'], action.privacy);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import push_notifications from './push_notifications';
|
||||
import status_lists from './status_lists';
|
||||
import mutes from './mutes';
|
||||
import blocks from './blocks';
|
||||
import boosts from './boosts';
|
||||
import reports from './reports';
|
||||
import contexts from './contexts';
|
||||
import compose from './compose';
|
||||
@ -57,6 +58,7 @@ const reducers = {
|
||||
push_notifications,
|
||||
mutes,
|
||||
blocks,
|
||||
boosts,
|
||||
reports,
|
||||
contexts,
|
||||
compose,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
|
||||
import { TIMELINE_DELETE } from '../actions/timelines';
|
||||
|
||||
const initialState = {
|
||||
modalType: null,
|
||||
@ -11,6 +12,8 @@ export default function modal(state = initialState, action) {
|
||||
return { modalType: action.modalType, modalProps: action.modalProps };
|
||||
case MODAL_CLOSE:
|
||||
return (action.modalType === undefined || action.modalType === state.modalType) ? initialState : state;
|
||||
case TIMELINE_DELETE:
|
||||
return (state.modalProps.statusId === action.id) ? initialState : state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import EXIF from 'exif-js';
|
||||
|
||||
const MAX_IMAGE_PIXELS = 1638400; // 1280x1280px
|
||||
const MAX_IMAGE_PIXELS = 2073600; // 1920x1080px
|
||||
|
||||
const _browser_quirks = {};
|
||||
|
||||
|
@ -4209,6 +4209,7 @@ a.status-card.compact:hover {
|
||||
border-radius: 4px;
|
||||
margin-left: 40px;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
|
||||
&.top {
|
||||
transform-origin: 50% 100%;
|
||||
@ -4219,6 +4220,15 @@ a.status-card.compact:hover {
|
||||
}
|
||||
}
|
||||
|
||||
.modal-root__container .privacy-dropdown {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.modal-root__container .privacy-dropdown__dropdown {
|
||||
pointer-events: auto;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.privacy-dropdown__option {
|
||||
color: $inverted-text-color;
|
||||
padding: 10px;
|
||||
|
@ -12,10 +12,19 @@
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
> * {
|
||||
> div {
|
||||
flex: 1;
|
||||
max-height: 235px;
|
||||
background: url('~images/elephant_ui_plane.svg') no-repeat left bottom / contain;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,14 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
||||
def perform
|
||||
target_account = account_from_uri(object_uri)
|
||||
|
||||
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
|
||||
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id'])
|
||||
|
||||
# Update id of already-existing follow requests
|
||||
existing_follow_request = ::FollowRequest.find_by(account: @account, target_account: target_account)
|
||||
unless existing_follow_request.nil?
|
||||
existing_follow_request.update!(uri: @json['id'])
|
||||
return
|
||||
end
|
||||
|
||||
if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved? || target_account.instance_actor?
|
||||
reject_follow_request!(target_account)
|
||||
@ -14,7 +21,9 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
||||
end
|
||||
|
||||
# Fast-forward repeat follow requests
|
||||
if @account.following?(target_account)
|
||||
existing_follow = ::Follow.find_by(account: @account, target_account: target_account)
|
||||
unless existing_follow.nil?
|
||||
existing_follow.update!(uri: @json['id'])
|
||||
AuthorizeFollowService.new.call(@account, target_account, skip_follow_request: true, follow_request_uri: @json['id'])
|
||||
return
|
||||
end
|
||||
|
@ -88,10 +88,18 @@ class Webfinger
|
||||
end
|
||||
|
||||
def standard_url
|
||||
"https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
|
||||
if @domain.ends_with? ".onion"
|
||||
"http://#{@domain}/.well-known/webfinger?resource=#{@uri}"
|
||||
else
|
||||
"https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
|
||||
end
|
||||
end
|
||||
|
||||
def host_meta_url
|
||||
"https://#{@domain}/.well-known/host-meta"
|
||||
if @domain.ends_with? ".onion"
|
||||
"http://#{@domain}/.well-known/host-meta"
|
||||
else
|
||||
"https://#{@domain}/.well-known/host-meta"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -14,6 +14,8 @@ module AccountFinderConcern
|
||||
|
||||
def representative
|
||||
Account.find(-99)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
Account.create!(id: -99, actor_type: 'Application', locked: true, username: Rails.configuration.x.local_domain)
|
||||
end
|
||||
|
||||
def find_local(username)
|
||||
|
@ -59,7 +59,7 @@ class MediaAttachment < ApplicationRecord
|
||||
|
||||
IMAGE_STYLES = {
|
||||
original: {
|
||||
pixels: 1_638_400, # 1280x1280px
|
||||
pixels: 2_073_600, # 1920x1080px
|
||||
file_geometry_parser: FastGeometryParser,
|
||||
}.freeze,
|
||||
|
||||
|
@ -38,7 +38,17 @@ class FetchOEmbedService
|
||||
|
||||
return if @endpoint_url.blank?
|
||||
|
||||
@endpoint_url = (Addressable::URI.parse(@url) + @endpoint_url).to_s
|
||||
@endpoint_url = begin
|
||||
base_url = Addressable::URI.parse(@url)
|
||||
|
||||
# If the OEmbed endpoint is given as http but the URL we opened
|
||||
# was served over https, we can assume OEmbed will be available
|
||||
# through https as well
|
||||
|
||||
(base_url + @endpoint_url).tap do |absolute_url|
|
||||
absolute_url.scheme = base_url.scheme if base_url.scheme == 'https'
|
||||
end.to_s
|
||||
end
|
||||
|
||||
cache_endpoint!
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
|
@ -11,5 +11,6 @@
|
||||
.container-alt= yield
|
||||
.modal-layout__mastodon
|
||||
%div
|
||||
%img{alt:'', draggable:'false', src:"#{mascot_url}"}
|
||||
|
||||
= render template: 'layouts/application'
|
||||
|
Reference in New Issue
Block a user