Adding exclusive lists feature
Major new feature, currently testing in a branch right now. Fixes #1
This commit is contained in:
parent
dc15521b3a
commit
bfc0fbab2c
@ -38,6 +38,6 @@ class Api::V1::ListsController < Api::BaseController
|
||||
end
|
||||
|
||||
def list_params
|
||||
params.permit(:title)
|
||||
params.permit(:title, :is_exclusive)
|
||||
end
|
||||
end
|
||||
|
@ -10,9 +10,10 @@ export const LISTS_FETCH_REQUEST = 'LISTS_FETCH_REQUEST';
|
||||
export const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS';
|
||||
export const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL';
|
||||
|
||||
export const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE';
|
||||
export const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET';
|
||||
export const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP';
|
||||
export const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE';
|
||||
export const LIST_EDITOR_IS_EXCLUSIVE_CHANGE = 'LIST_EDITOR_IS_EXCLUSIVE_CHANGE';
|
||||
export const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET';
|
||||
export const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP';
|
||||
|
||||
export const LIST_CREATE_REQUEST = 'LIST_CREATE_REQUEST';
|
||||
export const LIST_CREATE_SUCCESS = 'LIST_CREATE_SUCCESS';
|
||||
@ -100,13 +101,14 @@ export const fetchListsFail = error => ({
|
||||
});
|
||||
|
||||
export const submitListEditor = shouldReset => (dispatch, getState) => {
|
||||
const listId = getState().getIn(['listEditor', 'listId']);
|
||||
const title = getState().getIn(['listEditor', 'title']);
|
||||
const listId = getState().getIn(['listEditor', 'listId']);
|
||||
const title = getState().getIn(['listEditor', 'title']);
|
||||
const isExclusive = getState().getIn(['listEditor', 'isExclusive']);
|
||||
|
||||
if (listId === null) {
|
||||
dispatch(createList(title, shouldReset));
|
||||
} else {
|
||||
dispatch(updateList(listId, title, shouldReset));
|
||||
dispatch(updateList(listId, title, shouldReset, isExclusive));
|
||||
}
|
||||
};
|
||||
|
||||
@ -124,6 +126,11 @@ export const changeListEditorTitle = value => ({
|
||||
value,
|
||||
});
|
||||
|
||||
export const changeListEditorIsExclusive = value => ({
|
||||
type: LIST_EDITOR_IS_EXCLUSIVE_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
export const createList = (title, shouldReset) => (dispatch, getState) => {
|
||||
dispatch(createListRequest());
|
||||
|
||||
@ -150,10 +157,10 @@ export const createListFail = error => ({
|
||||
error,
|
||||
});
|
||||
|
||||
export const updateList = (id, title, shouldReset) => (dispatch, getState) => {
|
||||
export const updateList = (id, title, shouldReset, isExclusive) => (dispatch, getState) => {
|
||||
dispatch(updateListRequest(id));
|
||||
|
||||
api(getState).put(`/api/v1/lists/${id}`, { title }).then(({ data }) => {
|
||||
api(getState).put(`/api/v1/lists/${id}`, { title, is_exclusive: !!isExclusive }).then(({ data }) => {
|
||||
dispatch(updateListSuccess(data));
|
||||
|
||||
if (shouldReset) {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
|
||||
import { changeListEditorTitle, changeListEditorIsExclusive, submitListEditor } from '../../../actions/lists';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import Toggle from 'react-toggle';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
|
||||
@ -17,6 +18,7 @@ const mapStateToProps = state => ({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChange: value => dispatch(changeListEditorTitle(value)),
|
||||
onSubmit: () => dispatch(submitListEditor(false)),
|
||||
onToggle: value => dispatch(changeListEditorIsExclusive(value)),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@ -26,6 +28,7 @@ class ListForm extends React.PureComponent {
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
isExclusive: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
@ -44,8 +47,12 @@ class ListForm extends React.PureComponent {
|
||||
this.props.onSubmit();
|
||||
}
|
||||
|
||||
handleToggle = e => {
|
||||
this.props.onToggle(e.target.checked);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, disabled, intl } = this.props;
|
||||
const { value, disabled, intl, isExclusive, hello } = this.props;
|
||||
|
||||
const title = intl.formatMessage(messages.title);
|
||||
|
||||
@ -57,6 +64,11 @@ class ListForm extends React.PureComponent {
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
<label htmlFor='is-exclusive-checkbox'>
|
||||
<Toggle className='is-exclusive-checkbox' defaultChecked={isExclusive} onChange={this.handleToggle}/>
|
||||
<FormattedMessage id='lists.is-exclusive' defaultMessage='Exclusive?' />
|
||||
</label>
|
||||
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
icon='check'
|
||||
|
@ -28,6 +28,7 @@ class ListEditor extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
listId: PropTypes.string.isRequired,
|
||||
isExclusive: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onInitialize: PropTypes.func.isRequired,
|
||||
@ -53,7 +54,7 @@ class ListEditor extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal list-editor'>
|
||||
<EditListForm />
|
||||
<EditListForm isExclusive={this.props.isExclusive} />
|
||||
|
||||
<Search />
|
||||
|
||||
|
@ -109,7 +109,7 @@ class ListTimeline extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleEditClick = () => {
|
||||
this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id }));
|
||||
this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id, isExclusive: this.props.list.get('is_exclusive') }));
|
||||
}
|
||||
|
||||
handleDeleteClick = () => {
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
LIST_EDITOR_RESET,
|
||||
LIST_EDITOR_SETUP,
|
||||
LIST_EDITOR_TITLE_CHANGE,
|
||||
LIST_EDITOR_IS_EXCLUSIVE_CHANGE,
|
||||
LIST_ACCOUNTS_FETCH_REQUEST,
|
||||
LIST_ACCOUNTS_FETCH_SUCCESS,
|
||||
LIST_ACCOUNTS_FETCH_FAIL,
|
||||
@ -52,6 +53,11 @@ export default function listEditorReducer(state = initialState, action) {
|
||||
map.set('title', action.value);
|
||||
map.set('isChanged', true);
|
||||
});
|
||||
case LIST_EDITOR_IS_EXCLUSIVE_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set('isExclusive', action.value);
|
||||
map.set('isChanged', true);
|
||||
});
|
||||
case LIST_CREATE_REQUEST:
|
||||
case LIST_UPDATE_REQUEST:
|
||||
return state.withMutations(map => {
|
||||
|
@ -2591,6 +2591,15 @@ a.account__display-name {
|
||||
border-color: $ui-highlight-color;
|
||||
}
|
||||
|
||||
label[for="is-exclusive-checkbox"] {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.is-exclusive-checkbox {
|
||||
transform: scale(0.7);
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
.column-link {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
color: $primary-text-color;
|
||||
|
@ -22,6 +22,8 @@ class FeedManager
|
||||
filter_from_home?(status, receiver_id)
|
||||
elsif timeline_type == :mentions
|
||||
filter_from_mentions?(status, receiver_id)
|
||||
elsif timeline_type == :list
|
||||
filter_from_home?(status, receiver_id, :list)
|
||||
else
|
||||
false
|
||||
end
|
||||
@ -152,10 +154,17 @@ class FeedManager
|
||||
(context == :home ? Mute.where(account_id: receiver_id, target_account_id: account_ids).any? : Mute.where(account_id: receiver_id, target_account_id: account_ids, hide_notifications: true).any?)
|
||||
end
|
||||
|
||||
def filter_from_home?(status, receiver_id)
|
||||
def filter_from_home?(status, receiver_id, timeline_type=:home)
|
||||
return false if receiver_id == status.account_id
|
||||
return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
|
||||
return true if phrase_filtered?(status, receiver_id, :home)
|
||||
# hometown: exclusive list rules
|
||||
unless timeline_type == :list
|
||||
# find all exclusive lists
|
||||
@list = List.where(account: Account.find(receiver_id), is_exclusive: true)
|
||||
# is there a list the receiver owns with this account on it? if so, return true
|
||||
return true if ListAccount.where(list: @list, account_id: status.account_id).exists?
|
||||
end
|
||||
|
||||
check_for_blocks = status.active_mentions.pluck(:account_id)
|
||||
check_for_blocks.concat([status.account_id])
|
||||
|
@ -3,11 +3,12 @@
|
||||
#
|
||||
# Table name: lists
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8) not null
|
||||
# title :string default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8) not null
|
||||
# title :string default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# is_exclusive :boolean default(FALSE)
|
||||
#
|
||||
|
||||
class List < ApplicationRecord
|
||||
|
@ -23,6 +23,7 @@
|
||||
# in_reply_to_account_id :bigint(8)
|
||||
# poll_id :bigint(8)
|
||||
# local_only :boolean
|
||||
# activity_pub_type :string
|
||||
#
|
||||
|
||||
class Status < ApplicationRecord
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::ListSerializer < ActiveModel::Serializer
|
||||
attributes :id, :title
|
||||
attributes :id, :title, :is_exclusive
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
|
@ -29,7 +29,7 @@ class FeedInsertWorker
|
||||
def feed_filtered?
|
||||
# Note: Lists are a variation of home, so the filtering rules
|
||||
# of home apply to both
|
||||
FeedManager.instance.filter?(:home, @status, @follower.id)
|
||||
FeedManager.instance.filter?(@type, @status, @follower.id)
|
||||
end
|
||||
|
||||
def perform_push
|
||||
|
6
db/migrate/20190726034905_add_is_exclusive_to_lists.rb
Normal file
6
db/migrate/20190726034905_add_is_exclusive_to_lists.rb
Normal file
@ -0,0 +1,6 @@
|
||||
class AddIsExclusiveToLists < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :lists, :is_exclusive, :boolean
|
||||
change_column_default :lists, :is_exclusive, false
|
||||
end
|
||||
end
|
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2019_07_12_233435) do
|
||||
ActiveRecord::Schema.define(version: 2019_07_26_034905) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@ -344,6 +344,7 @@ ActiveRecord::Schema.define(version: 2019_07_12_233435) do
|
||||
t.string "title", default: "", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "is_exclusive", default: false
|
||||
t.index ["account_id"], name: "index_lists_on_account_id"
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user