Skip to content

Commit

Permalink
Change max status characters to be configurable (mastodon#12265)
Browse files Browse the repository at this point in the history
  • Loading branch information
dwrss committed Jul 7, 2023
1 parent a4fb7d7 commit 7a3d157
Show file tree
Hide file tree
Showing 11 changed files with 61 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class ComposeForm extends ImmutablePureComponent {
isInReply: PropTypes.bool,
singleColumn: PropTypes.bool,
lang: PropTypes.string,
maxStatusChars: PropTypes.number.isRequired,
};

static defaultProps = {
Expand All @@ -90,7 +91,7 @@ class ComposeForm extends ImmutablePureComponent {
const fulltext = this.getFulltextForCharacterCounting();
const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;

return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia));
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > this.props.maxStatusChars || (isOnlyWhitespace && !anyMedia));
};

handleSubmit = (e) => {
Expand Down Expand Up @@ -280,7 +281,7 @@ class ComposeForm extends ImmutablePureComponent {
</div>

<div className='character-counter__wrapper'>
<CharacterCounter max={500} text={this.getFulltextForCharacterCounting()} />
<CharacterCounter max={this.props.maxStatusChars} text={this.getFulltextForCharacterCounting()} />
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const mapStateToProps = state => ({
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
lang: state.getIn(['compose', 'language']),
maxStatusChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters']),
});

const mapDispatchToProps = (dispatch) => ({
Expand Down
5 changes: 5 additions & 0 deletions app/javascript/mastodon/reducers/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
SERVER_DOMAIN_BLOCKS_FETCH_FAIL,
} from 'mastodon/actions/server';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
import { STORE_HYDRATE } from '../actions/store';

const initialState = ImmutableMap({
server: ImmutableMap({
Expand All @@ -27,8 +28,12 @@ const initialState = ImmutableMap({
}),
});

const hydrate = (state, server) => state.mergeDeep(server);

export default function server(state = initialState, action) {
switch (action.type) {
case STORE_HYDRATE:
return hydrate(state, action.state.get('server'));
case SERVER_FETCH_REQUEST:
return state.setIn(['server', 'isLoading'], true);
case SERVER_FETCH_SUCCESS:
Expand Down
31 changes: 23 additions & 8 deletions app/models/form/admin_settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

class Form::AdminSettings
include ActiveModel::Model
include ActiveModel::Callbacks

KEYS = %i(
site_contact_username
Expand Down Expand Up @@ -33,12 +34,14 @@ class Form::AdminSettings
content_cache_retention_period
backups_retention_period
status_page_url
status_max_chars
).freeze

INTEGER_KEYS = %i(
media_cache_retention_period
content_cache_retention_period
backups_retention_period
status_max_chars
).freeze

BOOLEAN_KEYS = %i(
Expand Down Expand Up @@ -67,11 +70,21 @@ class Form::AdminSettings
validates :bootstrap_timeline_accounts, existing_username: { multiple: true }, if: -> { defined?(@bootstrap_timeline_accounts) }
validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) }
validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) }
validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) }
validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period,
:status_max_chars, numericality: { only_integer: true }, allow_blank: true,
if: -> {
defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) ||
defined?(@status_max_chars)
}
validates :site_short_description, length: { maximum: 200 }, if: -> { defined?(@site_short_description) }
validates :status_page_url, url: true, allow_blank: true
validate :validate_site_uploads

define_model_callbacks :save
before_save do
@status_max_chars = StatusLengthValidator::DEFAULT_MAX_CHARS if @status_max_chars.blank?
end

KEYS.each do |key|
define_method(key) do
return instance_variable_get("@#{key}") if instance_variable_defined?("@#{key}")
Expand Down Expand Up @@ -103,14 +116,16 @@ def save
# So for now, return early if errors aren't empty.
return false unless errors.empty? && valid?

KEYS.each do |key|
next unless instance_variable_defined?("@#{key}")
run_callbacks(:save) do
KEYS.each do |key|
next unless instance_variable_defined?("@#{key}")

if UPLOAD_KEYS.include?(key)
public_send(key).save
else
setting = Setting.where(var: key).first_or_initialize(var: key)
setting.update(value: typecast_value(key, instance_variable_get("@#{key}")))
if UPLOAD_KEYS.include?(key)
public_send(key).save
else
setting = Setting.where(var: key).first_or_initialize(var: key)
setting.update(value: typecast_value(key, instance_variable_get("@#{key}")))
end
end
end
end
Expand Down
15 changes: 14 additions & 1 deletion app/serializers/initial_state_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class InitialStateSerializer < ActiveModel::Serializer

attributes :meta, :compose, :accounts,
:media_attachments, :settings,
:languages
:languages, :server

has_one :push_subscription, serializer: REST::WebPushSubscriptionSerializer
has_one :role, serializer: REST::RoleSerializer
Expand Down Expand Up @@ -107,6 +107,19 @@ def languages
LanguagesHelper::SUPPORTED_LOCALES.map { |(key, value)| [key, value[0], value[1]] }
end

def server
{
server: {
configuration:
{
statuses: {
max_characters: StatusLengthValidator.max_chars,
},
},
},
}
end

private

def instance_presenter
Expand Down
2 changes: 1 addition & 1 deletion app/serializers/rest/instance_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def configuration
},

