this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Basic text highlighting for composer

This will probably be very buggy

+166 -2
+83
src/components/compose.css
··· 619 619 #custom-emojis-sheet .custom-emojis-list button:is(:hover, :focus) img { 620 620 transform: scale(1.5); 621 621 } 622 + 623 + .compose-field-container { 624 + display: grid !important; 625 + 626 + &.debug { 627 + grid-template-columns: 1fr 1fr; 628 + } 629 + 630 + > * { 631 + grid-area: 1 / 1 / 2 / 2; 632 + } 633 + 634 + .compose-highlight { 635 + user-drag: none; 636 + user-select: none; 637 + pointer-events: none; 638 + touch-action: none; 639 + padding: 8px; 640 + color: transparent; 641 + background-color: transparent; 642 + border: 2px solid transparent; 643 + line-height: 1.4; 644 + overflow: auto; 645 + unicode-bidi: plaintext; 646 + -webkit-rtl-ordering: logical; 647 + rtl-ordering: logical; 648 + overflow-wrap: break-word; 649 + white-space: pre-wrap; 650 + min-height: 5em; 651 + max-height: 50vh; 652 + 653 + /* Follow textarea styles */ 654 + @media (min-width: 40em) { 655 + max-height: 65vh; 656 + } 657 + @media (width < 30em) { 658 + margin-inline: calc(-1 * var(--form-padding-inline)); 659 + width: 100vw !important; 660 + max-width: 100vw; 661 + border: 0; 662 + } 663 + 664 + mark { 665 + color: inherit; 666 + } 667 + 668 + .compose-highlight-url, 669 + .compose-highlight-hashtag { 670 + background-color: transparent; 671 + text-decoration: underline; 672 + text-decoration-color: var(--link-faded-color); 673 + text-decoration-thickness: 2px; 674 + text-underline-offset: 2px; 675 + } 676 + .compose-highlight-mention, 677 + .compose-highlight-emoji-shortcode, 678 + .compose-highlight-exceeded { 679 + mix-blend-mode: multiply; 680 + border-radius: 4px; 681 + box-shadow: 0 0 0 1px; 682 + } 683 + .compose-highlight-mention { 684 + background-color: var(--orange-light-bg-color); 685 + box-shadow-color: var(--orange-light-bg-color); 686 + } 687 + .compose-highlight-emoji-shortcode { 688 + background-color: var(--bg-faded-color); 689 + box-shadow-color: var(--bg-faded-color); 690 + } 691 + .compose-highlight-exceeded { 692 + background-color: var(--red-bg-color); 693 + box-shadow-color: var(--red-bg-color); 694 + } 695 + 696 + @media (prefers-color-scheme: dark) { 697 + .compose-highlight-mention, 698 + .compose-highlight-emoji-shortcode, 699 + .compose-highlight-exceeded { 700 + mix-blend-mode: screen; 701 + } 702 + } 703 + } 704 + }
+77 -2
src/components/compose.jsx
··· 104 104 .replace(usernameRegex, '$1@$3'); 105 105 } 106 106 107 + // https://github.com/mastodon/mastodon/blob/c03bd2a238741a012aa4b98dc4902d6cf948ab63/app/models/account.rb#L69 108 + const USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i; 109 + const MENTION_RE = new RegExp( 110 + `(?<![=\\/\\w])@((${USERNAME_RE.source})(?:@[\\w.-]+[\\w]+)?)`, 111 + 'ig', 112 + ); 113 + 114 + // AI-generated, all other regexes are too complicated 115 + const HASHTAG_RE = new RegExp( 116 + `(?<![=\\/\\w])#([a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?)(?![\\/\\w])`, 117 + 'ig', 118 + ); 119 + 120 + // https://github.com/mastodon/mastodon/blob/23e32a4b3031d1da8b911e0145d61b4dd47c4f96/app/models/custom_emoji.rb#L31 121 + const SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}'; 122 + const SCAN_RE = new RegExp( 123 + `(?<=[^A-Za-z0-9_:\\n]|^):(${SHORTCODE_RE_FRAGMENT}):(?=[^A-Za-z0-9_:]|$)`, 124 + 'g', 125 + ); 126 + 127 + function highlightText(text, { maxCharacters = Infinity }) { 128 + // Accept text string, return formatted HTML string 129 + let html = text; 130 + // Exceeded characters limit 131 + const { composerCharacterCount } = states; 132 + let leftoverHTML = ''; 133 + if (composerCharacterCount > maxCharacters) { 134 + const leftoverCount = composerCharacterCount - maxCharacters; 135 + leftoverHTML = html.slice(-leftoverCount); 136 + html = html.slice(0, -leftoverCount); 137 + // Highlight exceeded characters 138 + leftoverHTML = leftoverHTML.replace( 139 + new RegExp(`(.{${leftoverCount}})$`), 140 + '<mark class="compose-highlight-exceeded">$1</mark>', 141 + ); 142 + } 143 + 144 + html = html 145 + .replace(urlRegexObj, '$2<mark class="compose-highlight-url">$3</mark>') // URLs 146 + .replace(MENTION_RE, '<mark class="compose-highlight-mention">$&</mark>') // Mentions 147 + .replace(HASHTAG_RE, '<mark class="compose-highlight-hashtag">#$1</mark>') // Hashtags 148 + .replace( 149 + SCAN_RE, 150 + '<mark class="compose-highlight-emoji-shortcode">$&</mark>', 151 + ); // Emoji shortcodes 152 + 153 + return html + leftoverHTML; 154 + } 155 + 107 156 function Compose({ 108 157 onClose, 109 158 replyToStatus, ··· 1387 1436 handleCommited = (e) => { 1388 1437 const { input } = e.detail; 1389 1438 setText(input.value); 1439 + // fire input event 1440 + if (ref.current) { 1441 + const event = new Event('input', { bubbles: true }); 1442 + ref.current.dispatchEvent(event); 1443 + } 1390 1444 }; 1391 1445 1392 1446 textExpanderRef.current.addEventListener( ··· 1413 1467 }; 1414 1468 }, []); 1415 1469 1470 + const composeHighlightRef = useRef(); 1471 + 1416 1472 return ( 1417 - <text-expander ref={textExpanderRef} keys="@ # :"> 1473 + <text-expander 1474 + ref={textExpanderRef} 1475 + keys="@ # :" 1476 + class="compose-field-container" 1477 + > 1418 1478 <textarea 1419 1479 class="compose-field" 1420 1480 autoCapitalize="sentences" ··· 1466 1526 }} 1467 1527 onInput={(e) => { 1468 1528 const { target } = e; 1469 - setText(target.value); 1529 + const text = target.value; 1530 + setText(text); 1470 1531 autoResizeTextarea(target); 1471 1532 props.onInput?.(e); 1533 + composeHighlightRef.current.innerHTML = 1534 + highlightText(text, { 1535 + maxCharacters, 1536 + }) + '\n'; 1537 + // Newline to prevent multiple line breaks at the end from being collapsed, no idea why 1472 1538 }} 1473 1539 style={{ 1474 1540 width: '100%', 1475 1541 height: '4em', 1476 1542 // '--text-weight': (1 + charCount / 140).toFixed(1) || 1, 1477 1543 }} 1544 + onScroll={(e) => { 1545 + const { scrollTop } = e.target; 1546 + composeHighlightRef.current.scrollTop = scrollTop; 1547 + }} 1548 + /> 1549 + <div 1550 + ref={composeHighlightRef} 1551 + class="compose-highlight" 1552 + aria-hidden="true" 1478 1553 /> 1479 1554 </text-expander> 1480 1555 );
+6
src/index.css
··· 18 18 --purple-color: blueviolet; 19 19 --green-color: darkgreen; 20 20 --orange-color: darkorange; 21 + --orange-light-bg-color: color-mix( 22 + in srgb, 23 + var(--orange-color) 20%, 24 + transparent 25 + ); 21 26 --red-color: orangered; 27 + --red-bg-color: color-mix(in lch, var(--red-color) 40%, transparent); 22 28 --bg-color: #fff; 23 29 --bg-faded-color: #f0f2f5; 24 30 --bg-blur-color: #fff9;