about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/api/v1/tags_controller.rb1
-rw-r--r--app/javascript/flavours/glitch/components/hashtag.js18
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/image_loader.js26
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss6
-rw-r--r--app/javascript/flavours/glitch/styles/components/search.scss7
-rw-r--r--app/javascript/mastodon/components/hashtag.js18
-rw-r--r--app/javascript/mastodon/features/ui/components/image_loader.js26
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json8
-rw-r--r--app/javascript/mastodon/locales/en.json3
-rw-r--r--app/javascript/mastodon/locales/th.json6
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json10
-rw-r--r--app/javascript/styles/mastodon/components.scss13
-rw-r--r--app/models/account.rb2
-rw-r--r--app/models/tag.rb2
14 files changed, 97 insertions, 49 deletions
diff --git a/app/controllers/api/v1/tags_controller.rb b/app/controllers/api/v1/tags_controller.rb
index d45015ff5..9e5c53330 100644
--- a/app/controllers/api/v1/tags_controller.rb
+++ b/app/controllers/api/v1/tags_controller.rb
@@ -24,6 +24,7 @@ class Api::V1::TagsController < Api::BaseController
   private
 
   def set_or_create_tag
+    return not_found unless /\A(#{Tag::HASHTAG_NAME_RE})\z/.match?(params[:id])
     @tag = Tag.find_normalized(params[:id]) || Tag.new(name: Tag.normalize(params[:id]), display_name: params[:id])
   end
 end
diff --git a/app/javascript/flavours/glitch/components/hashtag.js b/app/javascript/flavours/glitch/components/hashtag.js
index 769185a2b..5bbf32c87 100644
--- a/app/javascript/flavours/glitch/components/hashtag.js
+++ b/app/javascript/flavours/glitch/components/hashtag.js
@@ -1,7 +1,7 @@
 // @ts-check
 import React from 'react';
 import { Sparklines, SparklinesCurve } from 'react-sparklines';
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Permalink from './permalink';
@@ -9,6 +9,10 @@ import ShortNumber from 'flavours/glitch/components/short_number';
 import Skeleton from 'flavours/glitch/components/skeleton';
 import classNames from 'classnames';
 
+const messages = defineMessages({
+  totalVolume: { id: 'hashtag.total_volume', defaultMessage: 'Total volume in the last {days, plural, one {day} other {{days} days}}' },
+});
+
 class SilentErrorBoundary extends React.Component {
 
   static propTypes = {
@@ -41,10 +45,11 @@ class SilentErrorBoundary extends React.Component {
 const accountsCountRenderer = (displayNumber, pluralReady) => (
   <FormattedMessage
     id='trends.counter_by_accounts'
-    defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} talking'
+    defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}'
     values={{
       count: pluralReady,
       counter: <strong>{displayNumber}</strong>,
+      days: 2,
     }}
   />
 );
@@ -64,7 +69,7 @@ ImmutableHashtag.propTypes = {
   hashtag: ImmutablePropTypes.map.isRequired,
 };
 
-const Hashtag = ({ name, href, to, people, uses, history, className }) => (
+const Hashtag = injectIntl(({ name, href, to, people, uses, history, className, intl }) => (
   <div className={classNames('trends__item', className)}>
     <div className='trends__item__name'>
       <Permalink href={href} to={to}>
@@ -74,9 +79,10 @@ const Hashtag = ({ name, href, to, people, uses, history, className }) => (
       {typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}
     </div>
 
-    <div className='trends__item__current'>
+    <abbr className='trends__item__current' title={intl.formatMessage(messages.totalVolume, { days: 2 })}>
       {typeof uses !== 'undefined' ? <ShortNumber value={uses} /> : <Skeleton width={42} height={36} />}
-    </div>
+      <span className='trends__item__current__asterisk'>*</span>
+    </abbr>
 
     <div className='trends__item__sparkline'>
       <SilentErrorBoundary>
@@ -86,7 +92,7 @@ const Hashtag = ({ name, href, to, people, uses, history, className }) => (
       </SilentErrorBoundary>
     </div>
   </div>
-);
+));
 
 Hashtag.propTypes = {
   name: PropTypes.string,
diff --git a/app/javascript/flavours/glitch/features/ui/components/image_loader.js b/app/javascript/flavours/glitch/features/ui/components/image_loader.js
index c6f16a792..dfa0efe49 100644
--- a/app/javascript/flavours/glitch/features/ui/components/image_loader.js
+++ b/app/javascript/flavours/glitch/features/ui/components/image_loader.js
@@ -1,10 +1,10 @@
-import React from 'react';
-import PropTypes from 'prop-types';
 import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React, { PureComponent } from 'react';
 import { LoadingBar } from 'react-redux-loading-bar';
 import ZoomableImage from './zoomable_image';
 
-export default class ImageLoader extends React.PureComponent {
+export default class ImageLoader extends PureComponent {
 
   static propTypes = {
     alt: PropTypes.string,
@@ -43,7 +43,7 @@ export default class ImageLoader extends React.PureComponent {
     this.loadImage(this.props);
   }
 
-  componentWillReceiveProps (nextProps) {
+  UNSAFE_componentWillReceiveProps (nextProps) {
     if (this.props.src !== nextProps.src) {
       this.loadImage(nextProps);
     }
@@ -139,14 +139,18 @@ export default class ImageLoader extends React.PureComponent {
 
     return (
       <div className={className}>
-        <LoadingBar loading={loading ? 1 : 0} className='loading-bar' style={{ width: this.state.width || width }} />
         {loading ? (
-          <canvas
-            className='image-loader__preview-canvas'
-            ref={this.setCanvasRef}
-            width={width}
-            height={height}
-          />
+          <>
+            <div className='loading-bar__container' style={{ width: this.state.width || width }}>
+              <LoadingBar className='loading-bar' loading={1} />
+            </div>
+            <canvas
+              className='image-loader__preview-canvas'
+              ref={this.setCanvasRef}
+              width={width}
+              height={height}
+            />
+          </>
         ) : (
           <ZoomableImage
             alt={alt}
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 7f9ed2186..b54c3f696 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -442,10 +442,14 @@
     object-fit: contain;
   }
 
-  .loading-bar {
+  .loading-bar__container {
     position: relative;
   }
 
+  .loading-bar {
+    position: absolute;
+  }
+
   &.image-loader--amorphous .image-loader__preview-canvas {
     display: none;
   }
diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss
index f7415368b..17a34db62 100644
--- a/app/javascript/flavours/glitch/styles/components/search.scss
+++ b/app/javascript/flavours/glitch/styles/components/search.scss
@@ -176,6 +176,13 @@
       padding-right: 15px;
       margin-left: 5px;
       color: $secondary-text-color;
+      text-decoration: none;
+
+      &__asterisk {
+        color: $darker-text-color;
+        font-size: 18px;
+        vertical-align: super;
+      }
     }
 
     &__sparkline {
diff --git a/app/javascript/mastodon/components/hashtag.js b/app/javascript/mastodon/components/hashtag.js
index 7f442d189..4e9cd3569 100644
--- a/app/javascript/mastodon/components/hashtag.js
+++ b/app/javascript/mastodon/components/hashtag.js
@@ -1,7 +1,7 @@
 // @ts-check
 import React from 'react';
 import { Sparklines, SparklinesCurve } from 'react-sparklines';
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Permalink from './permalink';
@@ -9,6 +9,10 @@ import ShortNumber from 'mastodon/components/short_number';
 import Skeleton from 'mastodon/components/skeleton';
 import classNames from 'classnames';
 
+const messages = defineMessages({
+  totalVolume: { id: 'hashtag.total_volume', defaultMessage: 'Total volume in the last {days, plural, one {day} other {{days} days}}' },
+});
+
 class SilentErrorBoundary extends React.Component {
 
   static propTypes = {
@@ -41,10 +45,11 @@ class SilentErrorBoundary extends React.Component {
 export const accountsCountRenderer = (displayNumber, pluralReady) => (
   <FormattedMessage
     id='trends.counter_by_accounts'
-    defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} talking'
+    defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}'
     values={{
       count: pluralReady,
       counter: <strong>{displayNumber}</strong>,
+      days: 2,
     }}
   />
 );
@@ -64,7 +69,7 @@ ImmutableHashtag.propTypes = {
   hashtag: ImmutablePropTypes.map.isRequired,
 };
 
-const Hashtag = ({ name, href, to, people, uses, history, className }) => (
+const Hashtag = injectIntl(({ name, href, to, people, uses, history, className, intl }) => (
   <div className={classNames('trends__item', className)}>
     <div className='trends__item__name'>
       <Permalink href={href} to={to}>
@@ -74,9 +79,10 @@ const Hashtag = ({ name, href, to, people, uses, history, className }) => (
       {typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}
     </div>
 
-    <div className='trends__item__current'>
+    <abbr className='trends__item__current' title={intl.formatMessage(messages.totalVolume, { days: 2 })}>
       {typeof uses !== 'undefined' ? <ShortNumber value={uses} /> : <Skeleton width={42} height={36} />}
-    </div>
+      <span className='trends__item__current__asterisk'>*</span>
+    </abbr>
 
     <div className='trends__item__sparkline'>
       <SilentErrorBoundary>
@@ -86,7 +92,7 @@ const Hashtag = ({ name, href, to, people, uses, history, className }) => (
       </SilentErrorBoundary>
     </div>
   </div>
-);
+));
 
 Hashtag.propTypes = {
   name: PropTypes.string,
diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js
index c6f16a792..dfa0efe49 100644
--- a/app/javascript/mastodon/features/ui/components/image_loader.js
+++ b/app/javascript/mastodon/features/ui/components/image_loader.js
@@ -1,10 +1,10 @@
-import React from 'react';
-import PropTypes from 'prop-types';
 import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React, { PureComponent } from 'react';
 import { LoadingBar } from 'react-redux-loading-bar';
 import ZoomableImage from './zoomable_image';
 
-export default class ImageLoader extends React.PureComponent {
+export default class ImageLoader extends PureComponent {
 
   static propTypes = {
     alt: PropTypes.string,
@@ -43,7 +43,7 @@ export default class ImageLoader extends React.PureComponent {
     this.loadImage(this.props);
   }
 
-  componentWillReceiveProps (nextProps) {
+  UNSAFE_componentWillReceiveProps (nextProps) {
     if (this.props.src !== nextProps.src) {
       this.loadImage(nextProps);
     }
@@ -139,14 +139,18 @@ export default class ImageLoader extends React.PureComponent {
 
     return (
       <div className={className}>
-        <LoadingBar loading={loading ? 1 : 0} className='loading-bar' style={{ width: this.state.width || width }} />
         {loading ? (
-          <canvas
-            className='image-loader__preview-canvas'
-            ref={this.setCanvasRef}
-            width={width}
-            height={height}
-          />
+          <>
+            <div className='loading-bar__container' style={{ width: this.state.width || width }}>
+              <LoadingBar className='loading-bar' loading={1} />
+            </div>
+            <canvas
+              className='image-loader__preview-canvas'
+              ref={this.setCanvasRef}
+              width={width}
+              height={height}
+            />
+          </>
         ) : (
           <ZoomableImage
             alt={alt}
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 9027c98bb..8c287f892 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -290,7 +290,11 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "{count, plural, one {{counter} person} other {{counter} people}} talking",
+        "defaultMessage": "Total volume in the last {days, plural, one {day} other {{days} days}}",
+        "id": "hashtag.total_volume"
+      },
+      {
+        "defaultMessage": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}",
         "id": "trends.counter_by_accounts"
       }
     ],
@@ -3765,4 +3769,4 @@
     ],
     "path": "app/javascript/mastodon/features/video/index.json"
   }
-]
\ No newline at end of file
+]
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index e7cbc7e01..0dfb50adc 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -224,6 +224,7 @@
   "hashtag.column_settings.tag_mode.any": "Any of these",
   "hashtag.column_settings.tag_mode.none": "None of these",
   "hashtag.column_settings.tag_toggle": "Include additional tags for this column",
+  "hashtag.total_volume": "Total volume in the last {days, plural, one {day} other {{days} days}}",
   "home.column_settings.basic": "Basic",
   "home.column_settings.show_reblogs": "Show boosts",
   "home.column_settings.show_replies": "Show replies",
@@ -522,7 +523,7 @@
   "timeline_hint.resources.followers": "Followers",
   "timeline_hint.resources.follows": "Follows",
   "timeline_hint.resources.statuses": "Older posts",
-  "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} talking",
+  "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}",
   "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "units.short.billion": "{count}B",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 74bbf0446..3bcae61b8 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -198,7 +198,7 @@
   "explore.trending_tags": "แฮชแท็ก",
   "follow_recommendations.done": "เสร็จสิ้น",
   "follow_recommendations.heading": "ติดตามผู้คนที่คุณต้องการเห็นโพสต์! นี่คือข้อเสนอแนะบางส่วน",
-  "follow_recommendations.lead": "Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!",
+  "follow_recommendations.lead": "โพสต์จากคนที่คุณติดตามจะแสดงตามลำดับเวลาบนฟีดหลักของคุณ อย่ากลัวที่จะทำผิดพลาด คุณสามารถเลิกติดตามผู้คนได้ง่ายๆ ทุกเมื่อ!",
   "follow_request.authorize": "อนุญาต",
   "follow_request.reject": "ปฏิเสธ",
   "follow_requests.unlocked_explanation": "แม้ว่าไม่มีการล็อคบัญชีของคุณ พนักงานของ {domain} คิดว่าคุณอาจต้องการตรวจทานคำขอติดตามจากบัญชีเหล่านี้ด้วยตนเอง",
@@ -268,7 +268,7 @@
   "lightbox.next": "ถัดไป",
   "lightbox.previous": "ก่อนหน้า",
   "limited_account_hint.action": "แสดงโปรไฟล์ต่อไป",
-  "limited_account_hint.title": "This profile has been hidden by the moderators of your server.",
+  "limited_account_hint.title": "โปรไฟล์นี้ถูกซ่อนไว้โดยโมเดอเรเตอร์ของเซิร์ฟเวอร์ของคุณ",
   "lists.account.add": "เพิ่มไปยังรายการ",
   "lists.account.remove": "เอาออกจากรายการ",
   "lists.delete": "ลบรายการ",
@@ -360,7 +360,7 @@
   "notifications.permission_denied_alert": "ไม่สามารถเปิดใช้งานการแจ้งเตือนบนเดสก์ท็อป เนื่องจากมีการปฏิเสธสิทธิอนุญาตเบราว์เซอร์ก่อนหน้านี้",
   "notifications.permission_required": "การแจ้งเตือนบนเดสก์ท็อปไม่พร้อมใช้งานเนื่องจากไม่ได้ให้สิทธิอนุญาตที่จำเป็น",
   "notifications_permission_banner.enable": "เปิดใช้งานการแจ้งเตือนบนเดสก์ท็อป",
-  "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
+  "notifications_permission_banner.how_to_control": "หากต้องการรับการแจ้งเตือนเมื่อไม่ได้เปิด Mastodon ให้เปิดใช้การแจ้งเตือนบนเดสก์ท็อป คุณสามารถควบคุมได้ตามความต้องการด้วยการโต้ตอบประเภทที่สร้างการแจ้งเตือนบนเดสก์ท็อปผ่านปุ่ม {icon} ด้านบนเมื่อเปิดใช้งาน",
   "notifications_permission_banner.title": "ไม่พลาดสิ่งใด",
   "picture_in_picture.restore": "นำกลับมา",
   "poll.closed": "ปิดแล้ว",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index e92227f72..dcb54735f 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -314,7 +314,7 @@
   "navigation_bar.preferences": "首选项",
   "navigation_bar.public_timeline": "跨站公共时间轴",
   "navigation_bar.security": "安全",
-  "notification.admin.report": "{name} reported {target}",
+  "notification.admin.report": "{name} 已报告 {target}",
   "notification.admin.sign_up": "{name} 注册了",
   "notification.favourite": "{name} 喜欢了你的嘟文",
   "notification.follow": "{name} 开始关注你",
@@ -327,7 +327,7 @@
   "notification.update": "{name} 编辑了嘟文",
   "notifications.clear": "清空通知列表",
   "notifications.clear_confirmation": "你确定要永久清空通知列表吗?",
-  "notifications.column_settings.admin.report": "New reports:",
+  "notifications.column_settings.admin.report": "新报告",
   "notifications.column_settings.admin.sign_up": "新注册:",
   "notifications.column_settings.alert": "桌面通知",
   "notifications.column_settings.favourite": "喜欢:",
@@ -433,7 +433,7 @@
   "report.thanks.title_actionable": "感谢提交举报,我们将会进行处理。",
   "report.unfollow": "取消关注 @{name}",
   "report.unfollow_explanation": "你正在关注此账户。如果要想在你的主页上不再看到他们的帖子,取消对他们的关注即可。",
-  "report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} posts}} attached",
+  "report_notification.attached_statuses": "{count, plural, one {{count} 嘟文} other {{count} 嘟文}} 附件",
   "report_notification.categories.other": "其他",
   "report_notification.categories.spam": "骚扰",
   "report_notification.categories.violation": "违反规则",
@@ -468,7 +468,7 @@
   "status.embed": "嵌入",
   "status.favourite": "喜欢",
   "status.filtered": "已过滤",
-  "status.hide": "Hide toot",
+  "status.hide": "屏蔽嘟文",
   "status.history.created": "{name} 创建于 {date}",
   "status.history.edited": "{name} 编辑于 {date}",
   "status.load_more": "加载更多",
@@ -492,7 +492,7 @@
   "status.report": "举报 @{name}",
   "status.sensitive_warning": "敏感内容",
   "status.share": "分享",
-  "status.show_filter_reason": "Show anyway",
+  "status.show_filter_reason": "继续显示",
   "status.show_less": "隐藏内容",
   "status.show_less_all": "隐藏全部内容",
   "status.show_more": "显示更多",
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 26f4c54a3..e9e9a2faa 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -1783,10 +1783,14 @@ a.account__display-name {
     object-fit: contain;
   }
 
-  .loading-bar {
+  .loading-bar__container {
     position: relative;
   }
 
+  .loading-bar {
+    position: absolute;
+  }
+
   &.image-loader--amorphous .image-loader__preview-canvas {
     display: none;
   }
@@ -7239,6 +7243,13 @@ noscript {
       padding-right: 15px;
       margin-left: 5px;
       color: $secondary-text-color;
+      text-decoration: none;
+
+      &__asterisk {
+        color: $darker-text-color;
+        font-size: 18px;
+        vertical-align: super;
+      }
     }
 
     &__sparkline {
diff --git a/app/models/account.rb b/app/models/account.rb
index ee8caebcc..9627cc608 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -62,7 +62,7 @@ class Account < ApplicationRecord
   )
 
   USERNAME_RE   = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
-  MENTION_RE    = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:alnum:]\.\-]+[[:alnum:]]+)?)/i
+  MENTION_RE    = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[[:word:]]+)?)/i
   URL_PREFIX_RE = /\Ahttp(s?):\/\/[^\/]+/
 
   include Attachmentable
diff --git a/app/models/tag.rb b/app/models/tag.rb
index eebf3b47d..8929baf66 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -27,7 +27,7 @@ class Tag < ApplicationRecord
   has_many :followers, through: :passive_relationships, source: :account
 
   HASHTAG_SEPARATORS = "_\u00B7\u200c"
-  HASHTAG_NAME_RE    = "([[:alnum:]_][[:alnum:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}][[:alnum:]#{HASHTAG_SEPARATORS}]*[[:alnum:]_])|([[:alnum:]_]*[[:alpha:]][[:alnum:]_]*)"
+  HASHTAG_NAME_RE    = "([[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}][[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_])|([[:word:]_]*[[:alpha:]][[:word:]_]*)"
   HASHTAG_RE         = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i
 
   validates :name, presence: true, format: { with: /\A(#{HASHTAG_NAME_RE})\z/i }