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 }