Change to treat the url to a account already owned as an internal link
This commit is contained in:
parent
dde6c4f845
commit
28b9828f06
5 changed files with 66 additions and 5 deletions
|
@ -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();
|
||||||
|
|
|
@ -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}",
|
||||||
|
|
|
@ -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}さんに投稿",
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue