1
Fork 0

Compare commits

..

7 Commits

40 changed files with 7747 additions and 4738 deletions

3
.envrc Normal file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
use flake

109
.gitignore vendored
View File

@ -1,107 +1,4 @@
# Logs .direnv/
logs .netlify/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Build output directory
build/ build/
node_modules

View File

@ -1,9 +0,0 @@
{
"extends": [
"stylelint-config-standard-scss"
],
"rules": {
"no-descending-specificity": null,
"string-quotes": "single"
}
}

41
Makefile.toml Normal file
View File

@ -0,0 +1,41 @@
# Build the website.
[tasks.build]
clear = true
command = "pnpm"
args = ["astro", "build"]
# Remove build directories.
[tasks.clean]
clear = true
command = "pnpm"
args = ["trash", "public"]
# Deploy the website to Netlify.
[tasks.deploy-netlify]
clear = true
command = "pnpm"
dependencies = ["clean", "lint", "build"]
args = ["netlify", "deploy", "-p", "-d", "build/", "-s", "holllo.org"]
# Run all other linting tasks.
[tasks.lint]
clear = true
dependencies = ["lint-js", "lint-scss"]
# Run XO.
[tasks.lint-js]
clear = true
command = "pnpm"
args = ["xo"]
# Run Stylelint.
[tasks.lint-scss]
clear = true
command = "pnpm"
args = ["stylelint", "source/**/*.scss"]
# Start a development server and watch for changes.
[tasks.watch]
clear = true
command = "pnpm"
args = ["astro", "dev"]

16
astro.config.ts Normal file
View File

@ -0,0 +1,16 @@
import path from "node:path";
import {defineConfig} from "astro/config";
const relative = (input: string) => new URL(input, import.meta.url).pathname;
const buildDir = relative("build");
const sourceDir = relative("source");
export default defineConfig({
// eslint-disable-next-line @typescript-eslint/naming-convention
compressHTML: true,
outDir: buildDir,
publicDir: path.join(sourceDir, "assets/"),
site: "https://holllo.org",
srcDir: sourceDir,
});

59
flake.lock Normal file
View File

@ -0,0 +1,59 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1705403940,
"narHash": "sha256-bl7E3w35Bleiexg01WsN0RuAQEL23HaQeNBC2zjt+9w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f0326542989e1bdac955ad6269b334a8da4b0c95",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

13
flake.nix Normal file
View File

@ -0,0 +1,13 @@
{
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = import ./shell.nix { inherit pkgs; };
}
);
}

View File

@ -1,34 +1,32 @@
{ {
"private": true, "private": true,
"scripts": {
"start": "vite",
"build": "vite build",
"deploy": "pnpm test && pnpm build -- --emptyOutDir && pnpm deploy:netlify",
"deploy:netlify": "netlify deploy -p -d 'build' -s holllo.org",
"test": "xo && stylelint 'source/**/*.scss' && tsc --noEmit"
},
"dependencies": { "dependencies": {
"@fontsource/inter": "^4.5.12", "@fontsource/inter": "^5.0.16",
"js-base64": "^3.7.3", "js-base64": "^3.7.5",
"modern-normalize": "^1.1.0" "modern-normalize": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.7.22", "@bauke/eslint-config": "^0.1.5",
"@types/nunjucks": "^3.2.1", "@bauke/prettier-config": "^0.1.5",
"netlify-cli": "^14.2.1", "@bauke/stylelint-config": "^0.1.5",
"sass": "^1.55.0", "@types/node": "^20.11.5",
"stylelint": "^14.12.1", "@types/nunjucks": "^3.2.6",
"stylelint-config-standard-scss": "^5.0.0", "astro": "^4.1.3",
"typescript": "^4.8.3", "netlify-cli": "^17.14.0",
"vite": "^3.1.3", "sass": "^1.69.7",
"vite-plugin-nunjucks": "^0.1.10", "stylelint": "^16.1.0",
"xo": "^0.52.3" "trash-cli": "^5.0.0",
"typescript": "^5.3.3",
"xo": "^0.56.0"
},
"prettier": "@bauke/prettier-config",
"stylelint": {
"extends": "@bauke/stylelint-config"
}, },
"xo": { "xo": {
"extends": "@bauke/eslint-config",
"extensions": ["astro"],
"prettier": true, "prettier": true,
"rules": {
"@typescript-eslint/consistent-type-definitions": "off"
},
"space": true "space": true
} }
} }

File diff suppressed because it is too large Load Diff

7
shell.nix Normal file
View File

@ -0,0 +1,7 @@
{ pkgs ? import <nixpkgs> { } }:
with pkgs;
mkShell rec {
packages = [ cargo-make nodejs nodePackages.pnpm ];
}

View File

@ -0,0 +1,47 @@
---
/** Props for the WebExtensionStore component. */
export interface Props {
/** The name of the WebExtension. */
name: string;
/** The store to create the link for. */
store: string;
/** The ID of the WebExtension to append to the store link. */
webExtensionId: string;
}
const {name, store, webExtensionId} = Astro.props;
let [storeImage, storeName, storeUrl] = ["", "", ""];
switch (store) {
case "chrome": {
storeImage = "/chrome-web-store.png";
storeName = "Chrome";
storeUrl = `https://chrome.google.com/webstore/detail/${webExtensionId}`;
break;
}
case "edge": {
storeImage = "/microsoft.png";
storeName = "Edge";
storeUrl = `https://microsoftedge.microsoft.com/addons/detail/${webExtensionId}`;
break;
}
case "firefox": {
storeImage = "/mozilla-addons.png";
storeName = "Firefox";
storeUrl = `https://addons.mozilla.org/firefox/addon/${webExtensionId}`;
break;
}
default: {
throw new Error(`Unrecognized WebExtension store: ${store}`);
}
}
const altText = `Get ${name} for ${storeName}`;
---
<a href={storeUrl} title={altText}>
<img alt={altText} src={storeImage} />
</a>

23
source/env.d.ts vendored Normal file
View File

