this repo has no description
0
fork

Configure Feed

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

Extend self-hosting variables

+179 -50
+4 -3
.env
··· 1 - VITE_CLIENT_NAME=Phanpy 2 - VITE_CLIENT_ID=social.phanpy 3 - VITE_WEBSITE=https://phanpy.social 1 + PHANPY_CLIENT_NAME=Phanpy 2 + PHANPY_WEBSITE=https://phanpy.social 3 + PHANPY_LINGVA_INSTANCES="lingva.phanpy.social lingva.lunar.icu lingva.garudalinux.org translate.plausibility.cloud" 4 + PHANPY_PRIVACY_POLICY_URL="https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD"
+59 -3
README.md
··· 126 126 127 127 Two ways (choose one): 128 128 129 - 1. (Recommended) Go to [Releases](https://github.com/cheeaun/phanpy/releases) and download the latest `phanpy-dist.zip`. It's pre-built so don't need to run any install/build commands. Extract it. Serve the folder of extracted files. 130 - 2. Download or `git clone` this repository. Build it by running `npm run build` (after `npm install`). Serve the `dist` folder. 129 + ### Easy way 130 + 131 + Go to [Releases](https://github.com/cheeaun/phanpy/releases) and download the latest `phanpy-dist.zip` or `phanpy-dist.tar.gz`. It's pre-built so don't need to run any install/build commands. Extract it. Serve the folder of extracted files. 132 + 133 + ### Custom-build way 134 + 135 + Download or `git clone` this repository. Build it by running `npm run build` (after `npm install`). Serve the `dist` folder. 136 + 137 + Customization can be done by passing environment variables to the build command. Examples: 138 + 139 + ```bash 140 + PHANPY_APP_TITLE="Phanpy Dev" \ 141 + PHANPY_WEBSITE="https://dev.phanpy.social" \ 142 + npm run build 143 + ``` 144 + 145 + ```bash 146 + PHANPY_DEFAULT_INSTANCE=hachyderm.io \ 147 + PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL=https://hachyderm.io/auth/sign_up \ 148 + PHANPY_PRIVACY_POLICY_URL=https://hachyderm.io/privacy-policy \ 149 + npm run build 150 + ``` 151 + 152 + It's also possible to set them in the `.env` file. 153 + 154 + Available variables: 131 155 132 - Try search for "how to self-host static sites" as there are many ways to do it. 156 + - `PHANPY_APP_TITLE` (optional, default: `Phanpy`) affects: 157 + - Web page title, shown in the browser window or tab title 158 + - App title, when installed as PWA, shown in the Home screen, macOS dock, Windows taskbar, etc 159 + - OpenGraph card title, when shared on social networks 160 + - Client name, when [registering the app for authentication](https://docs.joinmastodon.org/client/token/#app) and shown as client used on posts in some apps/clients 161 + - `PHANPY_WEBSITE` (optional but recommended, default: `https://phanpy.social`) affects: 162 + - Canonical URL of the website 163 + - OpenGraph card URL, when shared on social networks 164 + - Root path for the OpenGraph card image 165 + - Client URL, when [registering the app for authentication](https://docs.joinmastodon.org/client/token/#app) and shown as client used on posts in some apps/clients 166 + - `PHANPY_DEFAULT_INSTANCE` (optional, no defaults): 167 + - e.g. 'mastodon.social', without `https://` 168 + - Default instance for log-in 169 + - When logging in, the user will be redirected instantly to the instance's authentication page instead of having to manually type the instance URL and submit 170 + - `PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL` (optional, no defaults): 171 + - URL of the instance registration page 172 + - E.g. `https://mastodon.social/auth/sign_up` 173 + - `PHANPY_PRIVACY_POLICY_URL` (optional, default to official instance's privacy policy): 174 + - URL of the privacy policy page 175 + - May specify the instance's own privacy policy 176 + - `PHANPY_LINGVA_INSTANCES` (optional, space-separated list, default: `lingva.phanpy.social [...hard-coded list of fallback instances]`): 177 + - Specify a space-separated list of instances. First will be used as default before falling back to the subsequent instances. If there's only 1 instance, means no fallback. 178 + - May specify a self-hosted Lingva instance, powered by either [lingva-translate](https://github.com/thedaviddelta/lingva-translate) or [lingva-api](https://github.com/cheeaun/lingva-api) 179 + - List of fallback instances hard-coded in `/.env` 180 + - [↗️ List of lingva-translate instances](https://github.com/thedaviddelta/lingva-translate?tab=readme-ov-file#instances) 181 + 182 + ### Static site hosting 183 + 184 + Try online search for "how to self-host static sites" as there are many ways to do it. 185 + 186 + #### Lingva-translate or lingva-api hosting 187 + 188 + See documentation for [lingva-translate](https://github.com/thedaviddelta/lingva-translate) or [lingva-api](https://github.com/cheeaun/lingva-api). 133 189 134 190 ## Community deployments 135 191
+1 -1
compose/index.html
··· 4 4 <meta charset="UTF-8" /> 5 5 <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 - <title>Compose / %VITE_CLIENT_NAME%</title> 7 + <title>Compose / %PHANPY_CLIENT_NAME%</title> 8 8 <meta name="color-scheme" content="dark light" /> 9 9 <meta name="google" content="notranslate" /> 10 10 </head>
+6 -6
index.html
··· 6 6 name="viewport" 7 7 content="width=device-width, initial-scale=1, viewport-fit=cover" 8 8 /> 9 - <title>%VITE_CLIENT_NAME%</title> 9 + <title>%PHANPY_CLIENT_NAME%</title> 10 10 <meta 11 11 name="description" 12 12 content="Minimalistic opinionated Mastodon web client" ··· 14 14 <meta name="color-scheme" content="dark light" /> 15 15 <link rel="icon" type="image/x-icon" href="/favicon.ico" /> 16 16 <link rel="apple-touch-icon" href="/apple-touch-icon.png" /> 17 - <meta name="apple-mobile-web-app-title" content="%VITE_CLIENT_NAME%" /> 17 + <meta name="apple-mobile-web-app-title" content="%PHANPY_CLIENT_NAME%" /> 18 18 <meta name="apple-mobile-web-app-capable" content="yes" /> 19 19 <meta name="mobile-web-app-capable" content="yes" /> 20 - <link rel="canonical" href="%VITE_WEBSITE%" /> 20 + <link rel="canonical" href="%PHANPY_WEBSITE%" /> 21 21 <meta 22 22 name="" 23 23 data-theme-setting="manual" ··· 46 46 47 47 <!-- Metacrap https://broken-links.com/2015/12/01/little-less-metacrap/ --> 48 48 <meta property="twitter:card" content="summary_large_image" /> 49 - <meta property="og:url" content="%VITE_WEBSITE%" /> 50 - <meta property="og:title" content="%VITE_CLIENT_NAME%" /> 49 + <meta property="og:url" content="%PHANPY_WEBSITE%" /> 50 + <meta property="og:title" content="%PHANPY_CLIENT_NAME%" /> 51 51 <meta 52 52 property="og:description" 53 53 content="Minimalistic opinionated Mastodon web client" 54 54 /> 55 - <meta property="og:image" content="%VITE_WEBSITE%/og-image-2.jpg" /> 55 + <meta property="og:image" content="%PHANPY_WEBSITE%/og-image-2.jpg" /> 56 56 </head> 57 57 <body> 58 58 <div id="app"></div>
+2 -3
scripts/fetch-lingva-languages.js
··· 1 - // Fetch https://lingva.ml/api/v1/languages/{source|target} 2 1 import fs from 'fs'; 3 2 4 - fetch('https://lingva.ml/api/v1/languages/source') 3 + fetch('https://lingva.phanpy.social/api/v1/languages/source') 5 4 .then((response) => response.json()) 6 5 .then((json) => { 7 6 const file = './src/data/lingva-source-languages.json'; ··· 9 8 fs.writeFileSync(file, JSON.stringify(json.languages, null, '\t'), 'utf8'); 10 9 }); 11 10 12 - fetch('https://lingva.ml/api/v1/languages/target') 11 + fetch('https://lingva.phanpy.social/api/v1/languages/target') 13 12 .then((response) => response.json()) 14 13 .then((json) => { 15 14 const file = './src/data/lingva-target-languages.json';
+6 -9
src/components/translation-block.jsx
··· 12 12 import Icon from './icon'; 13 13 import Loader from './loader'; 14 14 15 + const { PHANPY_LINGVA_INSTANCES } = import.meta.env; 16 + const LINGVA_INSTANCES = PHANPY_LINGVA_INSTANCES 17 + ? PHANPY_LINGVA_INSTANCES.split(/\s+/) 18 + : []; 19 + 15 20 const throttle = pThrottle({ 16 21 limit: 1, 17 22 interval: 2000, 18 23 }); 19 24 20 - // Using other API instances instead of lingva.ml because of this bug (slashes don't work): 21 - // https://github.com/thedaviddelta/lingva-translate/issues/68 22 - const LINGVA_INSTANCES = [ 23 - 'lingva.phanpy.social', 24 - 'lingva.lunar.icu', 25 - 'lingva.garudalinux.org', 26 - 'translate.plausibility.cloud', 27 - ]; 28 25 let currentLingvaInstance = 0; 29 26 30 27 function _lingvaTranslate(text, source, target) { ··· 243 240 ); 244 241 } 245 242 246 - export default TranslationBlock; 243 + export default LINGVA_INSTANCES?.length ? TranslationBlock : () => null;
+16 -5
src/pages/login.jsx
··· 12 12 import store from '../utils/store'; 13 13 import useTitle from '../utils/useTitle'; 14 14 15 + const { PHANPY_DEFAULT_INSTANCE: DEFAULT_INSTANCE } = import.meta.env; 16 + 15 17 function Login() { 16 18 useTitle('Log in'); 17 19 const instanceURLRef = useRef(); ··· 19 21 const [uiState, setUIState] = useState('default'); 20 22 const [searchParams] = useSearchParams(); 21 23 const instance = searchParams.get('instance'); 24 + const submit = searchParams.get('submit'); 22 25 const [instanceText, setInstanceText] = useState( 23 26 instance || cachedInstanceURL?.toLowerCase() || '', 24 27 ); ··· 129 132 submitInstance(selectedInstanceText); 130 133 }; 131 134 135 + if (submit) { 136 + useEffect(() => { 137 + submitInstance(instance || selectedInstanceText); 138 + }, []); 139 + } 140 + 132 141 return ( 133 142 <main id="login" style={{ textAlign: 'center' }}> 134 143 <form onSubmit={onSubmit}> ··· 200 209 </div> 201 210 <Loader hidden={uiState !== 'loading'} /> 202 211 <hr /> 203 - <p> 204 - <a href="https://joinmastodon.org/servers" target="_blank"> 205 - Don't have an account? Create one! 206 - </a> 207 - </p> 212 + {!DEFAULT_INSTANCE && ( 213 + <p> 214 + <a href="https://joinmastodon.org/servers" target="_blank"> 215 + Don't have an account? Create one! 216 + </a> 217 + </p> 218 + )} 208 219 <p> 209 220 <Link to="/">Go home</Link> 210 221 </p>
+13 -2
src/pages/settings.jsx
··· 24 24 25 25 const DEFAULT_TEXT_SIZE = 16; 26 26 const TEXT_SIZES = [15, 16, 17, 18, 19, 20]; 27 + const { 28 + PHANPY_WEBSITE: WEBSITE, 29 + PHANPY_PRIVACY_POLICY_URL: PRIVACY_POLICY_URL, 30 + } = import.meta.env; 27 31 28 32 function Settings({ onClose }) { 29 33 const snapStates = useSnapshot(states); ··· 535 539 </a>{' '} 536 540 &middot;{' '} 537 541 <a 538 - href="https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD" 542 + href={PRIVACY_POLICY_URL} 539 543 target="_blank" 540 544 rel="noopener noreferrer" 541 545 > ··· 544 548 </p> 545 549 {__BUILD_TIME__ && ( 546 550 <p> 547 - Version:{' '} 551 + {WEBSITE && ( 552 + <> 553 + <span class="insignificant">Site:</span>{' '} 554 + {WEBSITE.replace(/https?:\/\//g, '').replace(/\/$/, '')} 555 + <br /> 556 + </> 557 + )} 558 + <span class="insignificant">Version:</span>{' '} 548 559 <input 549 560 type="text" 550 561 class="version-string"
+12
src/pages/welcome.css
··· 75 75 margin-top: 0; 76 76 } 77 77 78 + .app-site-version { 79 + text-align: center; 80 + opacity: 0.5; 81 + color: var(--text-insignificant-color); 82 + font-family: var(--monospace-font), monospace; 83 + 84 + small { 85 + font-size: 11px; 86 + letter-spacing: -0.2px; 87 + } 88 + } 89 + 78 90 #why-container { 79 91 padding: 0 16px; 80 92 }
+46 -11
src/pages/welcome.jsx
··· 12 12 import states from '../utils/states'; 13 13 import useTitle from '../utils/useTitle'; 14 14 15 + const { 16 + PHANPY_DEFAULT_INSTANCE: DEFAULT_INSTANCE, 17 + PHANPY_WEBSITE: WEBSITE, 18 + PHANPY_PRIVACY_POLICY_URL: PRIVACY_POLICY_URL, 19 + PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL: DEFAULT_INSTANCE_REGISTRATION_URL, 20 + } = import.meta.env; 21 + const appSite = WEBSITE 22 + ? WEBSITE.replace(/https?:\/\//g, '').replace(/\/$/, '') 23 + : null; 24 + const appVersion = __BUILD_TIME__ 25 + ? `${__BUILD_TIME__.slice(0, 10).replace(/-/g, '.')}${ 26 + __COMMIT_HASH__ ? `.${__COMMIT_HASH__}` : '' 27 + }` 28 + : null; 29 + 15 30 function Welcome() { 16 31 useTitle(null, ['/', '/welcome']); 17 32 return ( ··· 33 48 </h1> 34 49 <p class="desc">A minimalistic opinionated Mastodon web client.</p> 35 50 <p> 36 - <Link to="/login" class="button"> 37 - Log in with Mastodon 51 + <Link 52 + to={ 53 + DEFAULT_INSTANCE 54 + ? `/login?instance=${DEFAULT_INSTANCE}&submit=1` 55 + : '/login' 56 + } 57 + class="button" 58 + > 59 + {DEFAULT_INSTANCE ? 'Log in' : 'Log in with Mastodon'} 38 60 </Link> 39 61 </p> 40 - <p class="insignificant"> 62 + {DEFAULT_INSTANCE && DEFAULT_INSTANCE_REGISTRATION_URL && ( 63 + <p> 64 + <a href={DEFAULT_INSTANCE_REGISTRATION_URL} class="button plain5"> 65 + Sign up 66 + </a> 67 + </p> 68 + )} 69 + {!DEFAULT_INSTANCE && ( 70 + <p class="insignificant"> 71 + <small> 72 + Connect your existing Mastodon/Fediverse account. 73 + <br /> 74 + Your credentials are not stored on this server. 75 + </small> 76 + </p> 77 + )} 78 + </div> 79 + {(appSite || appVersion) && ( 80 + <p class="app-site-version"> 41 81 <small> 42 - Connect your existing Mastodon/Fediverse account. 43 - <br /> 44 - Your credentials are not stored on this server. 82 + {appSite} {appVersion} 45 83 </small> 46 84 </p> 47 - </div> 85 + )} 48 86 <p> 49 87 <a href="https://github.com/cheeaun/phanpy" target="_blank"> 50 88 Built ··· 61 99 @cheeaun 62 100 </a> 63 101 .{' '} 64 - <a 65 - href="https://github.com/cheeaun/phanpy/blob/main/PRIVACY.MD" 66 - target="_blank" 67 - > 102 + <a href={PRIVACY_POLICY_URL} target="_blank"> 68 103 Privacy Policy 69 104 </a> 70 105 .
+1 -1
src/utils/auth.js
··· 1 - const { VITE_CLIENT_NAME: CLIENT_NAME, VITE_WEBSITE: WEBSITE } = import.meta 1 + const { PHANPY_CLIENT_NAME: CLIENT_NAME, PHANPY_WEBSITE: WEBSITE } = import.meta 2 2 .env; 3 3 4 4 const SCOPES = 'read write follow push';
+1 -1
src/utils/useTitle.js
··· 4 4 5 5 import states from './states'; 6 6 7 - const { VITE_CLIENT_NAME: CLIENT_NAME } = import.meta.env; 7 + const { PHANPY_CLIENT_NAME: CLIENT_NAME } = import.meta.env; 8 8 9 9 export default function useTitle(title, path) { 10 10 function setTitle() {
+12 -5
vite.config.js
··· 2 2 import { execSync } from 'child_process'; 3 3 import fs from 'fs'; 4 4 import { resolve } from 'path'; 5 + import { uid } from 'uid/single'; 5 6 import { defineConfig, loadEnv, splitVendorChunkPlugin } from 'vite'; 6 7 import generateFile from 'vite-plugin-generate-file'; 7 8 import htmlPlugin from 'vite-plugin-html-config'; ··· 10 11 11 12 const { NODE_ENV } = process.env; 12 13 const { 13 - VITE_CLIENT_NAME: CLIENT_NAME, 14 - VITE_CLIENT_ID: CLIENT_ID, 15 - VITE_APP_ERROR_LOGGING: ERROR_LOGGING, 14 + PHANPY_CLIENT_NAME: CLIENT_NAME, 15 + PHANPY_APP_ERROR_LOGGING: ERROR_LOGGING, 16 16 } = loadEnv('production', process.cwd()); 17 17 18 18 const now = new Date(); 19 - const commitHash = execSync('git rev-parse --short HEAD').toString().trim(); 19 + let commitHash; 20 + try { 21 + commitHash = execSync('git rev-parse --short HEAD').toString().trim(); 22 + } catch (error) { 23 + // If error, means git is not installed or not a git repo (could be downloaded instead of git cloned) 24 + // Fallback to random hash which should be different on every build run 🤞 25 + commitHash = uid(); 26 + } 20 27 21 28 const rollbarCode = fs.readFileSync( 22 29 resolve(__dirname, './rollbar.js'), ··· 26 33 // https://vitejs.dev/config/ 27 34 export default defineConfig({ 28 35 base: './', 36 + envPrefix: ['VITE_', 'PHANPY_'], 29 37 mode: NODE_ENV, 30 38 define: { 31 39 __BUILD_TIME__: JSON.stringify(now), ··· 55 63 ]), 56 64 VitePWA({ 57 65 manifest: { 58 - id: CLIENT_ID, 59 66 name: CLIENT_NAME, 60 67 short_name: CLIENT_NAME, 61 68 description: 'Minimalistic opinionated Mastodon web client',