Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

本家v2.7.0追従へのハッシュタグタイムラインの改修方法 #213

Closed
lnanase opened this issue Jan 22, 2019 · 1 comment · Fixed by #219
Closed

本家v2.7.0追従へのハッシュタグタイムラインの改修方法 #213

lnanase opened this issue Jan 22, 2019 · 1 comment · Fixed by #219
Assignees
Labels
help wanted Solutions or ideas are needed priority high Should respond this quickly update Update version from the original repo

Comments

@lnanase
Copy link
Collaborator

lnanase commented Jan 22, 2019

@fvh-P @takayamaki
お疲れ様です。 v2.7.0 マージPRを作っていて困ったことになりましたので相談させてください。

今回のverupで、Add joining several hashtags in a single column mastodon#8904 の改修が入っていて、
これが既に実装済みのハッシュタグタイムライン周りとコンフリクトとしてます。

アイマストドン v2.7.0
7e2afb48269033bc ede1da97087fece0

お手数ですが方針をご検討頂けないでしょうか。
よろしくお願い致します。
(一旦、本家の状態にタイムラインに未収載を含めるかどうかの分岐だけ
マージした状態でリリースして、後ほど必要な機能を復活させていくのがベターですかね)

ちなみに今コンフリクト起きてるdiffです(5ファイル)
diff --cc app/javascript/mastodon/actions/streaming.js
index 68279c4b3,cd319709d..000000000
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@@ -51,6 -51,6 +51,6 @@@ const refreshHomeTimelineAndNotificatio
  export const connectUserStream      = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
  export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
  export const connectPublicStream    = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
- export const connectHashtagStream   = (tag, isLocal) => connectTimelineStream(`hashtag:${tag}`, `hashtag${ isLocal ? ':local' : '' }&tag=${tag}`);
 -export const connectHashtagStream   = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
++export const connectHashtagStream   = (id, tag, isLocal, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag${ isLocal ? ':local' : '' }&tag=${tag}`, null, accept);
  export const connectDirectStream    = () => connectTimelineStream('direct', 'direct');
  export const connectListStream      = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
diff --cc app/javascript/mastodon/actions/timelines.js
index a693f4ca7,6e7bd027c..000000000
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@@ -79,10 -96,17 +96,21 @@@ export const expandCommunityTimelin
  export const expandAccountTimeline         = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
  export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
  export const expandAccountMediaTimeline    = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
++<<<<<<< HEAD
 +export const expandHashtagTimeline         = (hashtag, { maxId, isLocal } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}${isLocal ? '?local=true' : ''}`, { max_id: maxId }, done);
++=======
++>>>>>>> v2.7.0
  export const expandListTimeline            = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
+ export const expandHashtagTimeline         = (hashtag, { maxId, tags } = {}, done = noOp) => {
+   return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
+     max_id: maxId,
+     any:    parseTags(tags, 'any'),
+     all:    parseTags(tags, 'all'),
+     none:   parseTags(tags, 'none'),
+   }, done);
+ };
  
