Browse Source

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

undefined
multiple creatures 1 month ago
parent
commit
267306300d

+ 2
- 0
Gemfile View File

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

+ 3
- 1
Gemfile.lock View File

@@ -533,6 +533,8 @@ GEM
533 533
       rainbow (>= 2.2.2, < 4.0)
534 534
       ruby-progressbar (~> 1.7)
535 535
       unicode-display_width (>= 1.4.0, < 1.7)
536
+    ruby-bbcode (2.0.3)
537
+      activesupport (>= 4.2.2)
536 538
     ruby-progressbar (1.10.0)
537 539
     ruby-saml (1.9.0)
538 540
       nokogiri (>= 1.5.10)
@@ -667,7 +669,6 @@ DEPENDENCIES
667 669
   brakeman (~> 4.5)
668 670
   browser
669 671
   bullet (~> 6.0)
670
-  bundler (~> 1.17)
671 672
   bundler-audit (~> 0.6)
672 673
   capistrano (~> 3.11)
673 674
   capistrano-rails (~> 1.4)
@@ -751,6 +752,7 @@ DEPENDENCIES
751 752
   rspec-rails (~> 3.8)
752 753
   rspec-sidekiq (~> 3.0)
753 754
   rubocop (~> 0.69)
755
+  ruby-bbcode (~> 2.0)
754 756
   sanitize (~> 5.0)
755 757
   scss_lint (~> 0.58)
756 758
   sidekiq (~> 5.2)

+ 25
- 5
app/javascript/flavours/glitch/features/compose/components/options.js View File

