about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/concerns/localized.rb7
-rw-r--r--app/helpers/settings_helper.rb2
-rw-r--r--app/javascript/mastodon/components/relative_timestamp.js2
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js15
-rw-r--r--app/javascript/mastodon/features/ui/components/document_title.js10
-rw-r--r--app/javascript/mastodon/locales/en-CY.json293
-rw-r--r--app/javascript/mastodon/locales/whitelist_en-CY.json2
-rw-r--r--app/javascript/packs/application.js2
-rw-r--r--app/javascript/packs/public.js43
-rw-r--r--app/javascript/styles/mastodon/containers.scss6
-rw-r--r--app/lib/activitypub/activity.rb7
-rw-r--r--app/lib/formatter.rb11
-rw-r--r--app/models/account.rb6
-rw-r--r--app/models/status.rb8
-rw-r--r--app/serializers/rest/instance_serializer.rb6
-rw-r--r--app/validators/status_length_validator.rb2
-rw-r--r--app/views/accounts/show.html.haml5
-rw-r--r--app/views/statuses/_simple_status.html.haml1
19 files changed, 412 insertions, 18 deletions
diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb
index fe1142f34..0d7af93f7 100644
--- a/app/controllers/concerns/localized.rb
+++ b/app/controllers/concerns/localized.rb
@@ -23,7 +23,12 @@ module Localized
     if ENV['DEFAULT_LOCALE'].present?
       I18n.default_locale
     else
-      request_locale || I18n.default_locale
+      case request_locale
+      when /en\b/, nil
+        I18n.default_locale
+      else
+        request_locale
+      end
     end
   end
 
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 5b39497b6..a5c0f32c6 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -2,6 +2,8 @@
 
 module SettingsHelper
   HUMAN_LOCALES = {
+    en: 'English',
+    'en-CY': 'English (Cybre)',
     ar: 'العربية',
     ast: 'Asturianu',
     bg: 'Български',
diff --git a/app/javascript/mastodon/components/relative_timestamp.js b/app/javascript/mastodon/components/relative_timestamp.js
index 711181dcd..2ad7a603c 100644
--- a/app/javascript/mastodon/components/relative_timestamp.js
+++ b/app/javascript/mastodon/components/relative_timestamp.js
@@ -66,6 +66,8 @@ const getUnitDelay = units => {
   }
 };
 
+const fallbackFormat = new Intl.DateTimeFormat('en', shortDateFormatOptions);
+
 export const timeAgoString = (intl, date, now, year, timeGiven = true) => {
   const delta = now - date.getTime();
 
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index b0aa42162..999507072 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -319,7 +319,7 @@ class StatusActionBar extends ImmutablePureComponent {
 
     return (
       <div className='status__action-bar'>
-        <IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
+        <IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} />
         <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate}  active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} />
         <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='floppy-o' onClick={this.handleFavouriteClick} />
         {shareButton}
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 8af806ec4..137a30506 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -96,7 +96,15 @@ class ComposeForm extends ImmutablePureComponent {
       this.props.onChange(this.autosuggestTextarea.textarea.value);
     }
 
+<<<<<<< HEAD
     if (!this.canSubmit()) {
+=======
+    // Submit disabled:
+    const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
+    const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
+
+    if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 1024 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
+>>>>>>> Feature: 1024-character posts in server and client
       return;
     }
 
@@ -188,6 +196,11 @@ class ComposeForm extends ImmutablePureComponent {
   render () {
     const { intl, onPaste, showSearch } = this.props;
     const disabled = this.props.isSubmitting;
+<<<<<<< HEAD
+=======
+    const text     = [this.props.spoilerText, countableText(this.props.text)].join('');
+    const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 1024 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
+>>>>>>> Feature: 1024-character posts in server and client
     let publishText = '';
 
     if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
@@ -249,7 +262,7 @@ class ComposeForm extends ImmutablePureComponent {
             <PrivacyDropdownContainer />
             <SpoilerButtonContainer />
           </div>
-          <div className='character-counter__wrapper'><CharacterCounter max={500} text={this.getFulltextForCharacterCounting()} /></div>
+          <div className='character-counter__wrapper'><CharacterCounter max={1024} text={this.getFulltextForCharacterCounting()} /></div>
         </div>
 
         <div className='compose-form__publish'>
diff --git a/app/javascript/mastodon/features/ui/components/document_title.js b/app/javascript/mastodon/features/ui/components/document_title.js
index cd081b20c..86ba738b0 100644
--- a/app/javascript/mastodon/features/ui/components/document_title.js
+++ b/app/javascript/mastodon/features/ui/components/document_title.js
@@ -23,15 +23,7 @@ class DocumentTitle extends PureComponent {
   }
 
   _sideEffects () {
-    const { unread } = this.props;
-
-    if (unread > 99) {
-      document.title = `(*) ${title}`;
-    } else if (unread > 0) {
-      document.title = `(${unread}) ${title}`;
-    } else {
-      document.title = title;
-    }
+    document.title = title;
   }
 
   render () {
diff --git a/app/javascript/mastodon/locales/en-CY.json b/app/javascript/mastodon/locales/en-CY.json
new file mode 100644
index 000000000..2ae215249
--- /dev/null
+++ b/app/javascript/mastodon/locales/en-CY.json
@@ -0,0 +1,293 @@
+{
+  "account.block": "Block @{name}",
+  "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
+  "account.disclaimer_full": "THESE NUMBERS ARE THE STUFF WHAT YOUR SERVER KNOWS ABOUT AND THERE MIGHT BE MORE THAT IT DONT KNOW ABOUT.",
+  "account.domain_blocked": "Domain hidden",
+  "account.edit_profile": "edit ~/.profile",
+  "account.follow": "Follow",
+  "account.followers": "Followers",
+  "account.follows": "Follows",
+  "account.follows_you": "Follows you",
+  "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.media": "Media",
+  "account.mention": "Mention @{name}",
+  "account.moved_to": "{name} has moved to:",
+  "account.mute": "Mute @{name}",
+  "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
+  "account.posts": "Pings",
+  "account.posts_with_replies": "Pings with replies",
+  "account.report": "Report @{name}",
+  "account.requested": "Awaiting approval. Click to cancel follow request",
+  "account.share": "Share @{name}'s profile",
+  "account.show_reblogs": "Show boosts from @{name}",
+  "account.unblock": "Unblock @{name}",
+  "account.unblock_domain": "Unhide {domain}",
+  "account.unfollow": "Unfollow",
+  "account.unmute": "Unmute @{name}",
+  "account.unmute_notifications": "Unmute notifications from @{name}",
+  "account.view_full_profile": "View full profile",
+  "boost_modal.combo": "You can press {combo} to skip this next time",
+  "bundle_column_error.body": "Something went wrong while loading this component.",
+  "bundle_column_error.retry": "Try again",
+  "bundle_column_error.title": "Network error",
+  "bundle_modal_error.close": "Close",
+  "bundle_modal_error.message": "Something went wrong while loading this component.",
+  "bundle_modal_error.retry": "Try again",
+  "column.blocks": "~/.blocked",
+  "column.community": "/timelines/local",
+  "column.direct": "~/.dms",
+  "column.favourites": "~/.florps",
+  "column.follow_requests": "~/.follow-requests",
+  "column.home": "/timelines/home",
+  "column.lists": "Lists",
+  "column.mutes": "~/.muted",
+  "column.notifications": "~/.notifications",
+  "column.pins": "~/.pinned",
+  "column.public": "/timelines/federated",
+  "column_back_button.label": "Back",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
+  "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
+  "column_header.unpin": "Unpin",
+  "column_subheading.navigation": "Navigation",
+  "column_subheading.settings": "Settings",
+  "compose.attach": "Attach...",
+  "compose.attach.doodle": "Draw something",
+  "compose.attach.upload": "Upload a file",
+  "compose_form.hashtag_warning": "This ping won't be listed under any hashtag as it is unlisted. Only public pings can be searched by hashtag.",
+  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+  "compose_form.lock_disclaimer.lock": "locked",
+  "compose_form.placeholder": "What is in your databanks?",
+  "compose_form.publish": "Ping",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.marked": "Media is marked as sensitive",
+  "compose_form.sensitive.unmarked": "Media is not marked as sensitive",
+  "compose_form.spoiler.marked": "Text is hidden behind warning",
+  "compose_form.spoiler.unmarked": "Text is not hidden",
+  "compose_form.spoiler_placeholder": "Write your warning here",
+  "confirmation_modal.cancel": "Cancel",
+  "confirmations.block.confirm": "Block",
+  "confirmations.block.message": "Are you sure you want to block {name}?",
+  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
+  "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.message": "Are you sure you want to mute {name}?",
+  "confirmations.unfollow.confirm": "Unfollow",
+  "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "doodle_button.label": "Add a drawing",
+  "embed.instructions": "Embed this status on your website by copying the code below.",
+  "embed.preview": "Here is what it will look like:",
+  "emoji_button.activity": "Activity",
+  "emoji_button.custom": "Custom",
+  "emoji_button.flags": "Flags",
+  "emoji_button.food": "Food & Drink",
+  "emoji_button.label": "Insert emoji",
+  "emoji_button.nature": "Nature",
+  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objects",
+  "emoji_button.people": "People",
+  "emoji_button.recent": "Frequently used",
+  "emoji_button.search": "Search...",
+  "emoji_button.search_results": "Search results",
+  "emoji_button.symbols": "Symbols",
+  "emoji_button.travel": "Travel & Places",
+  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.hashtag": "There is nothing in this hashtag yet.",
+  "empty_column.home": "Your home timeline is empty! Visit {public} or use query to get started and meet other users.",
+  "empty_column.home.public_timeline": "the public timeline",
+  "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "follow_request.authorize": "Authorize",
+  "follow_request.reject": "Reject",
+  "getting_started.appsshort": "Apps",
+  "getting_started.faq": "FAQ",
+  "getting_started.heading": "Getting started",
+  "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
+  "getting_started.userguide": "User Guide",
+  "home.column_settings.advanced": "Advanced",
+  "home.column_settings.basic": "Basic",
+  "home.column_settings.filter_regex": "Filter out by regular expressions",
+  "home.column_settings.show_reblogs": "Show relays",
+  "home.column_settings.show_replies": "Show replies",
+  "home.settings": "Column settings",
+  "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.boost": "to boost",
+  "keyboard_shortcuts.column": "to focus a status in one of the columns",
+  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.enter": "to open status",
+  "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.hotkey": "Hotkey",
+  "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.toot": "to start a brand new ping",
+  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.up": "to move up in the list",
+  "lightbox.close": "Close",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
+  "lists.account.add": "Add to list",
+  "lists.account.remove": "Remove from list",
+  "lists.delete": "Delete list",
+  "lists.edit": "Edit list",
+  "lists.new.create": "Add list",
+  "lists.new.title_placeholder": "New list title",
+  "lists.search": "Search among people you follow",
+  "lists.subheading": "Your lists",
+  "loading_indicator.label": "Loading...",
+  "media_gallery.toggle_visible": "Toggle visibility",
+  "missing_indicator.label": "Not found",
+  "missing_indicator.sublabel": "This resource could not be found",
+  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "navigation_bar.blocks": "~/.blocks",
+  "navigation_bar.community_timeline": "/timelines/local",
+  "navigation_bar.direct": "~/.dms",
+  "navigation_bar.edit_profile": "edit ~/.profile",
+  "navigation_bar.favourites": "~/.florps",
+  "navigation_bar.follow_requests": "~/.follow-requests",
+  "navigation_bar.info": "/about/more",
+  "navigation_bar.keyboard_shortcuts": "~/.kbd/shortcuts.conf",
+  "navigation_bar.lists": "~/.lists",
+  "navigation_bar.logout": "Jack out",
+  "navigation_bar.mutes": "~/.muted",
+  "navigation_bar.pins": "~/.pinned",
+  "navigation_bar.preferences": "edit ~/.config",
+  "navigation_bar.public_timeline": "/timelines/federated",
+  "notification.favourite": "{name} florped your ping",
+  "notification.follow": "{name} followed you",
+  "notification.mention": "{name} mentioned you",
+  "notification.reblog": "{name} relayed your ping",
+  "notifications.clear": "Clear notifications",
+  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.column_settings.alert": "Desktop notifications",
+  "notifications.column_settings.favourite": "Favourites:",
+  "notifications.column_settings.follow": "New followers:",
+  "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.push": "Push notifications",
+  "notifications.column_settings.push_meta": "This device",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Show in column",
+  "notifications.column_settings.sound": "Play sound",
+  "onboarding.done": "Done",
+  "onboarding.next": "Next",
+  "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
+  "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
+  "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
+  "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
+  "onboarding.page_one.welcome": "Welcome to Mastodon!",
+  "onboarding.page_six.admin": "Your instance's admin is {admin}.",
+  "onboarding.page_six.almost_done": "Almost done...",
+  "onboarding.page_six.appetoot": "Hang ten on the cybrewaves!",
+  "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+  "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
+  "onboarding.page_six.guidelines": "community guidelines",
+  "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
+  "onboarding.page_six.various_app": "mobile apps",
+  "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+  "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
+  "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
+  "onboarding.skip": "Skip",
+  "privacy.change": "Adjust status privacy",
+  "privacy.direct.long": "Post to mentioned users only",
+  "privacy.direct.short": "Direct",
+  "privacy.private.long": "Post to followers only",
+  "privacy.private.short": "Followers-only",
+  "privacy.public.long": "Post to public timelines",
+  "privacy.public.short": "Public",
+  "privacy.unlisted.long": "Do not post to public timelines",
+  "privacy.unlisted.short": "Unlisted",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "relative_time.days": "{number}d",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "now",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "Additional comments",
+  "report.submit": "Submit",
+  "report.target": "Reporting {target}",
+  "search.placeholder": "Query...",
+  "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.status": "status",
+  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Pings",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "standalone.public_title": "Peer into the data grid...",
+  "status.block": "Block @{name}",
+  "status.cannot_reblog": "This ping cannot be relayed",
+  "status.delete": "Delete",
+  "status.embed": "Embed",
+  "status.favourite": "Florp",
+  "status.load_more": "Load more",
+  "status.media_hidden": "Media hidden",
+  "status.mention": "Mention @{name}",
+  "status.more": "More",
+  "status.mute": "Mute @{name}",
+  "status.mute_conversation": "Mute conversation",
+  "status.open": "Expand this status",
+  "status.pin": "Pin on profile",
+  "status.pinned": "Pinned ping",
+  "status.reblog": "Relay",
+  "status.reblogged_by": "{name} relayed",
+  "status.reply": "Reply",
+  "status.replyAll": "Reply to thread",
+  "status.report": "Report @{name}",
+  "status.sensitive_toggle": "Click to view",
+  "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
+  "status.show_less": "Show less",
+  "status.show_less_all": "Show less for all",
+  "status.show_more": "Show more",
+  "status.show_more_all": "Show more for all",
+  "status.unmute_conversation": "Unmute conversation",
+  "status.unpin": "Unpin from profile",
+  "tabs_bar.federated_timeline": "/timelines/federated",
+  "tabs_bar.home": "/timelines/home",
+  "tabs_bar.local_timeline": "/timelines/local",
+  "tabs_bar.notifications": "~/.notifications",
+  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "upload_area.title": "Drag & drop to upload",
+  "upload_button.label": "Add media",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
+  "upload_form.undo": "Undo",
+  "upload_progress.label": "Uploading...",
+  "video.close": "Close video",
+  "video.exit_fullscreen": "Exit full screen",
+  "video.expand": "Expand video",
+  "video.fullscreen": "Full screen",
+  "video.hide": "Hide video",
+  "video.mute": "Mute sound",
+  "video.pause": "Pause",
+  "video.play": "Play",
+  "video.unmute": "Unmute sound",
+  "video_player.expand": "Expand video",
+  "video_player.toggle_sound": "Toggle sound",
+  "video_player.toggle_visible": "Toggle visibility",
+  "video_player.video_error": "Video could not be played"
+}
diff --git a/app/javascript/mastodon/locales/whitelist_en-CY.json b/app/javascript/mastodon/locales/whitelist_en-CY.json
new file mode 100644
index 000000000..0d4f101c7
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_en-CY.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 91240aecf..3d021a632 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -9,3 +9,5 @@ loadPolyfills().then(() => {
 }).catch(e => {
   console.error(e);
 });
+
+require('what-input');
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 8c5c15b8f..b2afe3fd6 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -20,6 +20,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();
+    }
   });
 });
 
