diff --git a/static/crossroad.css b/static/crossroad.css
index 5569ae5..89f0b37 100644
--- a/static/crossroad.css
+++ b/static/crossroad.css
@@ -61,6 +61,14 @@ abbr[title] {
height: 100%;
}
+.flex-hcenter {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+}
+
.flex-row {
display: flex;
flex-direction: row;
@@ -76,6 +84,13 @@ abbr[title] {
flex-direction: column-reverse;
}
+.flex-vevenly {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly;
+ height: 100%;
+}
+
.half-width {
min-width: 50%;
}
diff --git a/static/crossroad.html b/static/crossroad.html
index 4bff713..29e0141 100644
--- a/static/crossroad.html
+++ b/static/crossroad.html
@@ -29,7 +29,12 @@
-
+
+
+
+
+
+
diff --git a/static/crossroad.mts b/static/crossroad.mts
index 4901180..beb6e88 100644
--- a/static/crossroad.mts
+++ b/static/crossroad.mts
@@ -13,6 +13,7 @@ const detailsDialog = findDialogOrFail(document.body, "#instanceDetails");
const addDialog = findDialogOrFail(document.body, "#addInstance");
const instanceSelectForm = findFormOrFail(document.body, "#instanceSelectForm");
const redirectButton = findButtonOrFail(document.body, "#redirect");
+const redirectAlwaysButton = findButtonOrFail(document.body, "#redirectAlways");
export const {
showAddInstanceDialog,
@@ -48,24 +49,73 @@ function createInstanceSelectOptions() {
instanceSelectForm.appendChild(div);
}
const firstInput = instanceSelectForm.querySelector("input");
- if (firstInput) {
- firstInput.checked = true;
- redirectButton.disabled = false;
- } else {
- redirectButton.disabled = true;
- }
+ if (firstInput) firstInput.checked = true;
+ setRedirectButtonState(firstInput !== null);
}
createInstanceSelectOptions();
storageManager.addSaveCallback(createInstanceSelectOptions);
-function redirect() {
- // Can be assumed to not fail because the button is disabled if there are no options and the first one is selected by default
- const option = findInputOrFail(instanceSelectForm, `input[name="${radioButtonName}"]:checked`).value;
- const url = URL.parse(option)!;
+function setRedirectButtonState(enabled: boolean) {
+ redirectButton.disabled = !enabled;
+ redirectAlwaysButton.disabled = !enabled;
+}
+
+export function getTargetSoftwareOrGroup(): string {
const currentURL = URL.parse(location.href)!;
- url.pathname = currentURL.pathname.replace(/\/.*?\//, "/");
+ const target = currentURL.pathname.match(/\/+([^\/]*)\/?/)?.[1];
+ if (target == null) throw new Error("Crossroad was served on an invalid path (likely a backend routing mistake)");
+ const softwareName = Object.entries(knownSoftware.software).find(([name, software]) => software.aliases.includes(target))?.[0];
+ if (softwareName) return softwareName;
+ const groupName = Object.entries(knownSoftware.groups).find(([name, group]) => group.aliases.includes(target))?.[0];
+ if (groupName) return groupName;
+ throw new Error("Could not identify target software or group");
+}
+
+function getTargetPath(): string {
+ const currentURL = URL.parse(location.href)!;
+ return currentURL.pathname.replace(/\/+[^\/]*\/?/, "/");
+}
+
+function getSelectedOption(): string | null {
+ try {
+ return findInputOrFail(instanceSelectForm, `input[name="${radioButtonName}"]:checked`).value;
+ } catch {
+ return null;
+ }
+}
+
+function autoRedirect() {
+ const targetSoftware = getTargetSoftwareOrGroup();
+ const preferredFor = storageManager.storage.instances.find(instance => instance.preferredFor?.includes(targetSoftware));
+ if (preferredFor) redirect(preferredFor.origin);
+}
+
+autoRedirect();
+
+function setAutoRedirect(option: string) {
+ const instance = storageManager.storage.instances.find(e => e.origin === option);
+ if (!instance) throw new Error("Invalid argument");
+ instance.preferredFor ??= [];
+ instance.preferredFor.push(getTargetSoftwareOrGroup());
+ storageManager.save();
+}
+
+redirectAlwaysButton.addEventListener("click", e => {
+ // Can be assumed to not fail because the button is disabled if there are no options and the first one is selected by default
+ const option = getSelectedOption()!;
+ setAutoRedirect(option);
+ redirect(option);
+});
+
+function redirect(to: string) {
+ const url = URL.parse(to);
+ if (url === null) throw new Error("Couldn't parse destination");
+ url.pathname = getTargetPath();
location.href = url.toString();
}
-redirectButton.addEventListener("click", e => redirect());
+redirectButton.addEventListener("click", e => {
+ // Can be assumed to not fail because the button is disabled if there are no options and the first one is selected by default
+ redirect(getSelectedOption()!);
+});
diff --git a/static/storage_manager.mts b/static/storage_manager.mts
index dc89b95..7ab3957 100644
--- a/static/storage_manager.mts
+++ b/static/storage_manager.mts
@@ -25,6 +25,11 @@ export type Instance = {
* Make sure to sanitize this! Could lead to XSS
*/
iconURL?: string,
+ /**
+ * The list of software names and groups the user prefers to autoredirect to this instance
+ * @example ["sharkey", "misskey-compliant"]
+ */
+ preferredFor?: string[],
}
type LocalStorage = {