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
This commit is contained in:
commit
e9e6a26136
|
@ -88,7 +88,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
|
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
|
||||||
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
|
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||||
|
|
||||||
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
|
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 512 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
const { intl, onPaste, showSearch, anyMedia } = this.props;
|
const { intl, onPaste, showSearch, anyMedia } = this.props;
|
||||||
const disabled = this.props.isSubmitting;
|
const disabled = this.props.isSubmitting;
|
||||||
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||||
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
|
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 512 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
|
||||||
let publishText = '';
|
let publishText = '';
|
||||||
|
|
||||||
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||||
|
@ -243,7 +243,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<PrivacyDropdownContainer />
|
<PrivacyDropdownContainer />
|
||||||
<SpoilerButtonContainer />
|
<SpoilerButtonContainer />
|
||||||
</div>
|
</div>
|
||||||
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
|
<div className='character-counter__wrapper'><CharacterCounter max={512} text={text} /></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='compose-form__publish'>
|
<div className='compose-form__publish'>
|
||||||
|
|
|
@ -8,3 +8,5 @@ loadPolyfills().then(() => {
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
require('what-input');
|
||||||
|
|
|
@ -18,6 +18,12 @@ window.addEventListener('message', e => {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
height: document.getElementsByTagName('html')[0].scrollHeight,
|
height: document.getElementsByTagName('html')[0].scrollHeight,
|
||||||
}, '*');
|
}, '*');
|
||||||
|
|
||||||
|
if (document.fonts && document.fonts.ready) {
|
||||||
|
document.fonts.ready.then(sizeBioText);
|
||||||
|
} else {
|
||||||
|
sizeBioText();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -117,6 +123,25 @@ function main() {
|
||||||
|
|
||||||
delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
|
delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
|
||||||
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
|
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
|
||||||
|
|
||||||
|
if (document.body.classList.contains('with-modals')) {
|
||||||
|
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||||
|
const scrollbarWidthStyle = document.createElement('style');
|
||||||
|
scrollbarWidthStyle.id = 'scrollbar-width';
|
||||||
|
document.head.appendChild(scrollbarWidthStyle);
|
||||||
|
scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => {
|
||||||
|
const props = JSON.parse(content.getAttribute('data-props'));
|
||||||
|
ReactDOM.render(<CardContainer locale={locale} {...props} />, content);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (document.fonts && document.fonts.ready) {
|
||||||
|
document.fonts.ready.then(sizeBioText);
|
||||||
|
} else {
|
||||||
|
sizeBioText();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
|
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
|
||||||
|
@ -257,6 +282,22 @@ function main() {
|
||||||
target.style.display = 'block';
|
target.style.display = 'block';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
delegate(document, '#account_note', 'input', sizeBioText);
|
||||||
|
|
||||||
|
function sizeBioText() {
|
||||||
|
const noteCounter = document.querySelector('.note-counter');
|
||||||
|
const bioTextArea = document.querySelector('#account_note');
|
||||||
|
|
||||||
|
if (noteCounter) {
|
||||||
|
noteCounter.textContent = 413 - length(bioTextArea.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bioTextArea) {
|
||||||
|
bioTextArea.style.height = 'auto';
|
||||||
|
bioTextArea.style.height = (bioTextArea.scrollHeight+3) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPolyfills().then(main).catch(error => {
|
loadPolyfills().then(main).catch(error => {
|
||||||
|
|
|
@ -296,6 +296,12 @@
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-left {
|
.nav-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
|
@ -90,7 +90,7 @@ class ActivityPub::Activity
|
||||||
crawl_links(status)
|
crawl_links(status)
|
||||||
|
|
||||||
notify_about_reblog(status) if reblog_of_local_account?(status)
|
notify_about_reblog(status) if reblog_of_local_account?(status)
|
||||||
notify_about_mentions(status)
|
notify_about_mentions(status) unless spammy_mentions?(status)
|
||||||
|
|
||||||
# Only continue if the status is supposed to have arrived in real-time.
|
# Only continue if the status is supposed to have arrived in real-time.
|
||||||
# Note that if @options[:override_timestamps] isn't set, the status
|
# Note that if @options[:override_timestamps] isn't set, the status
|
||||||
|
@ -105,6 +105,11 @@ class ActivityPub::Activity
|
||||||
status.reblog? && status.reblog.account.local?
|
status.reblog? && status.reblog.account.local?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def spammy_mentions?(status)
|
||||||
|
status.has_non_mention_links? &&
|
||||||
|
@account.followers.local.count == 0
|
||||||
|
end
|
||||||
|
|
||||||
def notify_about_reblog(status)
|
def notify_about_reblog(status)
|
||||||
NotifyService.new.call(status.reblog.account, status)
|
NotifyService.new.call(status.reblog.account, status)
|
||||||
end
|
end
|
||||||
|
|
|
@ -262,8 +262,9 @@ class Formatter
|
||||||
|
|
||||||
def link_to_mention(entity, linkable_accounts)
|
def link_to_mention(entity, linkable_accounts)
|
||||||
acct = entity[:screen_name]
|
acct = entity[:screen_name]
|
||||||
|
username, domain = acct.split('@')
|
||||||
|
|
||||||
return link_to_account(acct) unless linkable_accounts
|
return link_to_account(acct) unless linkable_accounts and domain != "twitter.com"
|
||||||
|
|
||||||
account = linkable_accounts.find { |item| TagManager.instance.same_acct?(item.acct, acct) }
|
account = linkable_accounts.find { |item| TagManager.instance.same_acct?(item.acct, acct) }
|
||||||
account ? mention_html(account) : "@#{encode(acct)}"
|
account ? mention_html(account) : "@#{encode(acct)}"
|
||||||
|
@ -272,6 +273,10 @@ class Formatter
|
||||||
def link_to_account(acct)
|
def link_to_account(acct)
|
||||||
username, domain = acct.split('@')
|
username, domain = acct.split('@')
|
||||||
|
|
||||||
|
if domain == "twitter.com"
|
||||||
|
return mention_twitter_html(username)
|
||||||
|
end
|
||||||
|
|
||||||
domain = nil if TagManager.instance.local_domain?(domain)
|
domain = nil if TagManager.instance.local_domain?(domain)
|
||||||
account = EntityCache.instance.mention(username, domain)
|
account = EntityCache.instance.mention(username, domain)
|
||||||
|
|
||||||
|
@ -299,4 +304,8 @@ class Formatter
|
||||||
def mention_html(account)
|
def mention_html(account)
|
||||||
"<span class=\"h-card\"><a href=\"#{encode(ActivityPub::TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(account.username)}</span></a></span>"
|
"<span class=\"h-card\"><a href=\"#{encode(ActivityPub::TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(account.username)}</span></a></span>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mention_twitter_html(username)
|
||||||
|
"<span class=\"h-card\"><a href=\"https://twitter.com/#{username}\" class=\"u-url mention\">@<span>#{username}@twitter.com</span></a></span>"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -81,6 +81,7 @@ class Account < ApplicationRecord
|
||||||
validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
|
validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
|
||||||
validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
|
validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
|
||||||
validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? }
|
validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? }
|
||||||
|
validate :note_has_eight_newlines?, if: -> { local? && will_save_change_to_note? }
|
||||||
validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
|
validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
|
||||||
|
|
||||||
scope :remote, -> { where.not(domain: nil) }
|
scope :remote, -> { where.not(domain: nil) }
|
||||||
|
@ -310,9 +311,8 @@ class Account < ApplicationRecord
|
||||||
def save_with_optional_media!
|
def save_with_optional_media!
|
||||||
save!
|
save!
|
||||||
rescue ActiveRecord::RecordInvalid
|
rescue ActiveRecord::RecordInvalid
|
||||||
self.avatar = nil
|
self.avatar = nil if errors[:avatar].present?
|
||||||
self.header = nil
|
self.header = nil if errors[:header].present?
|
||||||
|
|
||||||
save!
|
save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -336,6 +336,10 @@ class Account < ApplicationRecord
|
||||||
shared_inbox_url.presence || inbox_url
|
shared_inbox_url.presence || inbox_url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def note_has_eight_newlines?
|
||||||
|
errors.add(:note, 'Bio can\'t have more then 8 newlines') unless note.count("\n") <= 8
|
||||||
|
end
|
||||||
|
|
||||||
class Field < ActiveModelSerializers::Model
|
class Field < ActiveModelSerializers::Model
|
||||||
attributes :name, :value, :verified_at, :account, :errors
|
attributes :name, :value, :verified_at, :account, :errors
|
||||||
|
|
||||||
|
|
|
@ -398,6 +398,14 @@ class Status < ApplicationRecord
|
||||||
super || build_status_stat
|
super || build_status_stat
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_non_mention_links?
|
||||||
|
if local?
|
||||||
|
text.match? %r{https?://\w}
|
||||||
|
else
|
||||||
|
Nokogiri::HTML.fragment(text).css('a:not(.mention)').present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def update_status_stat!(attrs)
|
def update_status_stat!(attrs)
|
||||||
|
|
|
@ -39,6 +39,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
||||||
instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('media/images/preview.jpg')
|
instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('media/images/preview.jpg')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def max_toot_chars
|
||||||
|
StatusLengthValidator::MAX_CHARS
|
||||||
|
end
|
||||||
|
|
||||||
def stats
|
def stats
|
||||||
{
|
{
|
||||||
user_count: instance_presenter.user_count,
|
user_count: instance_presenter.user_count,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class StatusLengthValidator < ActiveModel::Validator
|
class StatusLengthValidator < ActiveModel::Validator
|
||||||
MAX_CHARS = 500
|
MAX_CHARS = 512
|
||||||
|
|
||||||
def validate(status)
|
def validate(status)
|
||||||
return unless status.local? && !status.reblog?
|
return unless status.local? && !status.reblog?
|
||||||
|
|
|
@ -21,6 +21,11 @@
|
||||||
|
|
||||||
= render 'header', account: @account, with_bio: true
|
= render 'header', account: @account, with_bio: true
|
||||||
|
|
||||||
|
- if @account.user&.disabled?
|
||||||
|
.header
|
||||||
|
.nav-center
|
||||||
|
%h2 This user's account has been locked by a moderator.
|
||||||
|
|
||||||
.grid
|
.grid
|
||||||
.column-0
|
.column-0
|
||||||
.h-feed
|
.h-feed
|
||||||
|
|
|
@ -52,7 +52,6 @@
|
||||||
= fa_icon 'reply fw'
|
= fa_icon 'reply fw'
|
||||||
- else
|
- else
|
||||||
= fa_icon 'reply-all fw'
|
= fa_icon 'reply-all fw'
|
||||||
.status__action-bar__counter__label= obscured_counter status.replies_count
|
|
||||||
= 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
|
= 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
|
||||||
- if status.distributable?
|
- if status.distributable?
|
||||||
= fa_icon 'retweet fw'
|
= fa_icon 'retweet fw'
|
||||||
|
|
|
@ -22,7 +22,7 @@ setup_redis_env_url
|
||||||
setup_redis_env_url(:cache, false)
|
setup_redis_env_url(:cache, false)
|
||||||
|
|
||||||
namespace = ENV.fetch('REDIS_NAMESPACE') { nil }
|
namespace = ENV.fetch('REDIS_NAMESPACE') { nil }
|
||||||
cache_namespace = namespace ? namespace + '_cache' : 'cache'
|
cache_namespace = [namespace, 'cache', `git rev-parse --short HEAD`.strip].compact.join('_')
|
||||||
|
|
||||||
REDIS_CACHE_PARAMS = {
|
REDIS_CACHE_PARAMS = {
|
||||||
expires_in: 10.minutes,
|
expires_in: 10.minutes,
|
||||||
|
|
|
@ -167,7 +167,8 @@
|
||||||
"webpack-bundle-analyzer": "^3.3.2",
|
"webpack-bundle-analyzer": "^3.3.2",
|
||||||
"webpack-cli": "^3.3.7",
|
"webpack-cli": "^3.3.7",
|
||||||
"webpack-merge": "^4.2.1",
|
"webpack-merge": "^4.2.1",
|
||||||
"websocket.js": "^0.1.12"
|
"websocket.js": "^0.1.12",
|
||||||
|
"what-input": "^4.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
|
|
|
@ -10793,6 +10793,10 @@ websocket.js@^0.1.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
backoff "^2.4.1"
|
backoff "^2.4.1"
|
||||||
|
|
||||||
|
what-input@^4.3.1:
|
||||||
|
version "4.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/what-input/-/what-input-4.3.1.tgz#b8ea7554ba1d9171887c4c6addf28185fec3d31d"
|
||||||
|
|
||||||
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
|
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
|
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
|
||||||
|
|
Loading…
Reference in New Issue