summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2020-12-15 12:56:43 +0100
committerGitHub <noreply@github.com>2020-12-15 12:56:43 +0100
commit1f564051b6b447e3663852c482982b3ff5a2f238 (patch)
tree97e9d7b5d77971e1ad9013247e4c64b1c3a3b58d
parent1045549f85041b13002801808b30a332c3a68c61 (diff)
Change RTL detection to rely on unicode-bidi paragraph by paragraph (#14573)
-rw-r--r--app/helpers/statuses_helper.rb20
-rw-r--r--app/javascript/mastodon/components/autosuggest_input.js8
-rw-r--r--app/javascript/mastodon/components/autosuggest_textarea.js8
-rw-r--r--app/javascript/mastodon/components/status_content.js18
-rw-r--r--app/javascript/mastodon/features/compose/components/reply_indicator.js6
-rw-r--r--app/javascript/mastodon/rtl.js32
-rw-r--r--app/javascript/styles/mailer.scss12
-rw-r--r--app/javascript/styles/mastodon/components.scss2
-rw-r--r--app/views/notification_mailer/_status.html.haml4
-rw-r--r--app/views/statuses/_detailed_status.html.haml2
-rw-r--r--app/views/statuses/_simple_status.html.haml2
-rw-r--r--spec/helpers/statuses_helper_spec.rb18
12 files changed, 26 insertions, 106 deletions
diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb
index daed9048f45..1f654f34fc9 100644
--- a/app/helpers/statuses_helper.rb
+++ b/app/helpers/statuses_helper.rb
@@ -92,22 +92,6 @@ module StatusesHelper
end
end
- def rtl_status?(status)
- status.local? ? rtl?(status.text) : rtl?(strip_tags(status.text))
- end
-
- def rtl?(text)
- text = simplified_text(text)
- rtl_words = text.scan(/[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}]+/m)
-
- if rtl_words.present?
- total_size = text.size.to_f
- rtl_size(rtl_words) / total_size > 0.3
- else
- false
- end
- end
-
def fa_visibility_icon(status)
case status.visibility
when 'public'
@@ -143,10 +127,6 @@ module StatusesHelper
end
end
- def rtl_size(words)
- words.reduce(0) { |acc, elem| acc + elem.size }.to_f
- end
-
def embedded_view?
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
end
diff --git a/app/javascript/mastodon/components/autosuggest_input.js b/app/javascript/mastodon/components/autosuggest_input.js
index 6d2035add01..5187f95c847 100644
--- a/app/javascript/mastodon/components/autosuggest_input.js
+++ b/app/javascript/mastodon/components/autosuggest_input.js
@@ -4,7 +4,6 @@ import AutosuggestEmoji from './autosuggest_emoji';
import AutosuggestHashtag from './autosuggest_hashtag';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import classNames from 'classnames';
import { List as ImmutableList } from 'immutable';
@@ -189,11 +188,6 @@ export default class AutosuggestInput extends ImmutablePureComponent {
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props;
const { suggestionsHidden } = this.state;
- const style = { direction: 'ltr' };
-
- if (isRtl(value)) {
- style.direction = 'rtl';
- }
return (
<div className='autosuggest-input'>
@@ -212,7 +206,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
- style={style}
+ dir='auto'
aria-autocomplete='list'
id={id}
className={className}
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index 58ec4f6eb66..08b9cd80bbf 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -4,7 +4,6 @@ import AutosuggestEmoji from './autosuggest_emoji';
import AutosuggestHashtag from './autosuggest_hashtag';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
import classNames from 'classnames';
@@ -195,11 +194,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
const { suggestionsHidden } = this.state;
- const style = { direction: 'ltr' };
-
- if (isRtl(value)) {
- style.direction = 'rtl';
- }
return [
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
@@ -220,7 +214,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
- style={style}
+ dir='auto'
aria-autocomplete='list'
/>
</label>
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 3200f2d82f6..185a2a663f9 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -1,7 +1,6 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
import { FormattedMessage } from 'react-intl';
import Permalink from './permalink';
import classnames from 'classnames';
@@ -186,17 +185,12 @@ export default class StatusContent extends React.PureComponent {
const content = { __html: status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };
- const directionStyle = { direction: 'ltr' };
const classNames = classnames('status__content', {
'status__content--with-action': this.props.onClick && this.context.router,
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
'status__content--collapsed': renderReadMore,
});
- if (isRtl(status.get('search_index'))) {
- directionStyle.direction = 'rtl';
- }
-
const showThreadButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick}>
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
@@ -225,7 +219,7 @@ export default class StatusContent extends React.PureComponent {
}
return (
- <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
+ <div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
<span dangerouslySetInnerHTML={spoilerContent} />
{' '}
@@ -234,7 +228,7 @@ export default class StatusContent extends React.PureComponent {
{mentionsPlaceholder}
- <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
+ <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} dangerouslySetInnerHTML={content} />
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
@@ -243,8 +237,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'>
- <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
+ <div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>
+ <div className='status__content__text status__content__text--visible' dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
@@ -259,8 +253,8 @@ export default class StatusContent extends React.PureComponent {
return output;
} else {
return (
- <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle}>
- <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
+ <div className={classNames} ref={this.setRef} tabIndex='0'>
+ <div className='status__content__text status__content__text--visible' dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js
index 66dc8574216..85638389324 100644
--- a/app/javascript/mastodon/features/compose/components/reply_indicator.js
+++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js
@@ -6,7 +6,6 @@ import IconButton from '../../../components/icon_button';
import DisplayName from '../../../components/display_name';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { isRtl } from '../../../rtl';
import AttachmentList from 'mastodon/components/attachment_list';
const messages = defineMessages({
@@ -45,9 +44,6 @@ class ReplyIndicator extends ImmutablePureComponent {
}
const content = { __html: status.get('contentHtml') };
- const style = {
- direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
- };
return (
<div className='reply-indicator'>
@@ -60,7 +56,7 @@ class ReplyIndicator extends ImmutablePureComponent {
</a>
</div>
- <div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />
+ <div className='reply-indicator__content' dangerouslySetInnerHTML={content} />
{status.get('media_attachments').size > 0 && (
<AttachmentList
diff --git a/app/javascript/mastodon/rtl.js b/app/javascript/mastodon/rtl.js
deleted file mode 100644
index 89bed6de888..00000000000
--- a/app/javascript/mastodon/rtl.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// U+0590 to U+05FF - Hebrew
-// U+0600 to U+06FF - Arabic
-// U+0700 to U+074F - Syriac
-// U+0750 to U+077F - Arabic Supplement
-// U+0780 to U+07BF - Thaana
-// U+07C0 to U+07FF - N'Ko
-// U+0800 to U+083F - Samaritan
-// U+08A0 to U+08FF - Arabic Extended-A
-// U+FB1D to U+FB4F - Hebrew presentation forms
-// U+FB50 to U+FDFF - Arabic presentation forms A
-// U+FE70 to U+FEFF - Arabic presentation forms B
-
-const rtlChars = /[\u0590-\u083F]|[\u08A0-\u08FF]|[\uFB1D-\uFDFF]|[\uFE70-\uFEFF]/mg;
-
-export function isRtl(text) {
- if (text.length === 0) {
- return false;
- }
-
- text = text.replace(/(?:^|[^\/\w])@([a-z0-9_]+(@[a-z0-9\.\-]+)?)/ig, '');
- text = text.replace(/(?:^|[^\/\w])#([\S]+)/ig, '');
- text = text.replace(/\s+/g, '');
- text = text.replace(/(\w\S+\.\w{2,}\S*)/g, '');
-
- const matches = text.match(rtlChars);
-
- if (!matches) {
- return false;
- }
-
- return matches.length / text.length > 0.3;
-};
diff --git a/app/javascript/styles/mailer.scss b/app/javascript/styles/mailer.scss
index e25a80c0432..55ebd309195 100644
--- a/app/javascript/styles/mailer.scss
+++ b/app/javascript/styles/mailer.scss
@@ -58,6 +58,16 @@ td {
vertical-align: top;
}
+.auto-dir {
+ p {
+ unicode-bidi: plaintext;
+ }
+
+ a {
+ unicode-bidi: isolate;
+ }
+}
+
.email-table,
.content-section,
.column,
@@ -96,7 +106,7 @@ body {
.col-3,
.col-4,
.col-5,
-.col-6, {
+.col-6 {
font-size: 0;
display: inline-block;
width: 100%;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 2fc1c12de9c..22bf025fb69 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -831,6 +831,7 @@
p {
margin-bottom: 20px;
white-space: pre-wrap;
+ unicode-bidi: plaintext;
&:last-child {
margin-bottom: 0;
@@ -840,6 +841,7 @@
a {
color: $secondary-text-color;
text-decoration: none;
+ unicode-bidi: isolate;
&:hover {
text-decoration: underline;
diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml
index e992e5563dc..9b7e1b65c63 100644
--- a/app/views/notification_mailer/_status.html.haml
+++ b/app/views/notification_mailer/_status.html.haml
@@ -26,11 +26,11 @@
= "@#{status.account.acct}"
- if status.spoiler_text?
- %div{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
+ %div.auto-dir
%p
= Formatter.instance.format_spoiler(status)
- %div{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
+ %div.auto-dir
= Formatter.instance.format(status)
- if status.media_attachments.size > 0
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index a4dd8534fa5..4c879472d8c 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -20,7 +20,7 @@
%p<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more')
- .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
+ .e-content
= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
- if status.preloadable_poll
= react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index d60ade22ff1..236948a84c2 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -29,7 +29,7 @@
%p<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more')
- .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
+ .e-content
= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
- if status.preloadable_poll
= react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
diff --git a/spec/helpers/statuses_helper_spec.rb b/spec/helpers/statuses_helper_spec.rb
index 940ff072e60..cba659bfb5b 100644
--- a/spec/helpers/statuses_helper_spec.rb
+++ b/spec/helpers/statuses_helper_spec.rb
@@ -149,22 +149,4 @@ RSpec.describe StatusesHelper, type: :helper do
expect(css_class).to eq 'h-cite'
end
end
-
- describe '#rtl?' do
- it 'is false if text is empty' do
- expect(helper).not_to be_rtl ''
- end
-
- it 'is false if there are no right to left characters' do
- expect(helper).not_to be_rtl 'hello world'
- end
-
- it 'is false if right to left characters are fewer than 1/3 of total text' do
- expect(helper).not_to be_rtl 'hello ݟ world'
- end
-
- it 'is true if right to left characters are greater than 1/3 of total text' do
- expect(helper).to be_rtl 'aaݟaaݟ'
- end
- end
end