statuses: {
max_characters: StatusLengthValidator::MAX_CHARS,
max_characters: StatusLengthValidator.max_chars,
max_media_attachments: 4,
characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
},
Expand Down
2 changes: 1 addition & 1 deletion app/serializers/rest/v1/instance_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def configuration
},

statuses: {
max_characters: StatusLengthValidator::MAX_CHARS,
max_characters: StatusLengthValidator.max_chars,
max_media_attachments: 4,
characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
},
Expand Down
10 changes: 7 additions & 3 deletions app/validators/status_length_validator.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
# frozen_string_literal: true

class StatusLengthValidator < ActiveModel::Validator
MAX_CHARS = 500
DEFAULT_MAX_CHARS = 500
URL_PLACEHOLDER_CHARS = 23
URL_PLACEHOLDER = 'x' * 23

def self.max_chars
Setting.status_max_chars || DEFAULT_MAX_CHARS
end

def validate(status)
return unless status.local? && !status.reblog?

status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?(status)
status.errors.add(:text, I18n.t('statuses.over_character_limit', max: StatusLengthValidator.max_chars)) if too_long?(status)
end

private

def too_long?(status)
countable_length(combined_text(status)) > MAX_CHARS
countable_length(combined_text(status)) > StatusLengthValidator.max_chars
end

def countable_length(str)
Expand Down
3 changes: 3 additions & 0 deletions app/views/admin/settings/appearance/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@
= fa_icon 'trash fw'
= t('admin.site_uploads.delete')

.fields-group
= f.input :status_max_chars, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }

.actions
= f.button :button, t('generic.save_changes'), type: :submit
2 changes: 2 additions & 0 deletions config/locales/simple_form.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ en:
site_terms: Use your own privacy policy or leave blank to use the default. Can be structured with Markdown syntax.
site_title: How people may refer to your server besides its domain name.
status_page_url: URL of a page where people can see the status of this server during an outage
status_max_chars: Maximum number of characters allowed in a status. Leave blank to reset to the default.
theme: Theme that logged out visitors and new users see.
thumbnail: A roughly 2:1 image displayed alongside your server information.
timeline_preview: Logged out visitors will be able to browse the most recent public posts available on the server.
Expand Down Expand Up @@ -254,6 +255,7 @@ en:
site_terms: Privacy Policy
site_title: Server name
status_page_url: Status page URL
status_max_chars: Max status characters
theme: Default theme
thumbnail: Server thumbnail
timeline_preview: Allow unauthenticated access to public timelines
Expand Down
1 change: 1 addition & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ defaults: &defaults
show_domain_blocks_rationale: 'disabled'
require_invite_text: false
backups_retention_period: 7
status_max_chars: 500

development:
<<: *defaults
Expand Down

0 comments on commit 7a3d157

Please sign in to comment.