this repo has no description
0
fork

Configure Feed

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

Adjustments to welcome and login pages

+148 -125
+9
src/index.css
··· 242 242 :is(button, .button).plain4:not(:disabled, .disabled):is(:hover, :focus) { 243 243 color: var(--text-color); 244 244 } 245 + :is(button, .button).plain5 { 246 + background-color: transparent; 247 + color: var(--link-color); 248 + text-decoration: underline; 249 + text-decoration-color: var(--link-faded-color); 250 + } 251 + :is(button, .button).plain5:not(:disabled, .disabled):is(:hover, :focus) { 252 + text-decoration: underline; 253 + } 245 254 :is(button, .button).light { 246 255 background-color: var(--bg-faded-color); 247 256 color: var(--text-color);
+1 -1
src/pages/login.css
··· 30 30 31 31 #instances-suggestions { 32 32 margin: 0.2em 0 0; 33 - padding: 0; 33 + padding: 0 0 0 1.2em; 34 34 list-style: none; 35 35 width: 90vw; 36 36 max-width: 40em;
+55 -22
src/pages/login.jsx
··· 3 3 import { useEffect, useRef, useState } from 'preact/hooks'; 4 4 import { useSearchParams } from 'react-router-dom'; 5 5 6 + import logo from '../assets/logo.svg'; 7 + 6 8 import Link from '../components/link'; 7 9 import Loader from '../components/loader'; 8 10 import instancesListURL from '../data/instances.json?url'; ··· 42 44 // }, []); 43 45 44 46 const submitInstance = (instanceURL) => { 47 + if (!instanceURL) return; 45 48 store.local.set('instanceURL', instanceURL); 46 49 47 50 (async () => { ··· 72 75 })(); 73 76 }; 74 77 75 - const onSubmit = (e) => { 76 - e.preventDefault(); 77 - const { elements } = e.target; 78 - let instanceURL = elements.instanceURL.value.toLowerCase(); 79 - // Remove protocol from instance URL 80 - instanceURL = instanceURL.replace(/^https?:\/\//, '').replace(/\/+$/, ''); 81 - // Remove @acct@ or acct@ from instance URL 82 - instanceURL = instanceURL.replace(/^@?[^@]+@/, ''); 83 - if (!/\./.test(instanceURL)) { 84 - instanceURL = instancesList.find((instance) => 85 - instance.includes(instanceURL), 86 - ); 87 - } 88 - submitInstance(instanceURL); 89 - }; 78 + const cleanInstanceText = instanceText 79 + ? instanceText 80 + .replace(/^https?:\/\//, '') // Remove protocol from instance URL 81 + .replace(/\/+$/, '') // Remove trailing slash 82 + .replace(/^@?[^@]+@/, '') // Remove @?acct@ 83 + .trim() 84 + : null; 85 + const instanceTextLooksLikeDomain = 86 + /[^\s\r\n\t\/\\]+\.[^\s\r\n\t\/\\]+/.test(cleanInstanceText) && 87 + !/[\s\/\\@]/.test(cleanInstanceText); 90 88 91 - const instancesSuggestions = instanceText 89 + const instancesSuggestions = cleanInstanceText 92 90 ? instancesList 93 91 .filter((instance) => instance.includes(instanceText)) 94 92 .sort((a, b) => { ··· 106 104 .slice(0, 10) 107 105 : []; 108 106 107 + const selectedInstanceText = instanceTextLooksLikeDomain 108 + ? cleanInstanceText 109 + : instancesSuggestions?.length 110 + ? instancesSuggestions[0] 111 + : instanceText 112 + ? instancesList.find((instance) => instance.includes(instanceText)) 113 + : null; 114 + 115 + const onSubmit = (e) => { 116 + e.preventDefault(); 117 + // const { elements } = e.target; 118 + // let instanceURL = elements.instanceURL.value.toLowerCase(); 119 + // // Remove protocol from instance URL 120 + // instanceURL = instanceURL.replace(/^https?:\/\//, '').replace(/\/+$/, ''); 121 + // // Remove @acct@ or acct@ from instance URL 122 + // instanceURL = instanceURL.replace(/^@?[^@]+@/, ''); 123 + // if (!/\./.test(instanceURL)) { 124 + // instanceURL = instancesList.find((instance) => 125 + // instance.includes(instanceURL), 126 + // ); 127 + // } 128 + // submitInstance(instanceURL); 129 + submitInstance(selectedInstanceText); 130 + }; 131 + 109 132 return ( 110 133 <main id="login" style={{ textAlign: 'center' }}> 111 134 <form onSubmit={onSubmit}> 112 - <h1>Log in</h1> 135 + <h1> 136 + <img src={logo} alt="" width="80" height="80" /> 137 + <br /> 138 + Log in 139 + </h1> 113 140 <label> 114 141 <p>Instance</p> 115 142 <input ··· 132 159 /> 133 160 {instancesSuggestions?.length > 0 ? ( 134 161 <ul id="instances-suggestions"> 135 - {instancesSuggestions.map((instance) => ( 162 + {instancesSuggestions.map((instance, i) => ( 136 163 <li> 137 164 <button 138 165 type="button" 139 - class="plain4" 166 + class="plain5" 140 167 onClick={() => { 141 168 submitInstance(instance); 142 169 }} ··· 147 174 ))} 148 175 </ul> 149 176 ) : ( 150 - <div id="instances-eg">e.g. &ldquo;mastodon.social&rsquo;</div> 177 + <div id="instances-eg">e.g. &ldquo;mastodon.social&rdquo;</div> 151 178 )} 152 179 {/* <datalist id="instances-list"> 153 180 {instancesList.map((instance) => ( ··· 161 188 </p> 162 189 )} 163 190 <div> 164 - <button class="large" disabled={uiState === 'loading'}> 165 - Log in 191 + <button 192 + disabled={ 193 + uiState === 'loading' || !instanceText || !selectedInstanceText 194 + } 195 + > 196 + {selectedInstanceText 197 + ? `Continue with ${selectedInstanceText}` 198 + : 'Continue'} 166 199 </button>{' '} 167 200 </div> 168 201 <Loader hidden={uiState !== 'loading'} />
+33 -54
src/pages/welcome.css
··· 1 - @keyframes shine2 { 2 - 0% { 3 - left: -100%; 4 - } 5 - 20% { 6 - left: 100%; 7 - } 8 - 100% { 9 - left: 100%; 10 - } 11 - } 12 - 13 1 #welcome { 14 2 text-align: center; 15 3 background-image: radial-gradient( ··· 35 23 flex-direction: column; 36 24 } 37 25 26 + .hero-content { 27 + flex-grow: 1; 28 + display: flex; 29 + flex-direction: column; 30 + justify-content: center; 31 + } 32 + 38 33 h1 { 34 + display: flex; 35 + flex-direction: column; 36 + align-items: center; 39 37 margin: 0; 40 38 padding: 0; 41 39 font-size: 5em; 42 40 line-height: 1; 43 41 letter-spacing: -1px; 44 - flex-grow: 1; 45 - display: flex; 46 - flex-direction: column; 47 - justify-content: center; 48 - align-items: center; 49 42 position: relative; 50 - mix-blend-mode: multiply; 51 - 52 - @media (prefers-color-scheme: dark) { 53 - mix-blend-mode: normal; 54 - } 55 - 56 - &:before { 57 - content: ''; 58 - position: absolute; 59 - z-index: 2; 60 - width: 100%; 61 - height: 100%; 62 - background-image: linear-gradient( 63 - 100deg, 64 - rgba(255, 255, 255, 0) 30%, 65 - rgba(255, 255, 255, 0.4), 66 - rgba(255, 255, 255, 0) 70% 67 - ); 68 - top: 0; 69 - left: -100%; 70 - pointer-events: none; 71 - animation: shine2 5s ease-in-out 1s infinite; 72 - 73 - @media (prefers-color-scheme: dark) { 74 - content: none; 75 - } 76 - } 77 43 78 44 img { 79 45 filter: drop-shadow(-1px -1px var(--bg-blur-color)) ··· 99 65 font-size: 1.4em; 100 66 text-wrap: balance; 101 67 opacity: 0.7; 68 + 69 + & ~ p { 70 + margin-top: 0; 71 + } 102 72 } 103 73 104 74 .hero-container > p { ··· 148 118 } 149 119 150 120 @media (width > 40em) { 151 - display: grid; 121 + /* display: grid; 152 122 grid-template-columns: 1fr 1fr; 153 - grid-template-rows: 1fr auto; 154 123 height: 100vh; 155 - height: 100svh; 124 + height: 100svh; */ 125 + width: 100%; 156 126 157 127 .hero-container { 158 128 height: auto; 129 + max-height: none; 130 + position: fixed; 131 + left: 0; 132 + top: 0; 133 + bottom: 0; 134 + width: 50%; 135 + align-items: flex-end; 136 + 137 + > * { 138 + max-width: 40em; 139 + width: 100%; 140 + } 159 141 } 160 142 161 143 #why-container { 162 - padding: 32px; 163 - overflow: auto; 164 - mask-image: linear-gradient(to top, transparent 16px, black 64px); 165 - } 144 + padding: 32px 32px 32px 8px; 145 + margin-left: 50%; 166 146 167 - footer { 168 - grid-row: 2; 169 - grid-column: 1 / span 2; 147 + /* overflow: auto; 148 + mask-image: linear-gradient(to top, transparent 16px, black 64px); */ 170 149 } 171 150 } 172 151
+50 -48
src/pages/welcome.jsx
··· 17 17 return ( 18 18 <main id="welcome"> 19 19 <div class="hero-container"> 20 - <h1> 21 - <img 22 - src={logo} 23 - alt="" 24 - width="200" 25 - height="200" 26 - style={{ 27 - aspectRatio: '1/1', 28 - marginBlockEnd: -16, 20 + <div class="hero-content"> 21 + <h1> 22 + <img 23 + src={logo} 24 + alt="" 25 + width="160" 26 + height="160" 27 + style={{ 28 + aspectRatio: '1/1', 29 + marginBlockEnd: -16, 30 + }} 31 + /> 32 + <img src={logoText} alt="Phanpy" width="200" /> 33 + </h1> 34 + <p class="desc">A minimalistic opinionated Mastodon web client.</p> 35 + <p> 36 + <Link to="/login" class="button"> 37 + Log in with Mastodon 38 + </Link> 39 + </p> 40 + <p class="insignificant"> 41 + <small> 42 + Connect your existing Mastodon/Fediverse account. 43 + <br /> 44 + Your credentials are not stored on this server. 45 + </small> 46 + </p> 47 + </div> 48 + <p> 49 + <a href="https://github.com/cheeaun/phanpy" target="_blank"> 50 + Built 51 + </a>{' '} 52 + by{' '} 53 + <a 54 + href="https://mastodon.social/@cheeaun" 55 + target="_blank" 56 + onClick={(e) => { 57 + e.preventDefault(); 58 + states.showAccount = 'cheeaun@mastodon.social'; 29 59 }} 30 - /> 31 - <img src={logoText} alt="Phanpy" width="250" /> 32 - </h1> 33 - <p> 34 - <big> 35 - <b> 36 - <Link to="/login" class="button"> 37 - Log in 38 - </Link> 39 - </b> 40 - </big> 60 + > 61 + @cheeaun 62 + </a> 63 + .{' '} 64 + <a 65 + href="https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD" 66 + target="_blank" 67 + > 68 + Privacy Policy 69 + </a> 70 + . 41 71 </p> 42 - <p class="desc">A minimalistic opinionated Mastodon web client.</p> 43 72 </div> 44 73 <div id="why-container"> 45 74 <div class="sections"> ··· 98 127 </section> 99 128 </div> 100 129 </div> 101 - <footer> 102 - <hr /> 103 - <p> 104 - <a href="https://github.com/cheeaun/phanpy" target="_blank"> 105 - Built 106 - </a>{' '} 107 - by{' '} 108 - <a 109 - href="https://mastodon.social/@cheeaun" 110 - target="_blank" 111 - onClick={(e) => { 112 - e.preventDefault(); 113 - states.showAccount = 'cheeaun@mastodon.social'; 114 - }} 115 - > 116 - @cheeaun 117 - </a> 118 - .{' '} 119 - <a 120 - href="https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD" 121 - target="_blank" 122 - > 123 - Privacy Policy 124 - </a> 125 - . 126 - </p> 127 - </footer> 128 130 </main> 129 131 ); 130 132 }