fedi.click/index.html

229 lines
No EOL
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="darkreader-lock">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css">
<title>fediclick</title>
<style>
:root {
color-scheme: dark;
}
body > header, body > main { padding-block: 0; }
nav li { padding-top: 0; }
h1, h2, p, fieldset, input, label, details { margin: 0 !important; }
label { display: unset; }
.app {
padding: 1rem;
display: flex;
flex-direction: column;
row-gap: 1rem;
}
.header {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.redirector {
display: flex;
flex-direction: column;
row-gap: 1rem;
}
.redirector__form {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
align-items: center;
}
.redirector__form__label {
grid-column: 1 / 2;
}
.redirector__form__input {
grid-column: 2 / 3;
}
.redirector__form__submit {
grid-column: 1 / 3;
}
</style>
<script type="importmap">
{
"imports": {
"vue": "https://cdn.jsdelivr.net/npm/vue@3/dist/vue.esm-browser.prod.js",
"vue-router": "https://cdn.jsdelivr.net/npm/vue-router@4/dist/vue-router.esm-browser.prod.js",
"@vue/devtools-api": "https://cdn.jsdelivr.net/npm/@vue/devtools-api@7/+esm",
"axios": "https://cdn.jsdelivr.net/npm/axios@1.10/dist/esm/axios.min.js"
}
}
</script>
<script type="module">
import { createApp, defineComponent } from 'vue';
import { createRouter, createWebHashHistory } from 'vue-router';
import axios from 'axios';
axios.defaults.adapter = 'fetch';
const
Header = defineComponent({
template: `
<header class="header"><nav>
<ul><li>
<strong>fediclick</strong>
</li></ul>
<ul>
<li><router-link to="/settings">Settings</router-link></li>
<li><router-link to="/about">About</router-link></li>
</ul>
</nav></header>
`
}),
Settings = defineComponent({
components: {
Header
},
template: `
<Header />
`
}),
About = defineComponent({
components: {
Header
},
template: `
<Header />
<main>
<p>fediclick is a universal link redirector for the Fediverse.</p>
</main>
`
}),
Redirector = defineComponent({
components: {
Header
},
data: () => ({
isLoading: true,
remoteSoftware: undefined,
localHost: undefined,
localHostInput: document.referrer ? new URL(document.referrer).host : undefined,
localSoftware: undefined
}),
computed: {
remoteHost: function(){
return this.$route.params.host;
},
remotePathParams: function(){
return this.$route.params.path;
},
remotePath: function(){
return this.remotePathParams.join('/');
},
remoteUrl: function(){
return `https://${this.remoteHost}/${this.remotePath}`;
},
localHostInputPlaceholder: function(){
return {
'lemmy': 'lemmy.ml'
}[this.remoteSoftware];
},
localUrl: function(){
return this.localHost ? {
'lemmy': `https://${this.localHost}/search?q=${encodeURIComponent(this.remoteUrl)}&type=Posts`
}[this.localSoftware] : undefined;
}
},
methods: {
getSoftware: async function(host){
const softwareCache = JSON.parse(localStorage.getItem('softwareCache') || '{}');
if(softwareCache[host])
return softwareCache[host];
const
nodeInfoEndpoint = (await axios(`https://${host}/.well-known/nodeinfo`)).data['links'][0]['href'],
software = (await axios(nodeInfoEndpoint)).data['software']['name'];
softwareCache[host] = software;
localStorage.setItem('softwareCache', JSON.stringify(softwareCache));
return software;
},
loadLocalHost: function(){
this.localHost = JSON.parse(localStorage.getItem('localHostCache') || '{}')[this.remoteSoftware];
},
saveLocalHost: function(){
localStorage.setItem(
'localHostCache',
JSON.stringify({
...JSON.parse(localStorage.getItem('localHostCache') || '{}'),
[this.remoteSoftware]: this.localHost
})
);
},
submitLocalHost: async function(){
this.isLoading = true;
this.localHost = this.localHostInput;
this.saveLocalHost();
this.localSoftware = await this.getSoftware(this.localHost);
window.location.assign(this.localUrl);
}
},
mounted: async function(){
this.remoteSoftware = await this.getSoftware(this.remoteHost);
this.loadLocalHost();
if(this.localHost){
this.localSoftware = await this.getSoftware(this.localHost);
return window.location.assign(this.localUrl);
}
this.isLoading = false;
},
template: `
<Header />
<main class="redirector">
<p v-if="isLoading">Loading...</p>
<template v-else>
<h2>Where's your {{ remoteSoftware }} home ?</h2>
<p>
Choose which Fediverse instance you want to open {{ remoteSoftware }} links with.
<br>
It doesn't necessarily have to be a {{ remoteSoftware }} instance.
</p>
<form
@submit.prevent="submitLocalHost"
class="redirector__form"
>
<label style="display: contents">
<span class="redirector__form__label">Instance host :</span>
<input
class="redirector__form__input"
v-model="localHostInput"
type="text"
:placeholder="localHostInputPlaceholder"
autofocus
>
</label>
<input
class="redirector__form__submit"
type="submit"
value="Submit"
>
</form>
</template>
</main>
`
}),
app = createApp({
template: `
<router-view></router-view>
`
});
app.use(createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', redirect: '/about' },
{ path: '/settings', component: Settings },
{ path: '/about', component: About },
{ path: '/:host/:path*', component: Redirector }
]
}));
document.addEventListener('DOMContentLoaded', () => app.mount(document.body));
</script>
</head>
<body class="app"></body>
</html>