@@ -142,6 +148,7 @@ function main() {
     delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
     delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
 
+<<<<<<< HEAD
     delegate(document, '.status__content__spoiler-link', 'click', function() {
       const statusEl = this.parentNode.parentNode;
 
@@ -161,6 +168,26 @@ function main() {
       const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
       spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
     });
+=======
+    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();
+    }
+>>>>>>> Bio length -> 500 characters
   });
 
   delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
@@ -291,6 +318,22 @@ function main() {
       }
     });
   });
+
+  delegate(document, '#account_note', 'input', sizeBioText);
+
+  function sizeBioText() {
+    const noteCounter = document.querySelector('.note-counter');
+    const bioTextArea = document.querySelector('#account_note');
+
+    if (noteCounter) {
+      noteCounter.textContent = 500 - length(bioTextArea.value);
+    }
+
+    if (bioTextArea) {
+      bioTextArea.style.height = 'auto';
+      bioTextArea.style.height = (bioTextArea.scrollHeight+3) + 'px';
+    }
+  }
 }
 
 loadPolyfills()
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index 1df9af91d..3684c699c 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -296,6 +296,12 @@
       min-height: 1px;
     }
 
+    h2 {
+      font-size: 1rem;
+      align-items: center;
+      display: flex;
+    }
+
     .nav-left {
       display: flex;
       align-items: stretch;
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 2b5d3ffc2..85a3370ee 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -98,7 +98,7 @@ class ActivityPub::Activity
     crawl_links(status)
 
     notify_about_reblog(status) if reblog_of_local_account?(status) && !reblog_by_following_group_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.
     # Note that if @options[:override_timestamps] isn't set, the status
@@ -117,6 +117,11 @@ class ActivityPub::Activity
     status.reblog? && status.account.group? && status.reblog.account.following?(status.account)
   end
 
+  def spammy_mentions?(status)
+    status.has_non_mention_links? &&
+    @account.followers.local.count == 0
+  end
+
   def notify_about_reblog(status)
     NotifyService.new.call(status.reblog.account, :reblog, status)
   end
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 7f217ae9f..e0e5012de 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -267,8 +267,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)}"
@@ -277,6 +278,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)
 
