about summary refs log tree commit diff
path: root/app/javascript/mastodon
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/mastodon')
-rw-r--r--app/javascript/mastodon/actions/importer/normalizer.js5
-rw-r--r--app/javascript/mastodon/actions/streaming.js5
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js6
-rw-r--r--app/javascript/mastodon/components/status_content.js20
-rw-r--r--app/javascript/mastodon/features/compose/components/poll_form.js4
-rw-r--r--app/javascript/mastodon/locales/en-MP.json181
-rw-r--r--app/javascript/mastodon/locales/locale-data/en-MP.js8
-rw-r--r--app/javascript/mastodon/locales/whitelist_en-MP.json2
-rw-r--r--app/javascript/mastodon/reducers/compose.js4
9 files changed, 227 insertions, 8 deletions
diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js
index dca44917a..d0a55538f 100644
--- a/app/javascript/mastodon/actions/importer/normalizer.js
+++ b/app/javascript/mastodon/actions/importer/normalizer.js
@@ -53,9 +53,12 @@ export function normalizeStatus(status, normalOldStatus) {
     normalStatus.poll = status.poll.id;
   }
 
+  const oldUpdatedAt = normalOldStatus ? normalOldStatus.updated_at || normalOldStatus.created_at : null;
+  const newUpdatedAt = normalStatus ? normalStatus.updated_at || normalStatus.created_at : null;
+
   // Only calculate these values when status first encountered
   // Otherwise keep the ones already in the reducer
