diff --git a/source/re-nav/share/index.html b/source/re-nav/share/index.html new file mode 100644 index 0000000..09256bd --- /dev/null +++ b/source/re-nav/share/index.html @@ -0,0 +1,61 @@ +{% extends "source/includes/base.html" %} +{% import "source/includes/macros.html" as macros %} + +{% set title = "Re-Nav ↩ Share" %} + +{% block head %} + + +{% endblock %} + +{% block body %} + + +
+

+ Welcome to Re-Nav's share page! +
+ Use the share button in Re-Nav's options page to create a link with your + redirect. +

+ +
+

+ + {{-":"-}} + +

+

+

+ + {{-":"-}} + +

+
+ + {# + This element will be shown by JS on page load, and if Re-Nav is installed, + it will be used as the root container for the import button. + #} +
+

+ With Re-Nav installed, you'll see a button here to import + this redirect. +

+
+
+ + + + +{% endblock %} diff --git a/source/scss/re-nav/share.scss b/source/scss/re-nav/share.scss new file mode 100644 index 0000000..67e859e --- /dev/null +++ b/source/scss/re-nav/share.scss @@ -0,0 +1,54 @@ +.page-main { + display: grid; + gap: var(--spacing-16); + grid-template-columns: repeat(1, 1fr); +} + +.subtitle { + font-weight: bold; + text-align: center; +} + +.redirect { + border: 1px solid var(--foreground-1); + display: grid; + gap: var(--spacing-08); + grid-template-columns: repeat(1, 1fr); + padding: var(--spacing-16); + + .matcher-data, + .redirect-data { + background-color: var(--background-color); + color: var(--background-1); + padding: var(--spacing-16); + overflow: auto; + text-align: center; + + span { + font-weight: bold; + } + } + + .matcher-data { + --background-color: var(--da-3); + } + + .redirect-data { + --background-color: var(--da-7); + } + + .arrow { + font-weight: bold; + text-align: center; + } +} + +.re-nav-import { + p { + text-align: center; + } +} + +.hidden-by-default { + display: none; +} diff --git a/source/ts/re-nav/share.ts b/source/ts/re-nav/share.ts new file mode 100644 index 0000000..356f5b5 --- /dev/null +++ b/source/ts/re-nav/share.ts @@ -0,0 +1,78 @@ +import {Base64} from 'js-base64'; + +const fragmentPrefix = '#json='; + +const exampleRedirect: Redirect = { + matcherType: 'hostname', + matcherValue: 'example.com', + redirectType: 'hostname', + redirectValue: 'example.org', +}; + +function decodeBase64(base64: string): T { + return JSON.parse(Base64.decode(base64)) as T; +} + +function encodeBase64(source: any): string { + return Base64.encode(JSON.stringify(source), true); +} + +window.addEventListener('DOMContentLoaded', () => { + window['Re-Nav'] = { + decodeBase64, + encodeBase64, + share, + }; + + console.log( + "Want to manually create a share link? Use the window['Re-Nav'].share", + 'function, where redirect is an object with matcherType, matcherValue,', + 'redirectType and redirectValue properties. Like this:', + ); + console.log(JSON.stringify(exampleRedirect, undefined, 2)); + console.log(window['Re-Nav'].share); + + if (!location.hash.startsWith(fragmentPrefix)) { + return; + } + + const decoded = decodeBase64( + location.hash.slice(fragmentPrefix.length), + ); + for (const [key, value] of Object.entries(decoded)) { + insertText(key as keyof Redirect, value); + } + + for (const element of document.querySelectorAll('.hidden-by-default')) { + element.classList.remove('hidden-by-default'); + } + + document.querySelector('.subtitle')!.textContent = + 'Someone shared a redirect with you!'; +}); + +function insertText(key: keyof Redirect, value: string): void { + const insert = (selector: string, text: string) => { + document.querySelector(selector)!.textContent = text; + }; + + // eslint-disable-next-line unicorn/prefer-switch + if (key === 'matcherType') { + insert('.matcher-type', value); + } else if (key === 'matcherValue') { + insert('.matcher-value', value); + } else if (key === 'redirectType') { + insert('.redirect-type', value); + } else if (key === 'redirectValue') { + insert('.redirect-value', value); + } else { + console.warn(`Unknown key: ${key as string}`); + } +} + +function share(redirect: Redirect): string { + const encoded = encodeBase64(redirect); + const url = new URL(window.location.href); + url.hash = `${fragmentPrefix}${encoded}`; + return url.href; +} diff --git a/source/types.d.ts b/source/types.d.ts index 15b1de9..2bf1e5a 100644 --- a/source/types.d.ts +++ b/source/types.d.ts @@ -13,6 +13,21 @@ declare global { readonly PROD: boolean; readonly VITE_BROWSER: 'chromium' | 'firefox'; } + + interface Window { + 'Re-Nav': { + decodeBase64(base64: string): T; + encodeBase64(source: any): string; + share(redirect: Redirect): string; + }; + } + + type Redirect = { + matcherType: string; + matcherValue: string; + redirectType: string; + redirectValue: string; + }; } // Make TypeScript see this file as a module. diff --git a/vite.config.ts b/vite.config.ts index 58a396c..669df3f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -19,6 +19,7 @@ export default defineConfig({ home: path.join(sourceDir, 'index.html'), queue: path.join(sourceDir, 'queue/index.html'), 're-nav': path.join(sourceDir, 're-nav/index.html'), + 're-nav/share': path.join(sourceDir, 're-nav/share/index.html'), }, }, sourcemap: true,