Browse Source

WIP

hometown-rich-text
Darius Kazemi 1 year ago
parent
commit
a5bbbfc1e0
  1. 26
      app/javascript/styles/mastodon/rich_text.scss
  2. 28
      app/lib/activitypub/activity/create.rb
  3. 2
      app/lib/activitypub/activity/update.rb
  4. 4
      app/lib/activitypub/parser/status_parser.rb
  5. 23
      lib/sanitize_ext/sanitize_config.rb

26
app/javascript/styles/mastodon/rich_text.scss

@ -54,6 +54,32 @@
ol { ol {
list-style-type: decimal; list-style-type: decimal;
} }
h1 {
font-size: 1.5em;
font-weight: 700;
margin: 1em 0;
}
h2 {
font-size: 1.25em;
font-weight: 700;
margin: 1em 0;
}
h3 {
font-size: 1.125em;
font-weight: 700;
margin: 1em 0;
}
h4,
h5,
h6 {
font-size: 1em;
font-weight: 700;
margin: 1em 0;
}
} }
.reply-indicator__content { .reply-indicator__content {

28
app/lib/activitypub/activity/create.rb

@ -78,8 +78,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
@silenced_account_ids = [] @silenced_account_ids = []
@params = {} @params = {}
process_inline_images if @object['content'].present? && @object['type'] == 'Article'
process_status_params process_status_params
process_inline_images if @object['content'].present? && @object['type'] == 'Article'
process_tags process_tags
process_audience process_audience
@ -117,7 +117,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
account: @account, account: @account,
text: converted_object_type? ? converted_text : (@status_parser.text || ''), text: converted_object_type? ? converted_text : (@status_parser.text || ''),
language: @status_parser.language, language: @status_parser.language,
spoiler_text: converted_object_type? ? '' : (@status_parser.spoiler_text || (@object['type'] == 'Article' && text_from_name) || ''), spoiler_text: converted_object_type? ? '' : ((@object['type'] == 'Article' && text_from_name) || @status_parser.spoiler_text || ''),
created_at: @status_parser.created_at, created_at: @status_parser.created_at,
edited_at: @status_parser.edited_at && @status_parser.edited_at != @status_parser.created_at ? @status_parser.edited_at : nil, edited_at: @status_parser.edited_at && @status_parser.edited_at != @status_parser.created_at ? @status_parser.edited_at : nil,
override_timestamps: @options[:override_timestamps], override_timestamps: @options[:override_timestamps],
@ -132,14 +132,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
} }
end end
class Handler < ::Ox::Sax class TextHandler < ::Ox::Sax
attr_reader :srcs, :alts attr_reader :srcs, :alts, :original_srcs
def initialize(_block) def initialize
super super
@stack = [] @stack = []
@srcs = [] @srcs = []
@alts = {} @alts = {}
@original_srcs = []
end end
def start_element(element_name) def start_element(element_name)
@ -148,8 +149,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def end_element(_element_name) def end_element(_element_name)
self_name, self_attributes = @stack[-1] self_name, self_attributes = @stack[-1]
# Create a list of image srcs and their alt text
if self_name == :img && !self_attributes[:src].nil? if self_name == :img && !self_attributes[:src].nil?
@srcs << self_attributes[:src] @srcs << self_attributes[:src]
@original_srcs << self_attributes[:src].clone
@alts[self_attributes[:src]] = self_attributes[:alt] @alts[self_attributes[:src]] = self_attributes[:alt]
end end
@stack.pop @stack.pop
@ -163,11 +166,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end end
def process_inline_images def process_inline_images
proc = proc { |name| Rails.logger.debug name } handler = TextHandler.new
handler = Handler.new(proc)
Ox.sax_parse(handler, @object['content']) Ox.sax_parse(handler, @object['content'])
handler.srcs.each do |src| handler.srcs.each do |src|
# Handle images where the src is formatted as "/foo/bar.png" # Handle images where the src is formatted as "/foo/bar.biz"
# we assume that the `url` field is populated which lets us infer # we assume that the `url` field is populated which lets us infer
# the protocol and domain of the _original_ article, as though # the protocol and domain of the _original_ article, as though
# we were looking at it via a web browser # we were looking at it via a web browser
@ -176,11 +178,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
src = site + src src = site + src
end end
# if media is set to reject from this domain, replace the src with an empty string
if skip_download? if skip_download?
@object['content'].gsub!(src, '') @object['content'].gsub!(src, '')
next next
end end
# Create a local proxy media file for each image in the post
# TODO: what about <video> or <audio> elements? should we handle them?
# TODO: is there a max number of images we should be handling? maybe if there's more than that or there is just too much rich media, we should not render the post at all and do a converted stub?
media_attachment = MediaAttachment.create(account: @account, remote_url: src, description: handler.alts[src], focus: nil) media_attachment = MediaAttachment.create(account: @account, remote_url: src, description: handler.alts[src], focus: nil)
media_attachment.download_file! media_attachment.download_file!
media_attachment.save media_attachment.save
@ -323,6 +329,12 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
next if media_attachment_parser.remote_url.blank? || media_attachments.size >= 4 next if media_attachment_parser.remote_url.blank? || media_attachments.size >= 4
begin begin
# get the src values of embedded images in a post, skip creating attachments for them since
# they will have attachments created in the process_inline_images method
handler = TextHandler.new
Ox.sax_parse(handler, @object['content'])
next if handler.original_srcs.include?(media_attachment_parser.remote_url)
media_attachment = MediaAttachment.create( media_attachment = MediaAttachment.create(
account: @account, account: @account,
remote_url: media_attachment_parser.remote_url, remote_url: media_attachment_parser.remote_url,

2
app/lib/activitypub/activity/update.rb

@ -8,7 +8,7 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
if equals_or_includes_any?(@object['type'], %w(Application Group Organization Person Service)) if equals_or_includes_any?(@object['type'], %w(Application Group Organization Person Service))
update_account update_account
elsif equals_or_includes_any?(@object['type'], %w(Note Question)) elsif equals_or_includes_any?(@object['type'], %w(Note Question Article))
update_status update_status
elsif converted_object_type? elsif converted_object_type?
Status.find_by(uri: object_uri, account_id: @account.id) Status.find_by(uri: object_uri, account_id: @account.id)

4
app/lib/activitypub/parser/status_parser.rb

@ -31,7 +31,9 @@ class ActivityPub::Parser::StatusParser
end end
def text def text
if @object['content'].present? if @object['content'].present? && @object['name'].present?
@object['content'].prepend("<h1>#{@object['name']}</h1>")
elsif @object['content'].present?
@object['content'] @object['content']
elsif content_language_map? elsif content_language_map?
@object['contentMap'].values.first @object['contentMap'].values.first

23
lib/sanitize_ext/sanitize_config.rb

@ -55,26 +55,17 @@ class Sanitize
current_node.replace(Nokogiri::XML::Text.new(current_node.text, current_node.document)) unless LINK_PROTOCOLS.include?(scheme) current_node.replace(Nokogiri::XML::Text.new(current_node.text, current_node.document)) unless LINK_PROTOCOLS.include?(scheme)
end end
UNSUPPORTED_ELEMENTS_TRANSFORMER = lambda do |env|
return unless %w(h6).include?(env[:node_name])
current_node = env[:node]
current_node.name = 'strong'
current_node.wrap('<p></p>')
end
MASTODON_STRICT ||= freeze_config( MASTODON_STRICT ||= freeze_config(
elements: %w(p br span a abbr del pre blockquote code b strong i em h1 h2 h3 h4 h5 ul ol li img u), elements: %w(p br span a del s pre blockquote code b strong i em ul ol li ruby rt rp img h1 h2 h3 h4 h5 h6),
attributes: { attributes: {
'abbr' => %w(title),
'blockquote' => %w(cite),
'img' => %w(src alt),
'a' => %w(href rel class translate title), 'a' => %w(href rel class translate title),
'span' => %w(class translate), 'span' => %w(class translate),
'ol' => %w(start reversed), 'ol' => %w(start reversed),
'li' => %w(value), 'li' => %w(value),
'img' => %w(src alt title),
'abbr' => %w(title),
'blockquote' => %w(cite),
}, },
add_attributes: { add_attributes: {
@ -84,12 +75,14 @@ class Sanitize
}, },
}, },
protocols: {}, protocols: {
'a' => { 'href' => HTTP_PROTOCOLS },
'blockquote' => { 'cite' => HTTP_PROTOCOLS },
},
transformers: [ transformers: [
CLASS_WHITELIST_TRANSFORMER, CLASS_WHITELIST_TRANSFORMER,
TRANSLATE_TRANSFORMER, TRANSLATE_TRANSFORMER,
UNSUPPORTED_ELEMENTS_TRANSFORMER,
UNSUPPORTED_HREF_TRANSFORMER, UNSUPPORTED_HREF_TRANSFORMER,
] ]
) )

Loading…
Cancel
Save