-  if (normalOldStatus) {
+  if (normalOldStatus && oldUpdatedAt === newUpdatedAt) {
     normalStatus.search_index = normalOldStatus.get('search_index');
     normalStatus.contentHtml = normalOldStatus.get('contentHtml');
     normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml');
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index beb5c6a4a..1adc1b815 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -18,6 +18,7 @@ import {
 } from './announcements';
 import { fetchFilters } from './filters';
 import { getLocale } from '../locales';
+import { resetCompose } from '../actions/compose';
 
 const { messages } = getLocale();
 
@@ -96,6 +97,10 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
         case 'announcement.delete':
           dispatch(deleteAnnouncement(data.payload));
           break;
+        case 'refresh':
+          dispatch(resetCompose());
+          window.location.reload();
+          break;
         }
       },
     };
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index 66b5a17ac..adcdb8a4e 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -240,10 +240,8 @@ class StatusActionBar extends ImmutablePureComponent {
     menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
     menu.push(null);
 
-    if (status.getIn(['account', 'id']) === me || withDismiss) {
-      menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
-      menu.push(null);
-    }
+    menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
+    menu.push(null);
 
     if (status.getIn(['account', 'id']) === me) {
       if (publicStatus) {
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 3200f2d82..df05d8515 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 import { isRtl } from '../rtl';
 import { FormattedMessage } from 'react-intl';
 import Permalink from './permalink';
+import RelativeTimestamp from './relative_timestamp';
 import classnames from 'classnames';
 import PollContainer from 'mastodon/containers/poll_container';
 import Icon from 'mastodon/components/icon';
@@ -180,6 +181,20 @@ export default class StatusContent extends React.PureComponent {
       return null;
     }
 
+    const edited = (status.get('edited') === 0) ? null : (
+      <div className='status__edit-notice'>
+        <FormattedMessage
+          id='status.edited'
+          defaultMessage='{count, plural, one {# edit} other {# edits}} · last update: {updated_at}'
+          key={`edit-${status.get('id')}`}
+          values={{
+            count: status.get('edited'),
+            updated_at: <RelativeTimestamp timestamp={status.get('updated_at')} />,
+          }}
+        />
+      </div>
+    );
+
     const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
     const renderReadMore = this.props.onClick && status.get('collapsed');
     const renderViewThread = this.props.showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
@@ -232,6 +247,7 @@ export default class StatusContent extends React.PureComponent {
             <button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button>
           </p>
 
+          {edited}
           {mentionsPlaceholder}
 
           <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
@@ -244,6 +260,8 @@ export default class StatusContent extends React.PureComponent {
     } else if (this.props.onClick) {
       const output = [
         <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>
+          {edited}
+
           <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
 
           {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
@@ -260,6 +278,8 @@ export default class StatusContent extends React.PureComponent {
     } else {
       return (
         <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle}>
+          {edited}
+
           <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
 
           {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
diff --git a/app/javascript/mastodon/features/compose/components/poll_form.js b/app/javascript/mastodon/features/compose/components/poll_form.js
index 88894ae59..72ffeff09 100644
--- a/app/javascript/mastodon/features/compose/components/poll_form.js
+++ b/app/javascript/mastodon/features/compose/components/poll_form.js
@@ -89,7 +89,7 @@ class Option extends React.PureComponent {
 
           <AutosuggestInput
             placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
-            maxLength={100}
+            maxlength={202}
             value={title}
             onChange={this.handleOptionTitleChange}
             suggestions={this.props.suggestions}
@@ -157,7 +157,7 @@ class PollForm extends ImmutablePureComponent {
         </ul>
 
         <div className='poll__footer'>
-          <button disabled={options.size >= 5} className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button>
+          <button disabled={options.size >= 33} className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button>
 
           {/* eslint-disable-next-line jsx-a11y/no-onchange */}
           <select value={expiresIn} onChange={this.handleSelectDuration}>
diff --git a/app/javascript/mastodon/locales/en-MP.json b/app/javascript/mastodon/locales/en-MP.json
new file mode 100644
index 000000000..3e021a5c1
--- /dev/null
+++ b/app/javascript/mastodon/locales/en-MP.json
@@ -0,0 +1,181 @@
+{
+  "account.add_account_note": "Add note for @{name}",
+  "account.disclaimer_full": "You're viewing the cached version of a profile from another server.",
+  "account.followers.empty": "No one follows this creature yet.",
+  "account.follows.empty": "This creature doesn't follow anyone yet.",
+  "account.follows": "Follows",
+  "account.locked_info": "This creature manually reviews who can follow them.",
+  "account.media": "Media",
+  "account.mentions": "Mentions",
+  "account.posts_with_replies": "Replies",
+  "account.posts": "Blog",
+  "account.reblogs": "Boosts",
+  "account.statuses_counter": "{count, plural, one {{counter} Roar} other {{counter} Roars}}",
+  "account.threads": "Threads",
+  "account.view_full_profile": "View the original",
+  "advanced_options.local-only.long": "Do not post to other servers",
+  "column_header.profile": "Creature",
+  "column.blocks": "Blocked creatures",
+  "column.community": "Monsterpit",
+  "column.directory": "Creature directory",
+  "column.favourites": "Admirations",
+  "column.mutes": "Muted creatures",
+  "column.pins": "Pins",
+  "column.public": "Tavern",
+  "column.toot": "Roars & Growls",
+  "community.column_settings.local_only": "Monsterpit only",
+  "community.column_settings.remote_only": "Rowdy Tavern mode",
+  "compose_form.clear": "Double-click to clear",
+  "compose_form.direct_message_warning": "This roar will only be sent to the mentioned creatures.",
+  "compose_form.hashtag_warning": "This roar won't be listed under any hashtag as it is unlisted. Only public roars 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.placeholder": "Roar shamelessly!",
+  "compose_form.publish": "Roar",
+  "compose_form.spoiler_placeholder": "Enter content notes here",
+  "compose_form.spoiler.marked": "Text is hidden behind content notes",
+  "compose_form.spoiler": "Enter content notes here",
+  "confirmations.delete.message": "Are you sure you want to delete this roar?",
+  "confirmations.mute.explanation": "This will hide roars from them and roars mentioning them, but it will still allow them to see your roars and follow you.",
+  "confirmations.publish.confirm": "Publish",
+  "confirmations.publish.message": "Are you ready to publish your roar?",
+  "confirmations.redraft.message": "Are you sure you want to delete and redraft this roar? Admirations and boosts will be lost, and replies to the original roar will be orphaned.",
+  "confirmations.domain_block.message": "You are about to block and defederate from the {domain} server.  If you proceed, Monsterpit will ask this server to permanently delete any data associated with your account.",
+  "content-type.change": "Content type",
+  "directory.federated": "From Tavern",
+  "directory.local": "From Monsterpit",
+  "embed.instructions": "Embed this roar on your website by copying the code below.",
+  "empty_column.account_timeline": "No roars here!",
+  "empty_column.blocks": "You haven't blocked any creatures yet.",
+  "empty_column.bookmarked_statuses": "You don't have any bookmarked roars yet. When you bookmark one, it will show up here.",
+  "empty_column.community": "The Monsterpit timeline is empty. Write something publicly to get the ball rolling!",
+  "empty_column.favourited_statuses": "You don't have any admired roars yet. When you admire one, it will show up here.",
+  "empty_column.favourites": "No one has admired this roar yet. When someone does, they will show up here.",
+  "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other creatures.",
+  "empty_column.list": "There is nothing in this list yet. When members of this list post new roars, they will appear here.",
+  "empty_column.mutes": "You haven't muted any creatures yet.",
+  "empty_column.public": "There is nothing here! Write something publicly, or manually follow creatures from other servers to fill it up",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Monsterpit through a different browser or native app.",
+  "follow_request.authorize": "Accept",
+  "getting_started.directory": "Creature directory",
+  "getting_started.invite": "Invite creatures",
+  "getting_started.open_source_notice": "Monsterfork is open source software.  If you'd like to explore its code, you may visit the repository on {monsterware}.",
+  "introduction.federation.federated.headline": "Tavern",
+  "introduction.federation.federated.text": "Public roars from other servers will appear in the Tavern timeline.",
+  "introduction.federation.home.text": "Roars from creatures you follow will appear in your home feed.",
+  "introduction.federation.local.headline": "Monsterpit",
+  "introduction.federation.local.text": "Public roars from people on Monsterpit will appear in the Monsterpit timeline.",
+  "introduction.interactions.action": "Finish tutorial",
+  "introduction.interactions.favourite.headline": "Admire",
+  "introduction.interactions.favourite.text": "You can save a roar for later, and let the author know that you liked it, by admiring it.",
+  "introduction.interactions.reblog.text": "You can share other creature's roars with your followers by boosting them.",
+  "introduction.interactions.reply.text": "You can reply to other creature's and your own roars, which will chain them together in a conversation.",
+  "keyboard_shortcuts.blocked": "to open blocked creatures list",
+  "keyboard_shortcuts.column": "to focus a roar in one of the columns",
+  "keyboard_shortcuts.enter": "to open roar",
+  "keyboard_shortcuts.favourite": "to admire",
+  "keyboard_shortcuts.favourites": "to open admirations list",
+  "keyboard_shortcuts.federated": "to open Tavern timeline",
+  "keyboard_shortcuts.local": "to open Monsterpit timeline",
+  "keyboard_shortcuts.muted": "to open muted creatures list",
+  "keyboard_shortcuts.pinned": "to open pinned roars list",
+  "keyboard_shortcuts.spoilers": "to show/hide content note field",
+  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind content notes",
+  "keyboard_shortcuts.toot": "to start a new roar",
+  "lists.search": "Search among creatures you follow",
+  "mute_modal.hide_notifications": "Hide notifications from this creature?",
+  "navigation_bar.blocks": "Blocked creatures",
+  "navigation_bar.community_timeline": "Monsterpit",
+  "navigation_bar.compose": "Compose new roar",
+  "navigation_bar.favourites": "Admirations",
+  "navigation_bar.logout": "Sleep",
+  "navigation_bar.mutes": "Muted creatures",
+  "navigation_bar.pins": "Pins",
+  "navigation_bar.public_timeline": "Tavern",
+  "notification_purge.start": "Enter notification cleaning mode",
+  "notification.favourite": "{name} admired your roar",
+  "notification.follow_request": "{name} wants to follow you",
+  "notification.follow": "{name} followed you",
+  "notification.mention": "{name} mentioned you",
+  "notification.own_poll": "Your poll has ended",
+  "notification.poll": "A poll you have voted in has ended",
+  "notification.reblog": "{name} boosted your roar",
+  "notifications.clear": "Clear notifications",
+  "notifications.column_settings.favourite": "Admirations:",
+  "notifications.filter.favourites": "Admirations",
+  "notifications_permission_banner.title": "You have desktop notifications enabled.",
+  "notifications_permission_banner.enable": "Request",
+  "notifications_permission_banner.disable": "Disable",
+  "notifications_permission_banner.how_to_control": "Permission is needed from your Web browser to use this feature.",
+  "poll.total_people": "{count, plural, one {# creature} other {# creatures}}",
+  "privacy.change": "Adjust roar privacy",
+  "privacy.direct.long": "Visible for mentioned creatures only",
+  "report.forward_hint": "The creature is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this creature below:",
+  "search_popout.tips.full_text": "Simple text returns roars you have written, admired, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.status": "roar",
+  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.user": "creature",
+  "search_results.accounts": "Creatures",
+  "search_results.statuses_fts_disabled": "Searching roars by their content is not enabled on this Mastodon server.",
+  "search_results.statuses": "Roars",
+  "settings.always_show_spoilers_field": "Always show content notes field",
+  "settings.auto_collapse_lengthy": "Lengthy roars",
+  "settings.auto_collapse_media": "Media",
+  "settings.collapsed_statuses": "Collapsed roars",
+  "settings.confirm_boost_missing_media_description": "Show confirmation dialog before boosting roars lacking media descriptions",
+  "settings.confirm_missing_media_description": "Show confirmation dialog before sending roars lacking media descriptions",
+  "settings.content_warnings_filter": "Avoid expanding roars with content notes containing:",
+  "settings.content_warnings": "Content notes",
+  "settings.enable_collapsed": "Enable collapsed roars",
+  "settings.enable_content_warnings_auto_unfold": "Auto-expand roars with content notes",
+  "settings.filtering_behavior.cw": "Add the filtered phrase to the roar's content notes",
+  "settings.image_backgrounds_media": "Preview collapsed media",
+  "settings.image_backgrounds_users": "Give collapsed roars an image background",
+  "settings.prepend_cw_re": "Prepend \"re:\" to content notes when replying",
+  "settings.rewrite_mentions": "Rewrite mentions in roars:",
+  "settings.show_action_bar": "Show action buttons in collapsed roars",
+  "settings.show_content_type_choice": "Show content-type choice when authoring roars",
+  "settings.side_arm_reply_mode.copy": "Copy privacy setting of the roar being replied to",
+  "settings.side_arm_reply_mode.keep": "Keep secondary roar button to set privacy",
+  "settings.side_arm_reply_mode.restrict": "Restrict privacy setting to that of the roar being replied to",
+  "settings.side_arm_reply_mode": "When replying to a roar:",
+  "settings.side_arm": "Secondary roar button:",
+  "status.admin_account": "Moderate @{name}",
+  "status.admin_status": "Moderate roar",
+  "status.article": "Article",
+  "status.cannot_reblog": "This roar cannot be boosted",
+  "status.copy": "Copy link to roar",
+  "status.edit": "Edit",
+  "status.edited": "{count, plural, one {# edit} other {# edits}} · last update: {updated_at}",
+  "status.favourite": "Admire",
+  "status.has_pictures": "Features attached pictures",
+  "status.in_reply_to": "This roar is a reply",
+  "status.is_poll": "This roar is a poll",
+  "status.local_only": "Monsterpit-only",
+  "status.media.description": "Attachment #{index}: ",
+  "status.media.descriptions": "Attachments {list}: ",
+  "status.open": "Open this roar",
+  "status.permissions.title": "Show extended permissions...",
+  "status.permissions.visibility.account": "{visibility} 🡲 {domain}",
+  "status.permissions.visibility.status": "{visibility} 🡲 {domain}",
+  "status.pinned": "Pinned",
+  "status.publish": "Publish",
+  "status.reblogged_by": "{name}",
+  "status.reblogs.empty": "No one has boosted this roar yet. When someone does, they will show up here.",
+  "status.show_article": "Show article",
+  "status.show_less_all": "Hide all",
+  "status.show_less": "Hide",
+  "status.show_more_all": "Reveal all",
+  "status.show_more": "Reveal",
+  "status.show_thread": "Reveal thread",
+  "status.tags": "Show all tags...",
+  "status.unpublished": "Unpublished",
+  "tabs_bar.federated_timeline": "Tavern",
+  "tabs_bar.local_timeline": "Monsterpit",
+  "timeline_hint.resources.statuses": "Older roars",
+  "trends.counter_by_accounts": "{count, plural, one {{counter} creature} other {{counter} creatures}} talking",
+  "ui.beforeunload": "Your draft will be lost if you leave the web page.",
+  "upload_form.edit": "Add description text",
+  "upload_modal.edit_media": "Add description text",
+  "video.expand": "Open video"
+}
diff --git a/app/javascript/mastodon/locales/locale-data/en-MP.js b/app/javascript/mastodon/locales/locale-data/en-MP.js
new file mode 100644
index 000000000..a2defe09a
--- /dev/null
+++ b/app/javascript/mastodon/locales/locale-data/en-MP.js
@@ -0,0 +1,8 @@
+/*eslint eqeqeq: "off"*/
+/*eslint no-nested-ternary: "off"*/
+/*eslint quotes: "off"*/
+
+export default [{
+  locale: 'en-MP',
+  parentLocale: 'en',
+}];
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/whitelist_en-MP.json b/app/javascript/mastodon/locales/whitelist_en-MP.json
new file mode 100644
index 000000000..32960f8ce
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_en-MP.json
@@ -0,0 +1,2 @@
+[
+]
\ No newline at end of file
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 4c0ba1c36..67ce96feb 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -205,7 +205,9 @@ const expandMentions = status => {
   const fragment = domParser.parseFromString(status.get('content'), 'text/html').documentElement;
 
   status.get('mentions').forEach(mention => {
-    fragment.querySelector(`a[href="${mention.get('url')}"]`).textContent = `@${mention.get('acct')}`;
+    const selection = fragment.querySelector(`a[href="${mention.get('url')}"]`);
+    if (!selection) return;
+    selection.textContent = `@${mention.get('acct')}`;
   });
 
   return fragment.innerHTML;