- export function expandTimelineRequest(timeline) {
+ export function expandTimelineRequest(timeline, isLoadingMore) {
    return {
      type: TIMELINE_EXPAND_REQUEST,
      timeline,
diff --cc app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
index f844a52f4,9c9f62d82..000000000
--- a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
@@@ -1,93 -1,100 +1,190 @@@
  import React from 'react';
  import PropTypes from 'prop-types';
  import ImmutablePropTypes from 'react-immutable-proptypes';
++<<<<<<< HEAD
 +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 +import Button from '../../../components/button';
 +import SettingToggle from '../components/setting_toggle';
 +import SettingText from '../components/setting_text';
 +import { Map as ImmutableMap } from 'immutable';
 +
 +const messages = defineMessages({
 +  filter_regex: { id: 'tag.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
 +  show_local_only: { id: 'tag.column_settings.show_local_only', defaultMessage: 'Show local only' },
 +  settings: { id: 'tag.settings', defaultMessage: 'Column settings' },
 +  add_favourite_tags_public: { id: 'tag.add_favourite.public', defaultMessage: 'add in the favourite tags (Public)' },
 +  add_favourite_tags_unlisted: { id: 'tag.add_favourite.unlisted', defaultMessage: 'add in the favourite tags (Unlisted)' },
 +  remove_favourite_tags: { id: 'tag.remove_favourite', defaultMessage: 'Remove from the favourite tags' },
 +});
 +
 +@injectIntl
 +export default class ColumnSettings extends React.PureComponent {
 +
 +  static propTypes = {
 +    tag: PropTypes.string.isRequired,
 +    settings: ImmutablePropTypes.map.isRequired,
 +    onChange: PropTypes.func.isRequired,
 +    addFavouriteTags: PropTypes.func.isRequired,
 +    removeFavouriteTags: PropTypes.func.isRequired,
 +    isRegistered: PropTypes.bool.isRequired,
 +    intl: PropTypes.object.isRequired,
 +  };
 +
 +  addFavouriteTags = (visibility) => {
 +    this.props.addFavouriteTags(this.props.tag, visibility);
 +  };
 +
 +  addPublic = () => {
 +    this.addFavouriteTags('public');
 +  };
 +
 +  addUnlisted = () => {
 +    this.addFavouriteTags('unlisted');
 +  };
 +
 +  removeFavouriteTags = () => {
 +    this.props.removeFavouriteTags(this.props.tag);
 +  };
 +
 +  render () {
 +    const { tag, settings, onChange, intl, isRegistered } = this.props;
 +    const initialSettings = ImmutableMap({
 +      shows: ImmutableMap({
 +        local: false,
 +      }),
 +
 +      regex: ImmutableMap({
 +        body: '',
 +      }),
 +    });
 +
 +    const favouriteTagButton = (isRegistered) => {
 +      if(isRegistered) {
 +        return (
 +          <div className='column-settings__row'>
 +            <Button className='favourite-tags__remove-button-in-column' text={intl.formatMessage(messages.remove_favourite_tags)} onClick={this.removeFavouriteTags} block />
 +          </div>
 +        );
 +      } else {
 +        return (
 +          <div className='column-settings__row'>
 +            <Button className='favourite-tags__add-button-in-column' text={intl.formatMessage(messages.add_favourite_tags_public)} onClick={this.addPublic} block />
 +            <Button className='favourite-tags__add-button-in-column' text={intl.formatMessage(messages.add_favourite_tags_unlisted)} onClick={this.addUnlisted} block />
 +          </div>
 +        );
 +      }
 +    };
 +
 +    return (
 +      <div>
 +        {favouriteTagButton(isRegistered)}
 +        <span className='column-settings__section'><FormattedMessage id='tag.column_settings.basic' defaultMessage='Basic' /></span>
 +
 +        <div className='column-settings__row'>
 +          <SettingToggle tag={tag} prefix='hashtag_timeline' settings={settings.get(`${tag}`, initialSettings)} settingKey={['shows', 'local']} onChange={onChange} label={intl.formatMessage(messages.show_local_only)} />
 +        </div>
 +
 +        <span className='column-settings__section'><FormattedMessage id='tag.column_settings.advanced' defaultMessage='Advanced' /></span>
 +
 +        <div className='column-settings__row'>
 +          <SettingText tag={tag} prefix='hashtag_timeline' settings={settings.get(`${tag}`, initialSettings)} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
 +        </div>
++=======
+ import { injectIntl, FormattedMessage } from 'react-intl';
+ import Toggle from 'react-toggle';
+ import AsyncSelect from 'react-select/lib/Async';
+ 
+ export default @injectIntl
+ class ColumnSettings extends React.PureComponent {
+ 
+   static propTypes = {
+     settings: ImmutablePropTypes.map.isRequired,
+     onChange: PropTypes.func.isRequired,
+     onLoad: PropTypes.func.isRequired,
+     intl: PropTypes.object.isRequired,
+   };
+ 
+   state = {
+     open: this.hasTags(),
+   };
+ 
+   hasTags () {
+     return ['all', 'any', 'none'].map(mode => this.tags(mode).length > 0).includes(true);
+   }
+ 
+   tags (mode) {
+     let tags = this.props.settings.getIn(['tags', mode]) || [];
+     if (tags.toJSON) {
+       return tags.toJSON();
+     } else {
+       return tags;
+     }
+   };
+ 
+   onSelect = (mode) => {
+     return (value) => {
+       this.props.onChange(['tags', mode], value);
+     };
+   };
+ 
+   onToggle = () => {
+     if (this.state.open && this.hasTags()) {
+       this.props.onChange('tags', {});
+     }
+     this.setState({ open: !this.state.open });
+   };
+ 
+   modeSelect (mode) {
+     return (
+       <div className='column-settings__section'>
+         {this.modeLabel(mode)}
+         <AsyncSelect
+           isMulti
+           autoFocus
+           value={this.tags(mode)}
+           settings={this.props.settings}
+           settingPath={['tags', mode]}
+           onChange={this.onSelect(mode)}
+           loadOptions={this.props.onLoad}
+           classNamePrefix='column-settings__hashtag-select'
+           name='tags'
+         />
+       </div>
+     );
+   }
+ 
+   modeLabel (mode) {
+     switch(mode) {
+     case 'any':  return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
+     case 'all':  return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
+     case 'none': return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
+     }
+     return '';
+   };
+ 
+   render () {
+     return (
+       <div>
+         <div className='column-settings__row'>
+           <div className='setting-toggle'>
+             <Toggle
+               id='hashtag.column_settings.tag_toggle'
+               onChange={this.onToggle}
+               checked={this.state.open}
+             />
+             <span className='setting-toggle__label'>
+               <FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' />
+             </span>
+           </div>
+         </div>
+         {this.state.open &&
+           <div className='column-settings__hashtags'>
+             {this.modeSelect('any')}
+             {this.modeSelect('all')}
+             {this.modeSelect('none')}
+           </div>
+         }
++>>>>>>> v2.7.0
        </div>
      );
    }
diff --cc app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
index f1aca44dd,c5098052c..000000000
--- a/app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
@@@ -1,31 -1,31 +1,60 @@@
  import { connect } from 'react-redux';
  import ColumnSettings from '../components/column_settings';
++<<<<<<< HEAD
 +import { changeSetting, saveSettings } from '../../../actions/settings';
 +import { addFavouriteTags, removeFavouriteTags } from '../../../actions/favourite_tags';
 +
 +const mapStateToProps = (state, { tag }) => ({
 +  settings: state.getIn(['settings', 'tag']),
 +  isRegistered: state.getIn(['favourite_tags', 'tags']).some(t => t.get('name') === tag),
 +});
 +
 +const mapDispatchToProps = dispatch => ({
 +
 +  onChange (tag, key, checked) {
 +    dispatch(changeSetting(['tag', `${tag}`, ...key], checked));
 +  },
 +
 +  onSave () {
 +    dispatch(saveSettings());
 +  },
 +
 +  addFavouriteTags (tag, visibility) {
 +    dispatch(addFavouriteTags(tag, visibility));
 +  },
 +
 +  removeFavouriteTags (tag) {
 +    dispatch(removeFavouriteTags(tag));
 +  },
 +
++=======
+ import { changeColumnParams } from '../../../actions/columns';
+ import api from '../../../api';
+ 
+ const mapStateToProps = (state, { columnId }) => {
+   const columns = state.getIn(['settings', 'columns']);
+   const index   = columns.findIndex(c => c.get('uuid') === columnId);
+ 
+   if (!(columnId && index >= 0)) {
+     return {};
+   }
+ 
+   return { settings: columns.get(index).get('params') };
+ };
+ 
+ const mapDispatchToProps = (dispatch, { columnId }) => ({
+   onChange (key, value) {
+     dispatch(changeColumnParams(columnId, key, value));
+   },
+ 
+   onLoad (value) {
+     return api().get('/api/v2/search', { params: { q: value } }).then(response => {
+       return (response.data.hashtags || []).map((tag) => {
+         return { value: tag.name, label: `#${tag.name}` };
+       });
+     });
+   },
++>>>>>>> v2.7.0
  });
  
  export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --cc app/javascript/mastodon/features/hashtag_timeline/index.js
index b877f6700,c2e026d13..000000000
--- a/app/javascript/mastodon/features/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.js
@@@ -5,7 -5,7 +5,11 @@@ import StatusListContainer from './cont
  import Column from '../../components/column';
  import ColumnHeader from '../../components/column_header';
  import ColumnSettingsContainer from './containers/column_settings_container';
++<<<<<<< HEAD
 +import { expandHashtagTimeline } from '../../actions/timelines';
++=======
+ import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines';
++>>>>>>> v2.7.0
  import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
  import { FormattedMessage } from 'react-intl';
  import { connectHashtagStream } from '../../actions/streaming';
@@@ -47,8 -72,18 +78,23 @@@ class HashtagTimeline extends React.Pur
      this.column.scrollTop();
    }
  
++<<<<<<< HEAD
 +  _subscribe (dispatch, id, isLocal) {
 +    this.disconnect = dispatch(connectHashtagStream(id, isLocal));
++=======
+   _subscribe (dispatch, id, tags = {}) {
+     let any  = (tags.any || []).map(tag => tag.value);
+     let all  = (tags.all || []).map(tag => tag.value);
+     let none = (tags.none || []).map(tag => tag.value);
+ 
+     [id, ...any].map((tag) => {
+       this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => {
+         let tags = status.tags.map(tag => tag.name);
+         return all.filter(tag => tags.includes(tag)).length === all.length &&
+                none.filter(tag => tags.includes(tag)).length === 0;
+       })));
+     });
++>>>>>>> v2.7.0
    }
  
    _unsubscribe () {
@@@ -59,18 -92,20 +103,35 @@@
    }
  
    componentDidMount () {
++<<<<<<< HEAD
 +    const { dispatch, isLocal } = this.props;
 +    const { id } = this.props.params;
 +
 +    dispatch(expandHashtagTimeline(id, isLocal));
 +    this._subscribe(dispatch, id, isLocal);
 +  }
 +
 +  componentWillReceiveProps (nextProps) {
 +    if (nextProps.params.id !== this.props.params.id || nextProps.isLocal !== this.props.isLocal) {
 +      this.props.dispatch(expandHashtagTimeline(nextProps.params.id, nextProps.isLocal));
 +      this._unsubscribe();
 +      this._subscribe(this.props.dispatch, nextProps.params.id, nextProps.isLocal);
++=======
+     const { dispatch } = this.props;
+     const { id, tags } = this.props.params;
+ 
+     dispatch(expandHashtagTimeline(id, { tags }));
+   }
+ 
+   componentWillReceiveProps (nextProps) {
+     const { dispatch, params } = this.props;
+     const { id, tags } = nextProps.params;
+     if (id !== params.id || !isEqual(tags, params.tags)) {
+       this._unsubscribe();
+       this._subscribe(dispatch, id, tags);
+       this.props.dispatch(clearTimeline(`hashtag:${id}`));
+       this.props.dispatch(expandHashtagTimeline(id, { tags }));
++>>>>>>> v2.7.0
      }
    }
  
@@@ -83,7 -118,8 +144,12 @@@
    }
  
    handleLoadMore = maxId => {
++<<<<<<< HEAD
 +    this.props.dispatch(expandHashtagTimeline(this.props.params.id, { maxId, isLocal: this.props.isLocal }));
++=======
+     const { id, tags } = this.props.params;
+     this.props.dispatch(expandHashtagTimeline(id, { maxId, tags }));
++>>>>>>> v2.7.0
    }
  
    render () {
@@@ -104,9 -140,7 +170,13 @@@
            multiColumn={multiColumn}
            showBackButton
          >
++<<<<<<< HEAD
 +          <ColumnSettingsContainer
 +            tag={id}
 +          />
++=======
+           {columnId && <ColumnSettingsContainer columnId={columnId} />}
++>>>>>>> v2.7.0
          </ColumnHeader>
  
          <StatusListContainer
@lnanase lnanase added help wanted Solutions or ideas are needed priority high Should respond this quickly update Update version from the original repo labels Jan 22, 2019
@lnanase
Copy link
Collaborator Author

lnanase commented Jan 23, 2019

また、一つの案として、今回修正が入った hashtag_timeline はそのままとして(使わずに)
これまでアイマストドンが改修を加えてきた hashtag_timeline を正とし別名にして、
timeline_container.js が呼び出す hashtag_timeline を改修して
これまでのものを呼ぶようにする、修正もあるかと思います
(それでも修正量や今後の影響は少なくないですが

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Solutions or ideas are needed priority high Should respond this quickly update Update version from the original repo
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants