Compare commits
No commits in common. "459380cb6984944e287f364120ee620993dba27a" and "16ba17eb9c110e2681b13a2b16987c457d2089a9" have entirely different histories.
459380cb69
...
16ba17eb9c
|
@ -20,6 +20,7 @@
|
||||||
"@holllo/preact-components": "^0.2.3",
|
"@holllo/preact-components": "^0.2.3",
|
||||||
"htm": "^3.1.1",
|
"htm": "^3.1.1",
|
||||||
"modern-normalize": "^1.1.0",
|
"modern-normalize": "^1.1.0",
|
||||||
|
"nanoid": "^4.0.0",
|
||||||
"preact": "^10.11.0",
|
"preact": "^10.11.0",
|
||||||
"webextension-polyfill": "^0.10.0"
|
"webextension-polyfill": "^0.10.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,7 @@ specifiers:
|
||||||
c8: ^7.12.0
|
c8: ^7.12.0
|
||||||
htm: ^3.1.1
|
htm: ^3.1.1
|
||||||
modern-normalize: ^1.1.0
|
modern-normalize: ^1.1.0
|
||||||
|
nanoid: ^4.0.0
|
||||||
postcss: ^8.4.16
|
postcss: ^8.4.16
|
||||||
preact: ^10.11.0
|
preact: ^10.11.0
|
||||||
sass: ^1.55.0
|
sass: ^1.55.0
|
||||||
|
@ -29,6 +30,7 @@ dependencies:
|
||||||
'@holllo/preact-components': 0.2.3_htm@3.1.1+preact@10.11.0
|
'@holllo/preact-components': 0.2.3_htm@3.1.1+preact@10.11.0
|
||||||
htm: 3.1.1
|
htm: 3.1.1
|
||||||
modern-normalize: 1.1.0
|
modern-normalize: 1.1.0
|
||||||
|
nanoid: 4.0.0
|
||||||
preact: 10.11.0
|
preact: 10.11.0
|
||||||
webextension-polyfill: 0.10.0
|
webextension-polyfill: 0.10.0
|
||||||
|
|
||||||
|
@ -5197,6 +5199,12 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/nanoid/4.0.0:
|
||||||
|
resolution: {integrity: sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==}
|
||||||
|
engines: {node: ^14 || ^16 || >=18}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/natural-compare/1.4.0:
|
/natural-compare/1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64">
|
|
||||||
<path fill="#e6deff" d="M0,16 l48,0 l-24,32" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 121 B |
|
@ -1,6 +1,6 @@
|
||||||
import browser from 'webextension-polyfill';
|
import browser from 'webextension-polyfill';
|
||||||
|
|
||||||
import storage from '../redirect/storage.js';
|
import {parseRedirect} from '../redirect/exports.js';
|
||||||
|
|
||||||
async function browserActionClicked() {
|
async function browserActionClicked() {
|
||||||
await browser.runtime.openOptionsPage();
|
await browser.runtime.openOptionsPage();
|
||||||
|
@ -35,8 +35,11 @@ browser.webNavigation.onBeforeNavigate.addListener(async (details) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const redirect of await storage.getRedirects()) {
|
for (const [id, parameters] of Object.entries(
|
||||||
if (!redirect.parameters.enabled) {
|
await browser.storage.local.get(),
|
||||||
|
)) {
|
||||||
|
const redirect = parseRedirect(parameters, id);
|
||||||
|
if (redirect === undefined || !redirect.parameters.enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,208 +0,0 @@
|
||||||
import {ConfirmButton} from '@holllo/preact-components';
|
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component} from 'preact';
|
|
||||||
import browser from 'webextension-polyfill';
|
|
||||||
|
|
||||||
import {
|
|
||||||
matcherTypes,
|
|
||||||
narrowMatcherType,
|
|
||||||
narrowRedirectType,
|
|
||||||
parseRedirect,
|
|
||||||
Redirect,
|
|
||||||
RedirectParameters,
|
|
||||||
redirectTypes,
|
|
||||||
} from '../../redirect/exports.js';
|
|
||||||
import storage from '../../redirect/storage.js';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
redirect: Redirect;
|
|
||||||
removeRedirect: (id: number) => void;
|
|
||||||
saveRedirect: (redirect: Redirect) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
hasBeenEdited: boolean;
|
|
||||||
} & RedirectParameters;
|
|
||||||
|
|
||||||
export default class Editor extends Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
hasBeenEdited: false,
|
|
||||||
...props.redirect.parameters,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onInput = (event: Event, input: 'matcher' | 'redirect') => {
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
const value = target.value;
|
|
||||||
const newState: Partial<State> = {
|
|
||||||
hasBeenEdited: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (input === 'matcher') {
|
|
||||||
newState.matcherValue = value;
|
|
||||||
} else if (input === 'redirect') {
|
|
||||||
newState.redirectValue = value;
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unexpected input changed: ${input as string}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(newState);
|
|
||||||
};
|
|
||||||
|
|
||||||
onSelectChange = (event: Event, select: 'matcher' | 'redirect') => {
|
|
||||||
const target = event.target as HTMLSelectElement;
|
|
||||||
const value = target.value;
|
|
||||||
const newState: Partial<State> = {
|
|
||||||
hasBeenEdited: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (select === 'matcher' && narrowMatcherType(value)) {
|
|
||||||
newState.matcherType = value;
|
|
||||||
} else if (select === 'redirect' && narrowRedirectType(value)) {
|
|
||||||
newState.redirectType = value;
|
|
||||||
} else {
|
|
||||||
throw new Error(`${value} is not a valid MatcherType or RedirectType`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(newState);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMatcherInput = (event: Event) => {
|
|
||||||
this.onInput(event, 'matcher');
|
|
||||||
};
|
|
||||||
|
|
||||||
onMatcherTypeChange = (event: Event) => {
|
|
||||||
this.onSelectChange(event, 'matcher');
|
|
||||||
};
|
|
||||||
|
|
||||||
onRedirectInput = (event: Event) => {
|
|
||||||
this.onInput(event, 'redirect');
|
|
||||||
};
|
|
||||||
|
|
||||||
onRedirectTypeChange = (event: Event) => {
|
|
||||||
this.onSelectChange(event, 'redirect');
|
|
||||||
};
|
|
||||||
|
|
||||||
parseRedirect = (): Redirect => {
|
|
||||||
const redirect = parseRedirect({
|
|
||||||
enabled: this.state.enabled,
|
|
||||||
id: this.state.id,
|
|
||||||
matcherType: this.state.matcherType,
|
|
||||||
matcherValue: this.state.matcherValue,
|
|
||||||
redirectType: this.state.redirectType,
|
|
||||||
redirectValue: this.state.redirectValue,
|
|
||||||
});
|
|
||||||
if (redirect === undefined) {
|
|
||||||
throw new Error('Failed to parse redirect');
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect;
|
|
||||||
};
|
|
||||||
|
|
||||||
remove = async () => {
|
|
||||||
const redirect = this.props.redirect;
|
|
||||||
await browser.storage.local.remove(redirect.idString());
|
|
||||||
this.props.removeRedirect(redirect.parameters.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
save = async () => {
|
|
||||||
const redirect = this.parseRedirect();
|
|
||||||
await storage.save(redirect);
|
|
||||||
this.props.saveRedirect(redirect);
|
|
||||||
this.setState({hasBeenEdited: false});
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleEnabled = async () => {
|
|
||||||
const enabled = !this.state.enabled;
|
|
||||||
const redirect = this.props.redirect;
|
|
||||||
const prepared = await storage.prepareForStorage(redirect);
|
|
||||||
prepared[redirect.idString()].enabled = enabled;
|
|
||||||
await browser.storage.local.set(prepared);
|
|
||||||
this.setState({enabled});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
enabled,
|
|
||||||
hasBeenEdited,
|
|
||||||
matcherType,
|
|
||||||
matcherValue,
|
|
||||||
redirectType,
|
|
||||||
redirectValue,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const matcherTypeOptions = matcherTypes.map(
|
|
||||||
(value) => html`<option value=${value}>${value}</option>`,
|
|
||||||
);
|
|
||||||
const redirectTypeOptions = redirectTypes.map(
|
|
||||||
(value) => html`<option value=${value}>${value}</option>`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="editor ${hasBeenEdited ? 'has-been-edited' : ''}">
|
|
||||||
<select
|
|
||||||
class="select"
|
|
||||||
id="matcher-type"
|
|
||||||
title="Matcher Type"
|
|
||||||
value=${matcherType}
|
|
||||||
onChange=${this.onMatcherTypeChange}
|
|
||||||
>
|
|
||||||
${matcherTypeOptions}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<input
|
|
||||||
class="input"
|
|
||||||
id="matcher-value"
|
|
||||||
onInput=${this.onMatcherInput}
|
|
||||||
placeholder="Matcher Value"
|
|
||||||
title="Matcher Value"
|
|
||||||
value=${matcherValue}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span class="arrow-span">→</span>
|
|
||||||
|
|
||||||
<select
|
|
||||||
class="select"
|
|
||||||
id="redirect-type"
|
|
||||||
title="Redirect Type"
|
|
||||||
value=${redirectType}
|
|
||||||
onChange=${this.onRedirectTypeChange}
|
|
||||||
>
|
|
||||||
${redirectTypeOptions}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<input
|
|
||||||
class="input"
|
|
||||||
id="redirect-value"
|
|
||||||
onInput=${this.onRedirectInput}
|
|
||||||
placeholder="Redirect Value"
|
|
||||||
title="Redirect Value"
|
|
||||||
value=${redirectValue}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<${ConfirmButton}
|
|
||||||
attributes=${{title: 'Remove Redirect'}}
|
|
||||||
class="button destructive"
|
|
||||||
click=${this.remove}
|
|
||||||
confirmClass="confirm"
|
|
||||||
confirmText="✓"
|
|
||||||
text="✗"
|
|
||||||
timeout=${5 * 1000}
|
|
||||||
/>
|
|
||||||
<button class="button" title="Save Redirect" onClick=${this.save}>
|
|
||||||
💾
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="button ${enabled ? 'enabled' : 'disabled'}"
|
|
||||||
title="${enabled ? 'Currently Enabled' : 'Currently Disabled'}"
|
|
||||||
onClick=${this.toggleEnabled}
|
|
||||||
>
|
|
||||||
${enabled ? '●' : '○'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import {PrivacyLink} from '@holllo/preact-components';
|
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component} from 'preact';
|
|
||||||
import browser from 'webextension-polyfill';
|
|
||||||
|
|
||||||
export class PageFooter extends Component {
|
|
||||||
render() {
|
|
||||||
const manifest = browser.runtime.getManifest();
|
|
||||||
const version = manifest.version;
|
|
||||||
|
|
||||||
const donateAttributes = {
|
|
||||||
href: 'https://liberapay.com/Holllo',
|
|
||||||
};
|
|
||||||
const donateLink = html`
|
|
||||||
<${PrivacyLink} attributes="${donateAttributes}">Donate<//>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const versionLinkAttributes = {
|
|
||||||
href: `https://git.bauke.xyz/Holllo/re-nav/releases/tag/${version}`,
|
|
||||||
};
|
|
||||||
const versionLink = html`
|
|
||||||
<${PrivacyLink} attributes=${versionLinkAttributes}>v${version}<//>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<footer class="page-footer">
|
|
||||||
<p>
|
|
||||||
${donateLink} 💖 ${versionLink} © Holllo — Free and open-source,
|
|
||||||
forever.
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component} from 'preact';
|
|
||||||
|
|
||||||
export class PageHeader extends Component {
|
|
||||||
render() {
|
|
||||||
return html`
|
|
||||||
<header class="page-header">
|
|
||||||
<h1>
|
|
||||||
<img alt="Re-Nav Logo" src="/assets/re-nav.png" />
|
|
||||||
Re-Nav
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component} from 'preact';
|
|
||||||
|
|
||||||
import {Redirect, SimpleRedirect} from '../../redirect/exports.js';
|
|
||||||
import storage from '../../redirect/storage.js';
|
|
||||||
|
|
||||||
import Editor from './editor.js';
|
|
||||||
import Usage from './usage.js';
|
|
||||||
|
|
||||||
type Props = Record<string, unknown>;
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
redirects: Redirect[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PageMain extends Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
redirects: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
const redirects = await storage.getRedirects();
|
|
||||||
|
|
||||||
// Sort the redirects by:
|
|
||||||
// * Matcher Type
|
|
||||||
// * then Matcher Value
|
|
||||||
// * then Redirect Type
|
|
||||||
// * finally Redirect Value
|
|
||||||
redirects.sort((a, b) => {
|
|
||||||
const {
|
|
||||||
matcherType: mTypeA,
|
|
||||||
matcherValue: mValueA,
|
|
||||||
redirectType: rTypeA,
|
|
||||||
redirectValue: rValueA,
|
|
||||||
} = a.parameters;
|
|
||||||
const {
|
|
||||||
matcherType: mTypeB,
|
|
||||||
matcherValue: mValueB,
|
|
||||||
redirectType: rTypeB,
|
|
||||||
redirectValue: rValueB,
|
|
||||||
} = b.parameters;
|
|
||||||
|
|
||||||
if (mTypeA !== mTypeB) {
|
|
||||||
return mTypeA.localeCompare(mTypeB);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mValueA !== mValueB) {
|
|
||||||
return mValueA.localeCompare(mValueB);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rTypeA !== rTypeB) {
|
|
||||||
return rTypeA.localeCompare(rTypeB);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rValueA.localeCompare(rValueB);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({redirects});
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewRedirect = async () => {
|
|
||||||
const redirect = new SimpleRedirect({
|
|
||||||
enabled: true,
|
|
||||||
id: await storage.nextRedirectId(),
|
|
||||||
matcherType: 'hostname',
|
|
||||||
matcherValue: 'example.com',
|
|
||||||
redirectType: 'simple',
|
|
||||||
redirectValue: 'example.org',
|
|
||||||
});
|
|
||||||
await storage.savePrepared(await storage.prepareForStorage(redirect));
|
|
||||||
this.setState({
|
|
||||||
redirects: [redirect, ...this.state.redirects],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
removeRedirect = (id: number) => {
|
|
||||||
this.setState({
|
|
||||||
redirects: this.state.redirects.filter(
|
|
||||||
(redirect) => redirect.parameters.id !== id,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
saveRedirect = (redirect: Redirect) => {
|
|
||||||
const redirectIndex = this.state.redirects.findIndex(
|
|
||||||
(found) => found.parameters.id === redirect.parameters.id,
|
|
||||||
);
|
|
||||||
if (redirectIndex === -1) {
|
|
||||||
this.setState({
|
|
||||||
redirects: [redirect, ...this.state.redirects],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const redirects = [...this.state.redirects];
|
|
||||||
redirects[redirectIndex] = redirect;
|
|
||||||
this.setState({redirects});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const editors = this.state.redirects.map(
|
|
||||||
(redirect) =>
|
|
||||||
html`
|
|
||||||
<${Editor}
|
|
||||||
key=${redirect.idString()}
|
|
||||||
redirect=${redirect}
|
|
||||||
removeRedirect=${this.removeRedirect}
|
|
||||||
saveRedirect=${this.saveRedirect}
|
|
||||||
/>
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<main class="page-main">
|
|
||||||
<div class="editors">
|
|
||||||
<button class="button new-redirect" onClick=${this.addNewRedirect}>
|
|
||||||
Add New Redirect
|
|
||||||
</button>
|
|
||||||
|
|
||||||
${editors}
|
|
||||||
</div>
|
|
||||||
<${Usage} />
|
|
||||||
</main>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component} from 'preact';
|
|
||||||
|
|
||||||
export default class Usage extends Component {
|
|
||||||
render() {
|
|
||||||
return html`
|
|
||||||
<details class="usage">
|
|
||||||
<summary>How do I use Re-Nav?</summary>
|
|
||||||
|
|
||||||
<p>Creating redirects:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Click the green "Add new redirect" button.</li>
|
|
||||||
<li>Select a matcher type and enter what it should match on.</li>
|
|
||||||
<li>
|
|
||||||
Select a redirect type and enter where you want to be redirected.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
See the "Matchers" and "Redirects" sections below for lists of
|
|
||||||
everything available with examples.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Using redirects:</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Any time you are navigated to a link by your browser, the URL will
|
|
||||||
first be checked and you will be redirected automatically.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Editing redirects:</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
If a redirect has been edited, a yellow border will be shown around
|
|
||||||
it.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Changes to redirects are only saved when you click the save button.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
To enable or disable a redirect, click the button with the circle.
|
|
||||||
If it's filled in the redirect is enabled.
|
|
||||||
</li>
|
|
||||||
<li>To remove a redirect click the red button with the ✗ twice.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Some miscellaneous notes:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Only URLs starting with "http" will be checked.</li>
|
|
||||||
<li>
|
|
||||||
Navigation events won't be checked if it has been less than 100
|
|
||||||
milliseconds since the last successful redirect.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
A redirect will be cancelled if the exact same redirect happened
|
|
||||||
less than 30 seconds ago. This acts as a quick bypass so you don't
|
|
||||||
have to disable redirects in the options page whenever you don't
|
|
||||||
want to be redirected.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>As a quick-start you can also insert the examples from below:</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Note that this will reload the page so make sure your redirects have
|
|
||||||
been saved.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button class="button" onClick=${window.Holllo.insertExamples}>
|
|
||||||
Insert Examples
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details class="usage table">
|
|
||||||
<summary>Matchers</summary>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Match Directive</th>
|
|
||||||
<th>Match Examples</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class="bold center-text" rowspan="2">Hostname</td>
|
|
||||||
<td class="center-text" rowspan="2">tildes.net</td>
|
|
||||||
<td>http://<b>www.tildes.net</b>/<sup>1</sup></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>https://<b>tildes.net</b>/~creative.timasomo</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="alt">
|
|
||||||
<td class="bold center-text" rowspan="2">Regex</td>
|
|
||||||
<td class="center-text">HOL{3}O</td>
|
|
||||||
<td>https://git.bauke.xyz/<b>holllo</b><sup>2</sup></td>
|
|
||||||
</tr>
|
|
||||||
<tr class="alt">
|
|
||||||
<td class="center-text">^https?://www\\.holllo\\.org/?$</td>
|
|
||||||
<td><b>https://www.holllo.org/</b></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<ol class="footnotes">
|
|
||||||
<li>
|
|
||||||
Hostname matchers always remove "www." automatically, for
|
|
||||||
convenience.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Regular expressions are always tested with global and
|
|
||||||
case-insensitive flags enabled.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details class="usage table">
|
|
||||||
<summary>Redirects</summary>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Change To</th>
|
|
||||||
<th>Example URL</th>
|
|
||||||
<th>Redirected URL<sup>1</sup></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class="bold center-text" rowspan="2">Hostname</td>
|
|
||||||
<td>nitter.net</td>
|
|
||||||
<td>https://twitter.com/therealTDH</td>
|
|
||||||
<td>https://<b>nitter.net</b>/therealTDH</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>r.nf</td>
|
|
||||||
<td>https://www.reddit.com/r/TheDearHunter</td>
|
|
||||||
<td>https://<b>r.nf</b>/r/TheDearHunter</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="alt">
|
|
||||||
<td class="bold center-text">Simple</td>
|
|
||||||
<td>https://holllo.org</td>
|
|
||||||
<td>https://holllo.org/home</td>
|
|
||||||
<td><b>https://holllo.org</b></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<ol class="footnotes">
|
|
||||||
<li>The bold highlighted text shows what will be changed.</li>
|
|
||||||
</ol>
|
|
||||||
</details>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
import browser from 'webextension-polyfill';
|
|
||||||
|
|
||||||
import {RedirectParameters} from '../redirect/base.js';
|
|
||||||
import storage from '../redirect/storage.js';
|
|
||||||
|
|
||||||
const examples: RedirectParameters[] = [
|
|
||||||
{
|
|
||||||
enabled: true,
|
|
||||||
id: -1,
|
|
||||||
matcherType: 'hostname',
|
|
||||||
matcherValue: 'twitter.com',
|
|
||||||
redirectType: 'hostname',
|
|
||||||
redirectValue: 'nitter.net',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: true,
|
|
||||||
id: -1,
|
|
||||||
matcherType: 'hostname',
|
|
||||||
matcherValue: 'reddit.com',
|
|
||||||
redirectType: 'hostname',
|
|
||||||
redirectValue: 'r.nf',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: true,
|
|
||||||
id: -1,
|
|
||||||
matcherType: 'regex',
|
|
||||||
matcherValue: '^https?://holllo\\.org/renav/?$',
|
|
||||||
redirectType: 'simple',
|
|
||||||
redirectValue: 'https://holllo.org/re-nav',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function generateExamples(): Promise<
|
|
||||||
Record<string, RedirectParameters>
|
|
||||||
> {
|
|
||||||
const prepared: Record<string, RedirectParameters> = {};
|
|
||||||
let nextId = await storage.nextRedirectId();
|
|
||||||
for (const example of examples) {
|
|
||||||
example.id = nextId;
|
|
||||||
prepared[`redirect:${nextId}`] = example;
|
|
||||||
nextId += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
await browser.storage.local.set({latestId: nextId - 1});
|
|
||||||
return prepared;
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Re-Nav</title>
|
|
||||||
<link rel="shortcut icon" href="/assets/re-nav.png" type="image/png">
|
|
||||||
<link rel="stylesheet" href="./index.scss">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="love">
|
|
||||||
<noscript>
|
|
||||||
This WebExtension doesn't work without JavaScript enabled, sorry! 😭
|
|
||||||
</noscript>
|
|
||||||
|
|
||||||
<script type="module" src="./index.ts"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,41 +0,0 @@
|
||||||
@use '../../node_modules/modern-normalize/modern-normalize.css';
|
|
||||||
@use 'scss/reset';
|
|
||||||
@use 'scss/mixins';
|
|
||||||
@use 'scss/love';
|
|
||||||
@use 'scss/components/page-header';
|
|
||||||
@use 'scss/components/page-main';
|
|
||||||
@use 'scss/components/page-footer';
|
|
||||||
@use 'scss/components/editor';
|
|
||||||
@use 'scss/components/usage';
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 62.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--db-1);
|
|
||||||
color: var(--df-1);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--da-3);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--df-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:focus {
|
|
||||||
outline-color: var(--df-1);
|
|
||||||
outline-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-text {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component, render} from 'preact';
|
|
||||||
|
|
||||||
import storage from '../redirect/storage.js';
|
|
||||||
import {PageFooter} from './components/page-footer.js';
|
|
||||||
import {PageHeader} from './components/page-header.js';
|
|
||||||
import {PageMain} from './components/page-main.js';
|
|
||||||
import {generateExamples} from './examples.js';
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
window.Holllo = {
|
|
||||||
async insertExamples() {
|
|
||||||
await storage.savePrepared(await generateExamples());
|
|
||||||
location.reload();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
render(html`<${OptionsPage} />`, document.body);
|
|
||||||
});
|
|
||||||
|
|
||||||
class OptionsPage extends Component {
|
|
||||||
render() {
|
|
||||||
return html`
|
|
||||||
<${PageHeader} />
|
|
||||||
<${PageMain} />
|
|
||||||
<${PageFooter} />
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
.editor {
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--db-2);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
display: flex;
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
&.has-been-edited {
|
|
||||||
border: 1px solid var(--da-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-span {
|
|
||||||
aspect-ratio: 1;
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
font-weight: bold;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
aspect-ratio: 1;
|
|
||||||
height: 100%;
|
|
||||||
margin-left: 8px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&.enabled {
|
|
||||||
background-color: var(--da-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
background-color: var(--da-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
background-color: var(--db-1);
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
color: var(--df-1);
|
|
||||||
height: 100%;
|
|
||||||
padding: 4px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select {
|
|
||||||
appearance: none;
|
|
||||||
background: url('../../../assets/down-arrow.svg') no-repeat center right;
|
|
||||||
background-size: 1.5rem;
|
|
||||||
background-color: var(--db-1);
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
border-radius: 0;
|
|
||||||
color: var(--df-1);
|
|
||||||
cursor: pointer;
|
|
||||||
height: 100%;
|
|
||||||
margin-right: 8px;
|
|
||||||
padding: 4px 24px 4px 4px;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
@use '../mixins';
|
|
||||||
|
|
||||||
.page-footer {
|
|
||||||
@include mixins.responsive-container;
|
|
||||||
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
@use '../mixins';
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
@include mixins.responsive-container;
|
|
||||||
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
background-color: var(--df-2);
|
|
||||||
display: inline-block;
|
|
||||||
height: 4.5rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
padding: 8px;
|
|
||||||
width: 4.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
@use '../mixins';
|
|
||||||
|
|
||||||
.page-main {
|
|
||||||
@include mixins.responsive-container;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
.button {
|
|
||||||
background-color: var(--da-3);
|
|
||||||
border: none;
|
|
||||||
color: var(--db-1);
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&.destructive {
|
|
||||||
background-color: var(--da-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.new-redirect {
|
|
||||||
background-color: var(--da-4);
|
|
||||||
padding: 8px;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--df-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.editors {
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
display: grid;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
.usage {
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
|
|
||||||
&[open] {
|
|
||||||
summary {
|
|
||||||
background-color: var(--df-2);
|
|
||||||
color: var(--db-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.table) summary {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> :not(summary) {
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--df-1);
|
|
||||||
color: var(--db-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: square;
|
|
||||||
margin: 4px 0 2rem 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
border-bottom: 1px solid var(--df-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
border-bottom: 1px solid var(--df-2);
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
&:not(:last-child) {
|
|
||||||
border-right: 1px solid var(--df-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
&.alt {
|
|
||||||
background-color: var(--db-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
border-bottom: 1px solid var(--df-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 4px;
|
|
||||||
|
|
||||||
b {
|
|
||||||
color: var(--da-3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footnotes {
|
|
||||||
margin-left: 12px;
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
code {
|
|
||||||
background-color: var(--db-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
The Love Theme CSS Custom Properties
|
|
||||||
https://love.holllo.cc - version 0.1.0
|
|
||||||
MIT license
|
|
||||||
*/
|
|
||||||
|
|
||||||
.love {
|
|
||||||
/* Love Dark */
|
|
||||||
--df-1: #f2efff;
|
|
||||||
--df-2: #e6deff;
|
|
||||||
--db-1: #1f1731;
|
|
||||||
--db-2: #2a2041;
|
|
||||||
--da-1: #f99fb1;
|
|
||||||
--da-2: #faa56c;
|
|
||||||
--da-3: #d2b83a;
|
|
||||||
--da-4: #96c839;
|
|
||||||
--da-5: #3bd18a;
|
|
||||||
--da-6: #3ecdbf;
|
|
||||||
--da-7: #41c8e5;
|
|
||||||
--da-8: #98b9f8;
|
|
||||||
--da-9: #d5a6f8;
|
|
||||||
--da-10: #f99add;
|
|
||||||
--dg-1: #e2e2e2;
|
|
||||||
--dg-2: #c6c6c6;
|
|
||||||
--dg-3: #ababab;
|
|
||||||
|
|
||||||
/* Love Light */
|
|
||||||
--lf-1: #1f1731;
|
|
||||||
--lf-2: #2a2041;
|
|
||||||
--lb-1: #f2efff;
|
|
||||||
--lb-2: #e6deff;
|
|
||||||
--la-1: #8b123c;
|
|
||||||
--la-2: #6a3b11;
|
|
||||||
--la-3: #514610;
|
|
||||||
--la-4: #384d10;
|
|
||||||
--la-5: #115133;
|
|
||||||
--la-6: #124f49;
|
|
||||||
--la-7: #144d5a;
|
|
||||||
--la-8: #17477e;
|
|
||||||
--la-9: #6f1995;
|
|
||||||
--la-10: #81156a;
|
|
||||||
--lg-1: #1b1b1b;
|
|
||||||
--lg-2: #303030;
|
|
||||||
--lg-3: #474747;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
@use 'variables';
|
|
||||||
|
|
||||||
@mixin responsive-container {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
width: variables.$large-breakpoint;
|
|
||||||
|
|
||||||
@media (max-width: variables.$large-breakpoint) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
li,
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
$small-breakpoint: 600px;
|
|
||||||
$medium-breakpoint: 900px;
|
|
||||||
$large-breakpoint: 1200px;
|
|
||||||
$extra-large-breakpoint: 1800px;
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {customAlphabet} from 'nanoid';
|
||||||
|
|
||||||
export const matcherTypes = ['hostname', 'regex'] as const;
|
export const matcherTypes = ['hostname', 'regex'] as const;
|
||||||
export const redirectTypes = ['hostname', 'simple'] as const;
|
export const redirectTypes = ['hostname', 'simple'] as const;
|
||||||
|
|
||||||
|
@ -14,7 +16,6 @@ export function narrowRedirectType(value: string): value is RedirectType {
|
||||||
|
|
||||||
export type RedirectParameters = {
|
export type RedirectParameters = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
id: number;
|
|
||||||
matcherType: MatcherType;
|
matcherType: MatcherType;
|
||||||
matcherValue: string;
|
matcherValue: string;
|
||||||
redirectType: RedirectType;
|
redirectType: RedirectType;
|
||||||
|
@ -22,14 +23,16 @@ export type RedirectParameters = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class Redirect {
|
export abstract class Redirect {
|
||||||
public static idString(id: number): string {
|
public static generateId(): string {
|
||||||
return `redirect:${id}`;
|
const alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
const nanoid = customAlphabet(`${alphabet}${alphabet.toUpperCase()}`, 20);
|
||||||
|
return nanoid();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public parameters: RedirectParameters) {}
|
public id: string;
|
||||||
|
|
||||||
public idString(): string {
|
constructor(public parameters: RedirectParameters, id?: string) {
|
||||||
return Redirect.idString(this.parameters.id);
|
this.id = id ?? Redirect.generateId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isMatch(url: URL): boolean {
|
public isMatch(url: URL): boolean {
|
||||||
|
|
|
@ -10,14 +10,15 @@ export type Redirects = HostnameRedirect | SimpleRedirect;
|
||||||
|
|
||||||
export function parseRedirect(
|
export function parseRedirect(
|
||||||
parameters: RedirectParameters,
|
parameters: RedirectParameters,
|
||||||
|
id: string,
|
||||||
): Redirects | undefined {
|
): Redirects | undefined {
|
||||||
const redirectType = parameters?.redirectType;
|
const redirectType = parameters?.redirectType;
|
||||||
|
|
||||||
if (redirectType === 'hostname') {
|
if (redirectType === 'hostname') {
|
||||||
return new HostnameRedirect(parameters);
|
return new HostnameRedirect(parameters, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redirectType === 'simple') {
|
if (redirectType === 'simple') {
|
||||||
return new SimpleRedirect(parameters);
|
return new SimpleRedirect(parameters, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
import browser from 'webextension-polyfill';
|
|
||||||
|
|
||||||
import {Redirect, RedirectParameters} from './base.js';
|
|
||||||
import {parseRedirect} from './exports.js';
|
|
||||||
|
|
||||||
const redirectKeyRegex = /^redirect:\d+$/i;
|
|
||||||
|
|
||||||
async function getRedirects(): Promise<Redirect[]> {
|
|
||||||
const redirects: Redirect[] = [];
|
|
||||||
const stored = await browser.storage.local.get();
|
|
||||||
for (const [key, value] of Object.entries(stored)) {
|
|
||||||
if (!redirectKeyRegex.test(key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirect = parseRedirect(value);
|
|
||||||
if (redirect !== undefined) {
|
|
||||||
redirects.push(redirect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirects;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function nextRedirectId(): Promise<number> {
|
|
||||||
const {latestId} = await browser.storage.local.get('latestId');
|
|
||||||
const id = Number(latestId);
|
|
||||||
|
|
||||||
let nextId: number | undefined;
|
|
||||||
if (Number.isNaN(id)) {
|
|
||||||
const redirects = await getRedirects();
|
|
||||||
nextId = redirects.length + 1;
|
|
||||||
} else {
|
|
||||||
nextId = id + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
await browser.storage.local.set({latestId: nextId});
|
|
||||||
return nextId;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function prepareForStorage(
|
|
||||||
redirect: Redirect,
|
|
||||||
): Promise<Record<string, RedirectParameters>> {
|
|
||||||
const prepared: Record<string, RedirectParameters> = {};
|
|
||||||
prepared[redirect.idString()] = redirect.parameters;
|
|
||||||
return prepared;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function save(redirect: Redirect): Promise<void> {
|
|
||||||
await savePrepared(await prepareForStorage(redirect));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function savePrepared(
|
|
||||||
prepared: Record<string, RedirectParameters>,
|
|
||||||
): Promise<void> {
|
|
||||||
await browser.storage.local.set(prepared);
|
|
||||||
}
|
|
||||||
|
|
||||||
const storage = {
|
|
||||||
getRedirects,
|
|
||||||
nextRedirectId,
|
|
||||||
prepareForStorage,
|
|
||||||
redirectKeyRegex,
|
|
||||||
save,
|
|
||||||
savePrepared,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default storage;
|
|
|
@ -16,11 +16,5 @@ declare global {
|
||||||
readonly VITE_BROWSER: 'chromium' | 'firefox';
|
readonly VITE_BROWSER: 'chromium' | 'firefox';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Window {
|
|
||||||
Holllo: {
|
|
||||||
insertExamples(): Promise<void>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type HtmComponent = ReturnType<typeof html>;
|
type HtmComponent = ReturnType<typeof html>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
|
|
||||||
const hostnameParameters: RedirectParameters = {
|
const hostnameParameters: RedirectParameters = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
id: 1,
|
|
||||||
matcherType: 'hostname',
|
matcherType: 'hostname',
|
||||||
matcherValue: 'example.com',
|
matcherValue: 'example.com',
|
||||||
redirectType: 'hostname',
|
redirectType: 'hostname',
|
||||||
|
@ -26,7 +25,6 @@ const hostnameParameters: RedirectParameters = {
|
||||||
|
|
||||||
const simpleParameters: RedirectParameters = {
|
const simpleParameters: RedirectParameters = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
id: 2,
|
|
||||||
matcherType: 'hostname',
|
matcherType: 'hostname',
|
||||||
matcherValue: 'example.com',
|
matcherValue: 'example.com',
|
||||||
redirectType: 'simple',
|
redirectType: 'simple',
|
||||||
|
@ -44,12 +42,13 @@ test('parseRedirect', (t) => {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const sample of samples) {
|
for (const sample of samples) {
|
||||||
const redirect = parseRedirect(sample);
|
const redirect = parseRedirect(sample, Redirect.generateId());
|
||||||
|
|
||||||
if (redirect === undefined) {
|
if (redirect === undefined) {
|
||||||
t.pass('parseRedirect returned undefined');
|
t.pass('parseRedirect returned undefined');
|
||||||
} else {
|
} else {
|
||||||
t.regex(redirect?.idString(), /^redirect:\d+$/i);
|
t.regex(redirect.id, /^[a-z]{20}$/i);
|
||||||
|
redirect.id = 'id';
|
||||||
t.snapshot(redirect, `Class ${redirect.constructor.name}`);
|
t.snapshot(redirect, `Class ${redirect.constructor.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +92,6 @@ test('Redirect.isMatch', (t) => {
|
||||||
|
|
||||||
const regexMatch = new HostnameRedirect({
|
const regexMatch = new HostnameRedirect({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
id: 3,
|
|
||||||
matcherType: 'regex',
|
matcherType: 'regex',
|
||||||
matcherValue: String.raw`^https://(www\.)?example.org/$`,
|
matcherValue: String.raw`^https://(www\.)?example.org/$`,
|
||||||
redirectType: 'simple',
|
redirectType: 'simple',
|
||||||
|
|
|
@ -9,9 +9,9 @@ Generated by [AVA](https://avajs.dev).
|
||||||
> Class HostnameRedirect
|
> Class HostnameRedirect
|
||||||
|
|
||||||
HostnameRedirect {
|
HostnameRedirect {
|
||||||
|
id: 'id',
|
||||||
parameters: {
|
parameters: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
id: 1,
|
|
||||||
matcherType: 'hostname',
|
matcherType: 'hostname',
|
||||||
matcherValue: 'example.com',
|
matcherValue: 'example.com',
|
||||||
redirectType: 'hostname',
|
redirectType: 'hostname',
|
||||||
|
@ -22,9 +22,9 @@ Generated by [AVA](https://avajs.dev).
|
||||||
> Class SimpleRedirect
|
> Class SimpleRedirect
|
||||||
|
|
||||||
SimpleRedirect {
|
SimpleRedirect {
|
||||||
|
id: 'id',
|
||||||
parameters: {
|
parameters: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
id: 2,
|
|
||||||
matcherType: 'hostname',
|
matcherType: 'hostname',
|
||||||
matcherValue: 'example.com',
|
matcherValue: 'example.com',
|
||||||
redirectType: 'simple',
|
redirectType: 'simple',
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue