this repo has no description
0
fork

Configure Feed

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

Experiment: allow minimize composer

+216 -30
+41
src/app.css
··· 1609 1609 bottom: calc(16px + env(safe-area-inset-bottom) + 52px); 1610 1610 } 1611 1611 } 1612 + #compose-button { 1613 + &.min { 1614 + outline: 2px solid var(--button-text-color); 1615 + 1616 + &:after { 1617 + content: ''; 1618 + display: block; 1619 + position: absolute; 1620 + top: 0; 1621 + right: 0; 1622 + width: 14px; 1623 + height: 14px; 1624 + border-radius: 50%; 1625 + background-color: var(--button-bg-color); 1626 + border: 2px solid var(--button-text-color); 1627 + box-shadow: 0 2px 8px var(--drop-shadow-color); 1628 + opacity: 0; 1629 + transition: opacity 0.2s ease-out 0.5s; 1630 + opacity: 1; 1631 + } 1632 + } 1633 + 1634 + &.loading { 1635 + outline-color: var(--button-bg-blur-color); 1636 + 1637 + &:before { 1638 + position: absolute; 1639 + inset: 0; 1640 + content: ''; 1641 + border-radius: 50%; 1642 + animation: spin 5s linear infinite; 1643 + border: 2px dashed var(--button-text-color); 1644 + } 1645 + } 1646 + 1647 + &.error { 1648 + &:after { 1649 + background-color: var(--red-color); 1650 + } 1651 + } 1652 + } 1612 1653 1613 1654 /* SHEET */ 1614 1655
+1
src/components/ICONS.jsx
··· 108 108 settings: () => import('@iconify-icons/mingcute/settings-6-line'), 109 109 'heart-break': () => import('@iconify-icons/mingcute/heart-crack-line'), 110 110 'user-x': () => import('@iconify-icons/mingcute/user-x-line'), 111 + minimize: () => import('@iconify-icons/mingcute/arrows-down-line'), 111 112 };
+3 -2
src/components/account-info.jsx
··· 19 19 import niceDateTime from '../utils/nice-date-time'; 20 20 import pmem from '../utils/pmem'; 21 21 import shortenNumber from '../utils/shorten-number'; 22 + import showCompose from '../utils/show-compose'; 22 23 import showToast from '../utils/show-toast'; 23 24 import states, { hideAllModals } from '../utils/states'; 24 25 import store from '../utils/store'; ··· 1081 1082 <> 1082 1083 <MenuItem 1083 1084 onClick={() => { 1084 - states.showCompose = { 1085 + showCompose({ 1085 1086 draftStatus: { 1086 1087 status: `@${currentInfo?.acct || acct} `, 1087 1088 }, 1088 - }; 1089 + }); 1089 1090 }} 1090 1091 > 1091 1092 <Icon icon="at" />
+17 -1
src/components/compose-button.jsx
··· 1 1 import { useHotkeys } from 'react-hotkeys-hook'; 2 + import { useSnapshot } from 'valtio'; 2 3 3 4 import openCompose from '../utils/open-compose'; 4 5 import openOSK from '../utils/open-osk'; ··· 7 8 import Icon from './icon'; 8 9 9 10 export default function ComposeButton() { 11 + const snapStates = useSnapshot(states); 12 + 10 13 function handleButton(e) { 14 + if (snapStates.composerState.minimized) { 15 + states.composerState.minimized = false; 16 + openOSK(); 17 + return; 18 + } 19 + 11 20 if (e.shiftKey) { 12 21 const newWin = openCompose(); 13 22 ··· 28 37 }); 29 38 30 39 return ( 31 - <button type="button" id="compose-button" onClick={handleButton}> 40 + <button 41 + type="button" 42 + id="compose-button" 43 + onClick={handleButton} 44 + class={`${snapStates.composerState.minimized ? 'min' : ''} ${ 45 + snapStates.composerState.publishing ? 'loading' : '' 46 + } ${snapStates.composerState.publishingError ? 'error' : ''}`} 47 + > 32 48 <Icon icon="quill" size="xl" alt="Compose" /> 33 49 </button> 34 50 );
+26 -1
src/components/compose.jsx
··· 514 514 // I don't think this warrant a draft mode for a status that's already posted 515 515 // Maybe it could be a big edit change but it should be rare 516 516 if (editStatus) return; 517 + if (states.composerState.minimized) return; 517 518 const key = draftKey(); 518 519 const backgroundDraft = { 519 520 key, ··· 670 671 [replyToStatus], 671 672 ); 672 673 674 + const onMinimize = () => { 675 + saveUnsavedDraft(); 676 + states.composerState.minimized = true; 677 + }; 678 + 673 679 return ( 674 680 <div id="compose-container-outer"> 675 681 <div id="compose-container" class={standalone ? 'standalone' : ''}> ··· 689 695 /> 690 696 )} 691 697 {!standalone ? ( 692 - <span> 698 + <span class="button-group"> 693 699 <button 694 700 type="button" 695 701 class="light pop-button" ··· 738 744 </button>{' '} 739 745 <button 740 746 type="button" 747 + class="light min-button" 748 + onClick={onMinimize} 749 + > 750 + <Icon icon="minimize" alt="Minimize" /> 751 + </button>{' '} 752 + <button 753 + type="button" 741 754 class="light close-button" 742 755 disabled={uiState === 'loading'} 743 756 onClick={() => { ··· 809 822 }, 10); 810 823 } else { 811 824 window.opener.__STATES__.showCompose = true; 825 + } 826 + if (window.opener.__STATES__.composerState.minimized) { 827 + // Maximize it 828 + window.opener.__STATES__.composerState.minimized = false; 812 829 } 813 830 }, 814 831 }); ··· 915 932 spoilerText = (sensitive && spoilerText) || undefined; 916 933 status = status === '' ? undefined : status; 917 934 935 + // states.composerState.minimized = true; 936 + states.composerState.publishing = true; 918 937 setUIState('loading'); 919 938 (async () => { 920 939 try { ··· 948 967 return result.status === 'rejected' || !result.value?.id; 949 968 }) 950 969 ) { 970 + states.composerState.publishing = false; 971 + states.composerState.publishingError = true; 951 972 setUIState('error'); 952 973 // Alert all the reasons 953 974 results.forEach((result) => { ··· 1021 1042 newStatus = await masto.v1.statuses.create(params); 1022 1043 } 1023 1044 } 1045 + states.composerState.minimized = false; 1046 + states.composerState.publishing = false; 1024 1047 setUIState('default'); 1025 1048 1026 1049 // Close ··· 1031 1054 instance, 1032 1055 }); 1033 1056 } catch (e) { 1057 + states.composerState.publishing = false; 1058 + states.composerState.publishingError = true; 1034 1059 console.error(e); 1035 1060 alert(e?.reason || e); 1036 1061 setUIState('error');
+40 -1
src/components/modal.css
··· 10 10 align-items: center; 11 11 background-color: var(--backdrop-color); 12 12 animation: appear 0.5s var(--timing-function) both; 13 + transition: all 0.5s var(--timing-function); 13 14 14 15 &.solid { 15 16 background-color: var(--backdrop-solid-color); 16 17 } 17 18 19 + --compose-button-dimension: 56px; 20 + --compose-button-dimension-half: calc(var(--compose-button-dimension) / 2); 21 + --compose-button-dimension-margin: 16px; 22 + 23 + &.min { 24 + /* Minimized */ 25 + pointer-events: none; 26 + user-select: none; 27 + overflow: hidden; 28 + transform: scale(0); 29 + --right: max( 30 + var(--compose-button-dimension-margin), 31 + env(safe-area-inset-right) 32 + ); 33 + --bottom: max( 34 + var(--compose-button-dimension-margin), 35 + env(safe-area-inset-bottom) 36 + ); 37 + --origin-right: calc( 38 + 100% - var(--compose-button-dimension-half) - var(--right) 39 + ); 40 + --origin-bottom: calc( 41 + 100% - var(--compose-button-dimension-half) - var(--bottom) 42 + ); 43 + transform-origin: var(--origin-right) var(--origin-bottom); 44 + } 45 + 18 46 .sheet { 19 47 transition: transform 0.3s var(--timing-function); 20 - transform-origin: center bottom; 48 + transform-origin: 80% 80%; 21 49 } 22 50 23 51 &:has(~ div) .sheet { 24 52 transform: scale(0.975); 25 53 } 26 54 } 55 + 56 + @media (max-width: calc(40em - 1px)) { 57 + #app[data-shortcuts-view-mode='tab-menu-bar'] ~ #modal-container > div.min { 58 + border: 2px solid red; 59 + 60 + --bottom: calc( 61 + var(--compose-button-dimension-margin) + env(safe-area-inset-bottom) + 62 + 52px 63 + ); 64 + } 65 + }
+20 -10
src/components/modal.jsx
··· 8 8 9 9 const $modalContainer = document.getElementById('modal-container'); 10 10 11 - function Modal({ children, onClose, onClick, class: className }) { 11 + function Modal({ children, onClose, onClick, class: className, minimized }) { 12 12 if (!children) return null; 13 13 14 14 const modalRef = useRef(); ··· 43 43 44 44 useEffect(() => { 45 45 const $deckContainers = document.querySelectorAll('.deck-container'); 46 - if (children) { 47 - $deckContainers.forEach(($deckContainer) => { 48 - $deckContainer.setAttribute('inert', ''); 49 - }); 46 + if (minimized) { 47 + // Similar to focusDeck in focus-deck.jsx 48 + // Focus last deck 49 + const page = $deckContainers[$deckContainers.length - 1]; // last one 50 + if (page && page.tabIndex === -1) { 51 + page.focus(); 52 + } 50 53 } else { 51 - $deckContainers.forEach(($deckContainer) => { 52 - $deckContainer.removeAttribute('inert'); 53 - }); 54 + if (children) { 55 + $deckContainers.forEach(($deckContainer) => { 56 + $deckContainer.setAttribute('inert', ''); 57 + }); 58 + } else { 59 + $deckContainers.forEach(($deckContainer) => { 60 + $deckContainer.removeAttribute('inert'); 61 + }); 62 + } 54 63 } 55 64 return () => { 56 65 $deckContainers.forEach(($deckContainer) => { 57 66 $deckContainer.removeAttribute('inert'); 58 67 }); 59 68 }; 60 - }, [children]); 69 + }, [children, minimized]); 61 70 62 71 const Modal = ( 63 72 <div ··· 72 81 onClose?.(e); 73 82 } 74 83 }} 75 - tabIndex="-1" 84 + tabIndex={minimized ? 0 : '-1'} 85 + inert={minimized} 76 86 onFocus={(e) => { 77 87 try { 78 88 if (e.target === e.currentTarget) {
+4 -1
src/components/modals.jsx
··· 39 39 return ( 40 40 <> 41 41 {!!snapStates.showCompose && ( 42 - <Modal class="solid"> 42 + <Modal 43 + class={`solid ${snapStates.composerState.minimized ? 'min' : ''}`} 44 + minimized={!!snapStates.composerState.minimized} 45 + > 43 46 <IntlSegmenterSuspense> 44 47 <Compose 45 48 replyToStatus={
+9 -8
src/components/status.jsx
··· 51 51 import pmem from '../utils/pmem'; 52 52 import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding'; 53 53 import shortenNumber from '../utils/shorten-number'; 54 + import showCompose from '../utils/show-compose'; 54 55 import showToast from '../utils/show-toast'; 55 56 import { speak, supportsTTS } from '../utils/speech'; 56 57 import states, { getStatus, saveStatus, statusKey } from '../utils/states'; ··· 524 525 }); 525 526 if (newWin) return; 526 527 } 527 - states.showCompose = { 528 + showCompose({ 528 529 replyToStatus: status, 529 - }; 530 + }); 530 531 }; 531 532 532 533 // Check if media has no descriptions ··· 771 772 menuExtras={ 772 773 <MenuItem 773 774 onClick={() => { 774 - states.showCompose = { 775 + showCompose({ 775 776 draftStatus: { 776 777 status: `\n${url}`, 777 778 }, 778 - }; 779 + }); 779 780 }} 780 781 > 781 782 <Icon icon="quote" /> ··· 1092 1093 {supports('@mastodon/post-edit') && ( 1093 1094 <MenuItem 1094 1095 onClick={() => { 1095 - states.showCompose = { 1096 + showCompose({ 1096 1097 editStatus: status, 1097 - }; 1098 + }); 1098 1099 }} 1099 1100 > 1100 1101 <Icon icon="pencil" /> ··· 2125 2126 menuExtras={ 2126 2127 <MenuItem 2127 2128 onClick={() => { 2128 - states.showCompose = { 2129 + showCompose({ 2129 2130 draftStatus: { 2130 2131 status: `\n${url}`, 2131 2132 }, 2132 - }; 2133 + }); 2133 2134 }} 2134 2135 > 2135 2136 <Icon icon="quote" />
+27
src/index.css
··· 388 388 background-color: transparent; 389 389 } 390 390 391 + .button-group { 392 + display: flex; 393 + 394 + button, 395 + .button { 396 + margin-inline: calc(-1 * var(--hairline-width)); 397 + 398 + &:first-child:not(:only-child) { 399 + border-top-right-radius: 0; 400 + border-bottom-right-radius: 0; 401 + } 402 + &:not(:first-child, :last-child, :only-child) { 403 + border-radius: 0; 404 + } 405 + &:last-child:not(:only-child) { 406 + border-top-left-radius: 0; 407 + border-bottom-left-radius: 0; 408 + } 409 + } 410 + } 411 + 391 412 pre { 392 413 tab-size: 2; 393 414 } ··· 547 568 .shazam-container-horizontal[hidden] { 548 569 grid-template-columns: 0fr; 549 570 } 571 + 572 + @keyframes spin { 573 + to { 574 + transform: rotate(360deg); 575 + } 576 + }
-6
src/pages/status.css
··· 23 23 } 24 24 } 25 25 26 - @keyframes spin { 27 - to { 28 - transform: rotate(360deg); 29 - } 30 - } 31 - 32 26 .hero-heading { 33 27 font-size: var(--text-size); 34 28 display: inline-block;
+27
src/utils/show-compose.js
··· 1 + import openOSK from './open-osk'; 2 + import showToast from './show-toast'; 3 + import states from './states'; 4 + 5 + const TOAST_DURATION = 5_000; // 5 seconds 6 + 7 + export default function showCompose(opts) { 8 + if (!opts) opts = true; 9 + 10 + if (states.showCompose) { 11 + if (states.composerState.minimized) { 12 + showToast({ 13 + duration: TOAST_DURATION, 14 + text: `A draft post is currently minimized. Post or discard it before creating a new one.`, 15 + }); 16 + } else { 17 + showToast({ 18 + duration: TOAST_DURATION, 19 + text: `A post is currently open. Post or discard it before creating a new one.`, 20 + }); 21 + } 22 + return; 23 + } 24 + 25 + openOSK(); 26 + states.showCompose = opts; 27 + }
+1
src/utils/states.js
··· 40 40 statusReply: {}, 41 41 accounts: {}, 42 42 routeNotification: null, 43 + composerState: {}, 43 44 // Modals 44 45 showCompose: false, 45 46 showSettings: false,