forked from glitch-soc/mastodon
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'TheEssem/feature/bubble-timeline' into …
…merge/TheEssem/20241202_2 Conflicts: app/services/fan_out_on_write_service.rb
- Loading branch information
Showing
34 changed files
with
634 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# frozen_string_literal: true | ||
|
||
module Admin | ||
class BubbleDomainsController < BaseController | ||
before_action :set_bubble_domain, except: [:index, :new, :create] | ||
|
||
def index | ||
authorize :bubble_domain, :update? | ||
@bubble_domains = BubbleDomain.all | ||
end | ||
|
||
def new | ||
authorize :bubble_domain, :update? | ||
@bubble_domain = BubbleDomain.new(domain: params[:_domain]) | ||
end | ||
|
||
def create | ||
authorize :bubble_domain, :update? | ||
|
||
domain = TagManager.instance.normalize_domain(resource_params[:domain]) | ||
|
||
@bubble_domain = BubbleDomain.new(domain: domain) | ||
|
||
if @bubble_domain.save! | ||
log_action :create, @bubble_domain | ||
redirect_to admin_bubble_domains_path, notice: I18n.t('admin.bubble_domains.created_msg') | ||
else | ||
render :new | ||
end | ||
end | ||
|
||
def destroy | ||
authorize :bubble_domain, :update? | ||
@bubble_domain.destroy | ||
log_action :destroy, @bubble_domain | ||
redirect_to admin_bubble_domains_path | ||
end | ||
|
||
private | ||
|
||
def set_bubble_domain | ||
@bubble_domain = BubbleDomain.find(params[:id]) | ||
end | ||
|
||
def resource_params | ||
params.require(:bubble_domain).permit(:domain) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
app/javascript/flavours/glitch/features/bubble_timeline/components/column_settings.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import PropTypes from 'prop-types'; | ||
import { PureComponent } from 'react'; | ||
|
||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||
|
||
import ImmutablePropTypes from 'react-immutable-proptypes'; | ||
|
||
import SettingText from 'flavours/glitch/components/setting_text'; | ||
import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle'; | ||
|
||
const messages = defineMessages({ | ||
filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' }, | ||
settings: { id: 'home.settings', defaultMessage: 'Column settings' }, | ||
}); | ||
|
||
class ColumnSettings extends PureComponent { | ||
|
||
static propTypes = { | ||
settings: ImmutablePropTypes.map.isRequired, | ||
onChange: PropTypes.func.isRequired, | ||
intl: PropTypes.object.isRequired, | ||
columnId: PropTypes.string, | ||
}; | ||
|
||
render () { | ||
const { settings, onChange, intl } = this.props; | ||
|
||
return ( | ||
<div className='column-settings'> | ||
<section> | ||
<div className='column-settings__row'> | ||
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='bubble.column_settings.media_only' defaultMessage='Media only' />} /> | ||
</div> | ||
</section> | ||
|
||
<section> | ||
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span> | ||
|
||
<div className='column-settings__row'> | ||
<SettingText settings={settings} settingPath={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} /> | ||
</div> | ||
</section> | ||
</div> | ||
); | ||
} | ||
|
||
} | ||
|
||
export default injectIntl(ColumnSettings); |
29 changes: 29 additions & 0 deletions
29
...vascript/flavours/glitch/features/bubble_timeline/containers/column_settings_container.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { connect } from 'react-redux'; | ||
|
||
import { changeColumnParams } from '../../../actions/columns'; | ||
import { changeSetting } from '../../../actions/settings'; | ||
import ColumnSettings from '../components/column_settings'; | ||
|
||
const mapStateToProps = (state, { columnId }) => { | ||
const uuid = columnId; | ||
const columns = state.getIn(['settings', 'columns']); | ||
const index = columns.findIndex(c => c.get('uuid') === uuid); | ||
|
||
return { | ||
settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'bubble']), | ||
}; | ||
}; | ||
|
||
const mapDispatchToProps = (dispatch, { columnId }) => { | ||
return { | ||
onChange (key, checked) { | ||
if (columnId) { | ||
dispatch(changeColumnParams(columnId, key, checked)); | ||
} else { | ||
dispatch(changeSetting(['bubble', ...key], checked)); | ||
} | ||
}, | ||
}; | ||
}; | ||
|
||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); |
165 changes: 165 additions & 0 deletions
165
app/javascript/flavours/glitch/features/bubble_timeline/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import PropTypes from 'prop-types'; | ||
import { PureComponent } from 'react'; | ||
|
||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||
|
||
import { Helmet } from 'react-helmet'; | ||
|
||
import { connect } from 'react-redux'; | ||
|
||
import BubbleChartIcon from '@/material-icons/400-24px/bubble_chart.svg?react'; | ||
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; | ||
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; | ||
import { domain } from 'flavours/glitch/initial_state'; | ||
|
||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; | ||
import { connectBubbleStream } from '../../actions/streaming'; | ||
import { expandBubbleTimeline } from '../../actions/timelines'; | ||
import Column from '../../components/column'; | ||
import ColumnHeader from '../../components/column_header'; | ||
import StatusListContainer from '../ui/containers/status_list_container'; | ||
|
||
import ColumnSettingsContainer from './containers/column_settings_container'; | ||
|
||
const messages = defineMessages({ | ||
title: { id: 'column.bubble', defaultMessage: 'Bubble timeline' }, | ||
}); | ||
|
||
const mapStateToProps = (state, { columnId }) => { | ||
const uuid = columnId; | ||
const columns = state.getIn(['settings', 'columns']); | ||
const index = columns.findIndex(c => c.get('uuid') === uuid); | ||
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'bubble', 'other', 'onlyMedia']); | ||
const regex = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'regex', 'body']) : state.getIn(['settings', 'bubble', 'regex', 'body']); | ||
const timelineState = state.getIn(['timelines', `bubble${onlyMedia ? ':media' : ''}`]); | ||
|
||
return { | ||
hasUnread: !!timelineState && timelineState.get('unread') > 0, | ||
onlyMedia, | ||
regex, | ||
}; | ||
}; | ||
|
||
class BubbleTimeline extends PureComponent { | ||
static defaultProps = { | ||
onlyMedia: false, | ||
}; | ||
|
||
static propTypes = { | ||
identity: identityContextPropShape, | ||
dispatch: PropTypes.func.isRequired, | ||
columnId: PropTypes.string, | ||
intl: PropTypes.object.isRequired, | ||
hasUnread: PropTypes.bool, | ||
multiColumn: PropTypes.bool, | ||
onlyMedia: PropTypes.bool, | ||
regex: PropTypes.string, | ||
}; | ||
|
||
handlePin = () => { | ||
const { columnId, dispatch, onlyMedia } = this.props; | ||
|
||
if (columnId) { | ||
dispatch(removeColumn(columnId)); | ||
} else { | ||
dispatch(addColumn('BUBBLE', { other: { onlyMedia } })); | ||
} | ||
}; | ||
|
||
handleMove = (dir) => { | ||
const { columnId, dispatch } = this.props; | ||
dispatch(moveColumn(columnId, dir)); | ||
}; | ||
|
||
handleHeaderClick = () => { | ||
this.column.scrollTop(); | ||
}; | ||
|
||
componentDidMount () { | ||
const { dispatch, onlyMedia } = this.props; | ||
const { signedIn } = this.props.identity; | ||
|
||
dispatch(expandBubbleTimeline({ onlyMedia })); | ||
|
||
if (signedIn) { | ||
this.disconnect = dispatch(connectBubbleStream({ onlyMedia })); | ||
} | ||
} | ||
|
||
componentDidUpdate (prevProps) { | ||
const { signedIn } = this.props.identity; | ||
|
||
if (prevProps.onlyMedia !== this.props.onlyMedia) { | ||
const { dispatch, onlyMedia } = this.props; | ||
|
||
if (this.disconnect) { | ||
this.disconnect(); | ||
} | ||
|
||
dispatch(expandBubbleTimeline({ onlyMedia })); | ||
|
||
if (signedIn) { | ||
this.disconnect = dispatch(connectBubbleStream({ onlyMedia })); | ||
} | ||
} | ||
} | ||
|
||
componentWillUnmount () { | ||
if (this.disconnect) { | ||
this.disconnect(); | ||
this.disconnect = null; | ||
} | ||
} | ||
|
||
setRef = c => { | ||
this.column = c; | ||
}; | ||
|
||
handleLoadMore = maxId => { | ||
const { dispatch, onlyMedia } = this.props; | ||
|
||
dispatch(expandBubbleTimeline({ maxId, onlyMedia })); | ||
}; | ||
|
||
render () { | ||
const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; | ||
const pinned = !!columnId; | ||
|
||
return ( | ||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> | ||
<ColumnHeader | ||
icon='bubble' | ||
iconComponent={BubbleChartIcon} | ||
active={hasUnread} | ||
title={intl.formatMessage(messages.title)} | ||
onPin={this.handlePin} | ||
onMove={this.handleMove} | ||
onClick={this.handleHeaderClick} | ||
pinned={pinned} | ||
multiColumn={multiColumn} | ||
> | ||
<ColumnSettingsContainer columnId={columnId} /> | ||
</ColumnHeader> | ||
|
||
<StatusListContainer | ||
prepend={<DismissableBanner id='bubble_timeline'><FormattedMessage id='dismissable_banner.bubble_timeline' defaultMessage='These are the most recent public posts from people on the fediverse whose accounts are on other servers selected by {domain}.' values={{ domain }} /></DismissableBanner>} | ||
trackScroll={!pinned} | ||
scrollKey={`bubble_timeline-${columnId}`} | ||
timelineId={`bubble${onlyMedia ? ':media' : ''}`} | ||
onLoadMore={this.handleLoadMore} | ||
emptyMessage={<FormattedMessage id='empty_column.bubble' defaultMessage='The bubble timeline is currently empty, but something might show up here soon!' />} | ||
bindToDocument={!multiColumn} | ||
regex={this.props.regex} | ||
/> | ||
|
||
<Helmet> | ||
<title>{intl.formatMessage(messages.title)}</title> | ||
<meta name='robots' content='noindex' /> | ||
</Helmet> | ||
</Column> | ||
); | ||
} | ||
|
||
} | ||
|
||
export default withIdentity(connect(mapStateToProps)(injectIntl(BubbleTimeline))); |
Oops, something went wrong.