Adding full Article support
This creates a new column in the `statuses` table which keeps track of activity_pub_type, so in the case of a Note it will be blank (the default) and it will be a string "Article" if the received remote object is an AP Article. There is now a bunch of special case code in the formatters and sanitizers to handle Articles differently, as well as on the clientside.
This commit is contained in:
parent
b3e65978b4
commit
0436aa9984
@ -409,7 +409,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
<div className={classNames('status__wrapper', `status__wrapper-type-${status.get('activity_pub_type') || 'none'}` , `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
||||||
|
@ -170,6 +170,12 @@ export default class StatusContent extends React.PureComponent {
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const readArticleButton = (
|
||||||
|
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
|
||||||
|
<FormattedMessage id='status.read_article' defaultMessage='Read article' /><Icon id='angle-right' fixedWidth />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
if (status.get('spoiler_text').length > 0) {
|
if (status.get('spoiler_text').length > 0) {
|
||||||
let mentionsPlaceholder = '';
|
let mentionsPlaceholder = '';
|
||||||
|
|
||||||
@ -185,21 +191,26 @@ export default class StatusContent extends React.PureComponent {
|
|||||||
mentionsPlaceholder = <div>{mentionLinks}</div>;
|
mentionsPlaceholder = <div>{mentionLinks}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const output = [
|
||||||
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
||||||
<span dangerouslySetInnerHTML={spoilerContent} lang={status.get('language')} />
|
<span dangerouslySetInnerHTML={spoilerContent} lang={status.get('language')} />
|
||||||
{' '}
|
{' '}
|
||||||
<button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button>
|
{status.get('activity_pub_type') === 'Article' ? '' : <div><button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button></div>}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{mentionsPlaceholder}
|
{mentionsPlaceholder}
|
||||||
|
|
||||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
|
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
|
||||||
|
|
||||||
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||||
</div>
|
</div>,
|
||||||
);
|
];
|
||||||
|
|
||||||
|
if (status.get('activity_pub_type') === 'Article' && !this.props.expanded) {
|
||||||
|
output.push(readArticleButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
} else if (this.props.onClick) {
|
} else if (this.props.onClick) {
|
||||||
const output = [
|
const output = [
|
||||||
<div
|
<div
|
||||||
|
@ -213,7 +213,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||||||
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
|
<StatusContent status={status} expanded={status.get('activity_pub_type') === 'Article' || !status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
|
|
||||||
|
@ -773,6 +773,7 @@
|
|||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -840,6 +841,10 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-type img {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__content.status__content--collapsed {
|
.status__content.status__content--collapsed {
|
||||||
|
@ -60,7 +60,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||||||
account: @account,
|
account: @account,
|
||||||
text: text_from_content || '',
|
text: text_from_content || '',
|
||||||
language: detected_language,
|
language: detected_language,
|
||||||
spoiler_text: converted_object_type? ? '' : (text_from_summary || ''),
|
spoiler_text: converted_object_type? ? '' : (text_from_summary || (@object['type'] == 'Article' && text_from_name) || ''),
|
||||||
created_at: @object['published'],
|
created_at: @object['published'],
|
||||||
override_timestamps: @options[:override_timestamps],
|
override_timestamps: @options[:override_timestamps],
|
||||||
reply: @object['inReplyTo'].present?,
|
reply: @object['inReplyTo'].present?,
|
||||||
@ -70,6 +70,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||||||
conversation: conversation_from_uri(@object['conversation']),
|
conversation: conversation_from_uri(@object['conversation']),
|
||||||
media_attachment_ids: process_attachments.take(4).map(&:id),
|
media_attachment_ids: process_attachments.take(4).map(&:id),
|
||||||
poll: process_poll,
|
poll: process_poll,
|
||||||
|
activity_pub_type: @object['type']
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -92,6 +92,7 @@ class Formatter
|
|||||||
|
|
||||||
def format_article(text)
|
def format_article(text)
|
||||||
text = text.gsub(/>\n+</, "><")
|
text = text.gsub(/>\n+</, "><")
|
||||||
|
text = "<span class='article-type'>#{text}</span>"
|
||||||
text.html_safe # rubocop:disable Rails/OutputSafety
|
text.html_safe # rubocop:disable Rails/OutputSafety
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -49,6 +49,9 @@ class Sanitize
|
|||||||
'rel' => 'nofollow noopener',
|
'rel' => 'nofollow noopener',
|
||||||
'target' => '_blank',
|
'target' => '_blank',
|
||||||
},
|
},
|
||||||
|
'span' => {
|
||||||
|
'class' => 'article-type',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
protocols: {
|
protocols: {
|
||||||
|
@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||||||
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
||||||
:sensitive, :spoiler_text, :visibility, :language,
|
:sensitive, :spoiler_text, :visibility, :language,
|
||||||
:uri, :url, :replies_count, :reblogs_count,
|
:uri, :url, :replies_count, :reblogs_count,
|
||||||
:favourites_count, :local_only
|
:favourites_count, :local_only, :activity_pub_type
|
||||||
|
|
||||||
attribute :favourited, if: :current_user?
|
attribute :favourited, if: :current_user?
|
||||||
attribute :reblogged, if: :current_user?
|
attribute :reblogged, if: :current_user?
|
||||||
@ -61,6 +61,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||||||
OStatus::TagManager.instance.uri_for(object)
|
OStatus::TagManager.instance.uri_for(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def activity_pub_type
|
||||||
|
object.activity_pub_type.to_s
|
||||||
|
end
|
||||||
|
|
||||||
def content
|
def content
|
||||||
Formatter.instance.format(object)
|
Formatter.instance.format(object)
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user