Progress
Albums almost done, group, chat moderation, photo, video page updates
This commit is contained in:
parent
a2ffbadedb
commit
ee91809e8d
@ -7,12 +7,57 @@ module Admin
|
||||
PER_PAGE = 100
|
||||
|
||||
def index
|
||||
authorize :account, :index?
|
||||
@followers = ChatMessage.where(from_account: @account).page(params[:page]).per(PER_PAGE)
|
||||
authorize :chat_message, :index?
|
||||
|
||||
@chat_messages = ChatMessage.where(from_account: @account).page(params[:page]).per(PER_PAGE)
|
||||
@form = Form::ChatMessageBatch.new
|
||||
end
|
||||
|
||||
def show
|
||||
authorize :chat_message, :index?
|
||||
|
||||
@chat_messages = @account.chat_messages.where(id: params[:id])
|
||||
authorize @chat_messages.first, :show?
|
||||
|
||||
@form = Form::ChatMessageBatch.new
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :chat_message, :update?
|
||||
|
||||
@form = Form::ChatMessageBatch.new(form_chat_message_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
|
||||
|
||||
redirect_to admin_account_chat_messages_path(@account.id, current_params)
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
|
||||
|
||||
redirect_to admin_account_chat_messages_path(@account.id, current_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def form_chat_message_batch_params
|
||||
params.require(:form_chat_message_batch).permit(:action, chat_message_ids: [])
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def current_params
|
||||
page = (params[:page] || 1).to_i
|
||||
|
||||
{
|
||||
media: params[:media],
|
||||
page: page > 1 && page,
|
||||
}.select { |_, value| value.present? }
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:delete]
|
||||
'delete'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -23,7 +23,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def hide_results?
|
||||
# : todo : where tf is this?
|
||||
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
end
|
||||
|
||||
|
@ -19,13 +19,6 @@ class Api::V1::ChatConversationController < Api::BaseController
|
||||
end
|
||||
|
||||
def create
|
||||
# : todo :
|
||||
# check if already created
|
||||
# check if blocked
|
||||
# check if chat blocked
|
||||
# check if allow anyone to message then create with approved:true
|
||||
# unique account id, participants
|
||||
|
||||
chat_conversation_account = find_or_create_conversation
|
||||
render json: chat_conversation_account, each_serializer: REST::ChatConversationAccountSerializer
|
||||
end
|
||||
@ -80,26 +73,8 @@ class Api::V1::ChatConversationController < Api::BaseController
|
||||
|
||||
def find_or_create_conversation
|
||||
chat = ChatConversationAccount.find_by(account: current_account, participant_account_ids: [@account.id.to_s])
|
||||
|
||||
return chat unless chat.nil?
|
||||
|
||||
chat_conversation = ChatConversation.create
|
||||
|
||||
my_chat = ChatConversationAccount.create!(
|
||||
account: current_account,
|
||||
participant_account_ids: [@account.id.to_s],
|
||||
chat_conversation: chat_conversation,
|
||||
is_approved: true
|
||||
)
|
||||
|
||||
# : todo : if multiple ids
|
||||
their_chat = ChatConversationAccount.create!(
|
||||
account: @account,
|
||||
participant_account_ids: [current_account.id.to_s],
|
||||
chat_conversation: chat_conversation,
|
||||
is_approved: false # default as request
|
||||
)
|
||||
|
||||
my_chat = CreateChatConversationService.new.call(current_account, [@account])
|
||||
return my_chat
|
||||
end
|
||||
|
||||
|
@ -34,10 +34,6 @@ class Api::V1::Groups::RemovedAccountsController < Api::BaseController
|
||||
render_empty_success
|
||||
end
|
||||
|
||||
def search
|
||||
# : todo :
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_group
|
||||
|
@ -3,7 +3,6 @@
|
||||
class Api::V1::StatusesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
# : todo : disable all oauth everything
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
|
||||
before_action :require_user!, except: [:show, :comments, :context, :card]
|
||||
|
@ -21,9 +21,6 @@ class Settings::GroupCategoriesController < Admin::BaseController
|
||||
end
|
||||
|
||||
def destroy
|
||||
# : todo :
|
||||
# don't destroy if any groups have this category
|
||||
|
||||
@category.destroy!
|
||||
log_action :destroy, @category
|
||||
flash[:notice] = I18n.t('promotions.destroyed_msg')
|
||||
|
@ -26,6 +26,10 @@ class Settings::ProfilesController < Settings::BaseController
|
||||
else
|
||||
# : todo :
|
||||
# only allowed to change username once per day
|
||||
if params[:account][:username] && @account.username != params[:account][:username]
|
||||
Redis.current.set("username_change:#{account.id}", true)
|
||||
Redis.current.expire("username_change:#{account.id}", 24.huors.seconds)
|
||||
end
|
||||
|
||||
if UpdateAccountService.new.call(@account, account_params)
|
||||
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
|
@ -101,7 +101,7 @@ module ApplicationHelper
|
||||
end
|
||||
|
||||
def theme
|
||||
# : todo :
|
||||
# : todo : remove
|
||||
return 'white'
|
||||
end
|
||||
|
||||
|
@ -7,10 +7,23 @@ export const ALBUMS_FETCH_REQUEST = 'ALBUMS_FETCH_REQUEST'
|
||||
export const ALBUMS_FETCH_SUCCESS = 'ALBUMS_FETCH_SUCCESS'
|
||||
export const ALBUMS_FETCH_FAIL = 'ALBUMS_FETCH_FAIL'
|
||||
|
||||
export const ALBUMS_CREATE_REQUEST = 'ALBUMS_CREATE_REQUEST'
|
||||
export const ALBUMS_CREATE_SUCCESS = 'ALBUMS_CREATE_SUCCESS'
|
||||
export const ALBUMS_CREATE_FAIL = 'ALBUMS_CREATE_FAIL'
|
||||
export const ALBUMS_EXPAND_REQUEST = 'ALBUMS_EXPAND_REQUEST'
|
||||
export const ALBUMS_EXPAND_SUCCESS = 'ALBUMS_EXPAND_SUCCESS'
|
||||
export const ALBUMS_EXPAND_FAIL = 'ALBUMS_EXPAND_FAIL'
|
||||
|
||||
export const ALBUM_CREATE_REQUEST = 'ALBUM_CREATE_REQUEST'
|
||||
export const ALBUM_CREATE_SUCCESS = 'ALBUM_CREATE_SUCCESS'
|
||||
export const ALBUM_CREATE_FAIL = 'ALBUM_CREATE_FAIL'
|
||||
|
||||
export const ALBUM_REMOVE_REQUEST = 'ALBUM_REMOVE_REQUEST'
|
||||
export const ALBUM_REMOVE_SUCCESS = 'ALBUM_REMOVE_SUCCESS'
|
||||
export const ALBUM_REMOVE_FAIL = 'ALBUM_REMOVE_FAIL'
|
||||
|
||||
export const ALBUM_EDIT_REQUEST = 'ALBUM_EDIT_REQUEST'
|
||||
export const ALBUM_EDIT_SUCCESS = 'ALBUM_EDIT_SUCCESS'
|
||||
export const ALBUM_EDIT_FAIL = 'ALBUM_EDIT_FAIL'
|
||||
|
||||
export const ALBUM_UPDATE_MEDIA_REQUEST = 'ALBUM_UPDATE_MEDIA_REQUEST'
|
||||
export const ALBUM_UPDATE_MEDIA_SUCCESS = 'ALBUM_UPDATE_MEDIA_SUCCESS'
|
||||
export const ALBUM_UPDATE_MEDIA_FAIL = 'ALBUM_UPDATE_MEDIA_FAIL'
|
||||
|
||||
export const ALBUMS_REMOVE_REQUEST = 'ALBUMS_REMOVE_REQUEST'
|
||||
export const ALBUMS_REMOVE_SUCCESS = 'ALBUMS_REMOVE_SUCCESS'
|
||||
export const ALBUMS_REMOVE_FAIL = 'ALBUMS_REMOVE_FAIL'
|
@ -1,8 +1,13 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { CX } from '../constants'
|
||||
import { openModal } from '../actions/modal'
|
||||
import {
|
||||
CX,
|
||||
MODAL_ALBUM_CREATE,
|
||||
} from '../constants'
|
||||
import Button from './button'
|
||||
import Icon from './icon'
|
||||
import Image from './image'
|
||||
@ -10,19 +15,14 @@ import Text from './text'
|
||||
|
||||
class Album extends React.PureComponent {
|
||||
|
||||
handleOnClick = (e) => {
|
||||
//
|
||||
}
|
||||
|
||||
handleOnOpenAlbumCreation = () => {
|
||||
|
||||
handleOnOpenAlbumCreate = () => {
|
||||
this.props.openAlbumCreate()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
album,
|
||||
isAddable,
|
||||
isDummy,
|
||||
} = this.props
|
||||
|
||||
const title = isAddable ? 'New album' : 'Album title'
|
||||
@ -31,29 +31,26 @@ class Album extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.minW162PX, _s.px5, _s.flex1].join(' ')}>
|
||||
{
|
||||
!isDummy &&
|
||||
<Button
|
||||
noClasses
|
||||
className={[_s.d, _s.noUnderline, _s.noOutline, _s.bgTransparent].join(' ')}
|
||||
to={to}
|
||||
onClick={isAddable ? this.handleOnOpenAlbumCreation : undefined}
|
||||
>
|
||||
<div className={[_s.d, _s.w100PC, _s.mt5, _s.mb10].join(' ')}>
|
||||
<div className={[_s.d, _s.w100PC, _s.pt100PC].join(' ')}>
|
||||
<div className={[_s.d, _s.posAbs, _s.top0, _s.w100PC, _s.right0, _s.bottom0, _s.left0].join(' ')}>
|
||||
<div className={[_s.d, _s.w100PC, _s.h100PC, _s.aiCenter, _s.jcCenter, _s.radiusSmall, _s.bgTertiary, _s.border1PX, _s.borderColorSecondary].join(' ')}>
|
||||
{ isAddable && <Icon id='add' size='20px' /> }
|
||||
</div>
|
||||
<Button
|
||||
noClasses
|
||||
className={[_s.d, _s.noUnderline, _s.cursorPointer, _s.outlineNone, _s.bgTransparent].join(' ')}
|
||||
to={to}
|
||||
onClick={isAddable ? this.handleOnOpenAlbumCreate : undefined}
|
||||
>
|
||||
<div className={[_s.d, _s.w100PC, _s.mt5, _s.mb10].join(' ')}>
|
||||
<div className={[_s.d, _s.w100PC, _s.pt100PC].join(' ')}>
|
||||
<div className={[_s.d, _s.posAbs, _s.top0, _s.w100PC, _s.right0, _s.bottom0, _s.left0].join(' ')}>
|
||||
<div className={[_s.d, _s.w100PC, _s.h100PC, _s.aiCenter, _s.jcCenter, _s.radiusSmall, _s.bgTertiary, _s.border1PX, _s.borderColorSecondary].join(' ')}>
|
||||
{ isAddable && <Icon id='add' size='20px' /> }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={[_s.d, _s.w100PC, _s.pt7, _s.mb15].join(' ')}>
|
||||
<Text weight='bold'>{title}</Text>
|
||||
{ !isAddable && <Text color='secondary' size='small' className={_s.mt5}>{subtitle}</Text> }
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
<div className={[_s.d, _s.w100PC, _s.pt7, _s.mb15].join(' ')}>
|
||||
<Text weight='bold'>{title}</Text>
|
||||
{ !isAddable && <Text color='secondary' size='small' className={_s.mt5}>{subtitle}</Text> }
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -63,7 +60,12 @@ class Album extends React.PureComponent {
|
||||
Album.propTypes = {
|
||||
album: ImmutablePropTypes.map,
|
||||
isAddable: PropTypes.bool,
|
||||
isDummy: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default Album
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
openAlbumCreate() {
|
||||
dispatch(openModal(MODAL_ALBUM_CREATE))
|
||||
}
|
||||
})
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Album)
|
@ -28,7 +28,7 @@ class DeckColumnHeader extends React.PureComponent {
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div data-sort-header className={[_s.d, _s.w100PC, _s.flexRow, _s.aiCenter, _s.h60PX, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary, _s.bgPrimary].join(' ')}>
|
||||
<div data-sort-header className={[_s.d, _s.w100PC, _s.overflowHidden, _s.flexRow, _s.aiCenter, _s.h60PX, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary, _s.bgPrimary].join(' ')}>
|
||||
<div data-sort-header className={[_s.d, _s.flexRow, _s.mr15, _s.cursorEWResize].join(' ')}>
|
||||
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.mr2, _s.bgSecondary].join(' ')} />
|
||||
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.mr2, _s.bgSecondary].join(' ')} />
|
||||
@ -36,7 +36,7 @@ class DeckColumnHeader extends React.PureComponent {
|
||||
</div>
|
||||
|
||||
{ !!icon && <Icon id={icon} className={[_s.cPrimary, _s.mr15].join(' ')} size='18px' /> }
|
||||
<div className={[_s.d, _s.flexRow, _s.aiEnd].join(' ')}>
|
||||
<div className={[_s.d, _s.flexRow, _s.aiEnd, _s.flexShrink1, _s.overflowHidden, _s.textOverflowEllipsis2].join(' ')}>
|
||||
{ !!title && <Text size='extraLarge' weight='medium'>{title}</Text> }
|
||||
{ !!subtitle && <Text className={_s.ml5} color='secondary'>{subtitle}</Text> }
|
||||
</div>
|
||||
|
@ -99,9 +99,6 @@ class GroupHeader extends ImmutablePureComponent {
|
||||
})
|
||||
}
|
||||
|
||||
// : todo :
|
||||
// {group.get('archived') && <Icon id='lock' title={intl.formatMessage(messages.group_archived)} />}
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.z1, _s.w100PC, _s.mb15].join(' ')}>
|
||||
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
|
||||
|
@ -14,23 +14,41 @@ class MediaItem extends ImmutablePureComponent {
|
||||
|
||||
state = {
|
||||
loaded: false,
|
||||
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
|
||||
visible: true,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.attachment.get('blurhash')) {
|
||||
const { attachment } = this.props
|
||||
if (!attachment) return
|
||||
|
||||
if (attachment.get('blurhash')) {
|
||||
this._decode()
|
||||
}
|
||||
|
||||
this.setState({
|
||||
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
|
||||
})
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
|
||||
const { attachment } = this.props
|
||||
const { prevAttachment } = prevProps
|
||||
|
||||
if (prevAttachment !== attachment) {
|
||||
this._decode()
|
||||
return
|
||||
}
|
||||
|
||||
if (prevAttachment.get('blurhash') !== attachment.get('blurhash') && attachment.get('blurhash')) {
|
||||
this._decode()
|
||||
}
|
||||
}
|
||||
|
||||
_decode() {
|
||||
const hash = this.props.attachment.get('blurhash')
|
||||
_decode = () => {
|
||||
const { attachment } = this.props
|
||||
if (!attachment) return
|
||||
|
||||
const hash = attachment.get('blurhash')
|
||||
const pixels = decode(hash, 160, 160)
|
||||
|
||||
if (pixels && this.canvas) {
|
||||
@ -41,7 +59,7 @@ class MediaItem extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
setCanvasRef = c => {
|
||||
setCanvasRef = (c) => {
|
||||
this.canvas = c
|
||||
}
|
||||
|
||||
@ -50,7 +68,10 @@ class MediaItem extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
hoverToPlay() {
|
||||
return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1
|
||||
const { attachment } = this.props
|
||||
if (!attachment) return
|
||||
|
||||
return !autoPlayGif && ['gifv', 'video'].indexOf(attachment.get('type')) !== -1
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -60,104 +81,152 @@ class MediaItem extends ImmutablePureComponent {
|
||||
isSmall,
|
||||
} = this.props
|
||||
const { visible, loaded } = this.state
|
||||
|
||||
|
||||
if (!attachment || !account) return null
|
||||
|
||||
const status = attachment.get('status')
|
||||
const title = status.get('spoiler_text') || attachment.get('description')
|
||||
|
||||
const attachmentType = attachment.get('type')
|
||||
const aspectRatio = attachment.getIn(['meta', 'aspect'])
|
||||
|
||||
const isVideo = attachmentType === 'video'
|
||||
let badge = null
|
||||
|
||||
if (attachmentType === 'video') {
|
||||
if (isVideo) {
|
||||
const duration = attachment.getIn(['meta', 'duration'])
|
||||
badge = (duration / 60).toFixed(2)
|
||||
} else if (attachmentType === 'gifv') {
|
||||
badge = 'GIF'
|
||||
}
|
||||
|
||||
const statusUrl = `/${account.getIn(['acct'])}/posts/${status.get('id')}`
|
||||
|
||||
const isSmallRatio = aspectRatio < 1
|
||||
const isSquare = aspectRatio === 1
|
||||
const containerClasses = CX({
|
||||
d: 1,
|
||||
posAbs: 1,
|
||||
top0: 1,
|
||||
h100PC: 1,
|
||||
// w100PC: 1,
|
||||
py2: !isSmall,
|
||||
px2: !isSmall,
|
||||
px5: 1,
|
||||
flex1: !isSmallRatio && !isSquare,
|
||||
minW198PX: !isVideo && !isSmallRatio && !isSquare,
|
||||
minW232PX: isVideo && !isSmallRatio && !isSquare,
|
||||
minW120PX: isSmallRatio,
|
||||
minW162PX: isSquare,
|
||||
})
|
||||
|
||||
const linkClasses = CX({
|
||||
const paddedContainerClasses = CX({
|
||||
d: 1,
|
||||
w100PC: 1,
|
||||
// h100PC: 1,
|
||||
overflowHidden: 1,
|
||||
border1PX: 1,
|
||||
borderColorPrimary: 1,
|
||||
h100PC: isSmallRatio || isSquare,
|
||||
pt100PC: isSmallRatio || isSquare || !isVideo,
|
||||
pt5625PC: isVideo && !isSmallRatio && !isSquare,
|
||||
})
|
||||
|
||||
const statusUrl = `/${account.getIn(['acct'])}/posts/${status.get('id')}`;
|
||||
|
||||
// : todo : fix dimensions to be like albums
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.pt25PC].join(' ')}>
|
||||
<div className={containerClasses}>
|
||||
<NavLink
|
||||
to={statusUrl}
|
||||
title={title}
|
||||
className={linkClasses}
|
||||
>
|
||||
{
|
||||
(!loaded || !visible) &&
|
||||
<canvas
|
||||
height='100%'
|
||||
width='100%'
|
||||
ref={this.setCanvasRef}
|
||||
className={[_s.d, _s.w100PC, _s.h100PC, _s.z2].join(' ')}
|
||||
/>
|
||||
}
|
||||
<div className={containerClasses}>
|
||||
<NavLink
|
||||
className={[_s.d, _s.noUnderline, _s.outlineNone, _s.bgTransparent, _s.flexGrow1].join(' ')}
|
||||
to={statusUrl}
|
||||
title={title}
|
||||
>
|
||||
<div className={[_s.d, _s.mt5, _s.mb10, _s.flexGrow1].join(' ')}>
|
||||
<div className={paddedContainerClasses}>
|
||||
<div className={[_s.d, _s.posAbs, _s.top0, _s.right0, _s.left0, _s.bottom0].join(' ')}>
|
||||
<div className={[_s.d, _s.h100PC, _s.aiCenter, _s.jcCenter, _s.radiusSmall, _s.overflowHidden].join(' ')}>
|
||||
{
|
||||
(!loaded || !visible) &&
|
||||
<canvas
|
||||
height='100%'
|
||||
width='100%'
|
||||
ref={this.setCanvasRef}
|
||||
className={[_s.d, _s.w100PC, _s.h100PC, _s.z2].join(' ')}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
visible &&
|
||||
<Image
|
||||
height='100%'
|
||||
width=''
|
||||
src={attachment.get('preview_url')}
|
||||
alt={attachment.get('description')}
|
||||
title={attachment.get('description')}
|
||||
onLoad={this.handleImageLoad}
|
||||
className={_s.z1}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
visible &&
|
||||
<Image
|
||||
height='100%'
|
||||
width=''
|
||||
src={attachment.get('preview_url')}
|
||||
alt={attachment.get('description')}
|
||||
title={attachment.get('description')}
|
||||
onLoad={this.handleImageLoad}
|
||||
className={_s.z1}
|
||||
/>
|
||||
}
|
||||
|
||||
<div className={[_s.d, _s.aiCenter, _s.jcCenter, _s.h100PC, _s.w100PC, _s.z3, _s.posAbs].join(' ')}>
|
||||
{
|
||||
!visible &&
|
||||
<Icon
|
||||
id='hidden'
|
||||
size='22px'
|
||||
className={[_s.cWhite].join('')}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!badge &&
|
||||
<div className={[_s.d, _s.posAbs, _s.radiusSmall, _s.bgBlackOpaque, _s.px5, _s.py5, _s.mr5, _s.mt5, _s.mb5, _s.bottom0, _s.right0].join(' ')}>
|
||||
<Text size='extraSmall' color='white'>
|
||||
{badge}
|
||||
</Text>
|
||||
{
|
||||
(!visible || !!badge) &&
|
||||
<div className={[_s.d, _s.aiCenter, _s.jcCenter, _s.h100PC, _s.w100PC, _s.z3, _s.posAbs].join(' ')}>
|
||||
{
|
||||
!visible &&
|
||||
<Icon
|
||||
id='hidden'
|
||||
size='22px'
|
||||
className={[_s.cWhite].join('')}
|
||||
/>
|
||||
}
|
||||
{
|
||||
!!badge &&
|
||||
<div className={[_s.d, _s.posAbs, _s.radiusSmall, _s.bgBlackOpaque, _s.px5, _s.py5, _s.mr5, _s.mt5, _s.mb5, _s.bottom0, _s.right0].join(' ')}>
|
||||
<Text size='extraSmall' color='white'>
|
||||
{badge}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</NavLink>
|
||||
</div>
|
||||
)
|
||||
|
||||
// return (
|
||||
// <div className={[_s.d, _s.pt25PC].join(' ')}>
|
||||
// <div className={containerClasses}>
|
||||
// <NavLink
|
||||
// to={statusUrl}
|
||||
// title={title}
|
||||
// className={linkClasses}
|
||||
// >
|
||||
// {
|
||||
// (!loaded || !visible) &&
|
||||
// <canvas
|
||||
// height='100%'
|
||||
// width='100%'
|
||||
// ref={this.setCanvasRef}
|
||||
// className={[_s.d, _s.w100PC, _s.h100PC, _s.z2].join(' ')}
|
||||
// />
|
||||
// }
|
||||
|
||||
// {
|
||||
// visible &&
|
||||
// <Image
|
||||
// height='100%'
|
||||
// width=''
|
||||
// src={attachment.get('preview_url')}
|
||||
// alt={attachment.get('description')}
|
||||
// title={attachment.get('description')}
|
||||
// onLoad={this.handleImageLoad}
|
||||
// className={_s.z1}
|
||||
// />
|
||||
// }
|
||||
|
||||
|
||||
// </NavLink>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MediaItem.propTypes = {
|
||||
isDummy: PropTypes.bool.isRequired,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
attachment: ImmutablePropTypes.map.isRequired,
|
||||
isSmall: PropTypes.bool,
|
||||
|
@ -1 +1,29 @@
|
||||
// : todo :
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ModalLayout from './modal_layout'
|
||||
import AlbumCreate from '../../features/album_create'
|
||||
|
||||
class AlbumCreateModal extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
const { onClose } = this.props
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
title='Create Album'
|
||||
width={500}
|
||||
onClose={onClose}
|
||||
>
|
||||
<AlbumCreate isModal />
|
||||
</ModalLayout>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AlbumCreateModal.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default AlbumCreateModal
|
@ -1,29 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ModalLayout from './modal_layout'
|
||||
import BookmarkCollectionEdit from '../../features/bookmark_collection_edit'
|
||||
|
||||
class BookmarkCollectionEditModal extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
const { onClose } = this.props
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
title='Edit Bookmark Collection'
|
||||
width={500}
|
||||
onClose={onClose}
|
||||
>
|
||||
<BookmarkCollectionEdit isModal />
|
||||
</ModalLayout>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BookmarkCollectionEditModal.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default BookmarkCollectionEditModal
|
@ -39,12 +39,6 @@ class MediaGalleryPanel extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// componentWillReceiveProps(nextProps) {
|
||||
// if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
|
||||
// this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, { limit: 8 }))
|
||||
// }
|
||||
// }
|
||||
|
||||
render() {
|
||||
const {
|
||||
account,
|
||||
|
@ -81,8 +81,6 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
|
||||
handleGroupRemoveAccount = () => {
|
||||
const { status } = this.props
|
||||
|
||||
// : todo : check
|
||||
this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id']))
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,19 @@ class DeckSidebar extends ImmutablePureComponent {
|
||||
|
||||
<div className={[_s.d].join(' ')}>
|
||||
|
||||
<Button
|
||||
to='/'
|
||||
isText
|
||||
title='Go home'
|
||||
aria-label='Go home'
|
||||
color='none'
|
||||
backgroundColor='none'
|
||||
className={[_s.d, _s.jcCenter, _s.noSelect, _s.noUnderline, _s.mt15, _s.mb15, _s.cursorPointer, _s.px10, _s.mr5].join(' ')}
|
||||
icon='back'
|
||||
iconSize='20px'
|
||||
iconClassName={_s.fillNavigationBrand}
|
||||
/>
|
||||
|
||||
<h1 className={[_s.d].join(' ')}>
|
||||
<Button
|
||||
to='/'
|
||||
|
@ -51,7 +51,6 @@ export const POPOVER_VIDEO_STATS = 'VIDEO_STATS'
|
||||
export const MODAL_ALBUM_CREATE = 'ALBUM_CREATE'
|
||||
export const MODAL_BLOCK_ACCOUNT = 'BLOCK_ACCOUNT'
|
||||
export const MODAL_BOOKMARK_COLLECTION_CREATE = 'BOOKMARK_COLLECTION_CREATE'
|
||||
export const MODAL_BOOKMARK_COLLECTION_EDIT = 'BOOKMARK_COLLECTION_EDIT'
|
||||
export const MODAL_BOOST = 'BOOST'
|
||||
export const MODAL_CHAT_CONVERSATION_CREATE = 'CHAT_CONVERSATION_CREATE'
|
||||
export const MODAL_CHAT_CONVERSATION_DELETE = 'CHAT_CONVERSATION_DELETE'
|
||||
|
@ -3,76 +3,60 @@ import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import { expandAccountMediaTimeline } from '../actions/timelines'
|
||||
import { getAccountGallery } from '../selectors'
|
||||
import { me } from '../initial_state'
|
||||
import {
|
||||
fetchAccountAlbums,
|
||||
expandAccountAlbums,
|
||||
} from '../actions/albums'
|
||||
import ColumnIndicator from '../components/column_indicator'
|
||||
import Heading from '../components/heading'
|
||||
import TabBar from '../components/tab_bar'
|
||||
import MediaItem from '../components/media_item'
|
||||
import LoadMore from '../components/load_more'
|
||||
import Block from '../components/block'
|
||||
import Image from '../components/image'
|
||||
import Album from '../components/album'
|
||||
import Dummy from '../components/dummy'
|
||||
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
||||
|
||||
class AccountAlbums extends ImmutablePureComponent {
|
||||
|
||||
// componentDidMount() {
|
||||
// const { accountId, mediaType } = this.props
|
||||
componentDidMount() {
|
||||
const { accountId, mediaType } = this.props
|
||||
|
||||
// if (accountId && accountId !== -1) {
|
||||
// this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType }))
|
||||
// }
|
||||
// }
|
||||
if (accountId && accountId !== -1) {
|
||||
this.props.onFetchAccountAlbums(accountId)
|
||||
}
|
||||
}
|
||||
|
||||
// componentWillReceiveProps(nextProps) {
|
||||
// if (
|
||||
// (nextProps.accountId && nextProps.accountId !== this.props.accountId) ||
|
||||
// (nextProps.accountId && nextProps.mediaType !== this.props.mediaType)
|
||||
// ) {
|
||||
// this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, {
|
||||
// mediaType: nextProps.mediaType,
|
||||
// }))
|
||||
// }
|
||||
// }
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
|
||||
this.props.onFetchAccountAlbums(nextProps.accountId)
|
||||
}
|
||||
}
|
||||
|
||||
// handleScrollToBottom = () => {
|
||||
// if (this.props.hasMore) {
|
||||
// this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined)
|
||||
// }
|
||||
// }
|
||||
handleLoadMore = () => {
|
||||
const { accountId, hasMore } = this.props
|
||||
if (accountId && accountId !== -1 && hasMore) {
|
||||
this.props.onExpandAccountAlbums(accountId)
|
||||
}
|
||||
}
|
||||
|
||||
// handleScroll = (e) => {
|
||||
// const { scrollTop, scrollHeight, clientHeight } = e.target
|
||||
// const offset = scrollHeight - scrollTop - clientHeight
|
||||
|
||||
// if (150 > offset && !this.props.isLoading) {
|
||||
// this.handleScrollToBottom()
|
||||
// }
|
||||
// }
|
||||
|
||||
// handleLoadMore = (maxId) => {
|
||||
// if (this.props.accountId && this.props.accountId !== -1) {
|
||||
// this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, {
|
||||
// maxId,
|
||||
// mediaType: this.props.mediaType,
|
||||
// }))
|
||||
// }
|
||||
// }
|
||||
|
||||
// handleLoadOlder = (e) => {
|
||||
// e.preventDefault()
|
||||
// this.handleScrollToBottom()
|
||||
// }
|
||||
handleLoadOlder = (e) => {
|
||||
e.preventDefault()
|
||||
this.handleLoadMore()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
account,
|
||||
isMe,
|
||||
albums,
|
||||
account,
|
||||
accountId,
|
||||
hasMore,
|
||||
isLoading,
|
||||
} = this.props
|
||||
|
||||
if (!account) return null
|
||||
const hasAlbums = !!albums ? albums.size > 0 : false
|
||||
|
||||
return (
|
||||
<Block>
|
||||
@ -95,103 +79,68 @@ class AccountAlbums extends ImmutablePureComponent {
|
||||
|
||||
<div className={[_s.d, _s.w100PC, _s.flexRow, _s.flexWrap, _s.px10, _s.mb15, _s.pb10].join(' ')}>
|
||||
{ isMe && <Album isAddable /> }
|
||||
<Album />
|
||||
<Album />
|
||||
<Album />
|
||||
<Album />
|
||||
<Album />
|
||||
|
||||
<Album isDummy />
|
||||
<Album isDummy />
|
||||
<Album isDummy />
|
||||
<Album isDummy />
|
||||
<Album isDummy />
|
||||
<Album isDummy />
|
||||
{
|
||||
hasAlbums &&
|
||||
albums.map((albums, i) => (
|
||||
<Album
|
||||
key={album.get('id')}
|
||||
album={album}
|
||||
account={account}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
{
|
||||
Array.apply(null, { length: 8}).map((_, i) => (
|
||||
<Dummy className={[_s.d, _s.minW162PX, _s.px5, _s.flex1].join(' ')} />
|
||||
))
|
||||
}
|
||||
|
||||
{
|
||||
!isLoading && !hasAlbums && me !== accountId &&
|
||||
<ColumnIndicator type='error' message='No albums exist' />
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
hasMore && !(isLoading && !hasAlbums) &&
|
||||
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
||||
}
|
||||
</Block>
|
||||
)
|
||||
// const {
|
||||
// attachments,
|
||||
// isLoading,
|
||||
// hasMore,
|
||||
// intl,
|
||||
// account,
|
||||
// } = this.props
|
||||
|
||||
|
||||
|
||||
// return (
|
||||
// <Block>
|
||||
// <div
|
||||
// role='feed'
|
||||
// onScroll={this.handleScroll}
|
||||
// className={[_s.d, _s.flexRow, _s.flexWrap, _s.py5, _s.px5].join(' ')}
|
||||
// >
|
||||
|
||||
// {
|
||||
// attachments.map((attachment, i) => (
|
||||
// <MediaItem
|
||||
// key={attachment.get('id')}
|
||||
// attachment={attachment}
|
||||
// account={account}
|
||||
// />
|
||||
// ))
|
||||
// }
|
||||
|
||||
// {
|
||||
// isLoading && attachments.size === 0 &&
|
||||
// <div className={[_s.d, _s.w100PC].join(' ')}>
|
||||
// <MediaGalleryPlaceholder />
|
||||
// </div>
|
||||
// }
|
||||
|
||||
// {
|
||||
// !isLoading && attachments.size === 0 &&
|
||||
// <ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
|
||||
// }
|
||||
// </div>
|
||||
|
||||
// {
|
||||
// hasMore && !(isLoading && attachments.size === 0) &&
|
||||
// <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
||||
// }
|
||||
// </Block>
|
||||
// )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
none: { id: 'account_gallery.none', defaultMessage: 'No media to show.' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, { account, mediaType }) => {
|
||||
const accountId = !!account ? account.get('id') : -1
|
||||
|
||||
return {
|
||||
accountId,
|
||||
attachments: getAccountGallery(state, accountId, mediaType),
|
||||
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
|
||||
albums: state.getIn(['album_lists', accountId, 'items']),
|
||||
isLoading: state.getIn(['album_lists', accountId, 'isLoading'], false),
|
||||
hasMore: state.getIn(['album_lists', accountId, 'hasMore'], false),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onFetchAccountAlbums(accountId) {
|
||||
|
||||
},
|
||||
onExpandAccountAlbums(accountId) {
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
AccountAlbums.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
account: ImmutablePropTypes.map,
|
||||
accountId: PropTypes.string,
|
||||
attachments: ImmutablePropTypes.list.isRequired,
|
||||
albums: ImmutablePropTypes.list,
|
||||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
mediaType: PropTypes.oneOf([
|
||||
'photo',
|
||||
'video',
|
||||
]),
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(AccountAlbums))
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AccountAlbums)
|
172
app/javascript/gabsocial/features/account_photo_gallery.js
Normal file
172
app/javascript/gabsocial/features/account_photo_gallery.js
Normal file
@ -0,0 +1,172 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import { expandAccountMediaTimeline } from '../actions/timelines'
|
||||
import { getAccountGallery } from '../selectors'
|
||||
import ColumnIndicator from '../components/column_indicator'
|
||||
import MediaItem from '../components/media_item'
|
||||
import Heading from '../components/heading'
|
||||
import TabBar from '../components/tab_bar'
|
||||
import LoadMore from '../components/load_more'
|
||||
import Block from '../components/block'
|
||||
import Dummy from '../components/dummy'
|
||||
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
||||
|
||||
|
||||
class AccountPhotoGallery extends ImmutablePureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
const { accountId } = this.props
|
||||
|
||||
if (accountId && accountId !== -1) {
|
||||
this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType: 'photo' }))
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
|
||||
this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, {
|
||||
mediaType: 'photo',
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
handleScrollToBottom = () => {
|
||||
if (this.props.hasMore) {
|
||||
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined)
|
||||
}
|
||||
}
|
||||
|
||||
handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target
|
||||
const offset = scrollHeight - scrollTop - clientHeight
|
||||
|
||||
if (150 > offset && !this.props.isLoading) {
|
||||
this.handleScrollToBottom()
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = (maxId) => {
|
||||
if (this.props.accountId && this.props.accountId !== -1) {
|
||||
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, {
|
||||
maxId,
|
||||
mediaType: 'photo',
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadOlder = (e) => {
|
||||
e.preventDefault()
|
||||
this.handleScrollToBottom()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
attachments,
|
||||
isLoading,
|
||||
hasMore,
|
||||
intl,
|
||||
account,
|
||||
} = this.props
|
||||
|
||||
if (!account) return null
|
||||
const hasAttachments = !!attachments ? attachments.size > 0 : false
|
||||
console.log("account, isLoading, attachments:", account, isLoading, attachments, hasAttachments)
|
||||
|
||||
return (
|
||||
<Block>
|
||||
<div className={[_s.d, _s.px10, _s.py10].join(' ')}>
|
||||
<div className={[_s.d, _s.px5, _s.py5, _s.mb10].join(' ')}>
|
||||
<Heading size='h2'>Photos</Heading>
|
||||
</div>
|
||||
<TabBar tabs={[
|
||||
{
|
||||
title: 'All Photos',
|
||||
to: `/${account.get('username')}/photos`,
|
||||
},
|
||||
{
|
||||
title: 'Albums',
|
||||
isActive: true,
|
||||
to: `/${account.get('username')}/albums`,
|
||||
},
|
||||
]}/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
role='feed'
|
||||
onScroll={this.handleScroll}
|
||||
className={[_s.d, _s.w100PC, _s.flexRow, _s.flexWrap, _s.px10, _s.mb15, _s.pb10].join(' ')}
|
||||
>
|
||||
|
||||
{
|
||||
hasAttachments &&
|
||||
<React.Fragment>
|
||||
{
|
||||
attachments.map((attachment, i) => (
|
||||
<MediaItem
|
||||
key={attachment.get('id')}
|
||||
attachment={attachment}
|
||||
account={account}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{
|
||||
Array.apply(null, { length: 8}).map((_, i) => (
|
||||
<Dummy className={[_s.d, _s.minW198PX, _s.px5, _s.flex1].join(' ')} />
|
||||
))
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{
|
||||
isLoading && !hasAttachments &&
|
||||
<div className={[_s.d, _s.w100PC].join(' ')}>
|
||||
<MediaGalleryPlaceholder />
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isLoading && !hasAttachments &&
|
||||
<ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
hasMore && !(isLoading && !hasAttachments) &&
|
||||
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
||||
}
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
none: { id: 'account_gallery.none', defaultMessage: 'No media to show.' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, { account }) => {
|
||||
const accountId = !!account ? account.get('id') : -1
|
||||
|
||||
return {
|
||||
accountId,
|
||||
attachments: getAccountGallery(state, accountId, 'photo'),
|
||||
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
|
||||
}
|
||||
}
|
||||
|
||||
AccountPhotoGallery.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
account: ImmutablePropTypes.map,
|
||||
accountId: PropTypes.string,
|
||||
attachments: ImmutablePropTypes.list.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps)(AccountPhotoGallery))
|
@ -8,27 +8,27 @@ import { expandAccountMediaTimeline } from '../actions/timelines'
|
||||
import { getAccountGallery } from '../selectors'
|
||||
import ColumnIndicator from '../components/column_indicator'
|
||||
import MediaItem from '../components/media_item'
|
||||
import Dummy from '../components/dummy'
|
||||
import LoadMore from '../components/load_more'
|
||||
import Block from '../components/block'
|
||||
import Heading from '../components/heading'
|
||||
import TabBar from '../components/tab_bar'
|
||||
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
||||
|
||||
class AccountGallery extends ImmutablePureComponent {
|
||||
class AccountVideoGallery extends ImmutablePureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
const { accountId, mediaType } = this.props
|
||||
const { accountId } = this.props
|
||||
|
||||
if (accountId && accountId !== -1) {
|
||||
this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType }))
|
||||
this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType: 'video' }))
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
(nextProps.accountId && nextProps.accountId !== this.props.accountId) ||
|
||||
(nextProps.accountId && nextProps.mediaType !== this.props.mediaType)
|
||||
) {
|
||||
if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
|
||||
this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, {
|
||||
mediaType: nextProps.mediaType,
|
||||
mediaType: 'video',
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -52,7 +52,7 @@ class AccountGallery extends ImmutablePureComponent {
|
||||
if (this.props.accountId && this.props.accountId !== -1) {
|
||||
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, {
|
||||
maxId,
|
||||
mediaType: this.props.mediaType,
|
||||
mediaType: 'video',
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -73,39 +73,63 @@ class AccountGallery extends ImmutablePureComponent {
|
||||
|
||||
if (!account) return null
|
||||
|
||||
const hasAttachments = !!attachments ? attachments.size > 0 : false
|
||||
|
||||
return (
|
||||
<Block>
|
||||
<div className={[_s.d, _s.px10, _s.py10].join(' ')}>
|
||||
<div className={[_s.d, _s.px5, _s.py5, _s.mb10].join(' ')}>
|
||||
<Heading size='h2'>Videos</Heading>
|
||||
</div>
|
||||
<TabBar tabs={[
|
||||
{
|
||||
title: 'All Videos',
|
||||
to: `/${account.get('username')}/videos`,
|
||||
},
|
||||
]}/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
role='feed'
|
||||
onScroll={this.handleScroll}
|
||||
className={[_s.d, _s.flexRow, _s.flexWrap, _s.py5, _s.px5].join(' ')}
|
||||
className={[_s.d, _s.w100PC, _s.flexRow, _s.flexWrap, _s.px10, _s.mb15, _s.pb10].join(' ')}
|
||||
>
|
||||
|
||||
{
|
||||
attachments.map((attachment, i) => (
|
||||
<MediaItem
|
||||
key={attachment.get('id')}
|
||||
attachment={attachment}
|
||||
account={account}
|
||||
/>
|
||||
))
|
||||
hasAttachments &&
|
||||
<React.Fragment>
|
||||
{
|
||||
attachments.map((attachment, i) => (
|
||||
<MediaItem
|
||||
key={attachment.get('id')}
|
||||
attachment={attachment}
|
||||
account={account}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{
|
||||
Array.apply(null, { length: 8 }).map((_, i) => (
|
||||
<Dummy className={[_s.d, _s.minW232PX, _s.px5, _s.flex1].join(' ')} />
|
||||
))
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{
|
||||
isLoading && attachments.size === 0 &&
|
||||
isLoading && !hasAttachments &&
|
||||
<div className={[_s.d, _s.w100PC].join(' ')}>
|
||||
<MediaGalleryPlaceholder />
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isLoading && attachments.size === 0 &&
|
||||
!isLoading && !hasAttachments &&
|
||||
<ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
hasMore && !(isLoading && attachments.size === 0) &&
|
||||
hasMore && !(isLoading && !hasAttachments) &&
|
||||
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
||||
}
|
||||
</Block>
|
||||
@ -118,18 +142,18 @@ const messages = defineMessages({
|
||||
none: { id: 'account_gallery.none', defaultMessage: 'No media to show.' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, { account, mediaType }) => {
|
||||
const mapStateToProps = (state, { account }) => {
|
||||
const accountId = !!account ? account.get('id') : -1
|
||||
|
||||
return {
|
||||
accountId,
|
||||
attachments: getAccountGallery(state, accountId, mediaType),
|
||||
attachments: getAccountGallery(state, accountId, 'video'),
|
||||
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
|
||||
}
|
||||
}
|
||||
|
||||
AccountGallery.propTypes = {
|
||||
AccountVideoGallery.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
account: ImmutablePropTypes.map,
|
||||
accountId: PropTypes.string,
|
||||
@ -137,14 +161,6 @@ AccountGallery.propTypes = {
|
||||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
mediaType: PropTypes.oneOf([
|
||||
'photo',
|
||||
'video',
|
||||
]),
|
||||
}
|
||||
|
||||
AccountGallery.defaultProps = {
|
||||
mediaType: 'both'
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps)(AccountGallery))
|
||||
export default injectIntl(connect(mapStateToProps)(AccountVideoGallery))
|
@ -1 +1,67 @@
|
||||
// : todo :
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { createAlbum } from '../actions/albums'
|
||||
import { closeModal } from '../actions/modal'
|
||||
import Button from '../components/button'
|
||||
import Input from '../components/input'
|
||||
import Form from '../components/form'
|
||||
import Text from '../components/text'
|
||||
|
||||
class AlbumCreate extends React.PureComponent {
|
||||
|
||||
state = {
|
||||
value: '',
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
this.setState({ value })
|
||||
}
|
||||
|
||||
handleOnSubmit = () => {
|
||||
this.props.onSubmit(this.state.value)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value } = this.state
|
||||
|
||||
const isDisabled = !value
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Input
|
||||
title='Title'
|
||||
placeholder='Album title'
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
|
||||
<Button
|
||||
isDisabled={isDisabled}
|
||||
onClick={this.handleOnSubmit}
|
||||
className={[_s.mt10].join(' ')}
|
||||
>
|
||||
<Text color='inherit' align='center'>
|
||||
Create
|
||||
</Text>
|
||||
</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch, { isModal }) => ({
|
||||
onSubmit(title) {
|
||||
if (isModal) dispatch(closeModal())
|
||||
dispatch(createBookmarkCollection(title))
|
||||
},
|
||||
})
|
||||
|
||||
AlbumCreate.propTypes = {
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
isModal: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(AlbumCreate)
|
@ -1,100 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import {
|
||||
updateBookmarkCollection,
|
||||
removeBookmarkCollection,
|
||||
} from '../actions/bookmarks'
|
||||
import { closeModal } from '../actions/modal'
|
||||
import Button from '../components/button'
|
||||
import Input from '../components/input'
|
||||
import Form from '../components/form'
|
||||
import Text from '../components/text'
|
||||
|
||||
class BookmarkCollectionEdit extends React.PureComponent {
|
||||
|
||||
state = {
|
||||
value: '',
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.bookmarkCollection) {
|
||||
this.props.onFetchBookmarkCollection(this.props.bookmarkCollectionId)
|
||||
}
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
this.setState({ value })
|
||||
}
|
||||
|
||||
handleOnSubmit = () => {
|
||||
this.props.onSubmit(this.state.value)
|
||||
}
|
||||
|
||||
handleOnRemove = () => {
|
||||
this.props.onRemove()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value } = this.state
|
||||
|
||||
const isDisabled = !value
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Input
|
||||
title='Title'
|
||||
placeholder='Bookmark collection title'
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
|
||||
<Button
|
||||
isDisabled={isDisabled}
|
||||
onClick={this.handleOnSubmit}
|
||||
className={[_s.mt10].join(' ')}
|
||||
>
|
||||
<Text color='inherit' align='center'>
|
||||
Update
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
backgroundColor='danger'
|
||||
color='white'
|
||||
onClick={this.handleOnRemove}
|
||||
className={[_s.mt10].join(' ')}
|
||||
>
|
||||
<Text color='inherit' align='center'>
|
||||
Update
|
||||
</Text>
|
||||
</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, { bookmarkCollectionId }) => ({
|
||||
bookmarkCollection: state.getIn(['bookmark_collections', bookmarkCollectionId]),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch, { isModal, bookmarkCollectionId }) => ({
|
||||
onSubmit(title) {
|
||||
if (isModal) dispatch(closeModal())
|
||||
dispatch(updateBookmarkCollection(title))
|
||||
},
|
||||
onRemove() {
|
||||
if (isModal) dispatch(closeModal())
|
||||
dispatch(removeBookmarkCollection(bookmarkCollectionId))
|
||||
},
|
||||
})
|
||||
|
||||
BookmarkCollectionEdit.propTypes = {
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
isModal: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BookmarkCollectionEdit)
|
@ -10,6 +10,7 @@ import {
|
||||
import { me, meUsername} from '../initial_state'
|
||||
import {
|
||||
GAB_DECK_MAX_ITEMS,
|
||||
URL_GAB_PRO,
|
||||
MODAL_PRO_UPGRADE,
|
||||
} from '../constants'
|
||||
import {
|
||||
@ -22,6 +23,7 @@ import { openModal } from '../actions/modal'
|
||||
import WrappedBundle from './ui/util/wrapped_bundle'
|
||||
import DeckColumn from '../components/deck_column'
|
||||
import Text from '../components/text'
|
||||
import Button from '../components/button'
|
||||
import {
|
||||
AccountTimeline,
|
||||
Compose,
|
||||
@ -186,6 +188,15 @@ class Deck extends React.PureComponent {
|
||||
|
||||
const isEmpty = gabDeckOrder.size === 0
|
||||
|
||||
const title = (
|
||||
<span className={[_s.d, _s.flexRow, _s.jcCenter, _s.aiCenter].join(' ')}>
|
||||
<span className={[_s.d, _s.mr2].join(' ')}>
|
||||
Gab Deck for Gab
|
||||
</span>
|
||||
<span className={[_s.bgPro, _s.cBlack, _s.radiusSmall, _s.px5, _s.py5].join(' ')}>PRO</span>
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<SortableContainer
|
||||
axis='x'
|
||||
@ -199,16 +210,29 @@ class Deck extends React.PureComponent {
|
||||
<DeckColumn title='Compose' icon='pencil' noButtons>
|
||||
<WrappedBundle component={Compose} />
|
||||
</DeckColumn>
|
||||
{ /** : todo : */
|
||||
{
|
||||
!isPro &&
|
||||
<DeckColumn title='Gab Deck for GabPRO' icon='pro' noButtons>
|
||||
<DeckColumn title={title} icon='pro' noButtons>
|
||||
<div className={[_s.d, _s.px15, _s.py15].join(' ')}>
|
||||
<Text>
|
||||
Gab Deck for GabPRO. Some text about what it does and some buttons on going pro to use it.
|
||||
GabDeck is a unique way to customize your Gab experience. Upgrade to GabPRO to unlock the GabDeck.
|
||||
</Text>
|
||||
<div className={[_s.mt15, _s.d, _s.flexRow].join(' ')}>
|
||||
<Button href={URL_GAB_PRO}>
|
||||
<Text color='inherit' className={_s.px10}>
|
||||
Upgrade to GabPRO
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DeckColumn>
|
||||
}
|
||||
<DeckColumn title='Explore' icon='explore' noButtons>
|
||||
<WrappedBundle component={ExploreTimeline} />
|
||||
</DeckColumn>
|
||||
<DeckColumn title='News' icon='news' noButtons>
|
||||
<WrappedBundle component={News} componentParams={{ isSmall: true }} />
|
||||
</DeckColumn>
|
||||
<DeckColumn />
|
||||
</React.Fragment>
|
||||
}
|
||||
|
@ -97,7 +97,6 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
getCurrentChatMessageIndex = (id) => {
|
||||
// : todo :
|
||||
return this.props.chatMessageIds.indexOf(id)
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ import DeckPage from '../../pages/deck_page'
|
||||
import {
|
||||
About,
|
||||
AccountAlbums,
|
||||
AccountGallery,
|
||||
AccountPhotoGallery,
|
||||
AccountVideoGallery,
|
||||
AccountTimeline,
|
||||
AccountCommentsTimeline,
|
||||
AlbumCreate,
|
||||
@ -277,17 +278,18 @@ class SwitchingArea extends React.PureComponent {
|
||||
<WrappedRoute path='/:username/followers' page={ProfilePage} component={Followers} content={children} />
|
||||
<WrappedRoute path='/:username/following' page={ProfilePage} component={Following} content={children} />
|
||||
|
||||
<WrappedRoute path='/:username/photos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true, mediaType: 'photo' }} />
|
||||
<WrappedRoute path='/:username/videos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true, mediaType: 'video' }} />
|
||||
<WrappedRoute path='/:username/albums' page={ProfilePage} component={AccountAlbums} content={children} componentParams={{ noSidebar: true, mediaType: 'photo' }} />
|
||||
<WrappedRoute path='/:username/photos' exact page={ProfilePage} component={AccountPhotoGallery} content={children} componentParams={{ noSidebar: true }} />
|
||||
{ /* <WrappedRoute path='/:username/albums/:albumId' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true }} /> */ }
|
||||
<WrappedRoute path='/:username/videos' exact page={ProfilePage} component={AccountVideoGallery} content={children} componentParams={{ noSidebar: true }} />
|
||||
<WrappedRoute path='/:username/albums' exact page={ProfilePage} component={AccountAlbums} content={children} componentParams={{ noSidebar: true }} />
|
||||
|
||||
<WrappedRoute path='/:username/album_create' page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Create Album', page: 'create-album' }} />
|
||||
<WrappedRoute path='/:username/album_edit/:albumId' page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Create Album', page: 'edit-album' }} />
|
||||
{ /* <WrappedRoute path='/:username/albums/create' exact page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Create Album', page: 'create-album' }} /> */ }
|
||||
{ /* <WrappedRoute path='/:username/albums/:albumId/edit' page={ModalPage} component={AlbumCreate} content={children} componentParams={{ title: 'Edit Album', page: 'edit-album' }} /> */ }
|
||||
|
||||
<WrappedRoute path='/:username/likes' page={ProfilePage} component={LikedStatuses} content={children} />
|
||||
<WrappedRoute path='/:username/bookmark_collections/create' page={ModalPage} component={BookmarkCollectionCreate} content={children} componentParams={{ title: 'Create Bookmark Collection', page: 'create-bookmark-collection' }} />
|
||||
<WrappedRoute path='/:username/bookmark_collections/:bookmarkCollectionId' page={ProfilePage} component={BookmarkedStatuses} content={children} />
|
||||
<WrappedRoute path='/:username/bookmark_collections/:bookmarkCollectionId/edit' page={ModalPage} component={BookmarkCollectionEdit} content={children} componentParams={{ title: 'Edit Bookmark Collection', page: 'edit-bookmark-collection' }} />
|
||||
<WrappedRoute path='/:username/bookmark_collections/:bookmarkCollectionId/edit' page={ModalPage} component={BookmarkCollectionCreate} content={children} componentParams={{ title: 'Edit Bookmark Collection', page: 'edit-bookmark-collection' }} />
|
||||
<WrappedRoute path='/:username/bookmark_collections' page={ProfilePage} component={BookmarkCollections} content={children} />
|
||||
|
||||
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={StatusFeature} content={children} componentParams={{ title: 'Status', page: 'status' }} />
|
||||
|
@ -3,7 +3,8 @@ export function AboutSidebar() { return import(/* webpackChunkName: "components/
|
||||
export function AccountTimeline() { return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline') }
|
||||
export function AccountCommentsTimeline() { return import(/* webpackChunkName: "features/account_comments_timeline" */'../../account_comments_timeline') }
|
||||
export function AccountAlbums() { return import(/* webpackChunkName: "features/account_albums" */'../../account_albums') }
|
||||
export function AccountGallery() { return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery') }
|
||||
export function AccountPhotoGallery() { return import(/* webpackChunkName: "features/account_photo_gallery" */'../../account_photo_gallery') }
|
||||
export function AccountVideoGallery() { return import(/* webpackChunkName: "features/account_video_gallery" */'../../account_video_gallery') }
|
||||
export function AlbumCreate() { return import(/* webpackChunkName: "features/album_create" */'../../album_create') }
|
||||
export function AlbumCreateModal() { return import(/* webpackChunkName: "components/album_create_modal" */'../../../components/modal/album_create_modal') }
|
||||
export function Assets() { return import(/* webpackChunkName: "features/about/assets" */'../../about/assets') }
|
||||
@ -12,8 +13,6 @@ export function BlockedAccounts() { return import(/* webpackChunkName: "features
|
||||
export function BookmarkCollections() { return import(/* webpackChunkName: "features/bookmark_collections" */'../../bookmark_collections') }
|
||||
export function BookmarkCollectionCreate() { return import(/* webpackChunkName: "features/bookmark_collection_create" */'../../bookmark_collection_create') }
|
||||
export function BookmarkCollectionCreateModal() { return import(/* webpackChunkName: "components/bookmark_collection_create_modal" */'../../../components/modal/bookmark_collection_create_modal') }
|
||||
export function BookmarkCollectionEdit() { return import(/* webpackChunkName: "features/bookmark_collection_edit" */'../../bookmark_collection_edit') }
|
||||
export function BookmarkCollectionEditModal() { return import(/* webpackChunkName: "components/bookmark_collection_edit_modal" */'../../../components/modal/bookmark_collection_edit_modal') }
|
||||
export function BookmarkedStatuses() { return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses') }
|
||||
export function BoostModal() { return import(/* webpackChunkName: "components/boost_modal" */'../../../components/modal/boost_modal') }
|
||||
export function CaliforniaConsumerProtection() { return import(/* webpackChunkName: "features/california_consumer_protection" */'../../about/california_consumer_protection') }
|
||||
|
@ -34,7 +34,10 @@ class GroupPage extends ImmutablePureComponent {
|
||||
const isMember = !!relationships ? relationships.get('member') : false
|
||||
const unavailable = isPrivate && !isMember
|
||||
|
||||
|
||||
if (!!group) {
|
||||
if (group.get('archived')) return null
|
||||
}
|
||||
|
||||
return (
|
||||
<GroupLayout
|
||||
title={'Group'}
|
||||
|
59
app/javascript/gabsocial/reducers/album_lists.js
Normal file
59
app/javascript/gabsocial/reducers/album_lists.js
Normal file
@ -0,0 +1,59 @@
|
||||
import {
|
||||
Map as ImmutableMap,
|
||||
List as ImmutableList,
|
||||
} from 'immutable'
|
||||
import { me } from '../initial_state'
|
||||
import {
|
||||
ALBUMS_FETCH_REQUEST,
|
||||
ALBUMS_FETCH_SUCCESS,
|
||||
ALBUMS_FETCH_FAIL,
|
||||
ALBUMS_EXPAND_REQUEST,
|
||||
ALBUMS_EXPAND_SUCCESS,
|
||||
ALBUMS_EXPAND_FAIL,
|
||||
} from '../actions/albums'
|
||||
|
||||
const initialState = ImmutableMap({})
|
||||
|
||||
const setListFailed = (state, id) => {
|
||||
return state.setIn([id], ImmutableMap({
|
||||
next: null,
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
}))
|
||||
}
|
||||
|
||||
const normalizeList = (state, id, albums, next) => {
|
||||
return state.setIn([id], ImmutableMap({
|
||||
next,
|
||||
items: ImmutableList(albums.map(item => item.id)),
|
||||
isLoading: false,
|
||||
}))
|
||||
}
|
||||
|
||||
const appendToList = (state, id, albums, next) => {
|
||||
return state.updateIn([id], (map) => {
|
||||
return map
|
||||
.set('next', next)
|
||||
.set('isLoading', false)
|
||||
.update('items', (list) => {
|
||||
return list.concat(albums.map(item => item.id))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default function album_lists(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ALBUMS_FETCH_REQUEST:
|
||||
case ALBUMS_EXPAND_REQUEST:
|
||||
return state.setIn([action.accountId, 'isLoading'], true)
|
||||
case ALBUMS_FETCH_SUCCESS:
|
||||
return normalizeList(state, action.accountId, action.albums, action.next)
|
||||
case ALBUMS_EXPAND_SUCCESS:
|
||||
return appendToList(state, action.accountId, action.albums, action.next)
|
||||
case ALBUMS_FETCH_FAIL:
|
||||
case ALBUMS_EXPAND_FAIL:
|
||||
return setListFailed(state, action.accountId)
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
@ -3,6 +3,7 @@ import { loadingBarReducer } from 'react-redux-loading-bar'
|
||||
import accounts from './accounts'
|
||||
import accounts_counters from './accounts_counters'
|
||||
import albums from './albums'
|
||||
import album_lists from './album_lists'
|
||||
import bookmark_collections from './bookmark_collections'
|
||||
import chats from './chats'
|
||||
import chat_conversation_lists from './chat_conversation_lists'
|
||||
@ -55,6 +56,8 @@ import user_lists from './user_lists'
|
||||
const reducers = {
|
||||
accounts,
|
||||
accounts_counters,
|
||||
// albums,
|
||||
// album_lists,
|
||||
bookmark_collections,
|
||||
chats,
|
||||
chat_conversation_lists,
|
||||
|
@ -603,6 +603,8 @@ pre {
|
||||
.maxW212PX { max-width: 212px; }
|
||||
|
||||
.minW330PX { min-width: 330px; }
|
||||
.minW232PX { min-width: 232px; }
|
||||
.minW198PX { min-width: 192px; }
|
||||
.minW162PX { min-width: 162px; }
|
||||
.minW120PX { min-width: 120px; }
|
||||
.minW84PX { min-width: 84px; }
|
||||
|
@ -17,7 +17,6 @@
|
||||
# is_muted :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
# : todo : expires
|
||||
# : todo : max per account
|
||||
class ChatConversationAccount < ApplicationRecord
|
||||
include Paginable
|
||||
|
26
app/models/form/chat_message_batch.rb
Normal file
26
app/models/form/chat_message_batch.rb
Normal file
@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Form::ChatMessageBatch
|
||||
include ActiveModel::Model
|
||||
include AccountableConcern
|
||||
|
||||
attr_accessor :chat_message_ids, :action, :current_account
|
||||
|
||||
def save
|
||||
case action
|
||||
when 'delete'
|
||||
delete_chat_messages
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_chat_messages
|
||||
ChatMessage.where(id: chat_message_ids).reorder(nil).find_each do |chat_message|
|
||||
DeleteChatMessageWorker.perform_async(chat_message.id)
|
||||
log_action :destroy, chat_message
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
@ -12,11 +12,10 @@
|
||||
#
|
||||
|
||||
class Shortcut < ApplicationRecord
|
||||
# : todo : enum 1,2, etc.
|
||||
# enum shortcut_type: {
|
||||
# account: 'account',
|
||||
# group: 'group'
|
||||
# }
|
||||
SHORTCUT_TYPE_MAP = {
|
||||
account: 'account',
|
||||
group: 'group',
|
||||
}.freeze
|
||||
|
||||
belongs_to :account
|
||||
|
||||
|
87
app/policies/chat_message_policy.rb
Normal file
87
app/policies/chat_message_policy.rb
Normal file
@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ChatMessagePolicy < ApplicationPolicy
|
||||
def initialize(current_account, record, preloaded_relations = {})
|
||||
super(current_account, record)
|
||||
|
||||
@preloaded_relations = preloaded_relations
|
||||
end
|
||||
|
||||
def index?
|
||||
staff?
|
||||
end
|
||||
|
||||
def show?
|
||||
if requires_mention?
|
||||
owned? || mention_exists?
|
||||
elsif private?
|
||||
owned? || following_author? || mention_exists?
|
||||
else
|
||||
current_account.nil? || !author_blocking?
|
||||
end
|
||||
end
|
||||
|
||||
def reblog?
|
||||
!requires_mention? && (!private? || owned?) && show? && !blocking_author?
|
||||
end
|
||||
|
||||
def favourite?
|
||||
show? && !blocking_author?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
staff? || owned?
|
||||
end
|
||||
|
||||
alias unreblog? destroy?
|
||||
|
||||
def update?
|
||||
staff? || owned?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def requires_mention?
|
||||
record.limited_visibility?
|
||||
end
|
||||
|
||||
def owned?
|
||||
author.id == current_account&.id
|
||||
end
|
||||
|
||||
def private?
|
||||
record.private_visibility?
|
||||
end
|
||||
|
||||
def mention_exists?
|
||||
return false if current_account.nil?
|
||||
|
||||
if record.mentions.loaded?
|
||||
record.mentions.any? { |mention| mention.account_id == current_account.id }
|
||||
else
|
||||
record.mentions.where(account: current_account).exists?
|
||||
end
|
||||
end
|
||||
|
||||
def blocking_author?
|
||||
return false if current_account.nil?
|
||||
|
||||
@preloaded_relations[:blocking] ? @preloaded_relations[:blocking][author.id] : current_account.blocking?(author)
|
||||
end
|
||||
|
||||
def author_blocking?
|
||||
return false if current_account.nil?
|
||||
|
||||
@preloaded_relations[:blocked_by] ? @preloaded_relations[:blocked_by][author.id] : author.blocking?(current_account)
|
||||
end
|
||||
|
||||
def following_author?
|
||||
return false if current_account.nil?
|
||||
|
||||
@preloaded_relations[:following] ? @preloaded_relations[:following][author.id] : current_account.following?(author)
|
||||
end
|
||||
|
||||
def author
|
||||
record.account
|
||||
end
|
||||
end
|
44
app/services/create_chat_conversation_service.rb
Normal file
44
app/services/create_chat_conversation_service.rb
Normal file
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateChatConversationService < BaseService
|
||||
|
||||
def call(current_account, other_accounts)
|
||||
@current_account = current_account
|
||||
@other_accounts = other_accounts
|
||||
|
||||
return nil if @other_accounts.nil? || @current_account.nil?
|
||||
|
||||
# check if already created
|
||||
chat = ChatConversationAccount.find_by(account: current_account, participant_account_ids: account_ids_as_array)
|
||||
|
||||
return chat unless chat.nil?
|
||||
|
||||
# : todo :
|
||||
# check if allow anyone to message then create with approved:true
|
||||
# unique account id, participants
|
||||
|
||||
chat_conversation = ChatConversation.create
|
||||
|
||||
my_chat = ChatConversationAccount.create!(
|
||||
account: current_account,
|
||||
participant_account_ids: [@account.id.to_s],
|
||||
chat_conversation: chat_conversation,
|
||||
is_approved: true
|
||||
)
|
||||
|
||||
# : todo : if multiple ids
|
||||
if @account.id != current_account.id
|
||||
their_chat = ChatConversationAccount.create!(
|
||||
account: @account,
|
||||
participant_account_ids: [current_account.id.to_s],
|
||||
chat_conversation: chat_conversation,
|
||||
is_approved: false # : todo : check if allow all else default as request
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def account_ids_as_array
|
||||
@other_accounts.map { |account| account.id.to_s }
|
||||
end
|
||||
|
||||
end
|
@ -78,16 +78,12 @@ class FetchLinkCardService < BaseService
|
||||
end
|
||||
|
||||
def parse_urls
|
||||
# : todo :
|
||||
if @status.local?
|
||||
urls = @status.text.scan(URL_PATTERN).map { |array| Addressable::URI.parse(array[0]).normalize }
|
||||
return urls.reject { |uri| bad_url?(uri) }.first
|
||||
else
|
||||
html = Nokogiri::HTML(@status.text)
|
||||
links = html.css('a')
|
||||
urls = links.map { |a| Addressable::URI.parse(a['href']).normalize unless skip_link?(a) }.compact
|
||||
return nil
|
||||
end
|
||||
|
||||
urls.reject { |uri| bad_url?(uri) }.first
|
||||
end
|
||||
|
||||
def bad_url?(uri)
|
||||
|
@ -2,7 +2,7 @@
|
||||
%td
|
||||
= admin_account_link_to(account)
|
||||
- if account.is_flagged_as_spam
|
||||
%span SPAM
|
||||
%span{ :style => "display:block;margin:5px 0 0 20px;font-size:12px;background-color:#781600;border-radius:6px;color:#fff;width:40px;line-height:22px;font-weight:600;padding:2px 0 0 6px;" } SPAM
|
||||
%td
|
||||
- if account.user_current_sign_in_ip
|
||||
%samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip
|
||||
|
@ -123,6 +123,14 @@
|
||||
%time.formatted{ datetime: @account.created_at.iso8601, title: l(@account.created_at) }= l @account.created_at
|
||||
%td
|
||||
|
||||
%tr
|
||||
%th Is flagged as spam
|
||||
%td
|
||||
- if @account.is_flagged_as_spam?
|
||||
%span YES
|
||||
- else
|
||||
%span no
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.most_recent_ip')
|
||||
%td= @account.user_current_sign_in_ip
|
||||
|
16
app/views/admin/chat_messages/_chat_message.html.haml
Normal file
16
app/views/admin/chat_messages/_chat_message.html.haml
Normal file
@ -0,0 +1,16 @@
|
||||
.batch-table__row
|
||||
%label.batch-table__row__select.batch-checkbox
|
||||
= f.check_box :chat_message_ids, { multiple: true, include_hidden: false }, chat_message.id
|
||||
.batch-table__row__content
|
||||
%div{:style=>"display:flex;flex-direction:column;"}
|
||||
%span= chat_message.text
|
||||
%div{:style=> "display:block;color:#555;"}
|
||||
%span Created:
|
||||
%span= chat_message.created_at
|
||||
- if chat_message.expires_at
|
||||
%div{:style=> "display:block;color:#555;"}
|
||||
%span Expires:
|
||||
%span= chat_message.expires_at
|
||||
%div{:style=> "display:block;color:#555;"}
|
||||
%span In conversation:
|
||||
%span= chat_message.chat_conversation_id
|
@ -1,28 +1,29 @@
|
||||
- content_for :header_tags do
|
||||
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
|
||||
|
||||
- content_for :page_title do
|
||||
= t('admin.followers.title', acct: @account.acct)
|
||||
%span Chat Messages
|
||||
\-
|
||||
= "@#{@account.acct}"
|
||||
|
||||
.filters
|
||||
.filter-subset
|
||||
%strong= t('admin.accounts.location.title')
|
||||
%ul
|
||||
%li= link_to t('admin.accounts.location.local'), admin_account_followers_path(@account.id), class: 'selected'
|
||||
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
|
||||
= link_to admin_account_path(@account.id) do
|
||||
= fa_icon 'chevron-left fw'
|
||||
= t('admin.followers.back_to_account')
|
||||
%span Back to account
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
.table-wrapper
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th= t('admin.accounts.username')
|
||||
%th= t('admin.accounts.role')
|
||||
%th= t('admin.accounts.most_recent_ip')
|
||||
%th= t('admin.accounts.most_recent_activity')
|
||||
%th
|
||||
%tbody
|
||||
= render partial: 'admin/accounts/account', collection: @followers
|
||||
= form_for(@form, url: admin_account_chat_messages_path(@account.id)) do |f|
|
||||
= hidden_field_tag :page, params[:page]
|
||||
|
||||
= paginate @followers
|
||||
.batch-table
|
||||
.batch-table__toolbar
|
||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
= check_box_tag :batch_checkbox_all, nil, false
|
||||
.batch-table__toolbar__actions
|
||||
= f.button safe_join([fa_icon('trash'), 'Delete']), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
||||
.batch-table__body
|
||||
= render partial: 'admin/chat_messages/chat_message', collection: @chat_messages, locals: { f: f }
|
||||
|
||||
= paginate @chat_messages
|
||||
|
@ -4,11 +4,14 @@
|
||||
%td= group.member_count
|
||||
%td
|
||||
- if group.is_featured?
|
||||
= t('admin.groups.featured')
|
||||
%span Y
|
||||
%td
|
||||
- if not group.is_featured?
|
||||
= table_link_to 'power-off', t('admin.groups.enable_featured'), enable_featured_admin_group_path(group, page: params[:page], **@filter_params), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
- else
|
||||
= table_link_to 'power-off', t('admin.groups.disable_featured'), disable_featured_admin_group_path(group, page: params[:page], **@filter_params), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
%td
|
||||
= table_link_to 'times', t('admin.groups.delete'), admin_group_path(group, page: params[:page], **@filter_params), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
= table_link_to '', 'Edit', admin_group_path(group)
|
||||
|
||||
-# %td
|
||||
-# - if not group.is_featured?
|
||||
-# = table_link_to 'power-off', t('admin.groups.enable_featured'), enable_featured_admin_group_path(group, page: params[:page], **@filter_params), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
-# - else
|
||||
-# = table_link_to 'power-off', t('admin.groups.disable_featured'), disable_featured_admin_group_path(group, page: params[:page], **@filter_params), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
-# %td
|
||||
-# = table_link_to 'times', t('admin.groups.delete'), admin_group_path(group, page: params[:page], **@filter_params), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
|
@ -17,7 +17,7 @@
|
||||
%th= t('admin.groups.id')
|
||||
%th= t('admin.groups.title')
|
||||
%th= t('admin.groups.member_count')
|
||||
%th
|
||||
%th Featured?
|
||||
%th
|
||||
%th
|
||||
%tbody
|
||||
|
@ -8,28 +8,60 @@
|
||||
-# delete
|
||||
-# view accounts
|
||||
-# view removed accounts
|
||||
-# number of accounts
|
||||
-# number of removed accounts
|
||||
-# number of posts
|
||||
|
||||
.card.h-card{:style => "height:300px"}
|
||||
.card__img
|
||||
= image_tag @group.cover_image.url, alt: '', :style => "height:300px"
|
||||
= image_tag full_asset_url(@group.cover_image.url), alt: '', :style => "height:300px"
|
||||
|
||||
.dashboard__counters{ style: 'margin-top: 10px' }
|
||||
%div
|
||||
%div
|
||||
.dashboard__counters__num= number_with_delimiter 0 #@account.statuses_count
|
||||
.dashboard__counters__num= number_with_delimiter Status.where(group:@group).count
|
||||
.dashboard__counters__label Status Count
|
||||
%div
|
||||
%div
|
||||
.dashboard__counters__num= number_to_human_size 0 #@account.media_attachments.sum('file_file_size')
|
||||
.dashboard__counters__num= number_with_delimiter @group.accounts.count
|
||||
.dashboard__counters__label Member Count
|
||||
%div
|
||||
%div
|
||||
.dashboard__counters__num= number_with_delimiter 0 #@account.local_followers_count
|
||||
.dashboard__counters__num= number_with_delimiter @group.removed_accounts.count
|
||||
.dashboard__counters__label Removed Members Count
|
||||
%div
|
||||
%div
|
||||
.dashboard__counters__num= number_with_delimiter 0 #@account.reports.count
|
||||
.dashboard__counters__label Member Requests Count
|
||||
.dashboard__counters__num= number_with_delimiter @group.join_requests.count
|
||||
.dashboard__counters__label Member Requests Count
|
||||
|
||||
= simple_form_for(@group, url: admin_group_path(@group.id), html: { method: :put }) do |f|
|
||||
= render 'shared/error_messages', object: @group
|
||||
|
||||
.fields-group
|
||||
= f.input :title, wrapper: :with_label, label: 'Title'
|
||||
|
||||
.fields-group
|
||||
= f.input :description, wrapper: :with_label, label: 'Description'
|
||||
|
||||
.fields-group
|
||||
= f.input :slug, wrapper: :with_label, label: 'Slug'
|
||||
|
||||
.fields-group
|
||||
= f.input :tags, wrapper: :with_label, label: 'Tags'
|
||||
|
||||
.fields-group
|
||||
= f.input :is_nsfw, as: :boolean, wrapper: :with_label, label: 'Is NSFW?'
|
||||
|
||||
.fields-group
|
||||
= f.input :is_featured, as: :boolean, wrapper: :with_label, label: 'Is Featured?'
|
||||
|
||||
.fields-group
|
||||
= f.input :is_private, as: :boolean, wrapper: :with_label, label: 'Is Private?'
|
||||
|
||||
.fields-group
|
||||
= f.input :is_visible, as: :boolean, wrapper: :with_label, label: 'Is Visible?'
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
||||
|
||||
-# : todo : delete
|
||||
-# : todo : list admins
|
||||
-# : todo : list mods
|
||||
-# : todo : make ME admin
|
Loading…
Reference in New Issue
Block a user