Change to treat the url to a account already owned as an internal link

This commit is contained in:
noellabo 2021-11-14 17:59:38 +09:00
parent dde6c4f845
commit 28b9828f06
5 changed files with 66 additions and 5 deletions

View file

@ -9,6 +9,7 @@ import Icon from 'mastodon/components/icon';
import { autoPlayGif, show_reply_tree_button } from 'mastodon/initial_state'; import { autoPlayGif, show_reply_tree_button } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
linkToAcct: { id: 'status.link_to_acct', defaultMessage: 'Link to @{acct}' },
postByAcct: { id: 'status.post_by_acct', defaultMessage: 'Post by @{acct}' }, postByAcct: { id: 'status.post_by_acct', defaultMessage: 'Post by @{acct}' },
}); });
@ -64,6 +65,9 @@ export default class StatusContent extends React.PureComponent {
link.setAttribute('title', mention.get('acct')); link.setAttribute('title', mention.get('acct'));
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
} else if (link.classList.contains('account-url-link')) {
link.setAttribute('title', this.props.intl.formatMessage(messages.linkToAcct, { acct: link.dataset.accountAcct }));
link.addEventListener('click', this.onAccountUrlClick.bind(this, link.dataset.accountId, link.dataset.accountActorType), false);
} else if (link.classList.contains('status-url-link')) { } else if (link.classList.contains('status-url-link')) {
link.setAttribute('title', this.props.intl.formatMessage(messages.postByAcct, { acct: link.dataset.statusAccountAcct })); link.setAttribute('title', this.props.intl.formatMessage(messages.postByAcct, { acct: link.dataset.statusAccountAcct }));
link.addEventListener('click', this.onStatusUrlClick.bind(this, link.dataset.statusId), false); link.addEventListener('click', this.onStatusUrlClick.bind(this, link.dataset.statusId), false);
@ -146,6 +150,13 @@ export default class StatusContent extends React.PureComponent {
} }
} }
onAccountUrlClick = (accountId, accountActorType, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`${accountActorType == 'Group' ? '/timelines/groups/' : '/accounts/'}${accountId}`);
}
}
onStatusUrlClick = (statusId, e) => { onStatusUrlClick = (statusId, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault(); e.preventDefault();

View file

@ -500,6 +500,7 @@
"status.emoji_reaction": "Emoji reaction", "status.emoji_reaction": "Emoji reaction",
"status.favourite": "Favourite", "status.favourite": "Favourite",
"status.filtered": "Filtered", "status.filtered": "Filtered",
"status.link_to_acct": "Link to @{acct}",
"status.load_more": "Load more", "status.load_more": "Load more",
"status.media_hidden": "Media hidden", "status.media_hidden": "Media hidden",
"status.mention": "Mention @{name}", "status.mention": "Mention @{name}",

View file

@ -501,6 +501,7 @@
"status.emoji_reaction": "絵文字リアクション", "status.emoji_reaction": "絵文字リアクション",
"status.favourite": "お気に入り", "status.favourite": "お気に入り",
"status.filtered": "フィルターされました", "status.filtered": "フィルターされました",
"status.link_to_acct": "@{acct}へのリンク",
"status.load_more": "もっと見る", "status.load_more": "もっと見る",
"status.media_hidden": "非表示のメディア", "status.media_hidden": "非表示のメディア",
"status.mention": "@{name}さんに投稿", "status.mention": "@{name}さんに投稿",

View file

@ -35,6 +35,30 @@ class EntityCache
shortcodes.filter_map { |shortcode| cached[to_key(:emoji, shortcode, domain)] || uncached[shortcode] } shortcodes.filter_map { |shortcode| cached[to_key(:emoji, shortcode, domain)] || uncached[shortcode] }
end end
def holding_account(url)
return Rails.cache.read(to_key(:holding_account, url)) if Rails.cache.exist?(to_key(:holding_account, url))
account = begin
if ActivityPub::TagManager.instance.local_uri?(url)
recognized_params = Rails.application.routes.recognize_path(url)
return nil unless recognized_params[:action] == 'show'
if recognized_params[:controller] == 'accounts'
Account.find_local(recognized_params[:username])
end
else
Account.where(uri: url).or(Account.where(url: url)).first
end
rescue ActiveRecord::RecordNotFound
nil
end
Rails.cache.write(to_key(:holding_account, url), account, expires_in: account.nil? ? MIN_EXPIRATION : MAX_EXPIRATION)
account
end
def holding_status_and_account(url) def holding_status_and_account(url)
return Rails.cache.read(to_key(:holding_status, url)) if Rails.cache.exist?(to_key(:holding_status, url)) return Rails.cache.read(to_key(:holding_status, url)) if Rails.cache.exist?(to_key(:holding_status, url))

View file

@ -243,6 +243,10 @@ class Formatter
Extractor.remove_overlapping_entities(special + standard + extra) Extractor.remove_overlapping_entities(special + standard + extra)
end end
def class_append(c, items)
(c || '').split.concat(items).uniq.join(' ')
end
def link_to_url(entity, options = {}) def link_to_url(entity, options = {})
url = Addressable::URI.parse(entity[:url]) url = Addressable::URI.parse(entity[:url])
html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' } html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
@ -250,11 +254,17 @@ class Formatter
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me] html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
status, account = url_to_holding_status_and_account(url.normalize.to_s) status, account = url_to_holding_status_and_account(url.normalize.to_s)
account = url_to_holding_account(url.normalize.to_s) if status.nil?
if account.present? if status.present? && account.present?
html_attrs[:class] = 'status-url-link' html_attrs[:class] = class_append(html_attrs[:class], ['status-url-link'])
html_attrs[:'data-status-id'] = status.id html_attrs[:'data-status-id'] = status.id
html_attrs[:'data-status-account-acct'] = account.acct html_attrs[:'data-status-account-acct'] = account.acct
elsif account.present?
html_attrs[:class] = class_append(html_attrs[:class], ['account-url-link'])
html_attrs[:'data-account-id'] = account.id
html_attrs[:'data-account-actor-type'] = account.actor_type
html_attrs[:'data-account-acct'] = account.acct
end end
Twitter::TwitterText::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs) Twitter::TwitterText::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
@ -266,17 +276,31 @@ class Formatter
doc = Nokogiri::HTML.parse(html, nil, 'utf-8') doc = Nokogiri::HTML.parse(html, nil, 'utf-8')
doc.css('a').map do |x| doc.css('a').map do |x|
status, account = url_to_holding_status_and_account(x['href']) status, account = url_to_holding_status_and_account(x['href'])
account = url_to_holding_account(x['href']) if status.nil?
if account.present? if status.present? && account.present?
x.add_class('status-url-link') x.add_class('status-url-link')
x['data-status-id'] = status.id x['data-status-id'] = status.id
x['data-status-account-acct'] = account.acct x['data-status-account-acct'] = account.acct
elsif account.present?
x.add_class('account-url-link')
x['data-account-id'] = account.id
x['data-account-actor-type'] = account.actor_type
x['data-account-acct'] = account.acct
end end
end end
html = doc.css('body')[0]&.inner_html || '' html = doc.css('body')[0]&.inner_html || ''
html.html_safe # rubocop:disable Rails/OutputSafety html.html_safe # rubocop:disable Rails/OutputSafety
end end
def url_to_holding_account(url)
url = url.split('#').first
return if url.nil?
EntityCache.instance.holding_account(url)
end
def url_to_holding_status_and_account(url) def url_to_holding_status_and_account(url)
url = url.split('#').first url = url.split('#').first