Adding exclusive lists feature

Major new feature, currently testing in a branch right now. Fixes #1
This commit is contained in:
Darius Kazemi 2019-07-26 01:51:20 -07:00
parent dc15521b3a
commit bfc0fbab2c
14 changed files with 76 additions and 23 deletions

View File

@ -38,6 +38,6 @@ class Api::V1::ListsController < Api::BaseController
end
def list_params
params.permit(:title)
params.permit(:title, :is_exclusive)
end
end

View File

@ -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) {

View File

@ -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'

View File

@ -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 />

View File

@ -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 = () => {

View File

@ -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 => {

View File

@ -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;

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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