reimplement monsterpit bbcode and markdown extensions on top of new glitch-soc formatting system + bbcode feature parity + new `i:am` footer + set content type from `format` bangtag

staging
multiple creatures 2019-05-18 13:03:36 -05:00
parent 09b7532805
commit 7a0dc34cad
14 changed files with 517 additions and 30 deletions

View File

@ -149,3 +149,5 @@ group :production do
end
gem 'concurrent-ruby', require: false
gem "ruby-bbcode", "~> 2.0"

View File

@ -533,6 +533,8 @@ GEM
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
ruby-bbcode (2.0.3)
activesupport (>= 4.2.2)
ruby-progressbar (1.10.0)
ruby-saml (1.9.0)
nokogiri (>= 1.5.10)
@ -667,7 +669,6 @@ DEPENDENCIES
brakeman (~> 4.5)
browser
bullet (~> 6.0)
bundler (~> 1.17)
bundler-audit (~> 0.6)
capistrano (~> 3.11)
capistrano-rails (~> 1.4)
@ -751,6 +752,7 @@ DEPENDENCIES
rspec-rails (~> 3.8)
rspec-sidekiq (~> 3.0)
rubocop (~> 0.69)
ruby-bbcode (~> 2.0)
sanitize (~> 5.0)
scss_lint (~> 0.58)
sidekiq (~> 5.2)

View File

