Merge branch 'kb_lts' into kb_development
This commit is contained in:
commit
5177e8d07f
36 changed files with 337 additions and 98 deletions
56
INSTALL.md
56
INSTALL.md
|
@ -33,58 +33,10 @@ Sudachiインストール終了後、追加で`/etc/elasticsearch/sudachi/config
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 新規インストールの場合
|
## インストール手順
|
||||||
|
|
||||||
1. 本家Mastodonとセットアップ手順はほとんど一緒です。kmyblueが独自に必須ソフトウェアを追加したわけではありません。ただしkmyblueはMastodonの開発中コードを取り込んでいるので、Rubyなどのバージョンアップ作業が必要になる場合があります。Mastodon公式のセットアップ手順を盲信せず、画面の指示に従ってインストールを進めてください。CloudFlareを組み合わせてセットアップしたとき、サーバーに接続すると400が出るなどのトラブルが出ることがありますが、大抵はMastodon本家由来のトラブルだと思われるので基本サポートはしません
|
[Wiki](https://github.com/kmycode/mastodon/wiki/Installation)を参照してください
|
||||||
2. ただひとつ差異があります。Gitリポジトリはこのkmyblueに向けてください。`kb_development`ブランチの最新コミットではなく、`kb`で始まる最新のタグを取り込むことを強くおすすめします
|
|
||||||
|
|
||||||
## 本家Mastodonからのマイグレーションの場合
|
## アップデート手順
|
||||||
|
|
||||||
kmyblueから本家Mastodonに戻りたい場合もあると思いますので、**必ずデータベースのバックアップをとってください**。
|
[Wiki](https://github.com/kmycode/mastodon/wiki/Updation)を参照してください
|
||||||
|
|
||||||
1. kmyblueのリリースノートに、kmyblueバージョンに対応した本家Mastodonのバージョンが記載されています。それを参照して、まず本家Mastodonをそのバージョンまでバージョンアップしてください
|
|
||||||
2. Gitのリモートにkmyblueを追加して、そのままチェックアウトしてください
|
|
||||||
3. データベースのマイグレーションなどを行ってください
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo systemctl stop mastodon-*
|
|
||||||
|
|
||||||
bundle install
|
|
||||||
yarn install
|
|
||||||
RAILS_ENV=production bin/rails db:migrate
|
|
||||||
RAILS_ENV=production bin/rails assets:clobber
|
|
||||||
RAILS_ENV=production bin/rails assets:precompile
|
|
||||||
|
|
||||||
# ElasticSearchを使用する場合
|
|
||||||
RAILS_ENV=production bin/tootctl search deploy
|
|
||||||
|
|
||||||
RAILS_ENV=production bin/tootctl cache clear
|
|
||||||
sudo systemctl start mastodon-web mastodon-streaming mastodon-sidekiq
|
|
||||||
```
|
|
||||||
|
|
||||||
## kmyblueのバージョンをアップデートする
|
|
||||||
|
|
||||||
リリースノートを参照して、自分に必要な作業を特定してください。面倒な場合は毎回全部実行してしまっても問題ありません。(プリコンパイルが失敗する可能性があるのでご注意ください)
|
|
||||||
|
|
||||||
```
|
|
||||||
# Rubyパッケージアップデート
|
|
||||||
bundle intall
|
|
||||||
|
|
||||||
# JSパッケージアップデート
|
|
||||||
yarn install
|
|
||||||
|
|
||||||
# DBマイグレーション
|
|
||||||
RAILS_ENV=production bin/rails db:migrate
|
|
||||||
|
|
||||||
# プリコンパイル
|
|
||||||
# うまくいかない場合(エラーは出ないのにWeb表示が崩れる)はclobberしてからprecompile
|
|
||||||
# それでもうまくいかない場合はsudo systemctl stop mastodon-webしてから試す
|
|
||||||
# それでもうまくいかない場合はサーバーOSを再起動してから試す
|
|
||||||
RAILS_ENV=production bin/rails assets:clobber # プリコンパイルがうまくいかない場合
|
|
||||||
RAILS_ENV=production bin/rails assets:precompile
|
|
||||||
|
|
||||||
# サーバー再起動
|
|
||||||
sudo systemctl restart mastodon-web
|
|
||||||
sudo systemctl restart mastodon-streaming
|
|
||||||
sudo systemctl restart mastodon-sidekiq
|
|
||||||
```
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Api::V1::Statuses::EmojiReactionedByAccountsController < Api::BaseControll
|
||||||
|
|
||||||
def paginated_emoji_reactions
|
def paginated_emoji_reactions
|
||||||
EmojiReaction.paginate_by_max_id(
|
EmojiReaction.paginate_by_max_id(
|
||||||
limit_param(1000), # limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
params[:max_id],
|
params[:max_id],
|
||||||
params[:since_id]
|
params[:since_id]
|
||||||
)
|
)
|
||||||
|
|
|
@ -785,11 +785,12 @@ export function insertExpirationCompose(position, data) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertReferenceCompose(position, url) {
|
export function insertReferenceCompose(position, url, attributeType) {
|
||||||
return {
|
return {
|
||||||
type: COMPOSE_REFERENCE_INSERT,
|
type: COMPOSE_REFERENCE_INSERT,
|
||||||
position,
|
position,
|
||||||
url,
|
url,
|
||||||
|
attributeType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,10 @@ export const EMOJI_REACTIONS_FETCH_REQUEST = 'EMOJI_REACTIONS_FETCH_REQUEST';
|
||||||
export const EMOJI_REACTIONS_FETCH_SUCCESS = 'EMOJI_REACTIONS_FETCH_SUCCESS';
|
export const EMOJI_REACTIONS_FETCH_SUCCESS = 'EMOJI_REACTIONS_FETCH_SUCCESS';
|
||||||
export const EMOJI_REACTIONS_FETCH_FAIL = 'EMOJI_REACTIONS_FETCH_FAIL';
|
export const EMOJI_REACTIONS_FETCH_FAIL = 'EMOJI_REACTIONS_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const EMOJI_REACTIONS_EXPAND_REQUEST = 'EMOJI_REACTIONS_EXPAND_REQUEST';
|
||||||
|
export const EMOJI_REACTIONS_EXPAND_SUCCESS = 'EMOJI_REACTIONS_EXPAND_SUCCESS';
|
||||||
|
export const EMOJI_REACTIONS_EXPAND_FAIL = 'EMOJI_REACTIONS_EXPAND_FAIL';
|
||||||
|
|
||||||
export const PIN_REQUEST = 'PIN_REQUEST';
|
export const PIN_REQUEST = 'PIN_REQUEST';
|
||||||
export const PIN_SUCCESS = 'PIN_SUCCESS';
|
export const PIN_SUCCESS = 'PIN_SUCCESS';
|
||||||
export const PIN_FAIL = 'PIN_FAIL';
|
export const PIN_FAIL = 'PIN_FAIL';
|
||||||
|
@ -547,8 +551,9 @@ export function fetchEmojiReactions(id) {
|
||||||
dispatch(fetchEmojiReactionsRequest(id));
|
dispatch(fetchEmojiReactionsRequest(id));
|
||||||
|
|
||||||
api(getState).get(`/api/v1/statuses/${id}/emoji_reactioned_by`).then(response => {
|
api(getState).get(`/api/v1/statuses/${id}/emoji_reactioned_by`).then(response => {
|
||||||
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(importFetchedAccounts(response.data.map((er) => er.account)));
|
dispatch(importFetchedAccounts(response.data.map((er) => er.account)));
|
||||||
dispatch(fetchEmojiReactionsSuccess(id, response.data));
|
dispatch(fetchEmojiReactionsSuccess(id, response.data, next ? next.uri : null));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchEmojiReactionsFail(id, error));
|
dispatch(fetchEmojiReactionsFail(id, error));
|
||||||
});
|
});
|
||||||
|
@ -562,11 +567,12 @@ export function fetchEmojiReactionsRequest(id) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchEmojiReactionsSuccess(id, accounts) {
|
export function fetchEmojiReactionsSuccess(id, accounts, next) {
|
||||||
return {
|
return {
|
||||||
type: EMOJI_REACTIONS_FETCH_SUCCESS,
|
type: EMOJI_REACTIONS_FETCH_SUCCESS,
|
||||||
id,
|
id,
|
||||||
accounts,
|
accounts,
|
||||||
|
next,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,6 +583,48 @@ export function fetchEmojiReactionsFail(id, error) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function expandEmojiReactions(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const url = getState().getIn(['user_lists', 'emoji_reactioned_by', id, 'next']);
|
||||||
|
if (url === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(expandEmojiReactionsRequest(id));
|
||||||
|
|
||||||
|
api(getState).get(url).then(response => {
|
||||||
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
|
|
||||||
|
dispatch(importFetchedAccounts(response.data.map((er) => er.account)));
|
||||||
|
dispatch(expandEmojiReactionsSuccess(id, response.data, next ? next.uri : null));
|
||||||
|
}).catch(error => dispatch(expandEmojiReactionsFail(id, error)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expandEmojiReactionsRequest(id) {
|
||||||
|
return {
|
||||||
|
type: EMOJI_REACTIONS_EXPAND_REQUEST,
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expandEmojiReactionsSuccess(id, accounts, next) {
|
||||||
|
return {
|
||||||
|
type: EMOJI_REACTIONS_EXPAND_SUCCESS,
|
||||||
|
id,
|
||||||
|
accounts,
|
||||||
|
next,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expandEmojiReactionsFail(id, error) {
|
||||||
|
return {
|
||||||
|
type: EMOJI_REACTIONS_EXPAND_FAIL,
|
||||||
|
id,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function fetchStatusReferences(id) {
|
export function fetchStatusReferences(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(fetchStatusReferencesRequest(id));
|
dispatch(fetchStatusReferencesRequest(id));
|
||||||
|
|
|
@ -52,6 +52,7 @@ const messages = defineMessages({
|
||||||
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
||||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
||||||
reference: { id: 'status.reference', defaultMessage: 'Add reference' },
|
reference: { id: 'status.reference', defaultMessage: 'Add reference' },
|
||||||
|
quote: { id: 'status.quote', defaultMessage: 'Add ref (quote in other servers)' },
|
||||||
hide: { id: 'status.hide', defaultMessage: 'Hide post' },
|
hide: { id: 'status.hide', defaultMessage: 'Hide post' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||||
|
@ -97,6 +98,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
onBookmarkCategoryAdder: PropTypes.func,
|
onBookmarkCategoryAdder: PropTypes.func,
|
||||||
onFilter: PropTypes.func,
|
onFilter: PropTypes.func,
|
||||||
onAddFilter: PropTypes.func,
|
onAddFilter: PropTypes.func,
|
||||||
|
onReference: PropTypes.func,
|
||||||
|
onQuote: PropTypes.func,
|
||||||
onInteractionModal: PropTypes.func,
|
onInteractionModal: PropTypes.func,
|
||||||
withDismiss: PropTypes.bool,
|
withDismiss: PropTypes.bool,
|
||||||
withCounters: PropTypes.bool,
|
withCounters: PropTypes.bool,
|
||||||
|
@ -271,6 +274,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
this.props.onReference(this.props.status);
|
this.props.onReference(this.props.status);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleQuote = () => {
|
||||||
|
this.props.onQuote(this.props.status);
|
||||||
|
};
|
||||||
|
|
||||||
handleHideClick = () => {
|
handleHideClick = () => {
|
||||||
this.props.onFilter();
|
this.props.onFilter();
|
||||||
};
|
};
|
||||||
|
@ -316,6 +323,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (publicStatus) {
|
if (publicStatus) {
|
||||||
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
||||||
|
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClickOriginal });
|
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClickOriginal });
|
||||||
|
|
|
@ -203,7 +203,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
onReference (status) {
|
onReference (status) {
|
||||||
dispatch(insertReferenceCompose(0, status.get('url')));
|
dispatch(insertReferenceCompose(0, status.get('url'), 'BT'));
|
||||||
|
},
|
||||||
|
|
||||||
|
onQuote (status) {
|
||||||
|
dispatch(insertReferenceCompose(0, status.get('url'), 'QT'));
|
||||||
},
|
},
|
||||||
|
|
||||||
onTranslate (status) {
|
onTranslate (status) {
|
||||||
|
|
|
@ -8,7 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { fetchEmojiReactions } from 'mastodon/actions/interactions';
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
|
import { fetchEmojiReactions, expandEmojiReactions } from 'mastodon/actions/interactions';
|
||||||
import ColumnHeader from 'mastodon/components/column_header';
|
import ColumnHeader from 'mastodon/components/column_header';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
|
@ -25,7 +27,9 @@ const messages = defineMessages({
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
return {
|
return {
|
||||||
accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId]),
|
accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'items']),
|
||||||
|
hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'next']),
|
||||||
|
isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'isLoading'], true),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +39,8 @@ class EmojiReactions extends ImmutablePureComponent {
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
accountIds: ImmutablePropTypes.list,
|
accountIds: ImmutablePropTypes.list,
|
||||||
|
hasMore: PropTypes.bool,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -45,18 +51,16 @@ class EmojiReactions extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
|
||||||
this.props.dispatch(fetchEmojiReactions(nextProps.params.statusId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRefresh = () => {
|
handleRefresh = () => {
|
||||||
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
|
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleLoadMore = debounce(() => {
|
||||||
|
this.props.dispatch(expandEmojiReactions(this.props.params.statusId));
|
||||||
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, accountIds, multiColumn } = this.props;
|
const { intl, accountIds, hasMore, isLoading, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -68,7 +72,7 @@ class EmojiReactions extends ImmutablePureComponent {
|
||||||
|
|
||||||
let groups = {};
|
let groups = {};
|
||||||
for (const emoji_reaction of accountIds) {
|
for (const emoji_reaction of accountIds) {
|
||||||
const key = emoji_reaction.account.id;
|
const key = emoji_reaction.account_id;
|
||||||
const value = emoji_reaction;
|
const value = emoji_reaction;
|
||||||
if (!groups[key]) groups[key] = [value];
|
if (!groups[key]) groups[key] = [value];
|
||||||
else groups[key].push(value);
|
else groups[key].push(value);
|
||||||
|
@ -82,12 +86,15 @@ class EmojiReactions extends ImmutablePureComponent {
|
||||||
showBackButton
|
showBackButton
|
||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
extraButton={(
|
extraButton={(
|
||||||
<button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
<button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleLoadMore}><Icon id='refresh' /></button>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='emoji_reactions'
|
scrollKey='emoji_reactions'
|
||||||
|
onLoadMore={this.handleLoadMore}
|
||||||
|
hasMore={hasMore}
|
||||||
|
isLoading={isLoading}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
|
|
|
@ -46,6 +46,7 @@ const messages = defineMessages({
|
||||||
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
||||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
||||||
reference: { id: 'status.reference', defaultMessage: 'Add reference' },
|
reference: { id: 'status.reference', defaultMessage: 'Add reference' },
|
||||||
|
quote: { id: 'status.quote', defaultMessage: 'Add ref (quote in other servers)' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||||
|
@ -74,6 +75,7 @@ class ActionBar extends PureComponent {
|
||||||
onFavourite: PropTypes.func.isRequired,
|
onFavourite: PropTypes.func.isRequired,
|
||||||
onEmojiReact: PropTypes.func.isRequired,
|
onEmojiReact: PropTypes.func.isRequired,
|
||||||
onReference: PropTypes.func.isRequired,
|
onReference: PropTypes.func.isRequired,
|
||||||
|
onQuote: PropTypes.func.isRequired,
|
||||||
onBookmark: PropTypes.func.isRequired,
|
onBookmark: PropTypes.func.isRequired,
|
||||||
onBookmarkCategoryAdder: PropTypes.func.isRequired,
|
onBookmarkCategoryAdder: PropTypes.func.isRequired,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
|
@ -208,6 +210,10 @@ class ActionBar extends PureComponent {
|
||||||
this.props.onReference(this.props.status);
|
this.props.onReference(this.props.status);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleQuote = () => {
|
||||||
|
this.props.onQuote(this.props.status);
|
||||||
|
};
|
||||||
|
|
||||||
handleEmojiPick = (data) => {
|
handleEmojiPick = (data) => {
|
||||||
this.props.onEmojiReact(this.props.status, data);
|
this.props.onEmojiReact(this.props.status, data);
|
||||||
};
|
};
|
||||||
|
@ -248,6 +254,7 @@ class ActionBar extends PureComponent {
|
||||||
|
|
||||||
if (publicStatus) {
|
if (publicStatus) {
|
||||||
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
||||||
|
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
|
||||||
}
|
}
|
||||||
menu.push({ text: intl.formatMessage(messages.bookmark_category), action: this.handleBookmarkCategoryAdderClick });
|
menu.push({ text: intl.formatMessage(messages.bookmark_category), action: this.handleBookmarkCategoryAdderClick });
|
||||||
|
|
||||||
|
|
|
@ -363,7 +363,11 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleReference = (status) => {
|
handleReference = (status) => {
|
||||||
this.props.dispatch(insertReferenceCompose(0, status.get('url')));
|
this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'BT'));
|
||||||
|
};
|
||||||
|
|
||||||
|
handleQuote = (status) => {
|
||||||
|
this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'QT'));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBookmarkClick = (status) => {
|
handleBookmarkClick = (status) => {
|
||||||
|
@ -750,6 +754,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onReblog={this.handleReblogClick}
|
onReblog={this.handleReblogClick}
|
||||||
onReblogForceModal={this.handleReblogForceModalClick}
|
onReblogForceModal={this.handleReblogForceModalClick}
|
||||||
onReference={this.handleReference}
|
onReference={this.handleReference}
|
||||||
|
onQuote={this.handleQuote}
|
||||||
onBookmark={this.handleBookmarkClick}
|
onBookmark={this.handleBookmarkClick}
|
||||||
onBookmarkCategoryAdder={this.handleBookmarkCategoryAdderClick}
|
onBookmarkCategoryAdder={this.handleBookmarkCategoryAdderClick}
|
||||||
onDelete={this.handleDeleteClick}
|
onDelete={this.handleDeleteClick}
|
||||||
|
|
|
@ -683,6 +683,7 @@
|
||||||
"status.open": "Expand this post",
|
"status.open": "Expand this post",
|
||||||
"status.pin": "Pin on profile",
|
"status.pin": "Pin on profile",
|
||||||
"status.pinned": "Pinned post",
|
"status.pinned": "Pinned post",
|
||||||
|
"status.quote": "Ref (quote in other servers)",
|
||||||
"status.read_more": "Read more",
|
"status.read_more": "Read more",
|
||||||
"status.reblog": "Boost",
|
"status.reblog": "Boost",
|
||||||
"status.reblog_private": "Boost with original visibility",
|
"status.reblog_private": "Boost with original visibility",
|
||||||
|
|
|
@ -769,6 +769,7 @@
|
||||||
"status.open": "詳細を表示",
|
"status.open": "詳細を表示",
|
||||||
"status.pin": "プロフィールに固定表示",
|
"status.pin": "プロフィールに固定表示",
|
||||||
"status.pinned": "固定された投稿",
|
"status.pinned": "固定された投稿",
|
||||||
|
"status.quote": "参照 (他サーバーで引用扱い)",
|
||||||
"status.read_more": "もっと見る",
|
"status.read_more": "もっと見る",
|
||||||
"status.reblog": "ブースト",
|
"status.reblog": "ブースト",
|
||||||
"status.reblog_private": "ブースト",
|
"status.reblog_private": "ブースト",
|
||||||
|
|
|
@ -252,10 +252,11 @@ const insertExpiration = (state, position, data) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const insertReference = (state, url) => {
|
const insertReference = (state, url, attributeType) => {
|
||||||
const oldText = state.get('text');
|
const oldText = state.get('text');
|
||||||
|
const attribute = attributeType || 'BT';
|
||||||
|
|
||||||
if (oldText.indexOf(`BT ${url}`) >= 0) {
|
if (oldText.indexOf(`${attribute} ${url}`) >= 0) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,12 +272,12 @@ const insertReference = (state, url) => {
|
||||||
|
|
||||||
if (oldText.length > 0) {
|
if (oldText.length > 0) {
|
||||||
const lastLine = oldText.slice(oldText.lastIndexOf('\n') + 1, oldText.length - 1);
|
const lastLine = oldText.slice(oldText.lastIndexOf('\n') + 1, oldText.length - 1);
|
||||||
if (lastLine.startsWith('BT ')) {
|
if (lastLine.startsWith(`${attribute} `)) {
|
||||||
newLine = '\n';
|
newLine = '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const referenceText = `${newLine}BT ${url}`;
|
const referenceText = `${newLine}${attribute} ${url}`;
|
||||||
const text = `${oldText}${referenceText}`;
|
const text = `${oldText}${referenceText}`;
|
||||||
|
|
||||||
return state.merge({
|
return state.merge({
|
||||||
|
@ -526,7 +527,7 @@ export default function compose(state = initialState, action) {
|
||||||
case COMPOSE_EXPIRATION_INSERT:
|
case COMPOSE_EXPIRATION_INSERT:
|
||||||
return insertExpiration(state, action.position, action.data);
|
return insertExpiration(state, action.position, action.data);
|
||||||
case COMPOSE_REFERENCE_INSERT:
|
case COMPOSE_REFERENCE_INSERT:
|
||||||
return insertReference(state, action.url);
|
return insertReference(state, action.url, action.attributeType);
|
||||||
case COMPOSE_UPLOAD_CHANGE_SUCCESS:
|
case COMPOSE_UPLOAD_CHANGE_SUCCESS:
|
||||||
return state
|
return state
|
||||||
.set('is_changing_upload', false)
|
.set('is_changing_upload', false)
|
||||||
|
|
|
@ -57,7 +57,12 @@ import {
|
||||||
FAVOURITES_EXPAND_REQUEST,
|
FAVOURITES_EXPAND_REQUEST,
|
||||||
FAVOURITES_EXPAND_SUCCESS,
|
FAVOURITES_EXPAND_SUCCESS,
|
||||||
FAVOURITES_EXPAND_FAIL,
|
FAVOURITES_EXPAND_FAIL,
|
||||||
|
EMOJI_REACTIONS_FETCH_REQUEST,
|
||||||
EMOJI_REACTIONS_FETCH_SUCCESS,
|
EMOJI_REACTIONS_FETCH_SUCCESS,
|
||||||
|
EMOJI_REACTIONS_FETCH_FAIL,
|
||||||
|
EMOJI_REACTIONS_EXPAND_REQUEST,
|
||||||
|
EMOJI_REACTIONS_EXPAND_SUCCESS,
|
||||||
|
EMOJI_REACTIONS_EXPAND_FAIL,
|
||||||
STATUS_REFERENCES_FETCH_SUCCESS,
|
STATUS_REFERENCES_FETCH_SUCCESS,
|
||||||
} from '../actions/interactions';
|
} from '../actions/interactions';
|
||||||
import {
|
import {
|
||||||
|
@ -101,12 +106,33 @@ const normalizeList = (state, path, accounts, next) => {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeEmojiReactionList = (state, path, rows, next) => {
|
||||||
|
return state.setIn(path, ImmutableMap({
|
||||||
|
next,
|
||||||
|
items: ImmutableList(rows.map(normalizeEmojiReactionRow)),
|
||||||
|
isLoading: false,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const appendToList = (state, path, accounts, next) => {
|
const appendToList = (state, path, accounts, next) => {
|
||||||
return state.updateIn(path, map => {
|
return state.updateIn(path, map => {
|
||||||
return map.set('next', next).set('isLoading', false).update('items', list => list.concat(accounts.map(item => item.id)));
|
return map.set('next', next).set('isLoading', false).update('items', list => list.concat(accounts.map(item => item.id)));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const appendToEmojiReactionList = (state, path, rows, next) => {
|
||||||
|
return state.updateIn(path, map => {
|
||||||
|
return map.set('next', next).set('isLoading', false).update('items', list => list.concat(rows.map(normalizeEmojiReactionRow)));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeEmojiReactionRow = (row) => {
|
||||||
|
const accountId = row.account ? row.account.id : 0;
|
||||||
|
delete row.account;
|
||||||
|
row.account_id = accountId;
|
||||||
|
return row;
|
||||||
|
};
|
||||||
|
|
||||||
const normalizeFollowRequest = (state, notification) => {
|
const normalizeFollowRequest = (state, notification) => {
|
||||||
return state.updateIn(['follow_requests', 'items'], list => {
|
return state.updateIn(['follow_requests', 'items'], list => {
|
||||||
return list.filterNot(item => item === notification.account.id).unshift(notification.account.id);
|
return list.filterNot(item => item === notification.account.id).unshift(notification.account.id);
|
||||||
|
@ -167,8 +193,16 @@ export default function userLists(state = initialState, action) {
|
||||||
case FAVOURITES_FETCH_FAIL:
|
case FAVOURITES_FETCH_FAIL:
|
||||||
case FAVOURITES_EXPAND_FAIL:
|
case FAVOURITES_EXPAND_FAIL:
|
||||||
return state.setIn(['favourited_by', action.id, 'isLoading'], false);
|
return state.setIn(['favourited_by', action.id, 'isLoading'], false);
|
||||||
|
case EMOJI_REACTIONS_FETCH_REQUEST:
|
||||||
|
case EMOJI_REACTIONS_EXPAND_REQUEST:
|
||||||
|
return state.setIn(['emoji_reactioned_by', action.id, 'isLoading'], true);
|
||||||
|
case EMOJI_REACTIONS_FETCH_FAIL:
|
||||||
|
case EMOJI_REACTIONS_EXPAND_FAIL:
|
||||||
|
return state.setIn(['emoji_reactioned_by', action.id, 'isLoading'], false);
|
||||||
case EMOJI_REACTIONS_FETCH_SUCCESS:
|
case EMOJI_REACTIONS_FETCH_SUCCESS:
|
||||||
return state.setIn(['emoji_reactioned_by', action.id], ImmutableList(action.accounts));
|
return normalizeEmojiReactionList(state, ['emoji_reactioned_by', action.id], action.accounts, action.next);
|
||||||
|
case EMOJI_REACTIONS_EXPAND_SUCCESS:
|
||||||
|
return appendToEmojiReactionList(state, ['emoji_reactioned_by', action.id], action.accounts, action.next);
|
||||||
case STATUS_REFERENCES_FETCH_SUCCESS:
|
case STATUS_REFERENCES_FETCH_SUCCESS:
|
||||||
return state.setIn(['referred_by', action.id], ImmutableList(action.statuses.map(item => item.id)));
|
return state.setIn(['referred_by', action.id], ImmutableList(action.statuses.map(item => item.id)));
|
||||||
case NOTIFICATIONS_UPDATE:
|
case NOTIFICATIONS_UPDATE:
|
||||||
|
|
|
@ -46,6 +46,7 @@ class Form::AdminSettings
|
||||||
receive_other_servers_emoji_reaction
|
receive_other_servers_emoji_reaction
|
||||||
streaming_other_servers_emoji_reaction
|
streaming_other_servers_emoji_reaction
|
||||||
enable_emoji_reaction
|
enable_emoji_reaction
|
||||||
|
check_lts_version_only
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
INTEGER_KEYS = %i(
|
INTEGER_KEYS = %i(
|
||||||
|
@ -72,6 +73,7 @@ class Form::AdminSettings
|
||||||
receive_other_servers_emoji_reaction
|
receive_other_servers_emoji_reaction
|
||||||
streaming_other_servers_emoji_reaction
|
streaming_other_servers_emoji_reaction
|
||||||
enable_emoji_reaction
|
enable_emoji_reaction
|
||||||
|
check_lts_version_only
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
UPLOAD_KEYS = %i(
|
UPLOAD_KEYS = %i(
|
||||||
|
|
|
@ -36,5 +36,13 @@ class SoftwareUpdate < ApplicationRecord
|
||||||
def urgent_pending?
|
def urgent_pending?
|
||||||
pending_to_a.any?(&:urgent?)
|
pending_to_a.any?(&:urgent?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def major_pending?
|
||||||
|
pending_to_a.any?(&:major_type?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def patch_pending?
|
||||||
|
pending_to_a.any?(&:patch_type?)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -226,6 +226,10 @@ class Status < ApplicationRecord
|
||||||
!reblog_of_id.nil?
|
!reblog_of_id.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def quote
|
||||||
|
reference_objects.where(attribute_type: 'QT').first&.target_status
|
||||||
|
end
|
||||||
|
|
||||||
def within_realtime_window?
|
def within_realtime_window?
|
||||||
created_at >= REAL_TIME_WINDOW.ago
|
created_at >= REAL_TIME_WINDOW.ago
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,6 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
|
|
||||||
attribute :quote_uri, if: :quote?
|
attribute :quote_uri, if: :quote?
|
||||||
attribute :misskey_quote, key: :_misskey_quote, if: :quote?
|
attribute :misskey_quote, key: :_misskey_quote, if: :quote?
|
||||||
attribute :misskey_content, key: :_misskey_content, if: :quote?
|
|
||||||
|
|
||||||
has_many :virtual_attachments, key: :attachment
|
has_many :virtual_attachments, key: :attachment
|
||||||
has_many :virtual_tags, key: :tag
|
has_many :virtual_tags, key: :tag
|
||||||
|
@ -172,21 +171,21 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def quote?
|
def quote?
|
||||||
object.references.count == 1 && object.account.user&.settings&.[]('single_ref_to_quote')
|
@quote ||= (object.reference_objects.count == 1 && object.account.user&.settings&.[]('single_ref_to_quote')) || object.reference_objects.where(attribute_type: 'QT').count == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def quote_post
|
||||||
|
@quote_post ||= object.quote || object.references.first
|
||||||
end
|
end
|
||||||
|
|
||||||
def quote_uri
|
def quote_uri
|
||||||
ActivityPub::TagManager.instance.uri_for(object.references.first)
|
ActivityPub::TagManager.instance.uri_for(quote_post)
|
||||||
end
|
end
|
||||||
|
|
||||||
def misskey_quote
|
def misskey_quote
|
||||||
quote_uri
|
quote_uri
|
||||||
end
|
end
|
||||||
|
|
||||||
def misskey_content
|
|
||||||
object.text
|
|
||||||
end
|
|
||||||
|
|
||||||
def poll_options
|
def poll_options
|
||||||
object.preloadable_poll.loaded_options
|
object.preloadable_poll.loaded_options
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,8 +47,8 @@ class DeliveryAntennaService
|
||||||
end
|
end
|
||||||
|
|
||||||
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
||||||
antennas = antennas.where(account: @status.account.followers) if [:public, :public_unlisted, :login, :limited].exclude?(@status.visibility.to_sym) && !@status.public_searchability?
|
antennas = antennas.where(account: @status.account.followers) if followers_only?
|
||||||
antennas = antennas.where(account: @status.mentioned_accounts) if @status.visibility.to_sym == :limited
|
antennas = antennas.where(account: @status.mentioned_accounts) if mentioned_users_only?
|
||||||
antennas = antennas.where(with_media_only: false) unless @status.with_media?
|
antennas = antennas.where(with_media_only: false) unless @status.with_media?
|
||||||
antennas = antennas.where(ignore_reblog: false) if @status.reblog?
|
antennas = antennas.where(ignore_reblog: false) if @status.reblog?
|
||||||
antennas = antennas.where(stl: false, ltl: false)
|
antennas = antennas.where(stl: false, ltl: false)
|
||||||
|
@ -116,6 +116,26 @@ class DeliveryAntennaService
|
||||||
collection.deliver!
|
collection.deliver!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def followers_only?
|
||||||
|
case @status.visibility.to_sym
|
||||||
|
when :public, :public_unlisted, :login, :limited
|
||||||
|
false
|
||||||
|
when :unlisted
|
||||||
|
if @status.local?
|
||||||
|
!@status.public_searchability?
|
||||||
|
else
|
||||||
|
info = InstanceInfo.find_by(domain: @status.account.domain)
|
||||||
|
info&.software == 'firefish' || !@status.public_searchability?
|
||||||
|
end
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mentioned_users_only?
|
||||||
|
@status.visibility.to_sym == :limited
|
||||||
|
end
|
||||||
|
|
||||||
class AntennaCollection
|
class AntennaCollection
|
||||||
def initialize(status, update, stl_home = false) # rubocop:disable Style/OptionalBooleanParameter
|
def initialize(status, update, stl_home = false) # rubocop:disable Style/OptionalBooleanParameter
|
||||||
@status = status
|
@status = status
|
||||||
|
|
|
@ -58,11 +58,11 @@ class ProcessReferencesService < BaseService
|
||||||
private
|
private
|
||||||
|
|
||||||
def references
|
def references
|
||||||
@references = @reference_parameters + scan_text!
|
@references ||= @reference_parameters + scan_text!
|
||||||
end
|
end
|
||||||
|
|
||||||
def old_references
|
def old_references
|
||||||
@old_references = @status.references.pluck(:id)
|
@old_references ||= @status.references.pluck(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def added_references
|
def added_references
|
||||||
|
@ -112,7 +112,7 @@ class ProcessReferencesService < BaseService
|
||||||
def create_notifications!
|
def create_notifications!
|
||||||
return if @added_objects.blank?
|
return if @added_objects.blank?
|
||||||
|
|
||||||
local_reference_objects = @added_objects.filter { |ref| ref.target_status.account.local? }
|
local_reference_objects = @added_objects.filter { |ref| ref.target_status.account.local? && StatusPolicy.new(ref.target_status.account, ref.status).show? }
|
||||||
return if local_reference_objects.empty?
|
return if local_reference_objects.empty?
|
||||||
|
|
||||||
LocalNotificationWorker.push_bulk(local_reference_objects) do |ref|
|
LocalNotificationWorker.push_bulk(local_reference_objects) do |ref|
|
||||||
|
|
|
@ -27,11 +27,18 @@ class SoftwareUpdateCheckService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_url
|
def api_url
|
||||||
ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check')
|
ENV.fetch('UPDATE_CHECK_URL', 'https://kmy.blue/update-check')
|
||||||
end
|
end
|
||||||
|
|
||||||
def version
|
def version
|
||||||
@version ||= Mastodon::Version.to_s.split('+')[0]
|
if ENV.fetch('UPDATE_CHECK_SOURCE', 'kmyblue') == 'kmyblue'
|
||||||
|
@version = "#{Mastodon::Version.kmyblue_major}.#{Mastodon::Version.kmyblue_minor}"
|
||||||
|
@version += '-lts' if Setting.check_lts_version_only
|
||||||
|
else
|
||||||
|
@version = Mastodon::Version.to_s.split('+')[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
@version
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_update_notices!(update_notices)
|
def process_update_notices!(update_notices)
|
||||||
|
|
|
@ -55,6 +55,9 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :authorized_fetch, as: :boolean, wrapper: :with_label, label: t('admin.settings.security.authorized_fetch'), warning_hint: authorized_fetch_overridden? ? t('admin.settings.security.authorized_fetch_overridden_hint') : nil, hint: t('admin.settings.security.authorized_fetch_hint'), disabled: authorized_fetch_overridden?, recommended: authorized_fetch_overridden? ? :overridden : nil
|
= f.input :authorized_fetch, as: :boolean, wrapper: :with_label, label: t('admin.settings.security.authorized_fetch'), warning_hint: authorized_fetch_overridden? ? t('admin.settings.security.authorized_fetch_overridden_hint') : nil, hint: t('admin.settings.security.authorized_fetch_hint'), disabled: authorized_fetch_overridden?, recommended: authorized_fetch_overridden? ? :overridden : nil
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :check_lts_version_only, as: :boolean, wrapper: :with_label, kmyblue: true, hint: false
|
||||||
|
|
||||||
%h4= t('admin.settings.discovery.follow_recommendations')
|
%h4= t('admin.settings.discovery.follow_recommendations')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
.fields-row
|
.fields-row
|
||||||
.fields-group.fields-row__column.fields-row__column-12
|
.fields-group.fields-row__column.fields-row__column-12
|
||||||
= ff.input :default_searchability, collection: Status.selectable_searchabilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability')
|
= ff.input :default_searchability, collection: Status.selectable_searchabilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_default_searchability')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= ff.input :disallow_unlisted_public_searchability, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_disallow_unlisted_public_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_disallow_unlisted_public_searchability')
|
= ff.input :disallow_unlisted_public_searchability, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_disallow_unlisted_public_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_disallow_unlisted_public_searchability')
|
||||||
|
|
|
@ -997,6 +997,9 @@ en:
|
||||||
other: Used by %{count} people over the last week
|
other: Used by %{count} people over the last week
|
||||||
title: Trends
|
title: Trends
|
||||||
trending: Trending
|
trending: Trending
|
||||||
|
update-pendings:
|
||||||
|
major: Major update pending
|
||||||
|
patch: Patch update pending
|
||||||
warning_presets:
|
warning_presets:
|
||||||
add_new: Add new
|
add_new: Add new
|
||||||
delete: Delete
|
delete: Delete
|
||||||
|
|
|
@ -991,6 +991,9 @@ ja:
|
||||||
other: 週間%{count}人に使用されました
|
other: 週間%{count}人に使用されました
|
||||||
title: トレンド
|
title: トレンド
|
||||||
trending: トレンド
|
trending: トレンド
|
||||||
|
update_pendings:
|
||||||
|
major: メジャーアップデートあり
|
||||||
|
patch: パッチアップデートあり
|
||||||
warning_presets:
|
warning_presets:
|
||||||
add_new: 追加
|
add_new: 追加
|
||||||
delete: 削除
|
delete: 削除
|
||||||
|
|
|
@ -62,6 +62,7 @@ en:
|
||||||
setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts)
|
setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts)
|
||||||
setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon
|
setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon
|
||||||
setting_bookmark_category_needed: When removing from all category, unbookmarked automatically
|
setting_bookmark_category_needed: When removing from all category, unbookmarked automatically
|
||||||
|
setting_default_searchability: kmyblue・Fedibirdでは検索許可設定に基づき検索されます。Misskeyでは当設定に関係なく、全ての公開・ローカル公開・未収載投稿が検索されます。Mastodon・Firefishでは検索許可の代わりにプロフィール設定の「公開投稿を他のサーバーで自由に検索できるようにする」設定が適用されます
|
||||||
setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click
|
setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click
|
||||||
setting_display_media_default: Hide media marked as sensitive
|
setting_display_media_default: Hide media marked as sensitive
|
||||||
setting_display_media_hide_all: Always hide media
|
setting_display_media_hide_all: Always hide media
|
||||||
|
@ -315,6 +316,7 @@ en:
|
||||||
activity_api_enabled: Publish aggregate statistics about user activity in the API
|
activity_api_enabled: Publish aggregate statistics about user activity in the API
|
||||||
backups_retention_period: User archive retention period
|
backups_retention_period: User archive retention period
|
||||||
bootstrap_timeline_accounts: Always recommend these accounts to new users
|
bootstrap_timeline_accounts: Always recommend these accounts to new users
|
||||||
|
check_lts_version_only: Check kmyblue LTS version only when update check
|
||||||
closed_registrations_message: Custom message when sign-ups are not available
|
closed_registrations_message: Custom message when sign-ups are not available
|
||||||
content_cache_retention_period: Content cache retention period
|
content_cache_retention_period: Content cache retention period
|
||||||
custom_css: Custom CSS
|
custom_css: Custom CSS
|
||||||
|
|
|
@ -65,6 +65,7 @@ ja:
|
||||||
setting_always_send_emails: 通常、Mastodon からメール通知は行われません。
|
setting_always_send_emails: 通常、Mastodon からメール通知は行われません。
|
||||||
setting_bookmark_category_needed: すべてのカテゴリから削除したとき、ブックマークが自動で外れるようになります
|
setting_bookmark_category_needed: すべてのカテゴリから削除したとき、ブックマークが自動で外れるようになります
|
||||||
setting_boost_modal: ブーストの公開範囲が指定できるようになります
|
setting_boost_modal: ブーストの公開範囲が指定できるようになります
|
||||||
|
setting_default_searchability: kmyblue・Fedibirdでは検索許可設定に基づき検索されます。Misskeyでは当設定に関係なく、全ての公開・ローカル公開・未収載投稿が検索されます。Mastodon・Firefishでは検索許可の代わりにプロフィール設定の「公開投稿を他のサーバーで自由に検索できるようにする」設定が適用されます
|
||||||
setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
|
setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
|
||||||
setting_disallow_unlisted_public_searchability: この設定を有効にすると、未収載投稿と検索範囲「誰でも」は両立できず不特定多数からの検索が不可になります。Fedibirdと同じ挙動になります
|
setting_disallow_unlisted_public_searchability: この設定を有効にすると、未収載投稿と検索範囲「誰でも」は両立できず不特定多数からの検索が不可になります。Fedibirdと同じ挙動になります
|
||||||
setting_display_media_default: 閲覧注意としてマークされたメディアは隠す
|
setting_display_media_default: 閲覧注意としてマークされたメディアは隠す
|
||||||
|
@ -330,6 +331,7 @@ ja:
|
||||||
activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する
|
activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する
|
||||||
backups_retention_period: ユーザーアーカイブの保持期間
|
backups_retention_period: ユーザーアーカイブの保持期間
|
||||||
bootstrap_timeline_accounts: おすすめユーザーに常に表示するアカウント
|
bootstrap_timeline_accounts: おすすめユーザーに常に表示するアカウント
|
||||||
|
check_lts_version_only: 更新チェックの時、LTSバージョンのみ確認する
|
||||||
closed_registrations_message: アカウント作成を停止している時のカスタムメッセージ
|
closed_registrations_message: アカウント作成を停止している時のカスタムメッセージ
|
||||||
content_cache_retention_period: コンテンツキャッシュの保持期間
|
content_cache_retention_period: コンテンツキャッシュの保持期間
|
||||||
custom_css: カスタムCSS
|
custom_css: カスタムCSS
|
||||||
|
|
|
@ -4,7 +4,11 @@ SimpleNavigation::Configuration.run do |navigation|
|
||||||
navigation.items do |n|
|
navigation.items do |n|
|
||||||
n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_path
|
n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_path
|
||||||
|
|
||||||
n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.critical_update_pending')]), admin_software_updates_path, if: -> { ENV['UPDATE_CHECK_URL'] != '' && current_user.can?(:view_devops) && SoftwareUpdate.urgent_pending? }, html: { class: 'warning' }
|
if ENV['UPDATE_CHECK_URL'] != '' && current_user.can?(:view_devops)
|
||||||
|
n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.critical_update_pending')]), admin_software_updates_path, if: -> { SoftwareUpdate.urgent_pending? }, html: { class: 'warning' }
|
||||||
|
n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.update_pendings.major')]), admin_software_updates_path, if: -> { !SoftwareUpdate.urgent_pending? && SoftwareUpdate.major_pending? }, html: { class: 'warning' }
|
||||||
|
n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.update_pendings.patch')]), admin_software_updates_path, if: -> { !SoftwareUpdate.urgent_pending? && SoftwareUpdate.patch_pending? }, html: { class: 'warning' }
|
||||||
|
end
|
||||||
|
|
||||||
n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_path, if: -> { current_user.functional? }, highlights_on: %r{/settings/profile|/settings/featured_tags|/settings/verification|/settings/privacy}
|
n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_path, if: -> { current_user.functional? }, highlights_on: %r{/settings/profile|/settings/featured_tags|/settings/verification|/settings/privacy}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ defaults: &defaults
|
||||||
receive_other_servers_emoji_reaction: false
|
receive_other_servers_emoji_reaction: false
|
||||||
streaming_other_servers_emoji_reaction: false
|
streaming_other_servers_emoji_reaction: false
|
||||||
enable_emoji_reaction: true
|
enable_emoji_reaction: true
|
||||||
|
check_lts_version_only: true
|
||||||
|
|
||||||
development:
|
development:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
|
|
|
@ -18,7 +18,7 @@ class AddUniqueIndexOnPreviewCardsStatuses < ActiveRecord::Migration[6.1]
|
||||||
def supports_concurrent_reindex?
|
def supports_concurrent_reindex?
|
||||||
@supports_concurrent_reindex ||= begin
|
@supports_concurrent_reindex ||= begin
|
||||||
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
|
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
|
||||||
version >= 12_000
|
version >= 120_000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -202,7 +202,7 @@ module Mastodon
|
||||||
def supports_add_column_with_default?
|
def supports_add_column_with_default?
|
||||||
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
|
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
|
||||||
|
|
||||||
version >= 11_000
|
version >= 110_000
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a foreign key with only minimal locking on the tables involved.
|
# Adds a foreign key with only minimal locking on the tables involved.
|
||||||
|
|
|
@ -62,7 +62,11 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def gem_version
|
def gem_version
|
||||||
@gem_version ||= Gem::Version.new(to_s.split('+')[0])
|
@gem_version ||= if ENV.fetch('UPDATE_CHECK_SOURCE', 'kmyblue') == 'kmyblue'
|
||||||
|
Gem::Version.new("#{kmyblue_major}.#{kmyblue_minor}")
|
||||||
|
else
|
||||||
|
Gem::Version.new(to_s.split('+')[0])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def repository
|
def repository
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
User-agent: GPTBot
|
User-agent: GPTBot
|
||||||
Disallow: /
|
Disallow: /
|
||||||
|
|
||||||
|
User-agent: CCBot
|
||||||
|
Disallow: /
|
||||||
|
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /media_proxy/
|
Disallow: /media_proxy/
|
||||||
Disallow: /interact/
|
Disallow: /interact/
|
||||||
|
|
|
@ -73,7 +73,6 @@ describe ActivityPub::NoteSerializer do
|
||||||
expect(subject['quoteUri']).to_not be_nil
|
expect(subject['quoteUri']).to_not be_nil
|
||||||
expect(subject['quoteUri']).to eq referred.uri
|
expect(subject['quoteUri']).to eq referred.uri
|
||||||
expect(subject['_misskey_quote']).to eq referred.uri
|
expect(subject['_misskey_quote']).to eq referred.uri
|
||||||
expect(subject['_misskey_content']).to eq referred.text
|
|
||||||
expect(subject['references']['first']['items']).to include referred.uri
|
expect(subject['references']['first']['items']).to include referred.uri
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,7 @@ RSpec.describe DeliveryAntennaService, type: :service do
|
||||||
let(:domain) { nil }
|
let(:domain) { nil }
|
||||||
let(:spoiler_text) { '' }
|
let(:spoiler_text) { '' }
|
||||||
let(:tags) { Tag.find_or_create_by_names(['hoge']) }
|
let(:tags) { Tag.find_or_create_by_names(['hoge']) }
|
||||||
|
let(:software) { nil }
|
||||||
let(:status) do
|
let(:status) do
|
||||||
url = domain.present? ? 'https://example.com/status' : nil
|
url = domain.present? ? 'https://example.com/status' : nil
|
||||||
status = Fabricate(:status, account: alice, spoiler_text: spoiler_text, visibility: visibility, searchability: searchability, text: 'Hello my body #hoge', url: url)
|
status = Fabricate(:status, account: alice, spoiler_text: spoiler_text, visibility: visibility, searchability: searchability, text: 'Hello my body #hoge', url: url)
|
||||||
|
@ -30,6 +31,8 @@ RSpec.describe DeliveryAntennaService, type: :service do
|
||||||
let(:mode) { :home }
|
let(:mode) { :home }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
Fabricate(:instance_info, domain: domain, software: software) if domain.present? && software.present?
|
||||||
|
|
||||||
bob.follow!(alice)
|
bob.follow!(alice)
|
||||||
alice.block!(ohagi)
|
alice.block!(ohagi)
|
||||||
|
|
||||||
|
@ -359,4 +362,42 @@ RSpec.describe DeliveryAntennaService, type: :service do
|
||||||
expect(antenna_feed_of(antenna)).to include status.id
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with federated unlisted post' do
|
||||||
|
let(:visibility) { :unlisted }
|
||||||
|
let(:searchability) { :public }
|
||||||
|
let(:domain) { 'fast.example.com' }
|
||||||
|
let!(:antenna) { antenna_with_keyword(bob, 'body') }
|
||||||
|
let!(:empty_antenna) { antenna_with_keyword(tom, 'body') }
|
||||||
|
|
||||||
|
context 'when unknown domain' do
|
||||||
|
let(:software) { nil }
|
||||||
|
|
||||||
|
it 'detecting antenna' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when misskey domain' do
|
||||||
|
let(:software) { 'misskey' }
|
||||||
|
|
||||||
|
it 'detecting antenna' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when firefish domain' do
|
||||||
|
let(:software) { 'firefish' }
|
||||||
|
|
||||||
|
it 'detecting antenna' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'not detecting antenna' do
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,10 +6,16 @@ RSpec.describe ProcessReferencesService, type: :service do
|
||||||
let(:text) { 'Hello' }
|
let(:text) { 'Hello' }
|
||||||
let(:account) { Fabricate(:user).account }
|
let(:account) { Fabricate(:user).account }
|
||||||
let(:visibility) { :public }
|
let(:visibility) { :public }
|
||||||
|
let(:target_status_visibility) { :public }
|
||||||
let(:status) { Fabricate(:status, account: account, text: text, visibility: visibility) }
|
let(:status) { Fabricate(:status, account: account, text: text, visibility: visibility) }
|
||||||
let(:target_status) { Fabricate(:status, account: Fabricate(:user).account) }
|
let(:target_status) { Fabricate(:status, account: Fabricate(:user).account, visibility: target_status_visibility) }
|
||||||
let(:target_status_uri) { ActivityPub::TagManager.instance.uri_for(target_status) }
|
let(:target_status_uri) { ActivityPub::TagManager.instance.uri_for(target_status) }
|
||||||
|
|
||||||
|
def notify?(target_status_id = nil)
|
||||||
|
target_status_id ||= target_status.id
|
||||||
|
StatusReference.exists?(id: Notification.where(type: 'status_reference').select(:activity_id), target_status_id: target_status_id)
|
||||||
|
end
|
||||||
|
|
||||||
describe 'posting new status' do
|
describe 'posting new status' do
|
||||||
subject do
|
subject do
|
||||||
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote)
|
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote)
|
||||||
|
@ -27,6 +33,7 @@ RSpec.describe ProcessReferencesService, type: :service do
|
||||||
expect(subject.size).to eq 1
|
expect(subject.size).to eq 1
|
||||||
expect(subject.pluck(0)).to include target_status.id
|
expect(subject.pluck(0)).to include target_status.id
|
||||||
expect(subject.pluck(1)).to include 'RT'
|
expect(subject.pluck(1)).to include 'RT'
|
||||||
|
expect(notify?).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -39,6 +46,59 @@ RSpec.describe ProcessReferencesService, type: :service do
|
||||||
expect(subject.size).to eq 2
|
expect(subject.size).to eq 2
|
||||||
expect(subject).to include [target_status.id, 'RT']
|
expect(subject).to include [target_status.id, 'RT']
|
||||||
expect(subject).to include [target_status2.id, 'BT']
|
expect(subject).to include [target_status2.id, 'BT']
|
||||||
|
expect(notify?).to be true
|
||||||
|
expect(notify?(target_status2.id)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when private post' do
|
||||||
|
let(:text) { "Hello RT #{target_status_uri}" }
|
||||||
|
let(:visibility) { :private }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
expect(subject.size).to eq 1
|
||||||
|
expect(subject.pluck(0)).to include target_status.id
|
||||||
|
expect(subject.pluck(1)).to include 'RT'
|
||||||
|
expect(notify?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when cannot show private post' do
|
||||||
|
let(:text) { "Hello RT #{target_status_uri}" }
|
||||||
|
let(:target_status_visibility) { :private }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
expect(subject.size).to eq 0
|
||||||
|
expect(notify?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with quote' do
|
||||||
|
let(:text) { "Hello QT #{target_status_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
expect(subject.size).to eq 1
|
||||||
|
expect(subject.pluck(0)).to include target_status.id
|
||||||
|
expect(subject.pluck(1)).to include 'QT'
|
||||||
|
expect(status.quote).to_not be_nil
|
||||||
|
expect(status.quote.id).to eq target_status.id
|
||||||
|
expect(notify?).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with quote and reference' do
|
||||||
|
let(:target_status2) { Fabricate(:status) }
|
||||||
|
let(:target_status2_uri) { ActivityPub::TagManager.instance.uri_for(target_status2) }
|
||||||
|
let(:text) { "Hello QT #{target_status_uri}\nBT #{target_status2_uri}" }
|
||||||
|
|
||||||
|
it 'post status' do
|
||||||
|
expect(subject.size).to eq 2
|
||||||
|
expect(subject).to include [target_status.id, 'QT']
|
||||||
|
expect(subject).to include [target_status2.id, 'BT']
|
||||||
|
expect(status.quote).to_not be_nil
|
||||||
|
expect(status.quote.id).to eq target_status.id
|
||||||
|
expect(notify?).to be true
|
||||||
|
expect(notify?(target_status2.id)).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -47,6 +107,7 @@ RSpec.describe ProcessReferencesService, type: :service do
|
||||||
|
|
||||||
it 'post status' do
|
it 'post status' do
|
||||||
expect(subject.size).to eq 0
|
expect(subject.size).to eq 0
|
||||||
|
expect(notify?).to be false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -143,6 +204,7 @@ RSpec.describe ProcessReferencesService, type: :service do
|
||||||
it 'post status' do
|
it 'post status' do
|
||||||
expect(subject.size).to eq 1
|
expect(subject.size).to eq 1
|
||||||
expect(subject).to include target_status.id
|
expect(subject).to include target_status.id
|
||||||
|
expect(notify?).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -154,6 +216,7 @@ RSpec.describe ProcessReferencesService, type: :service do
|
||||||
expect(subject.size).to eq 2
|
expect(subject.size).to eq 2
|
||||||
expect(subject).to include target_status.id
|
expect(subject).to include target_status.id
|
||||||
expect(subject).to include target_status2.id
|
expect(subject).to include target_status2.id
|
||||||
|
expect(notify?(target_status2.id)).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -173,6 +236,7 @@ RSpec.describe ProcessReferencesService, type: :service do
|
||||||
|
|
||||||
it 'post status' do
|
it 'post status' do
|
||||||
expect(subject.size).to eq 0
|
expect(subject.size).to eq 0
|
||||||
|
expect(notify?).to be false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -183,6 +247,7 @@ RSpec.describe ProcessReferencesService, type: :service do
|
||||||
it 'post status' do
|
it 'post status' do
|
||||||
expect(subject.size).to eq 1
|
expect(subject.size).to eq 1
|
||||||
expect(subject).to include target_status2.id
|
expect(subject).to include target_status2.id
|
||||||
|
expect(notify?(target_status2.id)).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ RSpec.describe SoftwareUpdateCheckService, type: :service do
|
||||||
subject { described_class.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
shared_examples 'when the feature is enabled' do
|
shared_examples 'when the feature is enabled' do
|
||||||
let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.to_s.split('+')[0]}" }
|
let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.kmyblue_major}.#{Mastodon::Version.kmyblue_minor}-lts" }
|
||||||
|
|
||||||
let(:devops_role) { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) }
|
let(:devops_role) { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) }
|
||||||
let(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
|
let(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
|
||||||
|
@ -139,7 +139,7 @@ RSpec.describe SoftwareUpdateCheckService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when using the default update checking API' do
|
context 'when using the default update checking API' do
|
||||||
let(:update_check_url) { 'https://api.joinmastodon.org/update-check' }
|
let(:update_check_url) { 'https://kmy.blue/update-check' }
|
||||||
|
|
||||||
it_behaves_like 'when the feature is enabled'
|
it_behaves_like 'when the feature is enabled'
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue