Browse Source

Merge branches 'dependency_whatinput', 'feature_512_char_toots', 'feature_disable_reply_counts', 'feature_fixes', 'feature_hotlink_twitter_mentions' and 'feature_longer_bios' into cybrespace-3.0

+ 3
- 3
app/javascript/mastodon/features/compose/components/compose_form.js View File

@@ -88,7 +88,7 @@ class ComposeForm extends ImmutablePureComponent {
88 88
     const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
89 89
     const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
90 90
 
91
-    if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
91
+    if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 512 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
92 92
       return;
93 93
     }
94 94
 
@@ -181,7 +181,7 @@ class ComposeForm extends ImmutablePureComponent {
181 181
     const { intl, onPaste, showSearch, anyMedia } = this.props;
182 182
     const disabled = this.props.isSubmitting;
183 183
     const text     = [this.props.spoilerText, countableText(this.props.text)].join('');
184
-    const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
184
+    const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 512 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
185 185
     let publishText = '';
186 186
 
187 187
     if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
@@ -243,7 +243,7 @@ class ComposeForm extends ImmutablePureComponent {
243 243
             <PrivacyDropdownContainer />
244 244
             <SpoilerButtonContainer />
245 245
           </div>
246
-          <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
246
+          <div className='character-counter__wrapper'><CharacterCounter max={512} text={text} /></div>
247 247
         </div>
248 248
 
249 249
         <div className='compose-form__publish'>

+ 2
- 0
app/javascript/packs/application.js View File

@@ -8,3 +8,5 @@ loadPolyfills().then(() => {
8 8
 }).catch(e => {
9 9
   console.error(e);
10 10
 });
11
+
12
+require('what-input');

+ 41
- 0
app/javascript/packs/public.js View File

@@ -18,6 +18,12 @@ window.addEventListener('message', e => {
18 18
       id: data.id,
19 19
       height: document.getElementsByTagName('html')[0].scrollHeight,
20 20
     }, '*');
21
+
22
+    if (document.fonts && document.fonts.ready) {
23
+      document.fonts.ready.then(sizeBioText);
24
+    } else {
25
+      sizeBioText();
26
+    }
21 27
   });
22 28
 });
23 29
 
@@ -117,6 +123,25 @@ function main() {
117 123
 
118 124
     delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
119 125
     delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
126
+
127
+    if (document.body.classList.contains('with-modals')) {
128
+      const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
129
+      const scrollbarWidthStyle = document.createElement('style');
130
+      scrollbarWidthStyle.id = 'scrollbar-width';
131
+      document.head.appendChild(scrollbarWidthStyle);
132
+      scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
133
+    }
134
+
135
+    [].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => {
136
+      const props = JSON.parse(content.getAttribute('data-props'));
137
+      ReactDOM.render(<CardContainer locale={locale} {...props} />, content);
138
+    });
139
+
140
+    if (document.fonts && document.fonts.ready) {
141
+      document.fonts.ready.then(sizeBioText);
142
+    } else {
143
+      sizeBioText();
144
+    }
120 145
   });
121 146
 
122 147
   delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
@@ -257,6 +282,22 @@ function main() {
257 282
       target.style.display = 'block';
258 283
     }
259 284
   });
285
+
286
+  delegate(document, '#account_note', 'input', sizeBioText);
287
+
288
+  function sizeBioText() {
289
+    const noteCounter = document.querySelector('.note-counter');
290
+    const bioTextArea = document.querySelector('#account_note');
291
+
292
+    if (noteCounter) {
293
+      noteCounter.textContent = 413 - length(bioTextArea.value);
294
+    }
295
+
296
+    if (bioTextArea) {
297
+      bioTextArea.style.height = 'auto';
298
+      bioTextArea.style.height = (bioTextArea.scrollHeight+3) + 'px';
299
+    }
300
+  }
260 301
 }
261 302
 
262 303
 loadPolyfills().then(main).catch(error => {

+ 6
- 0
app/javascript/styles/mastodon/containers.scss View File

@@ -296,6 +296,12 @@
296 296
       min-height: 1px;
297 297
     }
298 298
 
299
+    h2 {
300
+      font-size: 1rem;
301
+      align-items: center;
302
+      display: flex;
303
+    }
304
+
299 305
     .nav-left {
300 306
       display: flex;
301 307
       align-items: stretch;

+ 6
- 1
app/lib/activitypub/activity.rb View File

@@ -90,7 +90,7 @@ class ActivityPub::Activity
90 90
     crawl_links(status)
91 91
 
92 92
     notify_about_reblog(status) if reblog_of_local_account?(status)
93
-    notify_about_mentions(status)
93
+    notify_about_mentions(status) unless spammy_mentions?(status)
94 94
 
95 95
     # Only continue if the status is supposed to have arrived in real-time.
96 96
     # Note that if @options[:override_timestamps] isn't set, the status
@@ -105,6 +105,11 @@ class ActivityPub::Activity
105 105
     status.reblog? && status.reblog.account.local?
106 106
   end
107 107
 
108
+  def spammy_mentions?(status)
109
+    status.has_non_mention_links? &&
110
+    @account.followers.local.count == 0
111
+  end
112
+
108 113
   def notify_about_reblog(status)
109 114
     NotifyService.new.call(status.reblog.account, status)
110 115
   end

+ 10
- 1
app/lib/formatter.rb View File

@@ -262,8 +262,9 @@ class Formatter
262 262
 
263 263
   def link_to_mention(entity, linkable_accounts)
264 264
     acct = entity[:screen_name]
265
+    username, domain = acct.split('@')
265 266
 
266
-    return link_to_account(acct) unless linkable_accounts
267
+    return link_to_account(acct) unless linkable_accounts and domain != "twitter.com"
267 268
 
268 269
     account = linkable_accounts.find { |item| TagManager.instance.same_acct?(item.acct, acct) }
269 270
     account ? mention_html(account) : "@#{encode(acct)}"
@@ -272,6 +273,10 @@ class Formatter
272 273
   def link_to_account(acct)
273 274
     username, domain = acct.split('@')
274 275
 
276
+    if domain == "twitter.com"
277
+      return mention_twitter_html(username)
278
+    end
279
+
275 280
     domain  = nil if TagManager.instance.local_domain?(domain)
276 281
     account = EntityCache.instance.mention(username, domain)
277 282
 
@@ -299,4 +304,8 @@ class Formatter
299 304
   def mention_html(account)
300 305
     "<span class=\"h-card\"><a href=\"#{encode(ActivityPub::TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(account.username)}</span></a></span>"
301 306
   end
307
+
308
+  def mention_twitter_html(username)
309
+      "<span class=\"h-card\"><a href=\"https://twitter.com/#{username}\" class=\"u-url mention\">@<span>#{username}@twitter.com</span></a></span>"
310
+  end
302 311
 end

+ 7
- 3
app/models/account.rb View File

@@ -81,6 +81,7 @@ class Account < ApplicationRecord
81 81
   validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
82 82
   validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
83 83
   validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? }
84
+  validate :note_has_eight_newlines?, if: -> { local? && will_save_change_to_note? }
84 85
   validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
85 86
 
86 87
   scope :remote, -> { where.not(domain: nil) }
@@ -310,9 +311,8 @@ class Account < ApplicationRecord
310 311
   def save_with_optional_media!
311 312
     save!
312 313
   rescue ActiveRecord::RecordInvalid
313
-    self.avatar = nil
314
-    self.header = nil
315
-
314
+    self.avatar = nil if errors[:avatar].present?
315
+    self.header = nil if errors[:header].present?
316 316
     save!
317 317
   end
318 318
 
@@ -336,6 +336,10 @@ class Account < ApplicationRecord
336 336
     shared_inbox_url.presence || inbox_url
337 337
   end
338 338
 
339
+  def note_has_eight_newlines?
340
+    errors.add(:note, 'Bio can\'t have more then 8 newlines') unless note.count("\n") <= 8
341
+  end
342
+
339 343
   class Field < ActiveModelSerializers::Model
340 344
     attributes :name, :value, :verified_at, :account, :errors
341 345
 

+ 8
- 0
app/models/status.rb View File

@@ -398,6 +398,14 @@ class Status < ApplicationRecord
398 398
     super || build_status_stat
399 399
   end
400 400
 
401
+  def has_non_mention_links?
402
+    if local?
403
+      text.match? %r{https?://\w}
404
+    else
405
+      Nokogiri::HTML.fragment(text).css('a:not(.mention)').present?
406
+    end
407
+  end
408
+
401 409
   private
402 410
 
403 411
   def update_status_stat!(attrs)

+ 4
- 0
app/serializers/rest/instance_serializer.rb View File

@@ -39,6 +39,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
39 39
     instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('media/images/preview.jpg')
40 40
   end
41 41
 
42
+  def max_toot_chars
43
+    StatusLengthValidator::MAX_CHARS
44
+  end
45
+
42 46
   def stats
43 47
     {
44 48
       user_count: instance_presenter.user_count,

+ 1
- 1
app/validators/status_length_validator.rb View File

@@ -1,7 +1,7 @@
1 1
 # frozen_string_literal: true
2 2
 
3 3
 class StatusLengthValidator < ActiveModel::Validator
4
-  MAX_CHARS = 500
4
+  MAX_CHARS = 512
5 5
 
6 6
   def validate(status)
7 7
     return unless status.local? && !status.reblog?

+ 5
- 0
app/views/accounts/show.html.haml View File

@@ -21,6 +21,11 @@
21 21
 
22 22
 = render 'header', account: @account, with_bio: true
23 23
 
24
+- if @account.user&.disabled?
25
+  .header
26
+    .nav-center
27
+      %h2 This user's account has been locked by a moderator.
28
+
24 29
 .grid
25 30
   .column-0
26 31
     .h-feed

+ 0
- 1
app/views/statuses/_simple_status.html.haml View File

@@ -52,7 +52,6 @@
52 52
           = fa_icon 'reply fw'
53 53
         - else
54 54
           = fa_icon 'reply-all fw'
55
-      .status__action-bar__counter__label= obscured_counter status.replies_count
56 55
     = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
57 56
       - if status.distributable?
58 57
         = fa_icon 'retweet fw'

+ 1
- 1
lib/mastodon/redis_config.rb View File

@@ -22,7 +22,7 @@ setup_redis_env_url
22 22
 setup_redis_env_url(:cache, false)
23 23
 
24 24
 namespace       = ENV.fetch('REDIS_NAMESPACE') { nil }
25
-cache_namespace = namespace ? namespace + '_cache' : 'cache'
25
+cache_namespace = [namespace, 'cache', `git rev-parse --short HEAD`.strip].compact.join('_')
26 26
 
27 27
 REDIS_CACHE_PARAMS = {
28 28
   expires_in: 10.minutes,

+ 2
- 1
package.json View File

@@ -167,7 +167,8 @@
167 167
     "webpack-bundle-analyzer": "^3.3.2",
168 168
     "webpack-cli": "^3.3.7",
169 169
     "webpack-merge": "^4.2.1",
170
-    "websocket.js": "^0.1.12"
170
+    "websocket.js": "^0.1.12",
171
+    "what-input": "^4.3.1"
171 172
   },
172 173
   "devDependencies": {
173 174
     "babel-eslint": "^10.0.3",

+ 4
- 0
yarn.lock View File

@@ -10793,6 +10793,10 @@ websocket.js@^0.1.12:
10793 10793
   dependencies:
10794 10794
     backoff "^2.4.1"
10795 10795
 
10796
+what-input@^4.3.1:
10797
+  version "4.3.1"
10798
+  resolved "https://registry.yarnpkg.com/what-input/-/what-input-4.3.1.tgz#b8ea7554ba1d9171887c4c6addf28185fec3d31d"
10799
+
10796 10800
 whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
10797 10801
   version "1.0.5"
10798 10802
   resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"

Loading…
Cancel
Save