diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx
index 6bb04ddab88..facf7ae2272 100644
--- a/app/javascript/mastodon/components/status_content.jsx
+++ b/app/javascript/mastodon/components/status_content.jsx
@@ -108,11 +108,12 @@ class StatusContent extends PureComponent {
const { status, onCollapsedToggle } = this.props;
if (status.get('collapsed', null) === null && onCollapsedToggle) {
const { collapsible, onClick } = this.props;
+ const text = node.querySelector(':scope > .status__content__text');
const collapsed =
collapsible
&& onClick
- && node.clientHeight > MAX_HEIGHT
+ && (node.clientHeight > MAX_HEIGHT || (text !== null && text.scrollWidth > text.clientWidth))
&& status.get('spoiler_text').length === 0;
onCollapsedToggle(collapsed);
diff --git a/app/javascript/mastodon/components/status_quoted.stories.tsx b/app/javascript/mastodon/components/status_quoted.stories.tsx
index aa17a5422cc..5b78d3a3c57 100644
--- a/app/javascript/mastodon/components/status_quoted.stories.tsx
+++ b/app/javascript/mastodon/components/status_quoted.stories.tsx
@@ -1,5 +1,8 @@
+import { Map as ImmutableMap } from 'immutable';
+
import type { Meta, StoryObj } from '@storybook/react-vite';
+import type { ApiQuoteJSON } from '@/mastodon/api_types/quotes';
import { accountFactoryState, statusFactoryState } from '@/testing/factories';
import type { StatusQuoteManagerProps } from './status_quoted';
@@ -10,9 +13,6 @@ const meta = {
render(args) {
return ;
},
- args: {
- id: '1',
- },
parameters: {
state: {
accounts: {
@@ -21,8 +21,40 @@ const meta = {
statuses: {
'1': statusFactoryState({
id: '1',
+ language: 'en',
text: 'Hello world!',
}),
+ '2': statusFactoryState({
+ id: '2',
+ language: 'en',
+ text: 'Quote!',
+ quote: ImmutableMap({
+ state: 'accepted',
+ quoted_status: '1',
+ }) as unknown as ApiQuoteJSON,
+ }),
+ '1001': statusFactoryState({
+ id: '1001',
+ language: 'mn-Mong',
+ // meaning: Mongolia
+ text: 'ᠮᠤᠩᠭᠤᠯ',
+ }),
+ '1002': statusFactoryState({
+ id: '1002',
+ language: 'mn-Mong',
+ // meaning: All human beings are born free and equal in dignity and rights.
+ text: 'ᠬᠦᠮᠦᠨ ᠪᠦᠷ ᠲᠥᠷᠥᠵᠦ ᠮᠡᠨᠳᠡᠯᠡᠬᠦ ᠡᠷᠬᠡ ᠴᠢᠯᠥᠭᠡ ᠲᠡᠢ᠂ ᠠᠳᠠᠯᠢᠬᠠᠨ ᠨᠡᠷᠡ ᠲᠥᠷᠥ ᠲᠡᠢ᠂ ᠢᠵᠢᠯ ᠡᠷᠬᠡ ᠲᠡᠢ ᠪᠠᠢᠠᠭ᠃',
+ }),
+ '1003': statusFactoryState({
+ id: '1003',
+ language: 'mn-Mong',
+ // meaning: Mongolia
+ text: 'ᠮᠤᠩᠭᠤᠯ',
+ quote: ImmutableMap({
+ state: 'accepted',
+ quoted_status: '1002',
+ }) as unknown as ApiQuoteJSON,
+ }),
},
},
},
@@ -32,4 +64,34 @@ export default meta;
type Story = StoryObj;
-export const Default: Story = {};
+export const Default: Story = {
+ args: {
+ id: '1',
+ },
+};
+
+export const Quote: Story = {
+ args: {
+ id: '2',
+ },
+};
+
+export const TraditionalMongolian: Story = {
+ args: {
+ id: '1001',
+ },
+};
+
+export const LongTraditionalMongolian: Story = {
+ args: {
+ id: '1002',
+ },
+};
+
+// TODO: fix quoted rotated Mongolian script text
+// https://github.com/mastodon/mastodon/pull/37204#issuecomment-3661767226
+export const QuotedTraditionalMongolian: Story = {
+ args: {
+ id: '1003',
+ },
+};
diff --git a/app/javascript/styles/mastodon/_variables.scss b/app/javascript/styles/mastodon/_variables.scss
index d561f714547..7a947891d52 100644
--- a/app/javascript/styles/mastodon/_variables.scss
+++ b/app/javascript/styles/mastodon/_variables.scss
@@ -6,6 +6,9 @@ $backdrop-blur-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
// Language codes that uses CJK fonts
$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
+// Language codes that is written vertically
+$vertical-lr-langs: mn-Mong;
+
// Variables for components
$media-modal-media-max-width: 100%;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 93d77e2905a..acec3bc2d65 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -1343,6 +1343,38 @@ body > [data-popper-placement] {
}
}
+@each $lang in $vertical-lr-langs {
+ // writing-mode and width must be applied to a single element. When
+ // this element inherits a text direction that is opposite to its own,
+ // the start of this element's text is cut off.
+
+ .status:not(.status--is-quote)
+ > .status__content
+ > .status__content__text:lang(#{$lang}),
+ .conversation
+ > .conversation__content
+ > .status__content
+ > .status__content__text:lang(#{$lang}) {
+ writing-mode: vertical-lr;
+ width: 100%; // detecting overflow
+ max-width: calc(100% - mod(100%, 22px)); // avoid cut-offs
+ max-height: 209px; // roughly above 500 characters, readable
+ overflow-x: hidden; // read more
+ }
+
+ .autosuggest-textarea > .autosuggest-textarea__textarea:lang(#{$lang}) {
+ writing-mode: vertical-lr;
+ min-height: 209px; // writable
+ }
+
+ .detailed-status > .status__content > .status__content__text:lang(#{$lang}) {
+ writing-mode: vertical-lr;
+ width: 100%; // detecting overflow
+ max-height: 50vh;
+ overflow-x: auto;
+ }
+}
+
.status__content.status__content--collapsed {
max-height: 22px * 15; // 15 lines is roughly above 500 characters
}