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
|
PER_PAGE = 100
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :account, :index?
|
authorize :chat_message, :index?
|
||||||
@followers = ChatMessage.where(from_account: @account).page(params[:page]).per(PER_PAGE)
|
|
||||||
|
@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
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = Account.find(params[:account_id])
|
@account = Account.find(params[:account_id])
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
@ -23,7 +23,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def hide_results?
|
def hide_results?
|
||||||
# : todo : where tf is this?
|
|
||||||
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
|
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -19,13 +19,6 @@ class Api::V1::ChatConversationController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
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
|
chat_conversation_account = find_or_create_conversation
|
||||||
render json: chat_conversation_account, each_serializer: REST::ChatConversationAccountSerializer
|
render json: chat_conversation_account, each_serializer: REST::ChatConversationAccountSerializer
|
||||||
end
|
end
|
||||||
@ -80,26 +73,8 @@ class Api::V1::ChatConversationController < Api::BaseController
|
|||||||
|
|
||||||
def find_or_create_conversation
|
def find_or_create_conversation
|
||||||
chat = ChatConversationAccount.find_by(account: current_account, participant_account_ids: [@account.id.to_s])
|
chat = ChatConversationAccount.find_by(account: current_account, participant_account_ids: [@account.id.to_s])
|
||||||
|
|
||||||
return chat unless chat.nil?
|
return chat unless chat.nil?
|
||||||
|
my_chat = CreateChatConversationService.new.call(current_account, [@account])
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
return my_chat
|
return my_chat
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -34,10 +34,6 @@ class Api::V1::Groups::RemovedAccountsController < Api::BaseController
|
|||||||
render_empty_success
|
render_empty_success
|
||||||
end
|
end
|
||||||
|
|
||||||
def search
|
|
||||||
# : todo :
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_group
|
def set_group
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
class Api::V1::StatusesController < Api::BaseController
|
class Api::V1::StatusesController < Api::BaseController
|
||||||
include Authorization
|
include Authorization
|
||||||
|
|
||||||
# : todo : disable all oauth everything
|
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
|
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 -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
|
||||||
before_action :require_user!, except: [:show, :comments, :context, :card]
|
before_action :require_user!, except: [:show, :comments, :context, :card]
|
||||||
|
@ -21,9 +21,6 @@ class Settings::GroupCategoriesController < Admin::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
# : todo :
|
|
||||||
# don't destroy if any groups have this category
|
|
||||||
|
|
||||||
@category.destroy!
|
@category.destroy!
|
||||||
log_action :destroy, @category
|
log_action :destroy, @category
|
||||||
flash[:notice] = I18n.t('promotions.destroyed_msg')
|
flash[:notice] = I18n.t('promotions.destroyed_msg')
|
||||||
|
@ -26,6 +26,10 @@ class Settings::ProfilesController < Settings::BaseController
|
|||||||
else
|
else
|
||||||
# : todo :
|
# : todo :
|
||||||
# only allowed to change username once per day
|
# 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)
|
if UpdateAccountService.new.call(@account, account_params)
|
||||||
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
|
@ -101,7 +101,7 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def theme
|
def theme
|
||||||
# : todo :
|
# : todo : remove
|
||||||
return 'white'
|
return 'white'
|
||||||
end
|
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_SUCCESS = 'ALBUMS_FETCH_SUCCESS'
|
||||||
export const ALBUMS_FETCH_FAIL = 'ALBUMS_FETCH_FAIL'
|
export const ALBUMS_FETCH_FAIL = 'ALBUMS_FETCH_FAIL'
|
||||||
|
|
||||||
export const ALBUMS_CREATE_REQUEST = 'ALBUMS_CREATE_REQUEST'
|
export const ALBUMS_EXPAND_REQUEST = 'ALBUMS_EXPAND_REQUEST'
|
||||||
export const ALBUMS_CREATE_SUCCESS = 'ALBUMS_CREATE_SUCCESS'
|
export const ALBUMS_EXPAND_SUCCESS = 'ALBUMS_EXPAND_SUCCESS'
|
||||||
export const ALBUMS_CREATE_FAIL = 'ALBUMS_CREATE_FAIL'
|
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 React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
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 Button from './button'
|
||||||
import Icon from './icon'
|
import Icon from './icon'
|
||||||
import Image from './image'
|
import Image from './image'
|
||||||
@ -10,19 +15,14 @@ import Text from './text'
|
|||||||
|
|
||||||
class Album extends React.PureComponent {
|
class Album extends React.PureComponent {
|
||||||
|
|
||||||
handleOnClick = (e) => {
|
handleOnOpenAlbumCreate = () => {
|
||||||
//
|
this.props.openAlbumCreate()
|
||||||
}
|
|
||||||
|
|
||||||
handleOnOpenAlbumCreation = () => {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
album,
|
album,
|
||||||
isAddable,
|
isAddable,
|
||||||
isDummy,
|
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const title = isAddable ? 'New album' : 'Album title'
|
const title = isAddable ? 'New album' : 'Album title'
|
||||||
@ -31,13 +31,11 @@ class Album extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[_s.d, _s.minW162PX, _s.px5, _s.flex1].join(' ')}>
|
<div className={[_s.d, _s.minW162PX, _s.px5, _s.flex1].join(' ')}>
|
||||||
{
|
|
||||||
!isDummy &&
|
|
||||||
<Button
|
<Button
|
||||||
noClasses
|
noClasses
|
||||||
className={[_s.d, _s.noUnderline, _s.noOutline, _s.bgTransparent].join(' ')}
|
className={[_s.d, _s.noUnderline, _s.cursorPointer, _s.outlineNone, _s.bgTransparent].join(' ')}
|
||||||
to={to}
|
to={to}
|
||||||
onClick={isAddable ? this.handleOnOpenAlbumCreation : undefined}
|
onClick={isAddable ? this.handleOnOpenAlbumCreate : undefined}
|
||||||
>
|
>
|
||||||
<div className={[_s.d, _s.w100PC, _s.mt5, _s.mb10].join(' ')}>
|
<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.w100PC, _s.pt100PC].join(' ')}>
|
||||||
@ -53,7 +51,6 @@ class Album extends React.PureComponent {
|
|||||||
{ !isAddable && <Text color='secondary' size='small' className={_s.mt5}>{subtitle}</Text> }
|
{ !isAddable && <Text color='secondary' size='small' className={_s.mt5}>{subtitle}</Text> }
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -63,7 +60,12 @@ class Album extends React.PureComponent {
|
|||||||
Album.propTypes = {
|
Album.propTypes = {
|
||||||
album: ImmutablePropTypes.map,
|
album: ImmutablePropTypes.map,
|
||||||
isAddable: PropTypes.bool,
|
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
|
} = this.props
|
||||||
|
|
||||||
return (
|
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(' ')}>
|
<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(' ')} />
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
{ !!icon && <Icon id={icon} className={[_s.cPrimary, _s.mr15].join(' ')} size='18px' /> }
|
{ !!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> }
|
{ !!title && <Text size='extraLarge' weight='medium'>{title}</Text> }
|
||||||
{ !!subtitle && <Text className={_s.ml5} color='secondary'>{subtitle}</Text> }
|
{ !!subtitle && <Text className={_s.ml5} color='secondary'>{subtitle}</Text> }
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,9 +99,6 @@ class GroupHeader extends ImmutablePureComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// : todo :
|
|
||||||
// {group.get('archived') && <Icon id='lock' title={intl.formatMessage(messages.group_archived)} />}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[_s.d, _s.z1, _s.w100PC, _s.mb15].join(' ')}>
|
<div className={[_s.d, _s.z1, _s.w100PC, _s.mb15].join(' ')}>
|
||||||
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
|
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
|
||||||
|
@ -14,23 +14,41 @@ class MediaItem extends ImmutablePureComponent {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
|
visible: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.props.attachment.get('blurhash')) {
|
const { attachment } = this.props
|
||||||
|
if (!attachment) return
|
||||||
|
|
||||||
|
if (attachment.get('blurhash')) {
|
||||||
this._decode()
|
this._decode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
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()
|
this._decode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_decode() {
|
_decode = () => {
|
||||||
const hash = this.props.attachment.get('blurhash')
|
const { attachment } = this.props
|
||||||
|
if (!attachment) return
|
||||||
|
|
||||||
|
const hash = attachment.get('blurhash')
|
||||||
const pixels = decode(hash, 160, 160)
|
const pixels = decode(hash, 160, 160)
|
||||||
|
|
||||||
if (pixels && this.canvas) {
|
if (pixels && this.canvas) {
|
||||||
@ -41,7 +59,7 @@ class MediaItem extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setCanvasRef = c => {
|
setCanvasRef = (c) => {
|
||||||
this.canvas = c
|
this.canvas = c
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +68,10 @@ class MediaItem extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hoverToPlay() {
|
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() {
|
render() {
|
||||||
@ -61,50 +82,56 @@ class MediaItem extends ImmutablePureComponent {
|
|||||||
} = this.props
|
} = this.props
|
||||||
const { visible, loaded } = this.state
|
const { visible, loaded } = this.state
|
||||||
|
|
||||||
|
if (!attachment || !account) return null
|
||||||
|
|
||||||
const status = attachment.get('status')
|
const status = attachment.get('status')
|
||||||
const title = status.get('spoiler_text') || attachment.get('description')
|
const title = status.get('spoiler_text') || attachment.get('description')
|
||||||
|
|
||||||
const attachmentType = attachment.get('type')
|
const attachmentType = attachment.get('type')
|
||||||
|
const aspectRatio = attachment.getIn(['meta', 'aspect'])
|
||||||
|
|
||||||
|
const isVideo = attachmentType === 'video'
|
||||||
let badge = null
|
let badge = null
|
||||||
|
|
||||||
if (attachmentType === 'video') {
|
if (isVideo) {
|
||||||
const duration = attachment.getIn(['meta', 'duration'])
|
const duration = attachment.getIn(['meta', 'duration'])
|
||||||
badge = (duration / 60).toFixed(2)
|
badge = (duration / 60).toFixed(2)
|
||||||
} else if (attachmentType === 'gifv') {
|
} else if (attachmentType === 'gifv') {
|
||||||
badge = 'GIF'
|
badge = 'GIF'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statusUrl = `/${account.getIn(['acct'])}/posts/${status.get('id')}`
|
||||||
|
|
||||||
|
const isSmallRatio = aspectRatio < 1
|
||||||
|
const isSquare = aspectRatio === 1
|
||||||
const containerClasses = CX({
|
const containerClasses = CX({
|
||||||
d: 1,
|
d: 1,
|
||||||
posAbs: 1,
|
px5: 1,
|
||||||
top0: 1,
|
flex1: !isSmallRatio && !isSquare,
|
||||||
h100PC: 1,
|
minW198PX: !isVideo && !isSmallRatio && !isSquare,
|
||||||
// w100PC: 1,
|
minW232PX: isVideo && !isSmallRatio && !isSquare,
|
||||||
py2: !isSmall,
|
minW120PX: isSmallRatio,
|
||||||
px2: !isSmall,
|
minW162PX: isSquare,
|
||||||
})
|
})
|
||||||
|
|
||||||
const linkClasses = CX({
|
const paddedContainerClasses = CX({
|
||||||
d: 1,
|
d: 1,
|
||||||
w100PC: 1,
|
h100PC: isSmallRatio || isSquare,
|
||||||
// h100PC: 1,
|
pt100PC: isSmallRatio || isSquare || !isVideo,
|
||||||
overflowHidden: 1,
|
pt5625PC: isVideo && !isSmallRatio && !isSquare,
|
||||||
border1PX: 1,
|
|
||||||
borderColorPrimary: 1,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const statusUrl = `/${account.getIn(['acct'])}/posts/${status.get('id')}`;
|
|
||||||
|
|
||||||
// : todo : fix dimensions to be like albums
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[_s.d, _s.pt25PC].join(' ')}>
|
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
<NavLink
|
<NavLink
|
||||||
|
className={[_s.d, _s.noUnderline, _s.outlineNone, _s.bgTransparent, _s.flexGrow1].join(' ')}
|
||||||
to={statusUrl}
|
to={statusUrl}
|
||||||
title={title}
|
title={title}
|
||||||
className={linkClasses}
|
|
||||||
>
|
>
|
||||||
|
<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) &&
|
(!loaded || !visible) &&
|
||||||
<canvas
|
<canvas
|
||||||
@ -128,6 +155,8 @@ class MediaItem extends ImmutablePureComponent {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
(!visible || !!badge) &&
|
||||||
<div className={[_s.d, _s.aiCenter, _s.jcCenter, _s.h100PC, _s.w100PC, _s.z3, _s.posAbs].join(' ')}>
|
<div className={[_s.d, _s.aiCenter, _s.jcCenter, _s.h100PC, _s.w100PC, _s.z3, _s.posAbs].join(' ')}>
|
||||||
{
|
{
|
||||||
!visible &&
|
!visible &&
|
||||||
@ -137,7 +166,6 @@ class MediaItem extends ImmutablePureComponent {
|
|||||||
className={[_s.cWhite].join('')}
|
className={[_s.cWhite].join('')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!badge &&
|
!!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(' ')}>
|
<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(' ')}>
|
||||||
@ -146,18 +174,59 @@ class MediaItem extends ImmutablePureComponent {
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</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 = {
|
MediaItem.propTypes = {
|
||||||
|
isDummy: PropTypes.bool.isRequired,
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
attachment: ImmutablePropTypes.map.isRequired,
|
attachment: ImmutablePropTypes.map.isRequired,
|
||||||
isSmall: PropTypes.bool,
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
|
@ -81,8 +81,6 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
|||||||
|
|
||||||
handleGroupRemoveAccount = () => {
|
handleGroupRemoveAccount = () => {
|
||||||
const { status } = this.props
|
const { status } = this.props
|
||||||
|
|
||||||
// : todo : check
|
|
||||||
this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id']))
|
this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id']))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,19 @@ class DeckSidebar extends ImmutablePureComponent {
|
|||||||
|
|
||||||
<div className={[_s.d].join(' ')}>
|
<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(' ')}>
|
<h1 className={[_s.d].join(' ')}>
|
||||||
<Button
|
<Button
|
||||||
to='/'
|
to='/'
|
||||||
|
@ -51,7 +51,6 @@ export const POPOVER_VIDEO_STATS = 'VIDEO_STATS'
|
|||||||
export const MODAL_ALBUM_CREATE = 'ALBUM_CREATE'
|
export const MODAL_ALBUM_CREATE = 'ALBUM_CREATE'
|
||||||
export const MODAL_BLOCK_ACCOUNT = 'BLOCK_ACCOUNT'
|
export const MODAL_BLOCK_ACCOUNT = 'BLOCK_ACCOUNT'
|
||||||
export const MODAL_BOOKMARK_COLLECTION_CREATE = 'BOOKMARK_COLLECTION_CREATE'
|
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_BOOST = 'BOOST'
|
||||||
export const MODAL_CHAT_CONVERSATION_CREATE = 'CHAT_CONVERSATION_CREATE'
|
export const MODAL_CHAT_CONVERSATION_CREATE = 'CHAT_CONVERSATION_CREATE'
|
||||||
export const MODAL_CHAT_CONVERSATION_DELETE = 'CHAT_CONVERSATION_DELETE'
|
export const MODAL_CHAT_CONVERSATION_DELETE = 'CHAT_CONVERSATION_DELETE'
|
||||||
|
@ -3,76 +3,60 @@ import PropTypes from 'prop-types'
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { injectIntl, defineMessages } from 'react-intl'
|
import { me } from '../initial_state'
|
||||||
import { expandAccountMediaTimeline } from '../actions/timelines'
|
import {
|
||||||
import { getAccountGallery } from '../selectors'
|
fetchAccountAlbums,
|
||||||
|
expandAccountAlbums,
|
||||||
|
} from '../actions/albums'
|
||||||
import ColumnIndicator from '../components/column_indicator'
|
import ColumnIndicator from '../components/column_indicator'
|
||||||
import Heading from '../components/heading'
|
import Heading from '../components/heading'
|
||||||
import TabBar from '../components/tab_bar'
|
import TabBar from '../components/tab_bar'
|
||||||
import MediaItem from '../components/media_item'
|
|
||||||
import LoadMore from '../components/load_more'
|
import LoadMore from '../components/load_more'
|
||||||
import Block from '../components/block'
|
import Block from '../components/block'
|
||||||
import Image from '../components/image'
|
|
||||||
import Album from '../components/album'
|
import Album from '../components/album'
|
||||||
|
import Dummy from '../components/dummy'
|
||||||
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
||||||
|
|
||||||
class AccountAlbums extends ImmutablePureComponent {
|
class AccountAlbums extends ImmutablePureComponent {
|
||||||
|
|
||||||
// componentDidMount() {
|
componentDidMount() {
|
||||||
// const { accountId, mediaType } = this.props
|
const { accountId, mediaType } = this.props
|
||||||
|
|
||||||
// if (accountId && accountId !== -1) {
|
if (accountId && accountId !== -1) {
|
||||||
// this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType }))
|
this.props.onFetchAccountAlbums(accountId)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
// if (
|
if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
|
||||||
// (nextProps.accountId && nextProps.accountId !== this.props.accountId) ||
|
this.props.onFetchAccountAlbums(nextProps.accountId)
|
||||||
// (nextProps.accountId && nextProps.mediaType !== this.props.mediaType)
|
}
|
||||||
// ) {
|
}
|
||||||
// this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, {
|
|
||||||
// mediaType: nextProps.mediaType,
|
|
||||||
// }))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// handleScrollToBottom = () => {
|
handleLoadMore = () => {
|
||||||
// if (this.props.hasMore) {
|
const { accountId, hasMore } = this.props
|
||||||
// this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined)
|
if (accountId && accountId !== -1 && hasMore) {
|
||||||
// }
|
this.props.onExpandAccountAlbums(accountId)
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handleScroll = (e) => {
|
handleLoadOlder = (e) => {
|
||||||
// const { scrollTop, scrollHeight, clientHeight } = e.target
|
e.preventDefault()
|
||||||
// const offset = scrollHeight - scrollTop - clientHeight
|
this.handleLoadMore()
|
||||||
|
}
|
||||||
// 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()
|
|
||||||
// }
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
account,
|
|
||||||
isMe,
|
isMe,
|
||||||
|
albums,
|
||||||
|
account,
|
||||||
|
accountId,
|
||||||
|
hasMore,
|
||||||
|
isLoading,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
if (!account) return null
|
if (!account) return null
|
||||||
|
const hasAlbums = !!albums ? albums.size > 0 : false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Block>
|
<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(' ')}>
|
<div className={[_s.d, _s.w100PC, _s.flexRow, _s.flexWrap, _s.px10, _s.mb15, _s.pb10].join(' ')}>
|
||||||
{ isMe && <Album isAddable /> }
|
{ isMe && <Album isAddable /> }
|
||||||
<Album />
|
|
||||||
<Album />
|
|
||||||
<Album />
|
|
||||||
<Album />
|
|
||||||
<Album />
|
|
||||||
|
|
||||||
<Album isDummy />
|
{
|
||||||
<Album isDummy />
|
hasAlbums &&
|
||||||
<Album isDummy />
|
albums.map((albums, i) => (
|
||||||
<Album isDummy />
|
<Album
|
||||||
<Album isDummy />
|
key={album.get('id')}
|
||||||
<Album isDummy />
|
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>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
hasMore && !(isLoading && !hasAlbums) &&
|
||||||
|
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
||||||
|
}
|
||||||
</Block>
|
</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 mapStateToProps = (state, { account, mediaType }) => {
|
||||||
const accountId = !!account ? account.get('id') : -1
|
const accountId = !!account ? account.get('id') : -1
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountId,
|
accountId,
|
||||||
attachments: getAccountGallery(state, accountId, mediaType),
|
albums: state.getIn(['album_lists', accountId, 'items']),
|
||||||
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
|
isLoading: state.getIn(['album_lists', accountId, 'isLoading'], false),
|
||||||
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
|
hasMore: state.getIn(['album_lists', accountId, 'hasMore'], false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
onFetchAccountAlbums(accountId) {
|
||||||
|
|
||||||
|
},
|
||||||
|
onExpandAccountAlbums(accountId) {
|
||||||
|
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
AccountAlbums.propTypes = {
|
AccountAlbums.propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
accountId: PropTypes.string,
|
accountId: PropTypes.string,
|
||||||
attachments: ImmutablePropTypes.list.isRequired,
|
albums: ImmutablePropTypes.list,
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
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 { getAccountGallery } from '../selectors'
|
||||||
import ColumnIndicator from '../components/column_indicator'
|
import ColumnIndicator from '../components/column_indicator'
|
||||||
import MediaItem from '../components/media_item'
|
import MediaItem from '../components/media_item'
|
||||||
|
import Dummy from '../components/dummy'
|
||||||
import LoadMore from '../components/load_more'
|
import LoadMore from '../components/load_more'
|
||||||
import Block from '../components/block'
|
import Block from '../components/block'
|
||||||
|
import Heading from '../components/heading'
|
||||||
|
import TabBar from '../components/tab_bar'
|
||||||
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
import MediaGalleryPlaceholder from '../components/placeholder/media_gallery_placeholder'
|
||||||
|
|
||||||
class AccountGallery extends ImmutablePureComponent {
|
class AccountVideoGallery extends ImmutablePureComponent {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { accountId, mediaType } = this.props
|
const { accountId } = this.props
|
||||||
|
|
||||||
if (accountId && accountId !== -1) {
|
if (accountId && accountId !== -1) {
|
||||||
this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType }))
|
this.props.dispatch(expandAccountMediaTimeline(accountId, { mediaType: 'video' }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (
|
if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
|
||||||
(nextProps.accountId && nextProps.accountId !== this.props.accountId) ||
|
|
||||||
(nextProps.accountId && nextProps.mediaType !== this.props.mediaType)
|
|
||||||
) {
|
|
||||||
this.props.dispatch(expandAccountMediaTimeline(nextProps.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) {
|
if (this.props.accountId && this.props.accountId !== -1) {
|
||||||
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, {
|
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, {
|
||||||
maxId,
|
maxId,
|
||||||
mediaType: this.props.mediaType,
|
mediaType: 'video',
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,14 +73,31 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
|
|
||||||
if (!account) return null
|
if (!account) return null
|
||||||
|
|
||||||
|
const hasAttachments = !!attachments ? attachments.size > 0 : false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Block>
|
<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
|
<div
|
||||||
role='feed'
|
role='feed'
|
||||||
onScroll={this.handleScroll}
|
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(' ')}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{
|
||||||
|
hasAttachments &&
|
||||||
|
<React.Fragment>
|
||||||
{
|
{
|
||||||
attachments.map((attachment, i) => (
|
attachments.map((attachment, i) => (
|
||||||
<MediaItem
|
<MediaItem
|
||||||
@ -90,22 +107,29 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
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(' ')}>
|
<div className={[_s.d, _s.w100PC].join(' ')}>
|
||||||
<MediaGalleryPlaceholder />
|
<MediaGalleryPlaceholder />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isLoading && attachments.size === 0 &&
|
!isLoading && !hasAttachments &&
|
||||||
<ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
|
<ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
hasMore && !(isLoading && attachments.size === 0) &&
|
hasMore && !(isLoading && !hasAttachments) &&
|
||||||
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
|
||||||
}
|
}
|
||||||
</Block>
|
</Block>
|
||||||
@ -118,18 +142,18 @@ const messages = defineMessages({
|
|||||||
none: { id: 'account_gallery.none', defaultMessage: 'No media to show.' },
|
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
|
const accountId = !!account ? account.get('id') : -1
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountId,
|
accountId,
|
||||||
attachments: getAccountGallery(state, accountId, mediaType),
|
attachments: getAccountGallery(state, accountId, 'video'),
|
||||||
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
|
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
|
||||||
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
|
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountGallery.propTypes = {
|
AccountVideoGallery.propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
accountId: PropTypes.string,
|
accountId: PropTypes.string,
|
||||||
@ -137,14 +161,6 @@ AccountGallery.propTypes = {
|
|||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
mediaType: PropTypes.oneOf([
|
|
||||||
'photo',
|
|
||||||
'video',
|
|
||||||
]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountGallery.defaultProps = {
|
export default injectIntl(connect(mapStateToProps)(AccountVideoGallery))
|
||||||
mediaType: 'both'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(connect(mapStateToProps)(AccountGallery))
|
|
@ -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 { me, meUsername} from '../initial_state'
|
||||||
import {
|
import {
|
||||||
GAB_DECK_MAX_ITEMS,
|
GAB_DECK_MAX_ITEMS,
|
||||||
|
URL_GAB_PRO,
|
||||||
MODAL_PRO_UPGRADE,
|
MODAL_PRO_UPGRADE,
|
||||||
} from '../constants'
|
} from '../constants'
|
||||||
import {
|
import {
|
||||||
@ -22,6 +23,7 @@ import { openModal } from '../actions/modal'
|
|||||||
import WrappedBundle from './ui/util/wrapped_bundle'
|
import WrappedBundle from './ui/util/wrapped_bundle'
|
||||||
import DeckColumn from '../components/deck_column'
|
import DeckColumn from '../components/deck_column'
|
||||||
import Text from '../components/text'
|
import Text from '../components/text'
|
||||||
|
import Button from '../components/button'
|
||||||
import {
|
import {
|
||||||
AccountTimeline,
|
AccountTimeline,
|
||||||
Compose,
|
Compose,
|
||||||
@ -186,6 +188,15 @@ class Deck extends React.PureComponent {
|
|||||||
|
|
||||||
const isEmpty = gabDeckOrder.size === 0
|
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 (
|
return (
|
||||||
<SortableContainer
|
<SortableContainer
|
||||||
axis='x'
|
axis='x'
|
||||||
@ -199,16 +210,29 @@ class Deck extends React.PureComponent {
|
|||||||
<DeckColumn title='Compose' icon='pencil' noButtons>
|
<DeckColumn title='Compose' icon='pencil' noButtons>
|
||||||
<WrappedBundle component={Compose} />
|
<WrappedBundle component={Compose} />
|
||||||
</DeckColumn>
|
</DeckColumn>
|
||||||
{ /** : todo : */
|
{
|
||||||
!isPro &&
|
!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(' ')}>
|
<div className={[_s.d, _s.px15, _s.py15].join(' ')}>
|
||||||
<Text>
|
<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>
|
</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>
|
</div>
|
||||||
</DeckColumn>
|
</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 />
|
<DeckColumn />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,6 @@ class ChatMessageScrollingList extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getCurrentChatMessageIndex = (id) => {
|
getCurrentChatMessageIndex = (id) => {
|
||||||
// : todo :
|
|
||||||
return this.props.chatMessageIds.indexOf(id)
|
return this.props.chatMessageIds.indexOf(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,8 @@ import DeckPage from '../../pages/deck_page'
|
|||||||
import {
|
import {
|
||||||
About,
|
About,
|
||||||
AccountAlbums,
|
AccountAlbums,
|
||||||
AccountGallery,
|
AccountPhotoGallery,
|
||||||
|
AccountVideoGallery,
|
||||||
AccountTimeline,
|
AccountTimeline,
|
||||||
AccountCommentsTimeline,
|
AccountCommentsTimeline,
|
||||||
AlbumCreate,
|
AlbumCreate,
|
||||||
@ -277,17 +278,18 @@ class SwitchingArea extends React.PureComponent {
|
|||||||
<WrappedRoute path='/:username/followers' page={ProfilePage} component={Followers} content={children} />
|
<WrappedRoute path='/:username/followers' page={ProfilePage} component={Followers} content={children} />
|
||||||
<WrappedRoute path='/:username/following' page={ProfilePage} component={Following} 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/photos' exact page={ProfilePage} component={AccountPhotoGallery} content={children} componentParams={{ noSidebar: true }} />
|
||||||
<WrappedRoute path='/:username/videos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true, mediaType: 'video' }} />
|
{ /* <WrappedRoute path='/:username/albums/:albumId' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ noSidebar: true }} /> */ }
|
||||||
<WrappedRoute path='/:username/albums' page={ProfilePage} component={AccountAlbums} content={children} componentParams={{ noSidebar: true, mediaType: 'photo' }} />
|
<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/albums/create' exact 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/: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/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/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' 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/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' }} />
|
<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 AccountTimeline() { return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline') }
|
||||||
export function AccountCommentsTimeline() { return import(/* webpackChunkName: "features/account_comments_timeline" */'../../account_comments_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 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 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 AlbumCreateModal() { return import(/* webpackChunkName: "components/album_create_modal" */'../../../components/modal/album_create_modal') }
|
||||||
export function Assets() { return import(/* webpackChunkName: "features/about/assets" */'../../about/assets') }
|
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 BookmarkCollections() { return import(/* webpackChunkName: "features/bookmark_collections" */'../../bookmark_collections') }
|
||||||
export function BookmarkCollectionCreate() { return import(/* webpackChunkName: "features/bookmark_collection_create" */'../../bookmark_collection_create') }
|
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 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 BookmarkedStatuses() { return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses') }
|
||||||
export function BoostModal() { return import(/* webpackChunkName: "components/boost_modal" */'../../../components/modal/boost_modal') }
|
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') }
|
export function CaliforniaConsumerProtection() { return import(/* webpackChunkName: "features/california_consumer_protection" */'../../about/california_consumer_protection') }
|
||||||
|
@ -34,6 +34,9 @@ class GroupPage extends ImmutablePureComponent {
|
|||||||
const isMember = !!relationships ? relationships.get('member') : false
|
const isMember = !!relationships ? relationships.get('member') : false
|
||||||
const unavailable = isPrivate && !isMember
|
const unavailable = isPrivate && !isMember
|
||||||
|
|
||||||
|
if (!!group) {
|
||||||
|
if (group.get('archived')) return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GroupLayout
|
<GroupLayout
|
||||||
|
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 from './accounts'
|
||||||
import accounts_counters from './accounts_counters'
|
import accounts_counters from './accounts_counters'
|
||||||
import albums from './albums'
|
import albums from './albums'
|
||||||
|
import album_lists from './album_lists'
|
||||||
import bookmark_collections from './bookmark_collections'
|
import bookmark_collections from './bookmark_collections'
|
||||||
import chats from './chats'
|
import chats from './chats'
|
||||||
import chat_conversation_lists from './chat_conversation_lists'
|
import chat_conversation_lists from './chat_conversation_lists'
|
||||||
@ -55,6 +56,8 @@ import user_lists from './user_lists'
|
|||||||
const reducers = {
|
const reducers = {
|
||||||
accounts,
|
accounts,
|
||||||
accounts_counters,
|
accounts_counters,
|
||||||
|
// albums,
|
||||||
|
// album_lists,
|
||||||
bookmark_collections,
|
bookmark_collections,
|
||||||
chats,
|
chats,
|
||||||
chat_conversation_lists,
|
chat_conversation_lists,
|
||||||
|
@ -603,6 +603,8 @@ pre {
|
|||||||
.maxW212PX { max-width: 212px; }
|
.maxW212PX { max-width: 212px; }
|
||||||
|
|
||||||
.minW330PX { min-width: 330px; }
|
.minW330PX { min-width: 330px; }
|
||||||
|
.minW232PX { min-width: 232px; }
|
||||||
|
.minW198PX { min-width: 192px; }
|
||||||
.minW162PX { min-width: 162px; }
|
.minW162PX { min-width: 162px; }
|
||||||
.minW120PX { min-width: 120px; }
|
.minW120PX { min-width: 120px; }
|
||||||
.minW84PX { min-width: 84px; }
|
.minW84PX { min-width: 84px; }
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
# is_muted :boolean default(FALSE), not null
|
# is_muted :boolean default(FALSE), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
# : todo : expires
|
|
||||||
# : todo : max per account
|
# : todo : max per account
|
||||||
class ChatConversationAccount < ApplicationRecord
|
class ChatConversationAccount < ApplicationRecord
|
||||||
include Paginable
|
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
|
class Shortcut < ApplicationRecord
|
||||||
# : todo : enum 1,2, etc.
|
SHORTCUT_TYPE_MAP = {
|
||||||
# enum shortcut_type: {
|
account: 'account',
|
||||||
# account: 'account',
|
group: 'group',
|
||||||
# group: 'group'
|
}.freeze
|
||||||
# }
|
|
||||||
|
|
||||||
belongs_to :account
|
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
|
end
|
||||||
|
|
||||||
def parse_urls
|
def parse_urls
|
||||||
# : todo :
|
|
||||||
if @status.local?
|
if @status.local?
|
||||||
urls = @status.text.scan(URL_PATTERN).map { |array| Addressable::URI.parse(array[0]).normalize }
|
urls = @status.text.scan(URL_PATTERN).map { |array| Addressable::URI.parse(array[0]).normalize }
|
||||||
|
return urls.reject { |uri| bad_url?(uri) }.first
|
||||||
else
|
else
|
||||||
html = Nokogiri::HTML(@status.text)
|
return nil
|
||||||
links = html.css('a')
|
|
||||||
urls = links.map { |a| Addressable::URI.parse(a['href']).normalize unless skip_link?(a) }.compact
|
|
||||||
end
|
end
|
||||||
|
|
||||||
urls.reject { |uri| bad_url?(uri) }.first
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def bad_url?(uri)
|
def bad_url?(uri)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
%td
|
%td
|
||||||
= admin_account_link_to(account)
|
= admin_account_link_to(account)
|
||||||
- if account.is_flagged_as_spam
|
- 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
|
%td
|
||||||
- if account.user_current_sign_in_ip
|
- if account.user_current_sign_in_ip
|
||||||
%samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= 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
|
%time.formatted{ datetime: @account.created_at.iso8601, title: l(@account.created_at) }= l @account.created_at
|
||||||
%td
|
%td
|
||||||
|
|
||||||
|
%tr
|
||||||
|
%th Is flagged as spam
|
||||||
|
%td
|
||||||
|
- if @account.is_flagged_as_spam?
|
||||||
|
%span YES
|
||||||
|
- else
|
||||||
|
%span no
|
||||||
|
|
||||||
%tr
|
%tr
|
||||||
%th= t('admin.accounts.most_recent_ip')
|
%th= t('admin.accounts.most_recent_ip')
|
||||||
%td= @account.user_current_sign_in_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
|
- content_for :page_title do
|
||||||
= t('admin.followers.title', acct: @account.acct)
|
%span Chat Messages
|
||||||
|
\-
|
||||||
|
= "@#{@account.acct}"
|
||||||
|
|
||||||
.filters
|
.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' }
|
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
|
||||||
= link_to admin_account_path(@account.id) do
|
= link_to admin_account_path(@account.id) do
|
||||||
= fa_icon 'chevron-left fw'
|
= fa_icon 'chevron-left fw'
|
||||||
= t('admin.followers.back_to_account')
|
%span Back to account
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
.table-wrapper
|
= form_for(@form, url: admin_account_chat_messages_path(@account.id)) do |f|
|
||||||
%table.table
|
= hidden_field_tag :page, params[:page]
|
||||||
%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
|
|
||||||
|
|
||||||
= 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= group.member_count
|
||||||
%td
|
%td
|
||||||
- if group.is_featured?
|
- if group.is_featured?
|
||||||
= t('admin.groups.featured')
|
%span Y
|
||||||
%td
|
%td
|
||||||
- if not group.is_featured?
|
= table_link_to '', 'Edit', admin_group_path(group)
|
||||||
= 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
|
-# %td
|
||||||
= 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') }
|
-# - if not group.is_featured?
|
||||||
%td
|
-# = 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') }
|
||||||
= 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') }
|
-# - 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.id')
|
||||||
%th= t('admin.groups.title')
|
%th= t('admin.groups.title')
|
||||||
%th= t('admin.groups.member_count')
|
%th= t('admin.groups.member_count')
|
||||||
%th
|
%th Featured?
|
||||||
%th
|
%th
|
||||||
%th
|
%th
|
||||||
%tbody
|
%tbody
|
||||||
|
@ -8,28 +8,60 @@
|
|||||||
-# delete
|
-# delete
|
||||||
-# view accounts
|
-# view accounts
|
||||||
-# view removed accounts
|
-# view removed accounts
|
||||||
-# number of accounts
|
|
||||||
-# number of removed accounts
|
|
||||||
-# number of posts
|
|
||||||
|
|
||||||
.card.h-card{:style => "height:300px"}
|
.card.h-card{:style => "height:300px"}
|
||||||
.card__img
|
.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' }
|
.dashboard__counters{ style: 'margin-top: 10px' }
|
||||||
%div
|
%div
|
||||||
%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
|
.dashboard__counters__label Status Count
|
||||||
%div
|
%div
|
||||||
%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
|
.dashboard__counters__label Member Count
|
||||||
%div
|
%div
|
||||||
%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
|
.dashboard__counters__label Removed Members Count
|
||||||
%div
|
%div
|
||||||
%div
|
%div
|
||||||
.dashboard__counters__num= number_with_delimiter 0 #@account.reports.count
|
.dashboard__counters__num= number_with_delimiter @group.join_requests.count
|
||||||
.dashboard__counters__label Member 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…
x
Reference in New Issue
Block a user