Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Nitter redirection support #231

Merged
merged 7 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions static/openInApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ function detectOS() {
}

function openTweet(tweetId){
var preference = localStorage.getItem("openLinksPreference");
if (preference === "true"){
if (localStorage.getItem("openLinksPreference") === "true") {
const os = detectOS();

url = `twitter://status?id=${tweetId}`
if(os === 'android'){
window.location = url;
}else if (os === 'ios'){
window.location.replace(url);
}
}else{
} else if (localStorage.getItem("frontendToggle") === "true" && localStorage.getItem("frontendUrl") !== null) {
window.location = `${localStorage.getItem("frontendUrl")}/i/status/${tweetId}`
} else {
window.location = `https://x.com/i/status/${tweetId}`
}

setTimeout(() => {
window.location = `https://x.com/i/status/${tweetId}`
}, 1000)
}
}
75 changes: 68 additions & 7 deletions static/preferences.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,45 @@
<link rel="icon" href="/favicon.ico">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<script>
function savePreference() {
/* Open Links in App*/
var openlinksToggle = document.getElementById("openLinksCheckbox");
localStorage.setItem("openLinksPreference", openlinksToggle.checked);
function savePreference(name, value) {
localStorage.setItem(name, value);
}

function loadPreference() {
/* Open Links in App */
var openlinksToggle = document.getElementById("openLinksCheckbox");
var openlinksPreference = localStorage.getItem("openLinksPreference");
openlinksToggle.checked = openlinksPreference === "true";
/* Nitter toggle */
document.getElementById("frontendToggle").checked = localStorage.getItem("frontendToggle") === "true";
}

function queryInstances() {
let instanceList = document.getElementById("instanceList");
fetch("https://status.d420.de/api/v1/instances")
.then(response => response.json())
.then(data => {
let i = 0;
for (const instance of data.hosts) {
if (instance.points > 0) {
let labelOpen = `<label for="instance${i}">`;
let labelClose = `</label>`;
let row = document.createElement("tr");
let input = document.createElement("td");
input.innerHTML = `${labelOpen} <input type="radio" name="instance" id="instance${i}" value="${instance.url}" onclick="savePreference('frontendUrl', this.value)"> ${labelClose}`;
let name = document.createElement("td");
name.innerHTML = `${labelOpen} ${instance.domain} ${labelClose}`
let health = document.createElement("td");
health.innerHTML = `${labelOpen} ${instance.healthy} ${labelClose}`;
let score = document.createElement("td");
score.innerHTML = `${labelOpen} ${instance.points} ${labelClose}`;
row.append(input, name, health, score);
instanceList.append(row);
Comment on lines +28 to +42
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the way I assembled each <td> with innerHTML.

There's probably a more elegant way to handle it. That said: this solution works well enough, it's readable, and it's not performance-heavy enough to need optimizing.

I don't clear #instanceList at fetch time. In theory, this could create duplicates if someone requests instances more than once, but the API's rate limit makes that very unlikely to happen unless someone is specifically trying to make it fail.

i++;
}
}
document.getElementById("instancePicker").style.removeProperty("display");
})
}
</script>
<link rel="stylesheet" type="text/css" href="/style.css">
Expand All @@ -35,17 +63,50 @@ <h1>vxTwitter Preferences</h1>
</header>

<main>
<section class="optiongroup" id="redirectionSettings">
<h2 labels="redirectionSettings"> Redirection settings </h2>
<section class="optiongroup" id="appSettings">
<h2 labels="appSettings"> App settings </h2>
<label for="openLinksCheckbox">
<div class="checkboxcontainer">
<input type="checkbox" id="openLinksCheckbox" onchange="savePreference()">
<input type="checkbox" id="openLinksCheckbox" onchange="savePreference('openLinksPreference', this.checked)">
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M9.64 18.952l-5.55-4.861 1.317-1.504 3.951 3.459 8.459-10.948L19.4 6.32 9.64 18.952z"></path></svg>
</div>
<h3> Open links in app </h3>
<p> When you open a link on this device, vxTwitter will try to redirect you to the official X (formerly Twitter) app instead of using your browser. </p>
</label>
</section>

<section class="optiongroup" id="frontendSettings">
<h2 labels="frontendSettings"> Frontend settings <em>(EXPERIMENTAL)</em> </h2>
<label for="frontendToggle">
<div class="checkboxcontainer">
<input type="checkbox" id="frontendToggle" onchange="savePreference('frontendToggle', this.checked)">
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M9.64 18.952l-5.55-4.861 1.317-1.504 3.951 3.459 8.459-10.948L19.4 6.32 9.64 18.952z"></path></svg>
</div>
<h3> Redirect to Nitter </h3>
<p> When you open a link on this browser, vxTwitter will redirect you to your chosen Nitter instance instead of X&nbsp;/&nbsp;Twitter </p>
</label>
<label for="instanceSearch">
<button onclick="queryInstances()" id="instanceSearch"> Search </button>
<h3> Search for Nitter instances </h3>
<p> Use the <a href="https://status.d420.de/about" target="_blank">Nitter health tracker API</a> to query available instances. </p>
</label>
<label id="instancePicker" style="display: none;">
<h3> Choose a Nitter instance </h3>
<p> Choose which instance to use for redirection. You can only pick one instance, and Nitter is currently <a href="https://status.d420.de/rip" target="_blank">almost dead</a>, so the instance with the highest score is probably your best bet. </p>
<table>
<thead>
<tr>
<th scope="col"> Selected </th>
<th scope="col"> Instance </th>
<th scope="col"> Healthy </th>
<th scope="col"> Score </th>
</tr>
</thead>
<tbody id="instanceList">
<!-- queryInstances() appends instance data here -->
</tbody>
</table>
</label>
</main>

<aside class="bottom">
Expand Down
125 changes: 100 additions & 25 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,49 +70,83 @@ main {
grid-area: main;
}

label :has(input) {
em {
color: var(--accent);
}

.optiongroup:not(:first-of-type) {
margin: 1em auto;
border-top: 1px solid var(--lowcontrast);
}

label :where(:has(input), input) {
grid-area: input;
}

label h3 {
h3 {
grid-area: title;
margin: 0;
}

label p {
p {
grid-area: text;
color: var(--gray);
margin: 0;
}

/* Checkbox settings */
label:has(input[type=checkbox]) {
a {
color: var(--accent);
text-decoration: underline 1px dotted var(--accent);
transition: .2s all;
}

label a:where(:hover, :focus) {
color: var(--fg);
text-decoration-color: var(--fg);
}

label {
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: auto auto;
grid-template-areas:
"title input"
"text text";
"text text"
"options options";
}

label:has(input[type=checkbox]):hover {
label:not(:first-of-type) {
margin: 1em auto;
}

label:not(#instancePicker):hover {
cursor: pointer;
}

.checkboxcontainer {
position: relative;
}

input[type=checkbox] {
input[type=checkbox],
input[type=radio] {
appearance: none;
width: 20px;
height: 20px;
border: 2px solid var(--gray);
border-radius: 5px;
background: var(--bg);
transition: .2s all;
}

input[type=checkbox] {
width: 20px;
height: 20px;
border-radius: 5px;
}

input[type=radio] {
width: 15px;
height: 15px;
border-radius: 100%;
}

input[type=checkbox]+svg {
display: none;
width: 20px;
Expand All @@ -122,11 +156,13 @@ input[type=checkbox]+svg {
inset: 1px 2px;
}

input[type=checkbox]:hover {
input[type=checkbox]:hover,
input[type=radio]:hover {
box-shadow: 0 0 0 5px var(--lowcontrast);
}

input[type=checkbox]:checked {
input[type=checkbox]:checked,
input[type=radio]:checked {
background: var(--accent);
border-color: var(--accent);
}
Expand All @@ -135,6 +171,57 @@ input[type=checkbox]:checked+svg {
display: block;
}

button {
background: var(--accent);
color: var(--fg);
padding: 0 1ch;
border: none;
cursor: pointer;
border-radius: 1.5em;
font-weight: bold;
font-family: 'Roboto', sans-serif;
transition: .2s all;
}

button:where(:hover, :focus) {
background: var(--fg);
color: var(--bg);
}

#instancePicker {
animation: fadein ease-in .3s 1 forwards;
}

@keyframes fadein {
from { opacity: 0;}
to { opacity: 1;}
}

table {
grid-area: options;
width: fit-content;
width: 100%;
text-align: center;
margin: 1em auto;
}

th {
padding: 0 0.5ch;
}

tbody {
color: var(--gray);
}

tbody tr:has(input[type=radio]:checked) {
color: var(--fg);
}

td label input {
grid-area: options;
margin: auto;
}

/* Popup */
main:has(.popup) {
justify-content: center;
Expand Down Expand Up @@ -162,23 +249,11 @@ main:has(.popup) {
}

.popup button {
background: var(--accent);
color: var(--fg);
width: 60%;
height: 3em;
border-radius: 1.5em;
border: none;
cursor: pointer;
margin: 1em auto;
font-size: larger;
font-weight: bold;
font-family: 'Roboto', sans-serif;
transition: .2s all;
}

.popup button:where(:hover, :focus) {
background: var(--fg);
color: var(--bg);
}

/* "Not a phishing site" warning tape */
Expand Down