@@ -304,4 +309,8 @@ class Formatter
   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>"
   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
diff --git a/app/models/account.rb b/app/models/account.rb
index e6cf03fa8..9a1c4cd40 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -89,6 +89,7 @@ class Account < ApplicationRecord
   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, 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? }
 
   scope :remote, -> { where.not(domain: nil) }
@@ -346,7 +347,6 @@ class Account < ApplicationRecord
   rescue ActiveRecord::RecordInvalid
     self.avatar = nil
     self.header = nil
-
     save!
   end
 
@@ -382,6 +382,10 @@ class Account < ApplicationRecord
     return 'local' if local?
 
     @synchronization_uri_prefix ||= uri[/http(s?):\/\/[^\/]+\//]
+  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
diff --git a/app/models/status.rb b/app/models/status.rb
index b426f9d5b..5716b93d4 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -351,6 +351,14 @@ class Status < ApplicationRecord
     super || build_status_stat
   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
 
   def update_status_stat!(attrs)
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index b388e448e..e2aa35c4e 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -5,7 +5,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
 
   attributes :uri, :title, :short_description, :description, :email,
              :version, :urls, :stats, :thumbnail,
-             :languages, :registrations, :approval_required, :invites_enabled
+             :languages, :registrations, :approval_required, :invites_enabled, :max_toot_chars
 
   has_one :contact_account, serializer: REST::AccountSerializer
 
@@ -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')
   end
 
+  def max_toot_chars
+    StatusLengthValidator::MAX_CHARS
+  end
+
   def stats
     {
       user_count: instance_presenter.user_count,
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb
index 93bae2fa8..e67b794ab 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 = 1024
 
   def validate(status)
     return unless status.local? && !status.reblog?
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 1a81b96f6..aa2c83dec 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -19,6 +19,11 @@
 
 = 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
   .column-0
     .h-feed
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index 4ea79f10d..c70d199d7 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -60,7 +60,6 @@
         = fa_icon 'reply fw'
       - else
         = fa_icon 'reply-all fw'
-      %span.icon-button__counter= obscured_counter status.replies_count
     = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do
       - if status.distributable?
         = fa_icon 'retweet fw'