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.
parent
19b78604e9
commit
87f4b4d230
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
·
|
||||
<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>
|
||||
·
|
||||
<a href={`${status.get('url')}?rekey=0&toweb=1`} className='detailed-status__link'>
|
||||
<i className='fa fa-user-times' title='Revoke share key' />
|
||||
</a>
|
||||
·
|
||||
</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>
|
||||
·
|
||||
</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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ class StatusPolicy < ApplicationPolicy
|
|||
def author
|
||||
record.account
|
||||
end
|
||||
|
||||
|
||||
def local_only?
|
||||
record.local_only?
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
Loading…
Reference in New Issue