diff --git a/src/components/compose.css b/src/components/compose.css index 4870063..5ae798c 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -619,3 +619,86 @@ #custom-emojis-sheet .custom-emojis-list button:is(:hover, :focus) img { transform: scale(1.5); } + +.compose-field-container { + display: grid !important; + + &.debug { + grid-template-columns: 1fr 1fr; + } + + > * { + grid-area: 1 / 1 / 2 / 2; + } + + .compose-highlight { + user-drag: none; + user-select: none; + pointer-events: none; + touch-action: none; + padding: 8px; + color: transparent; + background-color: transparent; + border: 2px solid transparent; + line-height: 1.4; + overflow: auto; + unicode-bidi: plaintext; + -webkit-rtl-ordering: logical; + rtl-ordering: logical; + overflow-wrap: break-word; + white-space: pre-wrap; + min-height: 5em; + max-height: 50vh; + + /* Follow textarea styles */ + @media (min-width: 40em) { + max-height: 65vh; + } + @media (width < 30em) { + margin-inline: calc(-1 * var(--form-padding-inline)); + width: 100vw !important; + max-width: 100vw; + border: 0; + } + + mark { + color: inherit; + } + + .compose-highlight-url, + .compose-highlight-hashtag { + background-color: transparent; + text-decoration: underline; + text-decoration-color: var(--link-faded-color); + text-decoration-thickness: 2px; + text-underline-offset: 2px; + } + .compose-highlight-mention, + .compose-highlight-emoji-shortcode, + .compose-highlight-exceeded { + mix-blend-mode: multiply; + border-radius: 4px; + box-shadow: 0 0 0 1px; + } + .compose-highlight-mention { + background-color: var(--orange-light-bg-color); + box-shadow-color: var(--orange-light-bg-color); + } + .compose-highlight-emoji-shortcode { + background-color: var(--bg-faded-color); + box-shadow-color: var(--bg-faded-color); + } + .compose-highlight-exceeded { + background-color: var(--red-bg-color); + box-shadow-color: var(--red-bg-color); + } + + @media (prefers-color-scheme: dark) { + .compose-highlight-mention, + .compose-highlight-emoji-shortcode, + .compose-highlight-exceeded { + mix-blend-mode: screen; + } + } + } +} diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 18480db..0fe0610 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -104,6 +104,55 @@ function countableText(inputText) { .replace(usernameRegex, '$1@$3'); } +// https://github.com/mastodon/mastodon/blob/c03bd2a238741a012aa4b98dc4902d6cf948ab63/app/models/account.rb#L69 +const USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i; +const MENTION_RE = new RegExp( + `(? maxCharacters) { + const leftoverCount = composerCharacterCount - maxCharacters; + leftoverHTML = html.slice(-leftoverCount); + html = html.slice(0, -leftoverCount); + // Highlight exceeded characters + leftoverHTML = leftoverHTML.replace( + new RegExp(`(.{${leftoverCount}})$`), + '$1', + ); + } + + html = html + .replace(urlRegexObj, '$2$3') // URLs + .replace(MENTION_RE, '$&') // Mentions + .replace(HASHTAG_RE, '#$1') // Hashtags + .replace( + SCAN_RE, + '$&', + ); // Emoji shortcodes + + return html + leftoverHTML; +} + function Compose({ onClose, replyToStatus, @@ -1387,6 +1436,11 @@ const Textarea = forwardRef((props, ref) => { handleCommited = (e) => { const { input } = e.detail; setText(input.value); + // fire input event + if (ref.current) { + const event = new Event('input', { bubbles: true }); + ref.current.dispatchEvent(event); + } }; textExpanderRef.current.addEventListener( @@ -1413,8 +1467,14 @@ const Textarea = forwardRef((props, ref) => { }; }, []); + const composeHighlightRef = useRef(); + return ( - +