@ -25,6 +25,14 @@ const messages = defineMessages({
defaultMessage: 'Attach...',
id: 'compose.attach',
},
bbcode: {
defaultMessage: 'BBCode',
id: 'compose.content-type.bbcode',
},
bbdown: {
defaultMessage: 'BBdown',
id: 'compose.content-type.bbdown',
},
change_privacy: {
defaultMessage: 'Adjust status privacy',
id: 'privacy.change',
@ -232,7 +240,7 @@ class ComposerOptions extends ImmutablePureComponent {
const contentTypeItems = {
plain: {
icon: 'align-left',
icon: 'file-text',
name: 'text/plain',
text: <FormattedMessage {...messages.plain} />,
},
@ -242,10 +250,20 @@ class ComposerOptions extends ImmutablePureComponent {
text: <FormattedMessage {...messages.html} />,
},
markdown: {
icon: 'arrow-circle-down',
icon: 'hashtag',
name: 'text/markdown',
text: <FormattedMessage {...messages.markdown} />,
},
xbbcode: {
icon: 'thumb-tack',
name: 'text/x-bbcode',
text: <FormattedMessage {...messages.bbcode} />,
},
xbbcodemarkdown: {
icon: 'arrow-circle-down',
name: 'text/x-bbcode+markdown',
text: <FormattedMessage {...messages.bbdown} />,
},
};
// The result.
@ -315,11 +333,13 @@ class ComposerOptions extends ImmutablePureComponent {
{showContentTypeChoice && (
<Dropdown
disabled={disabled}
icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
icon={(contentTypeItems[contentType.split('/')[1].replace(/[+-]/g, '')] || {}).icon}
items={[
contentTypeItems.plain,
contentTypeItems.html,
contentTypeItems.xbbcodemarkdown,
contentTypeItems.markdown,
contentTypeItems.xbbcode,
contentTypeItems.html,
contentTypeItems.plain,
]}
onChange={onChangeContentType}
onModalClose={onModalClose}

View File

@ -0,0 +1,56 @@
/*
// Original:
// https://github.com/computerfairies/mastodon/blob/master/app/javascript/styles/mastodon/bbcode.scss
*/
.bbcode {
&__flip-horizontal {
display: inline-block;
-webkit-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
&__flip-vertical {
display: inline-block;
-webkit-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
}
@for $i from 1 through 6 {
&__size-#{$i} {
font-size: #{6 * $i}px;
& .emojione {
width: #{6 * $i}px !important;
height: #{6 * $i}px !important;
}
& .hoverplay {
padding-left: #{6 * $i}px !important;
}
}
}
@for $i from 1 through 2 {
&__size-#{$i}:hover {
font-size: 12px;
}
}
&__left { display: block; text-align: left; }
&__center { display: block; text-align: center; }
&__right { display: block; text-align: right; }
&__lfloat { float: left; }
&__rfloat { float: right; }
&__spoiler-wrapper {
background: black;
color: black;
padding: 1px 2em 1px 2em;
}
&__spoiler { color: black; visibility: hidden; }
&__spoiler-wrapper:hover > &__spoiler,
&__spoiler-wrapper:active > &__spoiler
{ color: white; visibility: visible; }
}

View File

@ -24,3 +24,5 @@
@import 'accessibility';
@import 'rtl';
@import 'dashboard';
@import 'bbcode';
@import 'monsterpit';

View File

@ -0,0 +1,80 @@
.status__content__text,
.reply-indicator__content,
.composer--reply > .content,
.account__header__content,
{
s { text-decoration: line-through; }
del { text-decoration: line-through; }
h6 { font-size: 8px; font-weight: bold; }
hr { border-color: lighten($dark-text-color, 10%); }
sub {
vertical-align: sub;
font-size: smaller;
}
sup {
vertical-align: super;
font-size: smaller;
}
pre, code {
color: lighten($dark-text-color, 33%);
}
mark {
background-color: #ccff15;
color: black;
}
blockquote {
font-style: italic;
}
.caption {
display: block;
margin: auto;
font-size: 12px !important;
padding-top: 0;
text-align: center;
max-width: 80%;
}
.caption-hidden {
display: none;
}
p.signature {
color: lighten($dark-text-color, 20%);
font-style: italic;
font-size: 12px;
text-align: right;
}
}
div.media-caption {
p {
font-size: 12px !important;
margin-bottom: 0;
text-align: center;
}
a {
color: $secondary-text-color;
text-decoration: none;
font-weight: bold;
&:hover {
text-decoration: underline;
.fa {
color: lighten($dark-text-color, 7%);
}
}
&.mention {
&:hover {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
.fa {
color: $dark-text-color;
}
}
}

View File

@ -26,3 +26,5 @@
@import 'mastodon/dashboard';
@import 'mastodon/rtl';
@import 'mastodon/accessibility';
@import 'mastodon/bbcode';
@import 'mastodon/monsterpit';

View File

@ -0,0 +1,56 @@
/*
// Original:
// https://github.com/computerfairies/mastodon/blob/master/app/javascript/styles/mastodon/bbcode.scss
*/
.bbcode {
&__flip-horizontal {
display: inline-block;
-webkit-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
&__flip-vertical {
display: inline-block;
-webkit-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
}
@for $i from 1 through 6 {
&__size-#{$i} {
font-size: #{6 * $i}px;
& .emojione {
width: #{6 * $i}px !important;
height: #{6 * $i}px !important;
}
& .hoverplay {
padding-left: #{6 * $i}px !important;
}
}
}
@for $i from 1 through 2 {
&__size-#{$i}:hover {
font-size: 12px;
}
}
&__left { display: block; text-align: left; }
&__center { display: block; text-align: center; }
&__right { display: block; text-align: right; }
&__lfloat { float: left; }
&__rfloat { float: right; }
&__spoiler-wrapper {
background: black;
color: black;
padding: 1px 2em 1px 2em;
}
&__spoiler { color: black; visibility: hidden; }
&__spoiler-wrapper:hover > &__spoiler,
&__spoiler-wrapper:active > &__spoiler
{ color: white; visibility: visible; }
}

View File

@ -0,0 +1,74 @@
.status__content__text,
.reply-indicator__content,
.composer--reply > .content,
.account__header__content,
{
s { text-decoration: line-through; }
del { text-decoration: line-through; }
h6 { font-size: 8px; font-weight: bold; }
hr { border-color: lighten($dark-text-color, 10%); }
sub {
vertical-align: sub;
font-size: smaller;
}
sup {
vertical-align: super;
font-size: smaller;
}
pre, code {
color: lighten($dark-text-color, 33%);
}
mark {
background-color: #ccff15;
color: black;
}
blockquote {
font-style: italic;
}
.caption {
display: block;
margin: auto;
font-size: 12px !important;
padding-top: 0;
text-align: center;
max-width: 80%;
}
.caption-hidden {
display: none;
}
}
div.media-caption {
p {
font-size: 12px !important;
margin-bottom: 0;
text-align: center;
}
a {
color: $secondary-text-color;
text-decoration: none;
font-weight: bold;
&:hover {
text-decoration: underline;
.fa {
color: lighten($dark-text-color, 7%);
}
}
&.mention {
&:hover {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
.fa {
color: $dark-text-color;
}
}
}

View File

@ -26,7 +26,7 @@ class Bangtags
# list of transformation commands
@tf_cmds = []
# list of post-processing commands
@post_cmds = [['signature']]
@post_cmds = []
# hash of bangtag variables
@vars = account.vars
# keep track of what variables we're appending the value of between chunks
@ -36,7 +36,7 @@ class Bangtags
end
def process
return unless status.text&.present?
return unless status.text&.present? && status.text.include?('#!')
status.text.gsub!('#!!', "#\u200c!")
@ -367,16 +367,19 @@ class Bangtags
who = cmd[2]
if who.blank?
@vars.delete('_they:are')
status.footer = nil
next
elsif who == 'not'
who = cmd[3]
next if who.blank?
name = who.downcase.gsub(/\s+/, '')
@vars.delete("_they:are:#{name}")
@vars.delete('_they:are') if @vars['_they:are'] == name
next unless @vars['_they:are'] == name
@vars.delete('_they:are')
status.footer = nil
next
end
name = who.downcase.gsub(/\s+/, '')
name = who.downcase.gsub(/\s+/, '').strip
description = cmd[3..-1].join(':').strip
if description.blank?
if @vars["_they:are:#{name}"].nil?
@ -385,7 +388,8 @@ class Bangtags
else
@vars["_they:are:#{name}"] = description
end
@vars['_they:are'] = name.strip
@vars['_they:are'] = name
status.footer = @vars["_they:are:#{name}"]
end
when 'sharekey'
next if cmd[1].nil?
@ -401,6 +405,30 @@ class Bangtags
@vore_stack.push('_draft')
@component_stack.push(:var)
add_tags(status, 'self:draft')
when 'format', 'type'
chunk = nil
next if cmd[1].nil?
content_types = {
't' => 'text/plain',
'txt' => 'text/plain',
'text' => 'text/plain',
'plain' => 'text/plain',
'plaintext' => 'text/plain',
'm' => 'text/markdown',
'md' => 'text/markdown',
'markdown' => 'text/markdown',
'b' => 'text/x-bbcode',
'bbc' => 'text/x-bbcode',
'bbcode' => 'text/x-bbcode',
'bm' => 'text/x-bbcode+markdown',
'bbm' => 'text/x-bbcode+markdown',
'bbdown' => 'text/x-bbcode+markdown',
}
v = cmd[1].downcase
status.content_type = content_types[c] unless content_types[c].nil?
when 'visibility'
chunk = nil
next if cmd[1].nil?
@ -421,7 +449,7 @@ class Bangtags
'world' => :public,
}
v = cmd[1].downcase
status.visibility = visibilities[v] if visibilities[v].nil?
status.visibility = visibilities[v] unless visibilities[v].nil?
end
end
@ -472,17 +500,6 @@ class Bangtags
def postprocess_before_save
@post_cmds.each do |post_cmd|
case post_cmd[0]
when 'signature'
name = @vars['_they:are']
next if name.blank?
description = @vars["_they:are:#{name}"]
next if description.blank? || @chunks.last(5).join.include?('—')
status.local_only = true if Status::LOCAL_ONLY_TOKENS.match?(@chunks.last)
if @chunks.first(5).any? { |c| c.strip.match?(/[\r\n]/) || c.lstrip.match?(/^(?:[>#]|```|---|\* |\d+\)|\[\wi+)/) }
@chunks << "\n\n[right]— #{description}\u200c[/right]"
else
@chunks << " [rfloat]— #{description}\u200c[/rfloat]"
end
when 'media'
media_idx = post_cmd[1]
media_cmd = post_cmd[2]

View File

@ -30,6 +30,141 @@ class Formatter
include ActionView::Helpers::TextHelper
BBCODE_TAGS = {
:url => {
:html_open => '<a href="%url%" rel="noopener nofollow" target="_blank">', :html_close => '</a>',
:description => '', :example => '',
:allow_quick_param => true, :allow_between_as_param => false,
:quick_param_format => /(\S+)/,
:quick_param_format_description => 'The size parameter \'%param%\' is incorrect, a number is expected',
:param_tokens => [{:token => :url}]
},
:ul => {
:html_open => '<ul>', :html_close => '</ul>',
:description => '', :example => '',
},
:ol => {
:html_open => '<ol>', :html_close => '</ol>',
:description => '', :example => '',
},
:li => {
:html_open => '<li>', :html_close => '</li>',
:description => '', :example => '',
},
:sub => {
:html_open => '<sub>', :html_close => '</sub>',
:description => '', :example => '',
},
:sup => {
:html_open => '<sup>', :html_close => '</sup>',
:description => '', :example => '',
},
:h1 => {
:html_open => '<h1>', :html_close => '</h1>',
:description => '', :example => '',
},
:h2 => {
:html_open => '<h2>', :html_close => '</h2>',
:description => '', :example => '',
},
:h3 => {
:html_open => '<h3>', :html_close => '</h3>',
:description => '', :example => '',
},
:h4 => {
:html_open => '<h4>', :html_close => '</h4>',
:description => '', :example => '',
},
:h5 => {
:html_open => '<h5>', :html_close => '</h5>',
:description => '', :example => '',
},
:h6 => {
:html_open => '<h6>', :html_close => '</h6>',
:description => '', :example => '',
},
:abbr => {
:html_open => '<abbr>', :html_close => '</abbr>',
:description => '', :example => '',
},
:hr => {
:html_open => '<hr>', :html_close => '</hr>',
:description => '', :example => '',
},
:b => {
:html_open => '<strong>', :html_close => '</strong>',
:description => '', :example => '',
},
:i => {
:html_open => '<em>', :html_close => '</em>',
:description => '', :example => '',
},
:flip => {
:html_open => '<span class="bbcode__flip-%direction%">', :html_close => '</span>',
:description => '', :example => '',
:allow_quick_param => true, :allow_between_as_param => false,
:quick_param_format => /(h|v)/,
:quick_param_format_description => 'The size parameter \'%param%\' is incorrect, a number is expected',
:param_tokens => [{:token => :direction}]
},
:size => {
:html_open => '<span class="bbcode__size-%size%">', :html_close => '</span>',
:description => '', :example => '',
:allow_quick_param => true, :allow_between_as_param => false,
:quick_param_format => /([1-6])/,
:quick_param_format_description => 'The size parameter \'%param%\' is incorrect, a number is expected',
:param_tokens => [{:token => :size}]
},
:quote => {
:html_open => '<blockquote>', :html_close => '</blockquote>',
:description => '', :example => '',
},
:kbd => {
:html_open => '<pre><code>', :html_close => '</code></pre>',
:description => '', :example => '',
},
:code => {
:html_open => '<pre>', :html_close => '</pre>',
:description => '', :example => '',
},
:u => {
:html_open => '<u>', :html_close => '</u>',
:description => '', :example => '',
},
:s => {
:html_open => '<s>', :html_close => '</s>',
:description => '', :example => '',
},
:del => {
:html_open => '<del>', :html_close => '</del>',
:description => '', :example => '',
},
:left => {
:html_open => '<span class="bbcode__left">', :html_close => '</span>',
:description => '', :example => '',
},
:center => {
:html_open => '<span class="bbcode__center">', :html_close => '</span>',
:description => '', :example => '',
},
:right => {
:html_open => '<span class="bbcode__right">', :html_close => '</span>',
:description => '', :example => '',
},
:lfloat => {
:html_open => '<span class="bbcode__lfloat">', :html_close => '</span>',
:description => '', :example => '',
},
:rfloat => {
:html_open => '<span class="bbcode__rfloat">', :html_close => '</span>',
:description => '', :example => '',
},
:spoiler => {
:html_open => '<span class="bbcode__spoiler-wrapper"><span class="bbcode__spoiler">', :html_close => '</span></span>',
:description => '', :example => '',
},
}
def format(status, **options)
if status.reblog?
prepend_reblog = status.reblog.account.acct
@ -57,15 +192,26 @@ class Formatter
html = raw_content
html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
html = format_markdown(html) if status.content_type == 'text/markdown'
html = encode_and_link_urls(html, linkable_accounts, keep_html: %w(text/markdown text/html).include?(status.content_type))
case status.content_type
when 'text/markdown'
html = format_markdown(html)
when 'text/x-bbcode'
html = format_bbcode(html)
when 'text/x-bbcode+markdown'
html = format_bbdown(html)
end
html = encode_and_link_urls(html, linkable_accounts, keep_html: %w(text/markdown text/x-bbcode text/x-bbcode+markdown text/html).include?(status.content_type))
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
unless %w(text/markdown text/html).include?(status.content_type)
unless %w(text/markdown text/x-bbcode text/x-bbcode+markdown text/html).include?(status.content_type)
html = simple_format(html, {}, sanitize: false)
html = html.delete("\n")
end
html = append_footer(html, status.footer)
html.html_safe # rubocop:disable Rails/OutputSafety
end
@ -74,6 +220,19 @@ class Formatter
html.delete("\r").delete("\n")
end
def format_bbcode(html, sanitize = true)
html = bbcode_formatter(html)
html = html.gsub(/<hr>.*<\/hr>/im, '<hr />')
return html unless sanitize
html = reformat(html)
html.delete("\n")
end
def format_bbdown(html)
html = format_bbcode(html, false)
format_markdown(html)
end
def reformat(html)
sanitize(html, Sanitize::Config::MASTODON_STRICT)
end
@ -134,6 +293,19 @@ class Formatter
private
def append_footer(html, footer)
return html if footer.blank?
"#{html.strip}<p class=\"signature\">— #{encode(footer)}</p>"
end
def bbcode_formatter(html)
begin
html = html.bbcode_to_html(false, BBCODE_TAGS, :enable, *BBCODE_TAGS.keys)
rescue Exception => e
end
html
end
def markdown_formatter
return @markdown_formatter if defined?(@markdown_formatter)

View File

@ -14,6 +14,8 @@ class Sanitize
next true if e =~ /^(h|p|u|dt|e)-/ # microformats classes
next true if e =~ /^(mention|hashtag)$/ # semantic classes
next true if e =~ /^(ellipsis|invisible)$/ # link formatting classes
next true if e =~ /^bbcode__([a-z1-6\-]+)$/ # bbcode
next true if e == 'signature'
end
node['class'] = class_list.join(' ')
@ -23,10 +25,11 @@ class Sanitize
elements: %w(p br span a abbr del pre sub sup blockquote code b strong u i em h1 h2 h3 h4 h5 h6 ul ol li hr),
attributes: {
'a' => %w(href rel class title),
'a' => %w(href rel class title alt),
'span' => %w(class),
'abbr' => %w(title),
'blockquote' => %w(cite),
'p' => %w(class),
},
add_attributes: {

View File

@ -23,11 +23,12 @@
# in_reply_to_account_id :bigint(8)
# local_only :boolean
# poll_id :bigint(8)
# content_type :string
# tsv :tsvector
# curated :boolean default(FALSE), not null
# sharekey :string
# network :boolean default(FALSE), not null
# content_type :string
# footer :text
#
class Status < ApplicationRecord
@ -81,7 +82,7 @@ class Status < ApplicationRecord
validates_with DisallowedHashtagsValidator
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
validates :content_type, inclusion: { in: %w(text/plain text/markdown text/html) }, allow_nil: true
validates :content_type, inclusion: { in: %w(text/plain text/markdown text/x-bbcode text/x-bbcode+markdown text/html) }, allow_nil: true
accepts_nested_attributes_for :poll

View File

@ -64,7 +64,7 @@ defaults: &defaults
show_known_fediverse_at_about_page: true
show_reblogs_in_public_timelines: false
show_replies_in_public_timelines: false
default_content_type: 'text/plain'
default_content_type: 'text/x-bbcode+markdown'
development:
<<: *defaults