From 5834fc5aa18b12ed15441e2937881d5149839575 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 26 Oct 2017 23:05:07 -0700 Subject: [PATCH 1/7] Feature: 512-character limit in the toot box on client and serverside --- .../mastodon/features/compose/components/compose_form.js | 6 +++--- app/validators/status_length_validator.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 0625ab223..acb654329 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -84,7 +84,7 @@ class ComposeForm extends ImmutablePureComponent { const { is_submitting, is_uploading, anyMedia } = this.props; const fulltext = [this.props.spoiler_text, countableText(this.props.text)].join(''); - if (is_submitting || is_uploading || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { + if (is_submitting || is_uploading || length(fulltext) > 512 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { return; } @@ -160,7 +160,7 @@ class ComposeForm extends ImmutablePureComponent { const { intl, onPaste, showSearch, anyMedia } = this.props; const disabled = this.props.is_submitting; const text = [this.props.spoiler_text, countableText(this.props.text)].join(''); - const disabledButton = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia); + const disabledButton = disabled || this.props.is_uploading || length(text) > 512 || (text.length !== 0 && text.trim().length === 0 && !anyMedia); let publishText = ''; if (this.props.privacy === 'private' || this.props.privacy === 'direct') { @@ -212,7 +212,7 @@ class ComposeForm extends ImmutablePureComponent { -
+
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb index ed5563f64..4d51fd3e1 100644 --- a/app/validators/status_length_validator.rb +++ b/app/validators/status_length_validator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class StatusLengthValidator < ActiveModel::Validator - MAX_CHARS = 500 + MAX_CHARS = 512 def validate(status) return unless status.local? && !status.reblog? From bf7b956a9996a2f387d048cbe5b32b051d9fb9c0 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 14 Sep 2018 15:13:02 -0700 Subject: [PATCH 2/7] Remove reply counts on non-expanded view --- app/javascript/mastodon/components/status_action_bar.js | 2 +- app/views/stream_entries/_simple_status.html.haml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index e7e5b0a6c..33586c24a 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -204,7 +204,7 @@ class StatusActionBar extends ImmutablePureComponent { return (
-
{obfuscatedCount(status.get('replies_count'))}
+
{shareButton} diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml index 1a1dc37eb..817d9695f 100644 --- a/app/views/stream_entries/_simple_status.html.haml +++ b/app/views/stream_entries/_simple_status.html.haml @@ -37,7 +37,6 @@ .status__action-bar__counter = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do = fa_icon 'reply fw' - .status__action-bar__counter__label= obscured_counter status.replies_count = link_to remote_interaction_path(status), 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.public_visibility? || status.unlisted_visibility? = fa_icon 'retweet fw' From df0855a91b596f3bd2d4d4c9bced7a30b201ed1b Mon Sep 17 00:00:00 2001 From: chr Date: Mon, 12 Nov 2018 20:30:47 -0800 Subject: [PATCH 3/7] Bio length -> 413 characters Increase the cybre.space profile bio text length limit to 413 characters. Also modifies the account settings page to automatically resize the textbox to the size of the contained text, so that it's easier to type longer bios. --- app/javascript/packs/public.js | 35 +++++++++++++++++++++- app/models/account.rb | 13 ++++---- app/views/settings/profiles/show.html.haml | 2 +- spec/models/account_spec.rb | 8 ++--- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 3b02b7c39..2aca2d160 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -1,4 +1,4 @@ -import loadPolyfills from '../mastodon/load_polyfills'; + import loadPolyfills from '../mastodon/load_polyfills'; import ready from '../mastodon/ready'; import { start } from '../mastodon/common'; @@ -17,6 +17,12 @@ window.addEventListener('message', e => { id: data.id, height: document.getElementsByTagName('html')[0].scrollHeight, }, '*'); + + if (document.fonts && document.fonts.ready) { + document.fonts.ready.then(sizeBioText); + } else { + sizeBioText(); + } }); }); @@ -93,6 +99,17 @@ function main() { detailedStatuses[0].scrollIntoView(); history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true }); } + + [].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => { + const props = JSON.parse(content.getAttribute('data-props')); + ReactDOM.render(, content); + }); + + if (document.fonts && document.fonts.ready) { + document.fonts.ready.then(sizeBioText); + } else { + sizeBioText(); + } }); delegate(document, '.webapp-btn', 'click', ({ target, button }) => { @@ -188,6 +205,22 @@ function main() { console.error(err); } }); + + 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 => { diff --git a/app/models/account.rb b/app/models/account.rb index 44963f3e6..7afc33014 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -75,7 +75,8 @@ class Account < ApplicationRecord validates_with UniqueUsernameValidator, 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 :note, length: { maximum: 160 }, if: -> { local? && will_save_change_to_note? } + validates :note, length: { maximum: 413 }, 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? } # Timelines @@ -279,10 +280,8 @@ class Account < ApplicationRecord def save_with_optional_media! save! rescue ActiveRecord::RecordInvalid - self.avatar = nil - self.header = nil - self[:avatar_remote_url] = '' - self[:header_remote_url] = '' + self.avatar = nil if errors[:avatar].present? + self.header = nil if errors[:header].present? save! end @@ -306,6 +305,10 @@ class Account < ApplicationRecord shared_inbox_url.presence || inbox_url 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 attributes :name, :value, :verified_at, :account, :errors diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index f5c50144b..1b2dee19e 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -7,7 +7,7 @@ .fields-row .fields-row__column.fields-group.fields-row__column-6 = f.input :display_name, wrapper: :with_label, input_html: { maxlength: 30 }, hint: false - = f.input :note, wrapper: :with_label, input_html: { maxlength: 160 }, hint: false + = f.input :note, wrapper: :with_label, input_html: { maxlength: 413 }, hint: false .fields-row .fields-row__column.fields-row__column-6 diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 60d13d32e..18bcd3e0b 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -596,8 +596,8 @@ RSpec.describe Account, type: :model do expect(account).to model_have_error_on_field(:display_name) end - it 'is invalid if the note is longer than 160 characters' do - account = Fabricate.build(:account, note: Faker::Lorem.characters(161)) + it 'is invalid if the note is longer than 413 characters' do + account = Fabricate.build(:account, note: Faker::Lorem.characters(414)) account.valid? expect(account).to model_have_error_on_field(:note) end @@ -636,8 +636,8 @@ RSpec.describe Account, type: :model do expect(account).not_to model_have_error_on_field(:display_name) end - it 'is valid even if the note is longer than 160 characters' do - account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(161)) + it 'is valid even if the note is longer than 413 characters' do + account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(414)) account.valid? expect(account).not_to model_have_error_on_field(:note) end From 99b3c18b28c63031a5c475e0fa419e967fc97690 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 17 Dec 2017 22:33:03 -0800 Subject: [PATCH 4/7] Hotlink twitter mentions Differentiate twitter mentions from normal mentions --- app/lib/formatter.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index d13884ec8..b93269e31 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -212,8 +212,9 @@ class Formatter def link_to_mention(entity, linkable_accounts) 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 ? mention_html(account) : "@#{encode(acct)}" @@ -222,6 +223,10 @@ class Formatter def link_to_account(acct) username, domain = acct.split('@') + if domain == "twitter.com" + return mention_twitter_html(username) + end + domain = nil if TagManager.instance.local_domain?(domain) account = EntityCache.instance.mention(username, domain) @@ -249,4 +254,8 @@ class Formatter def mention_html(account) "@#{encode(account.username)}" end + + def mention_twitter_html(username) + "@#{username}@twitter.com" + end end From ebed61cf8868fc73a1f5c8023c3982ca1ea872dc Mon Sep 17 00:00:00 2001 From: chr Date: Mon, 12 Nov 2018 21:48:32 -0800 Subject: [PATCH 5/7] Doodlebox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A modification to the Mastodon interface that allows you to select a doodle icon in addition to uploading a media file from your computer. The doodle interface allows you to draw using your mouse, finger, or pointing device in several colors. It was originally developed by Ondřej Hruška for use on the GlitchSoc fork. --- app/javascript/mastodon/actions/compose.js | 9 + .../mastodon/components/icon_button.js | 12 +- .../compose/components/attach_options.js | 133 ++++ .../compose/components/compose_dropdown.js | 76 +++ .../compose/components/compose_form.js | 7 +- .../features/ui/components/doodle_modal.js | 614 ++++++++++++++++++ .../features/ui/components/modal_root.js | 14 +- app/javascript/mastodon/reducers/compose.js | 14 + app/javascript/styles/doodle.scss | 96 +++ .../styles/mastodon/components.scss | 74 ++- package.json | 1 + yarn.lock | 4 + 12 files changed, 1046 insertions(+), 8 deletions(-) create mode 100644 app/javascript/mastodon/features/compose/components/attach_options.js create mode 100644 app/javascript/mastodon/features/compose/components/compose_dropdown.js create mode 100644 app/javascript/mastodon/features/ui/components/doodle_modal.js create mode 100644 app/javascript/styles/doodle.scss diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 86d83122f..927bad17b 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -49,6 +49,8 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST' export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'; export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL'; +export const COMPOSE_DOODLE_SET = 'COMPOSE_DOODLE_SET'; + export function changeCompose(text) { return { type: COMPOSE_CHANGE, @@ -178,6 +180,13 @@ export function submitComposeFail(error) { }; }; +export function doodleSet(options) { + return { + type: COMPOSE_DOODLE_SET, + options: options, + }; +}; + export function uploadCompose(files) { return function (dispatch, getState) { if (getState().getIn(['compose', 'media_attachments']).size > 3) { diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js index b96e48fd0..94e0dd86a 100644 --- a/app/javascript/mastodon/components/icon_button.js +++ b/app/javascript/mastodon/components/icon_button.js @@ -22,6 +22,7 @@ export default class IconButton extends React.PureComponent { animate: PropTypes.bool, overlay: PropTypes.bool, tabIndex: PropTypes.string, + label: PropTypes.string, }; static defaultProps = { @@ -42,14 +43,18 @@ export default class IconButton extends React.PureComponent { } render () { - const style = { + let style = { fontSize: `${this.props.size}px`, - width: `${this.props.size * 1.28571429}px`, height: `${this.props.size * 1.28571429}px`, lineHeight: `${this.props.size}px`, ...this.props.style, ...(this.props.active ? this.props.activeStyle : {}), }; + if (!this.props.label) { + style.width = `${this.props.size * 1.28571429}px`; + } else { + style.textAlign = 'left'; + } const { active, @@ -104,7 +109,8 @@ export default class IconButton extends React.PureComponent { style={style} tabIndex={tabIndex} > -