@ -0,0 +1,23 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference types="astro/client" />
declare global {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Window {
"Re-Nav": {
decodeBase64<T>(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.
export {};

View File

@ -1,45 +0,0 @@
{% extends "source/includes/base.html" %}
{% import "source/includes/macros.html" as macros %}
{% set title = "Fangs 🧛" %}
{% block head %}
<link rel="stylesheet" href="/scss/fangs/fangs.scss">
{% endblock %}
{% block body %}
<header class="page-header">
<h1>
<img src="../assets/fangs-128-opaque.png" alt="Fangs Logo">
Fangs
</h1>
<p class="byline">Inject custom DuckDuckGo Bangs.</p>
</header>
<main class="page-main">
<p class="store-links">
<a title="Get Fangs for Firefox"
href="https://addons.mozilla.org/firefox/addon/fangs">
<img src="../assets/mozilla-addons.png" alt="Get Fangs for Firefox">
</a>
<a title="Get Fangs for Chrome"
href="https://chrome.google.com/webstore/detail/fangs/dlllfannplfkhbiidhihagjkbmcolclf">
<img src="../assets/chrome-web-store.png" alt="Get Fangs for Chrome">
</a>
<a title="Get Fangs for Edge"
href="https://microsoftedge.microsoft.com/addons/detail/fangs/fgfkpbflhnljpfniippaagjjlncobhjd">
<img src="../assets/microsoft.png" alt="Get Fangs for Edge">
</a>
</p>
<img class="screenshot" alt="Fangs Screenshot" title="Fangs Screenshot"
src="../assets/fangs-screenshot.png">
</main>
<footer class="page-footer">
&copy;
<a href="https://git.bauke.xyz/Holllo/fangs">AGPL-3.0-or-later</a>
💖
<a href="mailto:helllo@holllo.org">helllo@holllo.org</a>
</footer>
{% endblock %}

View File

@ -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>{{ title }}</title>
<link rel="shortcut icon" href="/assets/holllo-mark-square.png" type="image/png">
<link rel="stylesheet" href="/scss/modern-normalize.scss">
<link rel="stylesheet" href="/scss/love.scss">
<link rel="stylesheet" href="/scss/common.scss">
{% block head %}{% endblock %}
</head>
<body class="love">
{% block body %}{% endblock %}
<script src="/ts/common.ts" type="module"></script>
</body>
</html>

View File

@ -1,43 +0,0 @@
{%- macro iconLink(link, text='Unknown', icon='none') -%}
{%- set link = linkFromType(link, icon) -%}
<a class="icon-link" href="{{ link }}" rel="noopener noreferrer">
<img alt="{{ text }}" src="/assets/{{ icon }}.png">
</a>
{%- endmacro -%}
{%- macro link(link, text='', type='default', linkAsText=false) -%}
{%- if linkAsText -%}
{%- set text = link -%}
{%- endif -%}
{%- set link = linkFromType(link, type) -%}
<a href="{{ link }}" rel="noopener noreferrer">{{ text }}</a>
{%- endmacro -%}
{%- macro linkFromType(link, type='default') -%}
{%- if type === 'github' -%}
{%- set text = 'GitHub' -%}
{%- set link = 'https://github.com/' + link -%}
{%- elif type === 'mozilla-firefox' -%}
{%- set text = 'Mozilla Firefox' -%}
{%- set link = 'https://addons.mozilla.org/firefox/addon/' + link -%}
{%- elif type === 'google-chrome' -%}
{%- set text = 'Google Chrome' -%}
{%- set link = 'https://chrome.google.com/webstore/detail/' + link -%}
{%- elif type === 'microsoft-edge' -%}
{%- set text = 'Microsoft Edge' -%}
{%- set link = 'https://microsoftedge.microsoft.com/addons/detail/' + link -%}
{%- elif type === 'crates-io' -%}
{%- set text = 'Crates.io' -%}
{%- set link = 'https://crates.io/crates/' + link -%}
{%- elif type === 'pypi' -%}
{%- set text = 'Python Package Index' -%}
{%- set link = 'https://pypi.org/project/' + link -%}
{%- elif type === 'npm' -%}
{%- set text = 'npm' -%}
{%- set link = 'https://www.npmjs.com/package/' + link -%}
{%- endif -%}
{{- link -}}
{%- endmacro -%}

33
source/layouts/base.astro Normal file
View File

@ -0,0 +1,33 @@
---
import "@fontsource/inter/latin.css";
import "../scss/common.scss";
/** Properties for the base layout. */
export interface Props {
/** Page frontmatter data. */
frontmatter: {
/** Path to an image to use as the favicon, defaults to the Holllo logo. */
favicon?: string;
/** The text to use for the `<title>` element. */
pageTitle: string;
};
}
const {frontmatter} = Astro.props;
const favicon = "/" + (frontmatter.favicon ?? "holllo-mark-square.png");
---
<!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>{frontmatter.pageTitle}</title>
<link rel="shortcut icon" href={favicon} type="image/png" />
</head>
<body class="love">
<slot />
</body>
</html>

View File

@ -0,0 +1,80 @@
---
import WebExtensionStore from "../components/web-extension-store.astro";
import BaseLayout, {Props as BaseProps} from "./base.astro";
/** Props for the WebExtension layout. */
export interface Props {
frontmatter: {
/** A one-liner for a short description. */
byline?: string;
/** Data for the footer. */
footer: {
/** A link to the code's git repository. */
link: string;
/** The SPDX license identifier of the project. */
license: string;
};
/** The name of the WebExtension to use in the header. */
name: string;
/** The path for the screenshot image. */
screenshot: string;
/** All the stores the WebExtension is available in. */
stores: {
/** The ID for the Chrome Web Store page. */
chrome?: string;
/** The ID for the Microsoft Edge Marketplace page. */
edge?: string;
/** The ID for the Mozilla Addons page. */
firefox?: string;
};
} & BaseProps["frontmatter"];
}
const {frontmatter} = Astro.props;
---
<BaseLayout {...Astro.props}>
<header class="page-header">
<h1>
<slot name="header-image" />
{frontmatter.name}
</h1>
{
frontmatter.byline ? (
<p class="byline">{frontmatter.byline}</p>
) : undefined
}
</header>
<main class="page-main">
<slot name="main-content">
<p class="store-links">
{
Object.entries(frontmatter.stores).map(([key, value]) => (
<WebExtensionStore
name={frontmatter.name}
store={key}
webExtensionId={value as string}
/>
))
}
</p>
<img
class="screenshot"
alt=`${frontmatter.name} Screenshot`
title=`${frontmatter.name} Screenshot`
src={frontmatter.screenshot}
/>
</slot>
</main>
<footer class="page-footer">
&copy;
<a href={frontmatter.footer.link}>{frontmatter.footer.license}</a>
💖
<a href="mailto:helllo@holllo.org">helllo@holllo.org</a>
<slot name="extra-footer-content" />
</footer>
</BaseLayout>

28
source/pages/fangs.astro Normal file
View File

@ -0,0 +1,28 @@
---
import "../scss/pages/fangs.scss";
import WebExtensionLayout, {
Props as WebExtensionProps,
} from "../layouts/web-extension.astro";
const props: WebExtensionProps = {
frontmatter: {
byline: "Inject custom DuckDuckGo Bangs.",
footer: {
link: "https://git.bauke.xyz/Holllo/fangs",
license: "AGPL-3.0-or-later",
},
name: "Fangs",
pageTitle: "Fangs 🧛",
screenshot: "/fangs-screenshot.png",
stores: {
firefox: "fangs",
chrome: "fangs/dlllfannplfkhbiidhihagjkbmcolclf",
edge: "fangs/fgfkpbflhnljpfniippaagjjlncobhjd",
},
},
};
---
<WebExtensionLayout {...props}>
<img alt="Fangs Logo" slot="header-image" src="/fangs-128-opaque.png" />
</WebExtensionLayout>

20
source/pages/index.astro Normal file
View File

@ -0,0 +1,20 @@
---
import "../scss/pages/home.scss";
import BaseLayout, {Props as BaseProps} from "../layouts/base.astro";
const props: BaseProps = {
frontmatter: {
pageTitle: "Holllo",
},
};
---
<BaseLayout {...props}>
<header class="page-header">
<img alt="Holllo" src="/holllo-mark.png" />
<p class="byline">Developing free and open-source software, forever.</p>
<a href="mailto:helllo@holllo.org">helllo@holllo.org</a>
</header>
</BaseLayout>

32
source/pages/queue.astro Normal file
View File

@ -0,0 +1,32 @@
---
import "../scss/pages/queue.scss";
import WebExtensionLayout, {
Props as WebExtensionProps,
} from "../layouts/web-extension.astro";
const props: WebExtensionProps = {
frontmatter: {
byline: "Effortless temporary bookmarks.",
footer: {
link: "https://github.com/Holllo/queue",
license: "AGPL-3.0-or-later",
},
name: "Queue",
pageTitle: "Queue ⇥",
screenshot: "/queue-screenshot.png",
stores: {
firefox: "holllo-queue",
chrome: "queue/epnbikemcmienphlfmidkimpjnmohcbl",
edge: "queue/aanjampfdpcnhoeglmfefmmegdbifaak",
},
},
};
---
<WebExtensionLayout {...props}>
<span class="icon" slot="header-image">⇥</span>
<span slot="extra-footer-content">
📚
<a href="https://git.bauke.xyz/Holllo/queue/wiki">Wiki</a>
</span>
</WebExtensionLayout>

View File

@ -0,0 +1,28 @@
---
import "../../scss/pages/re-nav.scss";
import WebExtensionLayout, {
Props as WebExtensionProps,
} from "../../layouts/web-extension.astro";
export const props: WebExtensionProps = {
frontmatter: {
byline: "Navigation redirects for the masses.",
footer: {
link: "https://github.com/Holllo/re-nav",
license: "AGPL-3.0-or-later",
},
name: "Re-Nav",
pageTitle: "Re-Nav ↩",
screenshot: "/re-nav-screenshot.png",
stores: {
firefox: "re-nav",
chrome: "re-nav/efjignaelidacjdhleaojfmkklganjjb",
edge: "renav/efnkhmlaemggdlpalglioeolbbhfpiic",
},
},
};
---
<WebExtensionLayout {...props}>
<img alt="Re-Nav Logo" src="/re-nav.png" slot="header-image" />
</WebExtensionLayout>

View File

@ -0,0 +1,43 @@
---
import "../../scss/pages/re-nav.scss";
import "../../scss/pages/re-nav-share.scss";
import WebExtensionLayout from "../../layouts/web-extension.astro";
import {props} from "./index.astro";
delete props.frontmatter.byline;
---
<WebExtensionLayout {...props}>
<img alt="Re-Nav Logo" src="/re-nav.png" slot="header-image" />
<slot slot="main-content">
<p class="subtitle">
Welcome to Re-Nav's share page!
<br />
Use the share button in Re-Nav's options page to create a link with your redirect.
</p>
<div class="hidden-by-default redirect">
<p class="matcher-data">
<span class="matcher-type"></span>
<span class="matcher-value"></span>
</p>
<p class="arrow">↓</p>
<p class="redirect-data">
<span class="redirect-type"></span>
<span class="redirect-value"></span>
</p>
</div>
<!-- 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. -->
<div class="hidden-by-default re-nav-import">
<p>
With <a href="..">Re-Nav</a> installed, you'll see a button here to import
this redirect.
</p>
</div>
</slot>
<script src="../../ts/re-nav-share.ts"></script>
</WebExtensionLayout>

View File

@ -1,47 +0,0 @@
{% extends "source/includes/base.html" %}
{% import "source/includes/macros.html" as macros %}
{% set title = "Queue ⇥" %}
{% block head %}
<link rel="stylesheet" href="/scss/queue/queue.scss">
{% endblock %}
{% block body %}
<header class="page-header">
<h1>
<span class="icon"></span>
Queue
</h1>
<p class="byline">Effortless temporary bookmarks.</p>
</header>
<main class="page-main">
<p class="store-links">
<a title="Get Queue for Firefox"
href="https://addons.mozilla.org/firefox/addon/holllo-queue">
<img src="../assets/mozilla-addons.png" alt="Get Queue for Firefox">
</a>
<a title="Get Queue for Chrome"
href="https://chrome.google.com/webstore/detail/queue/epnbikemcmienphlfmidkimpjnmohcbl">
<img src="../assets/chrome-web-store.png" alt="Get Queue for Chrome">
</a>
<a title="Get Queue for Edge"
href="https://microsoftedge.microsoft.com/addons/detail/queue/aanjampfdpcnhoeglmfefmmegdbifaak">
<img src="../assets/microsoft.png" alt="Get Queue for Edge">
</a>
</p>
<img class="screenshot" alt="Queue Screenshot" title="Queue Screenshot"
src="../assets/queue-screenshot.png">
</main>
<footer class="page-footer">
&copy;
<a href="https://git.bauke.xyz/Holllo/queue">AGPL-3.0-or-later</a>
💖
<a href="mailto:helllo@holllo.org">helllo@holllo.org</a>
📚
<a href="https://git.bauke.xyz/Holllo/queue/wiki">Wiki</a>
</footer>
{% endblock %}

View File

@ -1,50 +0,0 @@
{% extends "source/includes/base.html" %}
{% import "source/includes/macros.html" as macros %}
{% set title = "Re-Nav ↩" %}
{% block head %}
<link rel="stylesheet" href="/scss/re-nav/re-nav.scss">
{% endblock %}
{% block body %}
<header class="page-header">
<h1>
<img src="../assets/re-nav.png" alt="Re-Nav Logo">
Re-Nav
</h1>
<p class="byline">Navigation redirects for the masses.</p>
</header>
<main class="page-main">
<p class="store-links">
<a title="Get Re-Nav for Firefox"
href="https://addons.mozilla.org/firefox/addon/re-nav">
<img src="../assets/mozilla-addons.png" alt="Get Re-Nav for Firefox">
</a>
<a title="Get Re-Nav for Chrome"
href="https://chrome.google.com/webstore/detail/efjignaelidacjdhleaojfmkklganjjb">
<img src="../assets/chrome-web-store.png" alt="Get Re-Nav for Chrome">
</a>
<a title="Get Re-Nav for Edge"
href="https://microsoftedge.microsoft.com/addons/detail/efnkhmlaemggdlpalglioeolbbhfpiic">
<img src="../assets/microsoft.png" alt="Get Re-Nav for Edge">
</a>
</p>
<img class="screenshot" alt="Re-Nav Screenshot" title="Re-Nav Screenshot"
src="../assets/re-nav-screenshot.png">
<p class="timasomo-2022">
Re-Nav was created in one month during
<a href="https://tildes.net/~creative.timasomo/12ua/timasomo_2022_roll_call#comment-7htb">TiMaSoMo 2022</a>!
</p>
</main>
<footer class="page-footer">
&copy;
<a href="https://git.bauke.xyz/Holllo/re-nav">AGPL-3.0-or-later</a>
💖
<a href="mailto:helllo@holllo.org">helllo@holllo.org</a>
</footer>
{% endblock %}

View File

@ -1,61 +0,0 @@
{% extends "source/includes/base.html" %}
{% import "source/includes/macros.html" as macros %}
{% set title = "Re-Nav ↩ Share" %}
{% block head %}
<link rel="stylesheet" href="/scss/re-nav/re-nav.scss">
<link rel="stylesheet" href="/scss/re-nav/share.scss">
{% endblock %}
{% block body %}
<header class="page-header">
<h1>
<img src="../../assets/re-nav.png" alt="Re-Nav Logo">
Re-Nav
</h1>
</header>
<main class="page-main">
<p class="subtitle">
Welcome to Re-Nav's share page!
<br />
Use the share button in Re-Nav's options page to create a link with your
redirect.
</p>
<div class="hidden-by-default redirect">
<p class="matcher-data">
<span class="matcher-type"></span>
{{-":"-}}
<span class="matcher-value"></span>
</p>
<p class="arrow"></p>
<p class="redirect-data">
<span class="redirect-type"></span>
{{-":"-}}
<span class="redirect-value"></span>
</p>
</div>
{#
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.
#}
<div class="hidden-by-default re-nav-import">
<p>
With <a href="..">Re-Nav</a> installed, you'll see a button here to import
this redirect.
</p>
</div>
</main>
<footer class="page-footer">
&copy;
<a href="https://git.bauke.xyz/Holllo/re-nav">AGPL-3.0-or-later</a>
💖
<a href="mailto:helllo@holllo.org">helllo@holllo.org</a>
</footer>
<script src="../../ts/re-nav/share.ts" type="module"></script>
{% endblock %}

View File

@ -1,4 +1,6 @@
@use 'reset'; @use "modern-normalize/modern-normalize.css";
@use "reset";
@use "love";
html { html {
font-size: 62.5%; font-size: 62.5%;

View File

@ -17,8 +17,8 @@ $large-breakpoint: 1200px;
&::after { &::after {
--shadow-size: #{$size}; --shadow-size: #{$size};
background: url('/assets/shadow-pattern.png') repeat; background: url("/shadow-pattern.png") repeat;
content: ''; content: "";
height: 100%; height: 100%;
left: var(--shadow-size); left: var(--shadow-size);
position: absolute; position: absolute;

View File

@ -1 +0,0 @@
@use '../../node_modules/modern-normalize/modern-normalize.css';

View File

@ -1,4 +1,4 @@
@use '../mixins'; @use "../mixins";
body { body {
padding: var(--spacing-16); padding: var(--spacing-16);
@ -51,7 +51,7 @@ body {
justify-content: center; justify-content: center;
margin-bottom: var(--spacing-16); margin-bottom: var(--spacing-16);
@media (max-width: 600px) { @media (width < 600px) {
flex-direction: column; flex-direction: column;
} }

View File

@ -1,4 +1,4 @@
@use '../mixins'; @use "../mixins";
.sections { .sections {
@include mixins.responsive-container; @include mixins.responsive-container;

View File

@ -1,5 +1,5 @@
@use '../mixins'; @use "../mixins";
@use 'sections'; @use "home-sections";
.page-header { .page-header {
@include mixins.responsive-container; @include mixins.responsive-container;

View File

@ -1,4 +1,4 @@
@use '../mixins'; @use "../mixins";
body { body {
padding: var(--spacing-16); padding: var(--spacing-16);
@ -50,7 +50,7 @@ body {
justify-content: center; justify-content: center;
margin-bottom: var(--spacing-16); margin-bottom: var(--spacing-16);
@media (max-width: 600px) { @media (width < 600px) {
flex-direction: column; flex-direction: column;
} }

View File

@ -1,4 +1,4 @@
@use '../mixins'; @use "../mixins";
body { body {
padding: var(--spacing-16); padding: var(--spacing-16);
@ -51,7 +51,7 @@ body {
justify-content: center; justify-content: center;
margin-bottom: var(--spacing-16); margin-bottom: var(--spacing-16);
@media (max-width: 600px) { @media (width < 600px) {
flex-direction: column; flex-direction: column;
} }

View File

@ -1 +0,0 @@
import '../../node_modules/@fontsource/inter/latin.css';

View File

@ -1,12 +1,12 @@
import {Base64} from 'js-base64'; import {Base64} from "js-base64";
const fragmentPrefix = '#json='; const fragmentPrefix = "#json=";
const exampleRedirect: Redirect = { const exampleRedirect: Redirect = {
matcherType: 'hostname', matcherType: "hostname",
matcherValue: 'example.com', matcherValue: "example.com",
redirectType: 'hostname', redirectType: "hostname",
redirectValue: 'example.org', redirectValue: "example.org",
}; };
function decodeBase64<T>(base64: string): T { function decodeBase64<T>(base64: string): T {
@ -17,8 +17,8 @@ function encodeBase64(source: any): string {
return Base64.encode(JSON.stringify(source), true); return Base64.encode(JSON.stringify(source), true);
} }
window.addEventListener('DOMContentLoaded', () => { window.addEventListener("DOMContentLoaded", () => {
window['Re-Nav'] = { window["Re-Nav"] = {
decodeBase64, decodeBase64,
encodeBase64, encodeBase64,
share, share,
@ -26,11 +26,11 @@ window.addEventListener('DOMContentLoaded', () => {
console.log( console.log(
"Want to manually create a share link? Use the window['Re-Nav'].share", "Want to manually create a share link? Use the window['Re-Nav'].share",
'function, where redirect is an object with matcherType, matcherValue,', "function, where redirect is an object with matcherType, matcherValue,",
'redirectType and redirectValue properties. Like this:', "redirectType and redirectValue properties. Like this:",
); );
console.log(JSON.stringify(exampleRedirect, undefined, 2)); console.log(JSON.stringify(exampleRedirect, undefined, 2));
console.log(window['Re-Nav'].share); console.log(window["Re-Nav"].share);
if (!location.hash.startsWith(fragmentPrefix)) { if (!location.hash.startsWith(fragmentPrefix)) {
return; return;
@ -43,12 +43,12 @@ window.addEventListener('DOMContentLoaded', () => {
insertText(key as keyof Redirect, value); insertText(key as keyof Redirect, value);
} }
for (const element of document.querySelectorAll('.hidden-by-default')) { for (const element of document.querySelectorAll(".hidden-by-default")) {
element.classList.remove('hidden-by-default'); element.classList.remove("hidden-by-default");
} }
document.querySelector('.subtitle')!.textContent = document.querySelector(".subtitle")!.textContent =
'Someone shared a redirect with you!'; "Someone shared a redirect with you!";
}); });
function insertText(key: keyof Redirect, value: string): void { function insertText(key: keyof Redirect, value: string): void {
@ -57,14 +57,14 @@ function insertText(key: keyof Redirect, value: string): void {
}; };
// eslint-disable-next-line unicorn/prefer-switch // eslint-disable-next-line unicorn/prefer-switch
if (key === 'matcherType') { if (key === "matcherType") {
insert('.matcher-type', value); insert(".matcher-type", value);
} else if (key === 'matcherValue') { } else if (key === "matcherValue") {
insert('.matcher-value', value); insert(".matcher-value", value);
} else if (key === 'redirectType') { } else if (key === "redirectType") {
insert('.redirect-type', value); insert(".redirect-type", value);
} else if (key === 'redirectValue') { } else if (key === "redirectValue") {
insert('.redirect-value', value); insert(".redirect-value", value);
} else { } else {
console.warn(`Unknown key: ${key as string}`); console.warn(`Unknown key: ${key as string}`);
} }

34
source/types.d.ts vendored
View File

@ -1,34 +0,0 @@
declare global {
// See Vite documentation for `import.meta.env` usage.
// https://vitejs.dev/guide/env-and-mode.html
interface ImportMeta {
readonly env: ImportMetaEnv;
}
interface ImportMetaEnv {
readonly BASE_URL: string;
readonly DEV: boolean;
readonly MODE: string;
readonly PROD: boolean;
readonly VITE_BROWSER: 'chromium' | 'firefox';
}
interface Window {
'Re-Nav': {
decodeBase64<T>(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.
export {};

View File

@ -1,15 +1,14 @@
{ {
"compilerOptions": { "compilerOptions": {
"esModuleInterop": true, "esModuleInterop": true,
"module": "ESNext", "module": "ES2022",
"moduleResolution": "Node", "moduleResolution": "Node",
"noEmit": true, "noEmit": true,
"outDir": "build", "outDir": "build",
"strict": true, "strict": true,
"target": "ESNext" "target": "ES2022"
}, },
"include": [ "include": [
"source/**/*.ts", "source/**/*.ts"
"vite.config.ts"
] ]
} }

View File

@ -1,36 +0,0 @@
import path from 'node:path';
import url from 'node:url';
import {defineConfig} from 'vite';
import nunjucks from 'vite-plugin-nunjucks';
const currentDir = path.dirname(url.fileURLToPath(import.meta.url));
const buildDir = path.join(currentDir, 'build');
const sourceDir = path.join(currentDir, 'source');
export default defineConfig({
build: {
minify: false,
outDir: buildDir,
rollupOptions: {
input: {
fangs: path.join(sourceDir, 'fangs/index.html'),
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,
},
plugins: [
nunjucks({
nunjucksConfigure: {
throwOnUndefined: true,
},
}),
],
publicDir: path.join(sourceDir, 'static'),
root: sourceDir,
});