diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/concerns/localized.rb | 7 | ||||
-rw-r--r-- | app/helpers/settings_helper.rb | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/components/relative_timestamp.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/components/status_action_bar.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/compose_form.js | 15 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/document_title.js | 10 | ||||
-rw-r--r-- | app/javascript/mastodon/locales/en-CY.json | 293 | ||||
-rw-r--r-- | app/javascript/mastodon/locales/whitelist_en-CY.json | 2 | ||||
-rw-r--r-- | app/javascript/packs/application.js | 2 | ||||
-rw-r--r-- | app/javascript/packs/public.js | 43 | ||||
-rw-r--r-- | app/javascript/styles/mastodon/containers.scss | 6 | ||||
-rw-r--r-- | app/lib/activitypub/activity.rb | 7 | ||||
-rw-r--r-- | app/lib/formatter.rb | 11 | ||||
-rw-r--r-- | app/models/account.rb | 6 | ||||
-rw-r--r-- | app/models/status.rb | 8 | ||||
-rw-r--r-- | app/serializers/rest/instance_serializer.rb | 6 | ||||
-rw-r--r-- | app/validators/status_length_validator.rb | 2 | ||||
-rw-r--r-- | app/views/accounts/show.html.haml | 5 | ||||
-rw-r--r-- | app/views/statuses/_simple_status.html.haml | 1 |
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' |