@@ -25,6 +25,14 @@ const messages = defineMessages({
25 25
     defaultMessage: 'Attach...',
26 26
     id: 'compose.attach',
27 27
   },
28
+  bbcode: {
29
+    defaultMessage: 'BBCode',
30
+    id: 'compose.content-type.bbcode',
31
+  },
32
+  bbdown: {
33
+    defaultMessage: 'BBdown',
34
+    id: 'compose.content-type.bbdown',
35
+  },
28 36
   change_privacy: {
29 37
     defaultMessage: 'Adjust status privacy',
30 38
     id: 'privacy.change',
@@ -232,7 +240,7 @@ class ComposerOptions extends ImmutablePureComponent {
232 240
 
233 241
     const contentTypeItems = {
234 242
       plain: {
235
-        icon: 'align-left',
243
+        icon: 'file-text',
236 244
         name: 'text/plain',
237 245
         text: <FormattedMessage {...messages.plain} />,
238 246
       },
@@ -242,10 +250,20 @@ class ComposerOptions extends ImmutablePureComponent {
242 250
         text: <FormattedMessage {...messages.html} />,
243 251
       },
244 252
       markdown: {
245
-        icon: 'arrow-circle-down',
253
+        icon: 'hashtag',
246 254
         name: 'text/markdown',
247 255
         text: <FormattedMessage {...messages.markdown} />,
248 256
       },
257
+      xbbcode: {
258
+        icon: 'thumb-tack',
259
+        name: 'text/x-bbcode',
260
+        text: <FormattedMessage {...messages.bbcode} />,
261
+      },
262
+      xbbcodemarkdown: {
263
+        icon: 'arrow-circle-down',
264
+        name: 'text/x-bbcode+markdown',
265
+        text: <FormattedMessage {...messages.bbdown} />,
266
+      },
249 267
     };
250 268
 
251 269
     //  The result.
@@ -315,11 +333,13 @@ class ComposerOptions extends ImmutablePureComponent {
315 333
         {showContentTypeChoice && (
316 334
           <Dropdown
317 335
             disabled={disabled}
318
-            icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
336
+            icon={(contentTypeItems[contentType.split('/')[1].replace(/[+-]/g, '')] || {}).icon}
319 337
             items={[
320
-              contentTypeItems.plain,
321
-              contentTypeItems.html,
338
+              contentTypeItems.xbbcodemarkdown,
322 339
               contentTypeItems.markdown,
340
+              contentTypeItems.xbbcode,
341
+              contentTypeItems.html,
342
+              contentTypeItems.plain,
323 343
             ]}
324 344
             onChange={onChangeContentType}
325 345
             onModalClose={onModalClose}

+ 56
- 0
app/javascript/flavours/glitch/styles/bbcode.scss View File

@@ -0,0 +1,56 @@
1
+/*
2
+// Original:
3
+// https://github.com/computerfairies/mastodon/blob/master/app/javascript/styles/mastodon/bbcode.scss
4
+*/
5
+
6
+.bbcode {
7
+  &__flip-horizontal {
8
+    display: inline-block;
9
+    -webkit-transform: scale(-1, 1);
10
+    -ms-transform: scale(-1, 1);
11
+    transform: scale(-1, 1);
12
+  }
13
+
14
+  &__flip-vertical {
15
+    display: inline-block;
16
+    -webkit-transform: scale(1, -1);
17
+    -ms-transform: scale(1, -1);
18
+    transform: scale(1, -1);
19
+  }
20
+
21
+  @for $i from 1 through 6 {
22
+    &__size-#{$i} {
23
+      font-size: #{6 * $i}px;
24
+
25
+      & .emojione {
26
+        width: #{6 * $i}px !important;
27
+        height:  #{6 * $i}px !important;
28
+      }
29
+
30
+      & .hoverplay {
31
+        padding-left: #{6 * $i}px !important;
32
+      }
33
+    }
34
+  }
35
+
36
+  @for $i from 1 through 2 {
37
+    &__size-#{$i}:hover {
38
+      font-size: 12px;
39
+    }
40
+  }
41
+
42
+  &__left { display: block; text-align: left; }
43
+  &__center { display: block; text-align: center; }
44
+  &__right { display: block; text-align: right; }
45
+  &__lfloat { float: left; }
46
+  &__rfloat { float: right; }
47
+  &__spoiler-wrapper {
48
+    background: black;
49
+    color: black;
50
+    padding: 1px 2em 1px 2em;
51
+  }
52
+  &__spoiler { color: black; visibility: hidden; }
53
+  &__spoiler-wrapper:hover > &__spoiler,
54
+  &__spoiler-wrapper:active > &__spoiler
55
+  { color: white; visibility: visible; }
56
+}

+ 2
- 0
app/javascript/flavours/glitch/styles/index.scss View File

@@ -24,3 +24,5 @@
24 24
 @import 'accessibility';
25 25
 @import 'rtl';
26 26
 @import 'dashboard';
27
+@import 'bbcode';
28
+@import 'monsterpit';

+ 80
- 0
app/javascript/flavours/glitch/styles/monsterpit.scss View File

@@ -0,0 +1,80 @@
1
+.status__content__text,
2
+.reply-indicator__content,
3
+.composer--reply > .content,
4
+.account__header__content,
5
+{
6
+  s { text-decoration: line-through; }
7
+  del { text-decoration: line-through; }
8
+  h6 { font-size: 8px; font-weight: bold; }
9
+  hr { border-color: lighten($dark-text-color, 10%); }
10
+  sub {
11
+    vertical-align: sub;
12
+    font-size: smaller;
13
+  }
14
+  sup {
15
+    vertical-align: super;
16
+    font-size: smaller;
17
+  }
18
+  pre, code {
19
+    color: lighten($dark-text-color, 33%);
20
+  }
21
+  mark {
22
+    background-color: #ccff15;
23
+    color: black;
24
+  }
25
+  blockquote {
26
+    font-style: italic;
27
+  }
28
+  .caption {
29
+    display: block;
30
+    margin: auto;
31
+    font-size: 12px !important;
32
+    padding-top: 0;
33
+    text-align: center;
34
+    max-width: 80%;
35
+  }
36
+  .caption-hidden {
37
+    display: none;
38
+  }
39
+  p.signature {
40
+    color: lighten($dark-text-color, 20%);
41
+    font-style: italic;
42
+    font-size: 12px;
43
+    text-align: right;
44
+  }
45
+}
46
+
47
+div.media-caption {
48
+  p {
49
+    font-size: 12px !important;
50
+    margin-bottom: 0;
51
+    text-align: center;
52
+  }
53
+  a {
54
+		color: $secondary-text-color;
55
+		text-decoration: none;
56
+		font-weight: bold;
57
+
58
+		&:hover {
59
+			text-decoration: underline;
60
+
61
+			.fa {
62
+				color: lighten($dark-text-color, 7%);
63
+			}
64
+		}
65
+
66
+		&.mention {
67
+			&:hover {
68
+				text-decoration: none;
69
+
70
+				span {
71
+					text-decoration: underline;
72
+				}
73
+			}
74
+		}
75
+
76
+		.fa {
77
+			color: $dark-text-color;
78
+		}
79
+	}
80
+}

+ 2
- 0
app/javascript/styles/application.scss View File

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

+ 56
- 0
app/javascript/styles/mastodon/bbcode.scss View File

@@ -0,0 +1,56 @@
1
+/*
2
+// Original:
3
+// https://github.com/computerfairies/mastodon/blob/master/app/javascript/styles/mastodon/bbcode.scss
4
+*/
5
+
6
+.bbcode {
7
+  &__flip-horizontal {
8
+    display: inline-block;
9
+    -webkit-transform: scale(-1, 1);
10
+    -ms-transform: scale(-1, 1);
11
+    transform: scale(-1, 1);
12
+  }
13
+
14
+  &__flip-vertical {
15
+    display: inline-block;
16
+    -webkit-transform: scale(1, -1);
17
+    -ms-transform: scale(1, -1);
18
+    transform: scale(1, -1);
19
+  }
20
+
21
+  @for $i from 1 through 6 {
22
+    &__size-#{$i} {
23
+      font-size: #{6 * $i}px;
24
+
25
+      & .emojione {
26
+        width: #{6 * $i}px !important;
27
+        height:  #{6 * $i}px !important;
28
+      }
29
+
30
+      & .hoverplay {
31
+        padding-left: #{6 * $i}px !important;
32
+      }
33
+    }
34
+  }
35
+
36
+  @for $i from 1 through 2 {
37
+    &__size-#{$i}:hover {
38
+      font-size: 12px;
39
+    }
40
+  }
41
+
42
+  &__left { display: block; text-align: left; }
43
+  &__center { display: block; text-align: center; }
44
+  &__right { display: block; text-align: right; }
45
+  &__lfloat { float: left; }
46
+  &__rfloat { float: right; }
47
+  &__spoiler-wrapper {
48
+    background: black;
49
+    color: black;
50
+    padding: 1px 2em 1px 2em;
51
+  }
52
+  &__spoiler { color: black; visibility: hidden; }
53
+  &__spoiler-wrapper:hover > &__spoiler,
54
+  &__spoiler-wrapper:active > &__spoiler
55
+  { color: white; visibility: visible; }
56
+}

+ 74
- 0
app/javascript/styles/mastodon/monsterpit.scss View File

@@ -0,0 +1,74 @@
1
+.status__content__text,
2
+.reply-indicator__content,
3
+.composer--reply > .content,
4
+.account__header__content,
5
+{
6
+  s { text-decoration: line-through; }
7
+  del { text-decoration: line-through; }
8
+  h6 { font-size: 8px; font-weight: bold; }
9
+  hr { border-color: lighten($dark-text-color, 10%); }
10
+  sub {
11
+    vertical-align: sub;
12
+    font-size: smaller;
13
+  }
14
+  sup {
15
+    vertical-align: super;
16
+    font-size: smaller;
17
+  }
18
+  pre, code {
19
+    color: lighten($dark-text-color, 33%);
20
+  }
21
+  mark {
22
+    background-color: #ccff15;
23
+    color: black;
24
+  }
25
+  blockquote {
26
+    font-style: italic;
27
+  }
28
+  .caption {
29
+    display: block;
30
+    margin: auto;
31
+    font-size: 12px !important;
32
+    padding-top: 0;
33
+    text-align: center;
34
+    max-width: 80%;
35
+  }
36
+  .caption-hidden {
37
+    display: none;
38
+  }
39
+}
40
+
41
+div.media-caption {
42
+  p {
43
+    font-size: 12px !important;
44
+    margin-bottom: 0;
45
+    text-align: center;
46
+  }
47
+  a {
48
+		color: $secondary-text-color;
49
+		text-decoration: none;
50
+		font-weight: bold;
51
+
52
+		&:hover {
53
+			text-decoration: underline;
54
+
55
+			.fa {
56
+				color: lighten($dark-text-color, 7%);
57
+			}
58
+		}
59
+
60
+		&.mention {
61
+			&:hover {
62
+				text-decoration: none;
63
+
64
+				span {
65
+					text-decoration: underline;
66
+				}
67
+			}
68
+		}
69
+
70
+		.fa {
71
+			color: $dark-text-color;
72
+		}
73
+	}
74
+}

+ 34
- 17
app/lib/bangtags.rb View File

@@ -26,7 +26,7 @@ class Bangtags
26 26
     # list of transformation commands
27 27
     @tf_cmds = []
28 28
     # list of post-processing commands
29
-    @post_cmds = [['signature']]
29
+    @post_cmds = []
30 30
     # hash of bangtag variables
31 31
     @vars = account.vars
32 32
     # keep track of what variables we're appending the value of between chunks
@@ -36,7 +36,7 @@ class Bangtags
36 36
   end
37 37
 
38 38
   def process
39
-    return unless status.text&.present?
39
+    return unless status.text&.present? && status.text.include?('#!')
40 40
 
41 41
     status.text.gsub!('#!!', "#\u200c!")
42 42
 
@@ -367,16 +367,19 @@ class Bangtags
367 367
             who = cmd[2]
368 368
             if who.blank?
369 369
               @vars.delete('_they:are')
370
+              status.footer = nil
370 371
               next
371 372
             elsif who == 'not'
372 373
               who = cmd[3]
373 374
               next if who.blank?
374 375
               name = who.downcase.gsub(/\s+/, '')
375 376
               @vars.delete("_they:are:#{name}")
376
-              @vars.delete('_they:are') if @vars['_they:are'] == name
377
+              next unless @vars['_they:are'] == name
378
+              @vars.delete('_they:are')
379
+              status.footer = nil
377 380
               next
378 381
             end
379
-            name = who.downcase.gsub(/\s+/, '')
382
+            name = who.downcase.gsub(/\s+/, '').strip
380 383
             description = cmd[3..-1].join(':').strip
381 384
             if description.blank?
382 385
               if @vars["_they:are:#{name}"].nil?
@@ -385,7 +388,8 @@ class Bangtags
385 388
             else
386 389
               @vars["_they:are:#{name}"] = description
387 390
             end
388
-            @vars['_they:are'] = name.strip
391
+            @vars['_they:are'] = name
392
+            status.footer = @vars["_they:are:#{name}"]
389 393
           end
390 394
         when 'sharekey'
391 395
           next if cmd[1].nil?
@@ -401,6 +405,30 @@ class Bangtags
401 405
           @vore_stack.push('_draft')
402 406
           @component_stack.push(:var)
403 407
           add_tags(status, 'self:draft')
408
+        when 'format', 'type'
409
+          chunk = nil
410
+          next if cmd[1].nil?
411
+          content_types = {
412
+            't'           => 'text/plain',
413
+            'txt'         => 'text/plain',
414
+            'text'        => 'text/plain',
415
+            'plain'       => 'text/plain',
416
+            'plaintext'   => 'text/plain',
417
+
418
+            'm'           => 'text/markdown',
419
+            'md'          => 'text/markdown',
420
+            'markdown'    => 'text/markdown',
421
+
422
+            'b'           => 'text/x-bbcode',
423
+            'bbc'         => 'text/x-bbcode',
424
+            'bbcode'      => 'text/x-bbcode',
425
+
426
+            'bm'          => 'text/x-bbcode+markdown',
427
+            'bbm'         => 'text/x-bbcode+markdown',
428
+            'bbdown'      => 'text/x-bbcode+markdown',
429
+          }
430
+          v = cmd[1].downcase
431
+          status.content_type = content_types[c] unless content_types[c].nil?
404 432
         when 'visibility'
405 433
           chunk = nil
406 434
           next if cmd[1].nil?
@@ -421,7 +449,7 @@ class Bangtags
421 449
             'world'       => :public,
422 450
           }
423 451
           v = cmd[1].downcase
424
-          status.visibility = visibilities[v] if visibilities[v].nil?
452
+          status.visibility = visibilities[v] unless visibilities[v].nil?
425 453
         end
426 454
       end
427 455
 
@@ -472,17 +500,6 @@ class Bangtags
472 500
   def postprocess_before_save
473 501
     @post_cmds.each do |post_cmd|
474 502
       case post_cmd[0]
475
-      when 'signature'
476
-        name = @vars['_they:are']
477
-        next if name.blank?
478
-        description = @vars["_they:are:#{name}"]
479
-        next if description.blank? || @chunks.last(5).join.include?('—')
480
-        status.local_only = true if Status::LOCAL_ONLY_TOKENS.match?(@chunks.last)
481
-        if @chunks.first(5).any? { |c| c.strip.match?(/[\r\n]/) || c.lstrip.match?(/^(?:[>#]|```|---|\* |\d+\)|\[\wi+)/) }
482
-          @chunks << "\n\n[right]— #{description}\u200c[/right]"
483
-        else
484
-          @chunks << " [rfloat]— #{description}\u200c[/rfloat]"
485
-        end
486 503
       when 'media'
487 504
         media_idx = post_cmd[1]
488 505
         media_cmd = post_cmd[2]

+ 175
- 3
app/lib/formatter.rb View File

@@ -30,6 +30,141 @@ class Formatter
30 30
 
31 31
   include ActionView::Helpers::TextHelper
32 32
 
33
+	BBCODE_TAGS = {
34
+    :url => {
35
+			:html_open => '<a href="%url%" rel="noopener nofollow" target="_blank">', :html_close => '</a>',
36
+			:description => '', :example => '',
37
+			:allow_quick_param => true, :allow_between_as_param => false,
38
+			:quick_param_format => /(\S+)/,
39
+			:quick_param_format_description => 'The size parameter \'%param%\' is incorrect, a number is expected',
40
+			:param_tokens => [{:token => :url}]
41
+    },
42
+		:ul => {
43
+			:html_open => '<ul>', :html_close => '</ul>',
44
+			:description => '', :example => '',
45
+		},
46
+		:ol => {
47
+			:html_open => '<ol>', :html_close => '</ol>',
48
+			:description => '', :example => '',
49
+		},
50
+		:li => {
51
+			:html_open => '<li>', :html_close => '</li>',
52
+			:description => '', :example => '',
53
+		},
54
+		:sub => {
55
+			:html_open => '<sub>', :html_close => '</sub>',
56
+			:description => '', :example => '',
57
+		},
58
+		:sup => {
59
+			:html_open => '<sup>', :html_close => '</sup>',
60
+			:description => '', :example => '',
61
+		},
62
+		:h1 => {
63
+			:html_open => '<h1>', :html_close => '</h1>',
64
+			:description => '', :example => '',
65
+		},
66
+		:h2 => {
67
+			:html_open => '<h2>', :html_close => '</h2>',
68
+			:description => '', :example => '',
69
+		},
70
+		:h3 => {
71
+			:html_open => '<h3>', :html_close => '</h3>',
72
+			:description => '', :example => '',
73
+		},
74
+		:h4 => {
75
+			:html_open => '<h4>', :html_close => '</h4>',
76
+			:description => '', :example => '',
77
+		},
78
+		:h5 => {
79
+			:html_open => '<h5>', :html_close => '</h5>',
80
+			:description => '', :example => '',
81
+		},
82
+		:h6 => {
83
+			:html_open => '<h6>', :html_close => '</h6>',
84
+			:description => '', :example => '',
85
+		},
86
+		:abbr => {
87
+			:html_open => '<abbr>', :html_close => '</abbr>',
88
+			:description => '', :example => '',
89
+		},
90
+		:hr => {
91
+			:html_open => '<hr>', :html_close => '</hr>',
92
+			:description => '', :example => '',
93
+		},
94
+		:b => {
95
+			:html_open => '<strong>', :html_close => '</strong>',
96
+			:description => '', :example => '',
97
+		},
98
+		:i => {
99
+			:html_open => '<em>', :html_close => '</em>',
100
+			:description => '', :example => '',
101
+		},
102
+		:flip => {
103
+			:html_open => '<span class="bbcode__flip-%direction%">', :html_close => '</span>',
104
+			:description => '', :example => '',
105
+			:allow_quick_param => true, :allow_between_as_param => false,
106
+			:quick_param_format => /(h|v)/,
107
+			:quick_param_format_description => 'The size parameter \'%param%\' is incorrect, a number is expected',
108
+			:param_tokens => [{:token => :direction}]
109
+    },
110
+		:size => {
111
+			:html_open => '<span class="bbcode__size-%size%">', :html_close => '</span>',
112
+			:description => '', :example => '',
113
+			:allow_quick_param => true, :allow_between_as_param => false,
114
+			:quick_param_format => /([1-6])/,
115
+			:quick_param_format_description => 'The size parameter \'%param%\' is incorrect, a number is expected',
116
+			:param_tokens => [{:token => :size}]
117
+    },
118
+		:quote => {
119
+			:html_open => '<blockquote>', :html_close => '</blockquote>',
120
+			:description => '', :example => '',
121
+    },
122
+		:kbd => {
123
+			:html_open => '<pre><code>', :html_close => '</code></pre>',
124
+			:description => '', :example => '',
125
+    },
126
+		:code => {
127
+			:html_open => '<pre>', :html_close => '</pre>',
128
+			:description => '', :example => '',
129
+    },
130
+		:u => {
131
+			:html_open => '<u>', :html_close => '</u>',
132
+			:description => '', :example => '',
133
+    },
134
+		:s => {
135
+			:html_open => '<s>', :html_close => '</s>',
136
+			:description => '', :example => '',
137
+    },
138
+		:del => {
139
+			:html_open => '<del>', :html_close => '</del>',
140
+			:description => '', :example => '',
141
+    },
142
+		:left => {
143
+			:html_open => '<span class="bbcode__left">', :html_close => '</span>',
144
+			:description => '', :example => '',
145
+    },
146
+		:center => {
147
+			:html_open => '<span class="bbcode__center">', :html_close => '</span>',
148
+			:description => '', :example => '',
149
+    },
150
+		:right => {
151
+			:html_open => '<span class="bbcode__right">', :html_close => '</span>',
152
+			:description => '', :example => '',
153
+    },
154
+		:lfloat => {
155
+			:html_open => '<span class="bbcode__lfloat">', :html_close => '</span>',
156
+			:description => '', :example => '',
157
+    },
158
+		:rfloat => {
159
+			:html_open => '<span class="bbcode__rfloat">', :html_close => '</span>',
160
+			:description => '', :example => '',
161
+    },
162
+		:spoiler => {
163
+			:html_open => '<span class="bbcode__spoiler-wrapper"><span class="bbcode__spoiler">', :html_close => '</span></span>',
164
+			:description => '', :example => '',
165
+    },
166
+	}
167
+
33 168
   def format(status, **options)
34 169
     if status.reblog?
35 170
       prepend_reblog = status.reblog.account.acct
@@ -57,15 +192,26 @@ class Formatter
57 192
 
58 193
     html = raw_content
59 194
     html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
60
-    html = format_markdown(html) if status.content_type == 'text/markdown'
61
-    html = encode_and_link_urls(html, linkable_accounts, keep_html: %w(text/markdown text/html).include?(status.content_type))
195
+
196
+    case status.content_type
197
+    when 'text/markdown'
198
+      html = format_markdown(html)
199
+    when 'text/x-bbcode'
200
+      html = format_bbcode(html)
201
+    when 'text/x-bbcode+markdown'
202
+      html = format_bbdown(html)
203
+    end
204
+
205
+    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))
62 206
     html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
63 207
 
64
-    unless %w(text/markdown text/html).include?(status.content_type)
208
+    unless %w(text/markdown text/x-bbcode text/x-bbcode+markdown text/html).include?(status.content_type)
65 209
       html = simple_format(html, {}, sanitize: false)
66 210
       html = html.delete("\n")
67 211
     end
68 212
 
213
+    html = append_footer(html, status.footer)
214
+
69 215
     html.html_safe # rubocop:disable Rails/OutputSafety
70 216
   end
71 217
 
@@ -74,6 +220,19 @@ class Formatter
74 220
     html.delete("\r").delete("\n")
75 221
   end
76 222
 
223
+  def format_bbcode(html, sanitize = true)
224
+    html = bbcode_formatter(html)
225
+    html = html.gsub(/<hr>.*<\/hr>/im, '<hr />')
226
+    return html unless sanitize
227
+    html = reformat(html)
228
+    html.delete("\n")
229
+  end
230
+
231
+  def format_bbdown(html)
232
+    html = format_bbcode(html, false)
233
+    format_markdown(html)
234
+  end
235
+
77 236
   def reformat(html)
78 237
     sanitize(html, Sanitize::Config::MASTODON_STRICT)
79 238
   end
@@ -134,6 +293,19 @@ class Formatter
134 293
 
135 294
   private
136 295
 
296
+  def append_footer(html, footer)
297
+    return html if footer.blank?
298
+    "#{html.strip}<p class=\"signature\">— #{encode(footer)}</p>"
299
+  end
300
+
301
+  def bbcode_formatter(html)
302
+    begin
303
+      html = html.bbcode_to_html(false, BBCODE_TAGS, :enable, *BBCODE_TAGS.keys)
304
+    rescue Exception => e
305
+    end
306
+    html
307
+  end
308
+
137 309
   def markdown_formatter
138 310
     return @markdown_formatter if defined?(@markdown_formatter)
139 311
 

+ 4
- 1
app/lib/sanitize_config.rb View File

@@ -14,6 +14,8 @@ class Sanitize
14 14
         next true if e =~ /^(h|p|u|dt|e)-/ # microformats classes
15 15
         next true if e =~ /^(mention|hashtag)$/ # semantic classes
16 16
         next true if e =~ /^(ellipsis|invisible)$/ # link formatting classes
17
+        next true if e =~ /^bbcode__([a-z1-6\-]+)$/ # bbcode
18
+        next true if e == 'signature'
17 19
       end
18 20
 
19 21
       node['class'] = class_list.join(' ')
@@ -23,10 +25,11 @@ class Sanitize
23 25
       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),
24 26
 
25 27
       attributes: {
26
-        'a'          => %w(href rel class title),
28
+        'a'          => %w(href rel class title alt),
27 29
         'span'       => %w(class),
28 30
         'abbr'       => %w(title),
29 31
         'blockquote' => %w(cite),
32
+        'p'          => %w(class),
30 33
       },
31 34
 
32 35
       add_attributes: {

+ 3
- 2
app/models/status.rb View File

@@ -23,11 +23,12 @@
23 23
 #  in_reply_to_account_id :bigint(8)
24 24
 #  local_only             :boolean
25 25
 #  poll_id                :bigint(8)
26
-#  content_type           :string
27 26
 #  tsv                    :tsvector
28 27
 #  curated                :boolean          default(FALSE), not null
29 28
 #  sharekey               :string
30 29
 #  network                :boolean          default(FALSE), not null
30
+#  content_type           :string
31
+#  footer                 :text
31 32
 #
32 33
 
33 34
 class Status < ApplicationRecord
@@ -81,7 +82,7 @@ class Status < ApplicationRecord
81 82
   validates_with DisallowedHashtagsValidator
82 83
   validates :reblog, uniqueness: { scope: :account }, if: :reblog?
83 84
   validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
84
-  validates :content_type, inclusion: { in: %w(text/plain text/markdown text/html) }, allow_nil: true
85
+  validates :content_type, inclusion: { in: %w(text/plain text/markdown text/x-bbcode text/x-bbcode+markdown text/html) }, allow_nil: true
85 86
 
86 87
   accepts_nested_attributes_for :poll
87 88
 

+ 1
- 1
config/settings.yml View File

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

Loading…
Cancel
Save