From 87f4b4d230454d4baa7116e55d9aee42199eeb9b Mon Sep 17 00:00:00 2001 From: multiple creatures Date: Sat, 20 Apr 2019 01:00:45 -0500 Subject: [PATCH] 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. --- app/controllers/statuses_controller.rb | 24 ++++++++++- .../status/components/detailed_status.js | 38 ++++++++++++++--- .../glitch/styles/components/status.scss | 6 +++ app/lib/bangtags.rb | 41 +++++++++++++++++++ app/policies/status_policy.rb | 2 +- app/serializers/rest/status_serializer.rb | 9 +++- .../stream_entries/_detailed_status.html.haml | 29 ++++++++----- .../stream_entries/_simple_status.html.haml | 4 ++ 8 files changed, 132 insertions(+), 21 deletions(-) diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 28eebda28..299fe0cda 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -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 diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 1633d26ee..e9bbcaa90 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -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 = · {status.getIn(['application', 'name'])}; - } - 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 = ( + + + + +  ·  + + + +  ·  + + + +  · + + ); + } else if (status.getIn(['account', 'id']) == me) { + sharekeyLinks = ( + + + + +  · + + ); + } + if (this.context.router) { favouriteLink = ( @@ -229,9 +254,10 @@ export default class DetailedStatus extends ImmutablePureComponent { />
+ {sharekeyLinks} {reblogLink} · {favouriteLink} · - {applicationLink} · {reblogLink} · {favouriteLink} · +
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index 62f0a815f..4d9f08c1f 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -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; diff --git a/app/lib/bangtags.rb b/app/lib/bangtags.rb index d1a179a76..0567caf6d 100644 --- a/app/lib/bangtags.rb +++ b/app/lib/bangtags.rb @@ -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 diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index fcf19db62..0961ec3e2 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -86,7 +86,7 @@ class StatusPolicy < ApplicationPolicy def author record.account end - + def local_only? record.local_only? end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index ab3788d75..86e887463 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -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 diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index 042e5d950..372d29b7c 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -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' + diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml index d4b919b1a..f18844d02 100644 --- a/app/views/stream_entries/_simple_status.html.haml +++ b/app/views/stream_entries/_simple_status.html.haml @@ -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?