* Add: #920 アンテナ・リストに「お気に入りに登録」設定 * Fix test * Fix test * Add fedibird capabilities * Add kmyblue_favourite_antenna
This commit is contained in:
parent
ee49518125
commit
9201eb151b
22 changed files with 294 additions and 29 deletions
|
@ -42,6 +42,6 @@ class Api::V1::AntennasController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def antenna_params
|
def antenna_params
|
||||||
params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog)
|
params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog, :favourite)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,6 +38,16 @@ class Api::V1::ListsController < Api::BaseController
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def favourite
|
||||||
|
@list.favourite!
|
||||||
|
render json: @list, serializer: REST::ListSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfavourite
|
||||||
|
@list.unfavourite!
|
||||||
|
render json: @list, serializer: REST::ListSerializer
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_list
|
def set_list
|
||||||
|
@ -45,6 +55,6 @@ class Api::V1::ListsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_params
|
def list_params
|
||||||
params.permit(:title, :replies_policy, :exclusive, :notify)
|
params.permit(:title, :replies_policy, :exclusive, :notify, :favourite)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,8 @@ module KmyblueCapabilitiesHelper
|
||||||
kmyblue_circle_history
|
kmyblue_circle_history
|
||||||
kmyblue_list_notification
|
kmyblue_list_notification
|
||||||
kmyblue_server_features
|
kmyblue_server_features
|
||||||
|
favourite_list
|
||||||
|
kmyblue_favourite_antenna
|
||||||
)
|
)
|
||||||
|
|
||||||
capabilities << :full_text_search if Chewy.enabled?
|
capabilities << :full_text_search if Chewy.enabled?
|
||||||
|
|
|
@ -10,6 +10,7 @@ export interface ApiAntennaJSON {
|
||||||
insert_feeds: boolean;
|
insert_feeds: boolean;
|
||||||
with_media_only: boolean;
|
with_media_only: boolean;
|
||||||
ignore_reblog: boolean;
|
ignore_reblog: boolean;
|
||||||
|
favourite: boolean;
|
||||||
list: ApiListJSON | null;
|
list: ApiListJSON | null;
|
||||||
|
|
||||||
list_id: string | undefined;
|
list_id: string | undefined;
|
||||||
|
|
|
@ -10,5 +10,6 @@ export interface ApiListJSON {
|
||||||
exclusive: boolean;
|
exclusive: boolean;
|
||||||
replies_policy: RepliesPolicyType;
|
replies_policy: RepliesPolicyType;
|
||||||
notify: boolean;
|
notify: boolean;
|
||||||
|
favourite: boolean;
|
||||||
antennas?: ApiAntennaJSON[];
|
antennas?: ApiAntennaJSON[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ const NewAntenna: React.FC<{
|
||||||
const [ignoreReblog, setIgnoreReblog] = useState(false);
|
const [ignoreReblog, setIgnoreReblog] = useState(false);
|
||||||
const [mode, setMode] = useState('filtering');
|
const [mode, setMode] = useState('filtering');
|
||||||
const [destination, setDestination] = useState('timeline');
|
const [destination, setDestination] = useState('timeline');
|
||||||
|
const [favourite, setFavourite] = useState(true);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -80,6 +81,7 @@ const NewAntenna: React.FC<{
|
||||||
setListId(antenna.list?.id ?? '0');
|
setListId(antenna.list?.id ?? '0');
|
||||||
setWithMediaOnly(antenna.with_media_only);
|
setWithMediaOnly(antenna.with_media_only);
|
||||||
setIgnoreReblog(antenna.ignore_reblog);
|
setIgnoreReblog(antenna.ignore_reblog);
|
||||||
|
setFavourite(antenna.favourite);
|
||||||
|
|
||||||
if (antenna.stl) {
|
if (antenna.stl) {
|
||||||
setMode('stl');
|
setMode('stl');
|
||||||
|
@ -109,6 +111,7 @@ const NewAntenna: React.FC<{
|
||||||
setIgnoreReblog,
|
setIgnoreReblog,
|
||||||
setMode,
|
setMode,
|
||||||
setDestination,
|
setDestination,
|
||||||
|
setFavourite,
|
||||||
id,
|
id,
|
||||||
antenna,
|
antenna,
|
||||||
lists,
|
lists,
|
||||||
|
@ -179,6 +182,13 @@ const NewAntenna: React.FC<{
|
||||||
[setIgnoreReblog],
|
[setIgnoreReblog],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleFavouriteChange = useCallback(
|
||||||
|
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFavourite(checked);
|
||||||
|
},
|
||||||
|
[setFavourite],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
|
@ -193,6 +203,7 @@ const NewAntenna: React.FC<{
|
||||||
list_id: destination === 'list' ? listId : '0',
|
list_id: destination === 'list' ? listId : '0',
|
||||||
with_media_only: withMediaOnly,
|
with_media_only: withMediaOnly,
|
||||||
ignore_reblog: ignoreReblog,
|
ignore_reblog: ignoreReblog,
|
||||||
|
favourite,
|
||||||
}),
|
}),
|
||||||
).then(() => {
|
).then(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
@ -208,6 +219,7 @@ const NewAntenna: React.FC<{
|
||||||
list_id: destination === 'list' ? listId : '0',
|
list_id: destination === 'list' ? listId : '0',
|
||||||
with_media_only: withMediaOnly,
|
with_media_only: withMediaOnly,
|
||||||
ignore_reblog: ignoreReblog,
|
ignore_reblog: ignoreReblog,
|
||||||
|
favourite,
|
||||||
}),
|
}),
|
||||||
).then((result) => {
|
).then((result) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
@ -233,6 +245,7 @@ const NewAntenna: React.FC<{
|
||||||
withMediaOnly,
|
withMediaOnly,
|
||||||
ignoreReblog,
|
ignoreReblog,
|
||||||
destination,
|
destination,
|
||||||
|
favourite,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -460,6 +473,35 @@ const NewAntenna: React.FC<{
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className='fields-group'>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label className='app-form__toggle'>
|
||||||
|
<div className='app-form__toggle__label'>
|
||||||
|
<strong>
|
||||||
|
<FormattedMessage
|
||||||
|
id='antennas.favourite'
|
||||||
|
defaultMessage='Favorite'
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
<span className='hint'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='antennas.favourite_hint'
|
||||||
|
defaultMessage='When opening the Web Client on a PC, this antenna appears in the navigation.'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='app-form__toggle__toggle'>
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
checked={favourite}
|
||||||
|
onChange={handleFavouriteChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='actions'>
|
<div className='actions'>
|
||||||
<button className='button' type='submit'>
|
<button className='button' type='submit'>
|
||||||
{submitting ? (
|
{submitting ? (
|
||||||
|
|
|
@ -82,6 +82,7 @@ const NewList: React.FC<{
|
||||||
const [exclusive, setExclusive] = useState(false);
|
const [exclusive, setExclusive] = useState(false);
|
||||||
const [repliesPolicy, setRepliesPolicy] = useState<RepliesPolicyType>('list');
|
const [repliesPolicy, setRepliesPolicy] = useState<RepliesPolicyType>('list');
|
||||||
const [notify, setNotify] = useState(false);
|
const [notify, setNotify] = useState(false);
|
||||||
|
const [favourite, setFavourite] = useState(true);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -96,8 +97,17 @@ const NewList: React.FC<{
|
||||||
setExclusive(list.exclusive);
|
setExclusive(list.exclusive);
|
||||||
setRepliesPolicy(list.replies_policy);
|
setRepliesPolicy(list.replies_policy);
|
||||||
setNotify(list.notify);
|
setNotify(list.notify);
|
||||||
|
setFavourite(list.favourite);
|
||||||
}
|
}
|
||||||
}, [setTitle, setExclusive, setRepliesPolicy, setNotify, id, list]);
|
}, [
|
||||||
|
setTitle,
|
||||||
|
setExclusive,
|
||||||
|
setRepliesPolicy,
|
||||||
|
setNotify,
|
||||||
|
setFavourite,
|
||||||
|
id,
|
||||||
|
list,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleTitleChange = useCallback(
|
const handleTitleChange = useCallback(
|
||||||
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
|
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -127,6 +137,13 @@ const NewList: React.FC<{
|
||||||
[setNotify],
|
[setNotify],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleFavouriteChange = useCallback(
|
||||||
|
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFavourite(checked);
|
||||||
|
},
|
||||||
|
[setFavourite],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
|
@ -138,6 +155,7 @@ const NewList: React.FC<{
|
||||||
exclusive,
|
exclusive,
|
||||||
replies_policy: repliesPolicy,
|
replies_policy: repliesPolicy,
|
||||||
notify,
|
notify,
|
||||||
|
favourite,
|
||||||
}),
|
}),
|
||||||
).then(() => {
|
).then(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
@ -150,6 +168,7 @@ const NewList: React.FC<{
|
||||||
exclusive,
|
exclusive,
|
||||||
replies_policy: repliesPolicy,
|
replies_policy: repliesPolicy,
|
||||||
notify,
|
notify,
|
||||||
|
favourite,
|
||||||
}),
|
}),
|
||||||
).then((result) => {
|
).then((result) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
@ -171,6 +190,7 @@ const NewList: React.FC<{
|
||||||
exclusive,
|
exclusive,
|
||||||
repliesPolicy,
|
repliesPolicy,
|
||||||
notify,
|
notify,
|
||||||
|
favourite,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -324,6 +344,35 @@ const NewList: React.FC<{
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='fields-group'>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label className='app-form__toggle'>
|
||||||
|
<div className='app-form__toggle__label'>
|
||||||
|
<strong>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.favourite'
|
||||||
|
defaultMessage='Favorite'
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
<span className='hint'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.favourite_hint'
|
||||||
|
defaultMessage='When opening the Web Client on a PC, this list appears in the navigation.'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='app-form__toggle__toggle'>
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
checked={favourite}
|
||||||
|
onChange={handleFavouriteChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='actions'>
|
<div className='actions'>
|
||||||
<button className='button' type='submit'>
|
<button className='button' type='submit'>
|
||||||
{submitting ? (
|
{submitting ? (
|
||||||
|
|
|
@ -16,7 +16,7 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
||||||
return lists;
|
return lists;
|
||||||
}
|
}
|
||||||
|
|
||||||
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
|
return lists.toList().filter(item => !!item && item.get('favourite')).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => {
|
const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => {
|
||||||
|
@ -24,7 +24,7 @@ const getOrderedAntennas = createSelector([state => state.get('antennas')], ante
|
||||||
return antennas;
|
return antennas;
|
||||||
}
|
}
|
||||||
|
|
||||||
return antennas.toList().filter(item => !!item && !item.get('insert_feeds') && item.get('title') !== undefined).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
|
return antennas.toList().filter(item => !!item && item.get('favourite') && item.get('title') !== undefined).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ListPanel = () => {
|
export const ListPanel = () => {
|
||||||
|
|
|
@ -150,6 +150,8 @@
|
||||||
"antennas.exclude_domains": "Exclude domains",
|
"antennas.exclude_domains": "Exclude domains",
|
||||||
"antennas.exclude_keywords": "Exclude keywords",
|
"antennas.exclude_keywords": "Exclude keywords",
|
||||||
"antennas.exclude_tags": "Exclude tags",
|
"antennas.exclude_tags": "Exclude tags",
|
||||||
|
"antennas.favourite": "Favorite",
|
||||||
|
"antennas.favourite_hint": "When opening the Web Client on a PC, this antenna appears in the navigation.",
|
||||||
"antennas.filter_items": "Move to antenna filter setting",
|
"antennas.filter_items": "Move to antenna filter setting",
|
||||||
"antennas.filter_not": "Filter Not",
|
"antennas.filter_not": "Filter Not",
|
||||||
"antennas.find_users_to_add": "Find users to add",
|
"antennas.find_users_to_add": "Find users to add",
|
||||||
|
@ -630,6 +632,8 @@
|
||||||
"lists.edit": "Edit list",
|
"lists.edit": "Edit list",
|
||||||
"lists.exclusive": "Hide members in Home",
|
"lists.exclusive": "Hide members in Home",
|
||||||
"lists.exclusive_hint": "If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.",
|
"lists.exclusive_hint": "If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.",
|
||||||
|
"lists.favourite": "Favorite",
|
||||||
|
"lists.favourite_hint": "When opening the Web Client on a PC, this list appears in the navigation.",
|
||||||
"lists.find_users_to_add": "Find users to add",
|
"lists.find_users_to_add": "Find users to add",
|
||||||
"lists.list_members": "List members",
|
"lists.list_members": "List members",
|
||||||
"lists.list_members_count": "{count, plural, one {# member} other {# members}}",
|
"lists.list_members_count": "{count, plural, one {# member} other {# members}}",
|
||||||
|
|
|
@ -145,6 +145,8 @@
|
||||||
"antennas.exclude_domains": "除外するドメイン",
|
"antennas.exclude_domains": "除外するドメイン",
|
||||||
"antennas.exclude_keywords": "除外するキーワード",
|
"antennas.exclude_keywords": "除外するキーワード",
|
||||||
"antennas.exclude_tags": "除外するタグ",
|
"antennas.exclude_tags": "除外するタグ",
|
||||||
|
"antennas.favourite": "お気に入りに登録",
|
||||||
|
"antennas.favourite_hint": "お気に入りに登録したアンテナは、Webクライアントでナビゲーションに表示されます",
|
||||||
"antennas.filter_items": "絞り込み条件の設定に移動",
|
"antennas.filter_items": "絞り込み条件の設定に移動",
|
||||||
"antennas.filter_not": "絞り込み条件の例外",
|
"antennas.filter_not": "絞り込み条件の例外",
|
||||||
"antennas.ignore_reblog": "ブーストを除外",
|
"antennas.ignore_reblog": "ブーストを除外",
|
||||||
|
@ -579,6 +581,8 @@
|
||||||
"lists.antennas": "関連付けられたアンテナ",
|
"lists.antennas": "関連付けられたアンテナ",
|
||||||
"lists.delete": "リストを削除",
|
"lists.delete": "リストを削除",
|
||||||
"lists.edit": "リストを編集",
|
"lists.edit": "リストを編集",
|
||||||
|
"lists.favourite": "お気に入りに登録",
|
||||||
|
"lists.favourite_hint": "お気に入りに登録したリストは、Webクライアントでナビゲーションに表示されます",
|
||||||
"lists.memo_related_antenna": "アンテナ: {title}",
|
"lists.memo_related_antenna": "アンテナ: {title}",
|
||||||
"lists.notify": "これらの投稿を通知する",
|
"lists.notify": "これらの投稿を通知する",
|
||||||
"lists.replies_policy.followed": "フォロー中のユーザー全員",
|
"lists.replies_policy.followed": "フォロー中のユーザー全員",
|
||||||
|
|
|
@ -14,6 +14,7 @@ const AntennaFactory = Record<AntennaShape>({
|
||||||
insert_feeds: false,
|
insert_feeds: false,
|
||||||
with_media_only: false,
|
with_media_only: false,
|
||||||
ignore_reblog: false,
|
ignore_reblog: false,
|
||||||
|
favourite: true,
|
||||||
list: null,
|
list: null,
|
||||||
list_id: undefined,
|
list_id: undefined,
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@ const ListFactory = Record<ListShape>({
|
||||||
exclusive: false,
|
exclusive: false,
|
||||||
replies_policy: 'list',
|
replies_policy: 'list',
|
||||||
notify: false,
|
notify: false,
|
||||||
|
favourite: true,
|
||||||
antennas: [],
|
antennas: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,27 +5,28 @@
|
||||||
# Table name: antennas
|
# Table name: antennas
|
||||||
#
|
#
|
||||||
# id :bigint(8) not null, primary key
|
# id :bigint(8) not null, primary key
|
||||||
# account_id :bigint(8) not null
|
|
||||||
# list_id :bigint(8) not null
|
|
||||||
# title :string default(""), not null
|
|
||||||
# keywords :jsonb
|
|
||||||
# exclude_keywords :jsonb
|
|
||||||
# any_domains :boolean default(TRUE), not null
|
|
||||||
# any_tags :boolean default(TRUE), not null
|
|
||||||
# any_accounts :boolean default(TRUE), not null
|
# any_accounts :boolean default(TRUE), not null
|
||||||
|
# any_domains :boolean default(TRUE), not null
|
||||||
# any_keywords :boolean default(TRUE), not null
|
# any_keywords :boolean default(TRUE), not null
|
||||||
|
# any_tags :boolean default(TRUE), not null
|
||||||
# available :boolean default(TRUE), not null
|
# available :boolean default(TRUE), not null
|
||||||
# created_at :datetime not null
|
|
||||||
# updated_at :datetime not null
|
|
||||||
# expires_at :datetime
|
|
||||||
# with_media_only :boolean default(FALSE), not null
|
|
||||||
# exclude_domains :jsonb
|
|
||||||
# exclude_accounts :jsonb
|
# exclude_accounts :jsonb
|
||||||
|
# exclude_domains :jsonb
|
||||||
|
# exclude_keywords :jsonb
|
||||||
# exclude_tags :jsonb
|
# exclude_tags :jsonb
|
||||||
# stl :boolean default(FALSE), not null
|
# expires_at :datetime
|
||||||
|
# favourite :boolean default(TRUE), not null
|
||||||
# ignore_reblog :boolean default(FALSE), not null
|
# ignore_reblog :boolean default(FALSE), not null
|
||||||
# insert_feeds :boolean default(FALSE), not null
|
# insert_feeds :boolean default(FALSE), not null
|
||||||
|
# keywords :jsonb
|
||||||
# ltl :boolean default(FALSE), not null
|
# ltl :boolean default(FALSE), not null
|
||||||
|
# stl :boolean default(FALSE), not null
|
||||||
|
# title :string default(""), not null
|
||||||
|
# with_media_only :boolean default(FALSE), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint(8) not null
|
||||||
|
# list_id :bigint(8) not null
|
||||||
#
|
#
|
||||||
class Antenna < ApplicationRecord
|
class Antenna < ApplicationRecord
|
||||||
include Expireable
|
include Expireable
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
# Table name: lists
|
# Table name: lists
|
||||||
#
|
#
|
||||||
# id :bigint(8) not null, primary key
|
# id :bigint(8) not null, primary key
|
||||||
# account_id :bigint(8) not null
|
# exclusive :boolean default(FALSE), not null
|
||||||
|
# favourite :boolean default(TRUE), not null
|
||||||
|
# notify :boolean default(FALSE), not null
|
||||||
|
# replies_policy :integer default("list"), not null
|
||||||
# title :string default(""), not null
|
# title :string default(""), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# replies_policy :integer default("list"), not null
|
# account_id :bigint(8) not null
|
||||||
# exclusive :boolean default(FALSE), not null
|
|
||||||
# notify :boolean default(FALSE), not null
|
|
||||||
#
|
#
|
||||||
|
|
||||||
class List < ApplicationRecord
|
class List < ApplicationRecord
|
||||||
|
@ -35,6 +36,14 @@ class List < ApplicationRecord
|
||||||
|
|
||||||
before_destroy :clean_feed_manager
|
before_destroy :clean_feed_manager
|
||||||
|
|
||||||
|
def favourite!
|
||||||
|
update!(favourite: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfavourite!
|
||||||
|
update!(favourite: false)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def validate_account_lists_limit
|
def validate_account_lists_limit
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::AntennaSerializer < ActiveModel::Serializer
|
class REST::AntennaSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :title, :stl, :ltl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count
|
attributes :id, :title, :stl, :ltl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count, :favourite
|
||||||
|
|
||||||
class ListSerializer < ActiveModel::Serializer
|
class ListSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :title
|
attributes :id, :title
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::ListSerializer < ActiveModel::Serializer
|
class REST::ListSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :title, :replies_policy, :exclusive, :notify
|
attributes :id, :title, :replies_policy, :exclusive, :notify, :favourite
|
||||||
|
|
||||||
def id
|
def id
|
||||||
object.id.to_s
|
object.id.to_s
|
||||||
|
|
|
@ -232,6 +232,11 @@ namespace :api, format: false do
|
||||||
|
|
||||||
resources :lists, only: [:index, :create, :show, :update, :destroy] do
|
resources :lists, only: [:index, :create, :show, :update, :destroy] do
|
||||||
resource :accounts, only: [:show, :create, :destroy], module: :lists
|
resource :accounts, only: [:show, :create, :destroy], module: :lists
|
||||||
|
|
||||||
|
member do
|
||||||
|
post :favourite
|
||||||
|
post :unfavourite
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :antennas, only: [:index, :create, :show, :update, :destroy] do
|
resources :antennas, only: [:index, :create, :show, :update, :destroy] do
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddFavouriteToListsAndAntennas < ActiveRecord::Migration[7.2]
|
||||||
|
class Antenna < ApplicationRecord; end
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_column :lists, :favourite, :boolean, null: false, default: true
|
||||||
|
add_column :antennas, :favourite, :boolean, null: false, default: true
|
||||||
|
|
||||||
|
Antenna.where(insert_feeds: true).in_batches.update_all(favourite: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :lists, :favourite
|
||||||
|
remove_column :antennas, :favourite
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2024_12_05_135925) do
|
ActiveRecord::Schema[7.2].define(version: 2024_12_08_232829) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
@ -329,6 +329,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_05_135925) do
|
||||||
t.boolean "ignore_reblog", default: false, null: false
|
t.boolean "ignore_reblog", default: false, null: false
|
||||||
t.boolean "insert_feeds", default: false, null: false
|
t.boolean "insert_feeds", default: false, null: false
|
||||||
t.boolean "ltl", default: false, null: false
|
t.boolean "ltl", default: false, null: false
|
||||||
|
t.boolean "favourite", default: true, null: false
|
||||||
t.index ["account_id"], name: "index_antennas_on_account_id"
|
t.index ["account_id"], name: "index_antennas_on_account_id"
|
||||||
t.index ["any_accounts"], name: "index_antennas_on_any_accounts"
|
t.index ["any_accounts"], name: "index_antennas_on_any_accounts"
|
||||||
t.index ["any_domains"], name: "index_antennas_on_any_domains"
|
t.index ["any_domains"], name: "index_antennas_on_any_domains"
|
||||||
|
@ -786,6 +787,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_05_135925) do
|
||||||
t.integer "replies_policy", default: 0, null: false
|
t.integer "replies_policy", default: 0, null: false
|
||||||
t.boolean "exclusive", default: false, null: false
|
t.boolean "exclusive", default: false, null: false
|
||||||
t.boolean "notify", default: false, null: false
|
t.boolean "notify", default: false, null: false
|
||||||
|
t.boolean "favourite", default: true, null: false
|
||||||
t.index ["account_id"], name: "index_lists_on_account_id"
|
t.index ["account_id"], name: "index_lists_on_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace :dangerous do
|
||||||
end
|
end
|
||||||
|
|
||||||
target_migrations = %w(
|
target_migrations = %w(
|
||||||
|
20241208232829
|
||||||
20240828123604
|
20240828123604
|
||||||
20240709063700
|
20240709063700
|
||||||
20240426233435
|
20240426233435
|
||||||
|
@ -172,6 +173,7 @@ namespace :dangerous do
|
||||||
%w(domain_blocks reject_send_sensitive),
|
%w(domain_blocks reject_send_sensitive),
|
||||||
%w(domain_blocks reject_straight_follow),
|
%w(domain_blocks reject_straight_follow),
|
||||||
%w(favourites uri),
|
%w(favourites uri),
|
||||||
|
%w(lists favourite),
|
||||||
%w(lists notify),
|
%w(lists notify),
|
||||||
%w(statuses limited_scope),
|
%w(statuses limited_scope),
|
||||||
%w(statuses markdown),
|
%w(statuses markdown),
|
||||||
|
|
|
@ -19,6 +19,7 @@ RSpec.describe 'Antennas' do
|
||||||
Fabricate(:antenna, account: user.account, title: 'second antenna', with_media_only: true),
|
Fabricate(:antenna, account: user.account, title: 'second antenna', with_media_only: true),
|
||||||
Fabricate(:antenna, account: user.account, title: 'third antenna', stl: true),
|
Fabricate(:antenna, account: user.account, title: 'third antenna', stl: true),
|
||||||
Fabricate(:antenna, account: user.account, title: 'fourth antenna', ignore_reblog: true),
|
Fabricate(:antenna, account: user.account, title: 'fourth antenna', ignore_reblog: true),
|
||||||
|
Fabricate(:antenna, account: user.account, title: 'fourth antenna', favourite: false),
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ RSpec.describe 'Antennas' do
|
||||||
domains_count: 0,
|
domains_count: 0,
|
||||||
tags_count: 0,
|
tags_count: 0,
|
||||||
keywords_count: 0,
|
keywords_count: 0,
|
||||||
|
favourite: antenna.favourite,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -80,7 +82,8 @@ RSpec.describe 'Antennas' do
|
||||||
accounts_count: 0,
|
accounts_count: 0,
|
||||||
domains_count: 0,
|
domains_count: 0,
|
||||||
tags_count: 0,
|
tags_count: 0,
|
||||||
keywords_count: 0
|
keywords_count: 0,
|
||||||
|
favourite: true
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -137,7 +140,7 @@ RSpec.describe 'Antennas' do
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:antenna) { Fabricate(:antenna, account: user.account, title: 'my antenna') }
|
let(:antenna) { Fabricate(:antenna, account: user.account, title: 'my antenna') }
|
||||||
let(:params) { { title: 'antenna', ignore_reblog: 'true', insert_feeds: 'true' } }
|
let(:params) { { title: 'antenna', ignore_reblog: 'true', insert_feeds: 'true', favourite: 'false' } }
|
||||||
|
|
||||||
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
|
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
|
||||||
|
|
||||||
|
@ -146,6 +149,7 @@ RSpec.describe 'Antennas' do
|
||||||
.to change_antenna_title
|
.to change_antenna_title
|
||||||
.and change_antenna_ignore_reblog
|
.and change_antenna_ignore_reblog
|
||||||
.and change_antenna_insert_feeds
|
.and change_antenna_insert_feeds
|
||||||
|
.and change_antenna_favourite
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
antenna.reload
|
antenna.reload
|
||||||
|
@ -162,7 +166,8 @@ RSpec.describe 'Antennas' do
|
||||||
accounts_count: 0,
|
accounts_count: 0,
|
||||||
domains_count: 0,
|
domains_count: 0,
|
||||||
tags_count: 0,
|
tags_count: 0,
|
||||||
keywords_count: 0
|
keywords_count: 0,
|
||||||
|
favourite: false
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -178,6 +183,10 @@ RSpec.describe 'Antennas' do
|
||||||
change { antenna.reload.insert_feeds }.from(false).to(true)
|
change { antenna.reload.insert_feeds }.from(false).to(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def change_antenna_favourite
|
||||||
|
change { antenna.reload.favourite }.from(true).to(false)
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the antenna does not exist' do
|
context 'when the antenna does not exist' do
|
||||||
it 'returns http not found' do
|
it 'returns http not found' do
|
||||||
put '/api/v1/antennas/-1', headers: headers, params: params
|
put '/api/v1/antennas/-1', headers: headers, params: params
|
||||||
|
|
|
@ -20,6 +20,7 @@ RSpec.describe 'Lists' do
|
||||||
Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none),
|
Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none),
|
||||||
Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true),
|
Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true),
|
||||||
Fabricate(:list, account: user.account, title: 'fifth list', notify: true),
|
Fabricate(:list, account: user.account, title: 'fifth list', notify: true),
|
||||||
|
Fabricate(:list, account: user.account, title: 'fifth list', favourite: false),
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ RSpec.describe 'Lists' do
|
||||||
exclusive: list.exclusive,
|
exclusive: list.exclusive,
|
||||||
antennas: list.antennas,
|
antennas: list.antennas,
|
||||||
notify: list.notify,
|
notify: list.notify,
|
||||||
|
favourite: list.favourite,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -74,6 +76,7 @@ RSpec.describe 'Lists' do
|
||||||
exclusive: list.exclusive,
|
exclusive: list.exclusive,
|
||||||
antennas: list.antennas,
|
antennas: list.antennas,
|
||||||
notify: list.notify,
|
notify: list.notify,
|
||||||
|
favourite: true,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -150,7 +153,7 @@ RSpec.describe 'Lists' do
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:list) { Fabricate(:list, account: user.account, title: 'my list') }
|
let(:list) { Fabricate(:list, account: user.account, title: 'my list') }
|
||||||
let(:params) { { title: 'list', replies_policy: 'followed', exclusive: 'true' } }
|
let(:params) { { title: 'list', replies_policy: 'followed', exclusive: 'true', favourite: 'false' } }
|
||||||
|
|
||||||
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
|
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
|
||||||
|
|
||||||
|
@ -159,6 +162,7 @@ RSpec.describe 'Lists' do
|
||||||
.to change_list_title
|
.to change_list_title
|
||||||
.and change_list_replies_policy
|
.and change_list_replies_policy
|
||||||
.and change_list_exclusive
|
.and change_list_exclusive
|
||||||
|
.and change_list_favourite
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
|
@ -172,6 +176,7 @@ RSpec.describe 'Lists' do
|
||||||
exclusive: list.exclusive,
|
exclusive: list.exclusive,
|
||||||
antennas: list.antennas,
|
antennas: list.antennas,
|
||||||
notify: list.notify,
|
notify: list.notify,
|
||||||
|
favourite: false,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -187,6 +192,10 @@ RSpec.describe 'Lists' do
|
||||||
change { list.reload.exclusive }.from(false).to(true)
|
change { list.reload.exclusive }.from(false).to(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def change_list_favourite
|
||||||
|
change { list.reload.favourite }.from(true).to(false)
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the list does not exist' do
|
context 'when the list does not exist' do
|
||||||
it 'returns http not found' do
|
it 'returns http not found' do
|
||||||
put '/api/v1/lists/-1', headers: headers, params: params
|
put '/api/v1/lists/-1', headers: headers, params: params
|
||||||
|
@ -210,6 +219,102 @@ RSpec.describe 'Lists' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples 'check list permissions when post' do |path, params|
|
||||||
|
context 'when the list does not exist' do
|
||||||
|
it 'returns http not found' do
|
||||||
|
post path, headers: headers, params: params
|
||||||
|
|
||||||
|
expect(response).to have_http_status(404)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the list belongs to another user' do
|
||||||
|
let(:list) { Fabricate(:list) }
|
||||||
|
|
||||||
|
it 'returns http not found' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(404)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/lists/:id/favourite' do
|
||||||
|
subject do
|
||||||
|
post "/api/v1/lists/#{list.id}/favourite", headers: headers, params: {}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:list) { Fabricate(:list, account: user.account, title: 'my list', favourite: false) }
|
||||||
|
|
||||||
|
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
|
||||||
|
|
||||||
|
it 'returns the updated list and updates values', :aggregate_failures do
|
||||||
|
expect { subject }
|
||||||
|
.to change_favourite
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
list.reload
|
||||||
|
|
||||||
|
expect(response.parsed_body).to match({
|
||||||
|
id: list.id.to_s,
|
||||||
|
title: list.title,
|
||||||
|
replies_policy: list.replies_policy,
|
||||||
|
exclusive: list.exclusive,
|
||||||
|
antennas: list.antennas,
|
||||||
|
notify: list.notify,
|
||||||
|
favourite: true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_favourite
|
||||||
|
change { list.reload.favourite }.from(false).to(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'check list permissions when post', '/api/v1/lists/-1/favourite', {}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/lists/:id/unfavourite' do
|
||||||
|
subject do
|
||||||
|
post "/api/v1/lists/#{list.id}/unfavourite", headers: headers, params: {}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:list) { Fabricate(:list, account: user.account, title: 'my list', favourite: true) }
|
||||||
|
|
||||||
|
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
|
||||||
|
|
||||||
|
it 'returns the updated list and updates values', :aggregate_failures do
|
||||||
|
expect { subject }
|
||||||
|
.to change_favourite
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
list.reload
|
||||||
|
|
||||||
|
expect(response.parsed_body).to match({
|
||||||
|
id: list.id.to_s,
|
||||||
|
title: list.title,
|
||||||
|
replies_policy: list.replies_policy,
|
||||||
|
exclusive: list.exclusive,
|
||||||
|
antennas: list.antennas,
|
||||||
|
notify: list.notify,
|
||||||
|
favourite: false,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_favourite
|
||||||
|
change { list.reload.favourite }.from(true).to(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'check list permissions when post', '/api/v1/lists/-1/favourite', {}
|
||||||
|
end
|
||||||
|
|
||||||
describe 'DELETE /api/v1/lists/:id' do
|
describe 'DELETE /api/v1/lists/:id' do
|
||||||
subject do
|
subject do
|
||||||
delete "/api/v1/lists/#{list.id}", headers: headers
|
delete "/api/v1/lists/#{list.id}", headers: headers
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue