Implement share keys and related bangtags, add `sharekey`, `network`, and `curated` to the API, remove app info from the UI, and move timestamps to the right.

staging
multiple creatures 2019-04-20 01:00:45 -05:00
parent 19b78604e9
commit 87f4b4d230
8 changed files with 132 additions and 21 deletions

View File

@ -12,6 +12,8 @@ class StatusesController < ApplicationController
before_action :set_account
before_action :set_status
before_action :handle_sharekey_change, only: [:show], if: :user_signed_in?
before_action :handle_webapp_redirect, only: [:show], if: :user_signed_in?
before_action :set_instance_presenter
before_action :set_link_headers
before_action :check_account_suspension
@ -190,12 +192,32 @@ class StatusesController < ApplicationController
@stream_entry = @status.stream_entry
@type = @stream_entry.activity_type.downcase
authorize @status, :show?
if @status.sharekey.present? && params[:key] == @status.sharekey
skip_authorization
else
authorize @status, :show?
end
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404
raise ActiveRecord::RecordNotFound
end
def handle_sharekey_change
raise Mastodon::NotPermittedError unless current_account.id == @status.account_id
case params[:rekey]
when '1'
@status.sharekey = SecureRandom.urlsafe_base64(32)
@status.save
when '0'
@status.sharekey = nil
@status.save
end
end
def handle_webapp_redirect
redirect_to "/web/statuses/#{@status.id}" if params[:toweb] == '1'
end
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end

View File

@ -15,6 +15,7 @@ import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task';
import classNames from 'classnames';
import PollContainer from 'flavours/glitch/containers/poll_container';
import { me } from 'flavours/glitch/util/initial_state';
export default class DetailedStatus extends ImmutablePureComponent {
@ -114,10 +115,10 @@ export default class DetailedStatus extends ImmutablePureComponent {
let media = null;
let mediaIcon = null;
let applicationLink = '';
let reblogLink = '';
let reblogIcon = 'repeat';
let favouriteLink = '';
let sharekeyLinks = '';
if (this.props.measureHeight) {
outerStyle.height = `${this.state.height}px`;
@ -168,10 +169,6 @@ export default class DetailedStatus extends ImmutablePureComponent {
mediaIcon = 'link';
}
if (status.get('application')) {
applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
}
if (status.get('visibility') === 'direct') {
reblogIcon = 'envelope';
} else if (status.get('visibility') === 'private') {
@ -194,6 +191,34 @@ export default class DetailedStatus extends ImmutablePureComponent {
);
}
if (status.get('sharekey')) {
sharekeyLinks = (
<span>
<a href={`${status.get('url')}?key=${status.get('sharekey')}`} target='_blank' className='detailed-status__link'>
<i className='fa fa-key' title='Right-click or long press to copy share link with key' />
</a>
&nbsp;·&nbsp;
<a href={`${status.get('url')}?rekey=1&toweb=1`} className='detailed-status__link'>
<i className='fa fa-user-plus' title='Generate a new share key' />
</a>
&nbsp;·&nbsp;
<a href={`${status.get('url')}?rekey=0&toweb=1`} className='detailed-status__link'>
<i className='fa fa-user-times' title='Revoke share key' />
</a>
&nbsp;·
</span>
);
} else if (status.getIn(['account', 'id']) == me) {
sharekeyLinks = (
<span>
<a href={`${status.get('url')}?rekey=1&toweb=1`} className='detailed-status__link'>
<i className='fa fa-user-plus' title='Generate a new share key' />
</a>
&nbsp;·
</span>
);
}
if (this.context.router) {
favouriteLink = (
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
@ -229,9 +254,10 @@ export default class DetailedStatus extends ImmutablePureComponent {
/>
<div className='detailed-status__meta'>
{sharekeyLinks} {reblogLink} · {favouriteLink} · <VisibilityIcon visibility={status.get('visibility')} />
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
</a>{applicationLink} · {reblogLink} · {favouriteLink} · <VisibilityIcon visibility={status.get('visibility')} />
</a>
</div>
</div>
</div>

View File

@ -684,6 +684,12 @@
}
}
div > span.detailed-status__datetime,
div > a.detailed-status__datetime {
position: relative;
float: right;
}
.status-card {
display: flex;
font-size: 14px;

View File

@ -235,6 +235,40 @@ class Bangtags
mentions = Account.where(id: mention_ids).map { |a| "@#{a.username}" }
chunk = mentions.join(' ')
end
when 'sharekey'
case cmd[2]
when 'revoke'
if status.conversation_id.present?
roars = Status.where(conversation_id: status.conversation_id, account_id: @account.id)
roars.each do |roar|
if roar.sharekey.present?
roar.sharekey = nil
roar.save
end
end
end
when 'sync', 'new'
if status.conversation_id.present?
roars = Status.where(conversation_id: status.conversation_id, account_id: @account.id)
earliest_roar = roars.last # The results are in reverse-chronological order.
if cmd[2] == 'new' || earlist_roar.sharekey.blank?
sharekey = SecureRandom.urlsafe_base64(32)
earliest_roar.sharekey = sharekey
earliest_roar.save
else
sharekey = earliest_roar.sharekey
end
roars.each do |roar|
if roar.sharekey != sharekey
roar.sharekey = sharekey
roar.save
end
end
else
status.sharekey = SecureRandom.urlsafe_base64(32)
status.save
end
end
end
when 'parent'
chunk = nil
@ -326,6 +360,13 @@ class Bangtags
end
@vars['_they:are'] = name.strip
end
when 'sharekey'
case cmd[1]
when 'new'
chunk = nil
status.sharekey = SecureRandom.urlsafe_base64(32)
status.save
end
end
end

View File

@ -86,7 +86,7 @@ class StatusPolicy < ApplicationPolicy
def author
record.account
end
def local_only?
record.local_only?
end

View File

@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
:uri, :url, :replies_count, :reblogs_count,
:favourites_count
:favourites_count, :network, :curated
attribute :favourited, if: :current_user?
attribute :reblogged, if: :current_user?
@ -12,6 +12,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
attribute :bookmarked, if: :current_user?
attribute :pinned, if: :pinnable?
attribute :local_only if :local?
attribute :sharekey, if: :owner?
attribute :content, unless: :source_requested?
attribute :text, if: :source_requested?
@ -45,8 +46,12 @@ class REST::StatusSerializer < ActiveModel::Serializer
!current_user.nil?
end
def owner?
current_user? && current_user.account_id == object.account_id
end
def show_application?
object.account.user_shows_application? || (current_user? && current_user.account_id == object.account_id)
object.account.user_shows_application? || owner?
end
def visibility

View File

@ -37,17 +37,19 @@
= react_component :card, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
.detailed-status__meta
%data.dt-published{ value: status.created_at.to_time.iso8601 }
= link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
·
- if status.application && @account.user&.setting_show_application
- if status.application.website.blank?
%strong.detailed-status__application= status.application.name
- else
= link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener'
- if user_signed_in? && @account.id == status.account_id
- if status.sharekey.present?
= link_to "#{TagManager.instance.url_for(status)}?key=#{status.sharekey}", class: 'detailed-status__link', title: 'Right-click or long-press to copy share link with key', target: stream_link_target, rel: 'noopener' do
= fa_icon('key')
·
= link_to "#{TagManager.instance.url_for(status)}?rekey=1", class: 'detailed-status__link', title: 'Generate a new share key', target: stream_link_target, rel: 'noopener' do
= fa_icon('user-plus')
·
- if status.sharekey.present?
= link_to "#{TagManager.instance.url_for(status)}?rekey=0", class: 'detailed-status__link', title: 'Revoke share key', target: stream_link_target, rel: 'noopener' do
= fa_icon('user-times')
·
= link_to remote_interaction_path(status, type: :reply), class: 'modal-button detailed-status__link' do
- if status.in_reply_to_id.nil?
= fa_icon('reply')
@ -75,6 +77,11 @@
= fa_icon('star')
= " "
- if user_signed_in?
%span.detailed-status__datetime
%data.dt-published{ value: status.created_at.to_time.iso8601 }
= link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
·
= link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'detailed-status__application', target: '_blank'
- if user_signed_in?
= link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'detailed-status__application', target: '_blank'

View File

@ -42,6 +42,10 @@
= react_component :card, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
.status__action-bar
- if status.sharekey.present? && user_signed_in? && @account.id == status.account_id
= link_to "#{TagManager.instance.url_for(status)}?key=#{status.sharekey}", class: 'status__action-bar-button icon-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px', title: 'Right click or long-press to copy share link with key', target: stream_link_target, rel: 'noopener' do
= fa_icon('key')
.status__action-bar__counter
= link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
- if status.in_reply_to_id.nil?