From d75c8506ce820d19d85a65f8fa32bcdb50a7fb23 Mon Sep 17 00:00:00 2001 From: KMY Date: Thu, 28 Sep 2023 16:17:58 +0900 Subject: [PATCH] #38 Show alert when mention is added to limited post --- .../compose/containers/warning_container.jsx | 11 +++++-- app/javascript/mastodon/reducers/compose.js | 4 +++ app/javascript/mastodon/utils/mentions.ts | 29 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 app/javascript/mastodon/utils/mentions.ts diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.jsx b/app/javascript/mastodon/features/compose/containers/warning_container.jsx index cfa8e8ab7d..4daeeecb99 100644 --- a/app/javascript/mastodon/features/compose/containers/warning_container.jsx +++ b/app/javascript/mastodon/features/compose/containers/warning_container.jsx @@ -6,6 +6,7 @@ import { connect } from 'react-redux'; import { me } from 'mastodon/initial_state'; import { HASHTAG_PATTERN_REGEX } from 'mastodon/utils/hashtags'; +import { MENTION_PATTERN_REGEX } from 'mastodon/utils/mentions'; import Warning from '../components/warning'; @@ -14,10 +15,11 @@ const mapStateToProps = state => ({ hashtagWarning: !['public', 'public_unlisted', 'login'].includes(state.getIn(['compose', 'privacy'])) && state.getIn(['compose', 'searchability']) !== 'public' && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])), directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct', searchabilityWarning: state.getIn(['compose', 'searchability']) === 'limited', - limitedPostWarning: ['mutual', 'circle'].includes(state.getIn(['compose', 'privacy'])), + mentionWarning: ['mutual', 'circle', 'limited'].includes(state.getIn(['compose', 'privacy'])) && MENTION_PATTERN_REGEX.test(state.getIn(['compose', 'text'])), + limitedPostWarning: ['mutual', 'circle'].includes(state.getIn(['compose', 'privacy'])) && !state.getIn(['compose', 'limited_scope']), }); -const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning, searchabilityWarning, limitedPostWarning }) => { +const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning, searchabilityWarning, mentionWarning, limitedPostWarning }) => { if (needsLockWarning) { return }} />} />; } @@ -40,6 +42,10 @@ const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning return } />; } + if (mentionWarning) { + return } />; + } + if (limitedPostWarning) { return } />; } @@ -52,6 +58,7 @@ WarningWrapper.propTypes = { hashtagWarning: PropTypes.bool, directMessageWarning: PropTypes.bool, searchabilityWarning: PropTypes.bool, + mentionWarning: PropTypes.bool, limitedPostWarning: PropTypes.bool, }; diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 44665f23af..dc34e7f1af 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -144,6 +144,7 @@ function clearAll(state) { if (!state.get('in_reply_to')) { map.set('posted_on_this_session', true); } + map.set('limited_scope', null); map.set('id', null); map.set('in_reply_to', null); map.set('searchability', state.get('default_searchability')); @@ -411,6 +412,7 @@ export default function compose(state = initialState, action) { map.set('in_reply_to', action.status.get('id')); map.set('text', statusToTextMentions(state, action.status)); map.set('privacy', privacyPreference(action.status.get('visibility_ex'), state.get('default_privacy'))); + map.set('limited_scope', null); map.set('searchability', privacyPreference(action.status.get('searchability'), state.get('default_searchability'))); map.set('focusDate', new Date()); map.set('caretPosition', null); @@ -547,6 +549,7 @@ export default function compose(state = initialState, action) { map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status))); map.set('in_reply_to', action.status.get('in_reply_to_id')); map.set('privacy', action.status.get('visibility_ex')); + map.set('limited_scope', null); map.set('media_attachments', action.status.get('media_attachments').map((media) => media.set('unattached', true))); map.set('focusDate', new Date()); map.set('caretPosition', null); @@ -582,6 +585,7 @@ export default function compose(state = initialState, action) { } else { map.set('privacy', action.status.get('limited_scope') === 'mutual' ? 'mutual' : 'circle'); } + map.set('limited_scope', action.status.get('limited_scope')); map.set('media_attachments', action.status.get('media_attachments')); map.set('focusDate', new Date()); map.set('caretPosition', null); diff --git a/app/javascript/mastodon/utils/mentions.ts b/app/javascript/mastodon/utils/mentions.ts new file mode 100644 index 0000000000..0fbf28a3cf --- /dev/null +++ b/app/javascript/mastodon/utils/mentions.ts @@ -0,0 +1,29 @@ +const MENTION_SEPARATORS = '_\\u00b7\\u200c'; +const ALPHA = '\\p{L}\\p{M}'; +const WORD = '\\p{L}\\p{M}\\p{N}\\p{Pc}'; + +const buildMentionPatternRegex = () => { + try { + return new RegExp( + `(?:^|[^\\/\\)\\w])@(([${WORD}_][${WORD}${MENTION_SEPARATORS}]*[${ALPHA}${MENTION_SEPARATORS}][${WORD}${MENTION_SEPARATORS}]*[${WORD}_])|([${WORD}_]*[${ALPHA}][${WORD}_]*))`, + 'iu', + ); + } catch { + return /(?:^|[^/)\w])#(\w*[a-zA-Z·]\w*)/i; + } +}; + +const buildMentionRegex = () => { + try { + return new RegExp( + `^(([${WORD}_][${WORD}${MENTION_SEPARATORS}]*[${ALPHA}${MENTION_SEPARATORS}][${WORD}${MENTION_SEPARATORS}]*[${WORD}_])|([${WORD}_]*[${ALPHA}][${WORD}_]*))$`, + 'iu', + ); + } catch { + return /^(\w*[a-zA-Z·]\w*)$/i; + } +}; + +export const MENTION_PATTERN_REGEX = buildMentionPatternRegex(); + +export const MENTION_REGEX = buildMentionRegex();