chore: apply automatic linting

This commit is contained in:
Corbin Crutchley
2022-09-25 05:51:04 -07:00
parent 5d628203d3
commit 3900a13481
92 changed files with 4787 additions and 4694 deletions

View File

@@ -1,46 +1,46 @@
const tsRules = { const tsRules = {
"@typescript-eslint/ban-types": "off", "@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-empty-interface": "off" "@typescript-eslint/no-empty-interface": "off",
} };
module.exports = { module.exports = {
env: { env: {
node: true, node: true,
browser: true, browser: true,
}, },
extends: ['eslint:recommended', 'plugin:astro/recommended'], extends: ["eslint:recommended", "plugin:astro/recommended"],
parserOptions: { parserOptions: {
ecmaVersion: 'latest', ecmaVersion: "latest",
sourceType: 'module', sourceType: "module",
}, },
rules: { rules: {
"no-unused-vars": "off" "no-unused-vars": "off",
}, },
overrides: [ overrides: [
{ {
files: ['*.astro'], files: ["*.astro"],
parser: 'astro-eslint-parser', parser: "astro-eslint-parser",
parserOptions: { parserOptions: {
parser: '@typescript-eslint/parser', parser: "@typescript-eslint/parser",
extraFileExtensions: ['.astro'], extraFileExtensions: [".astro"],
}, },
rules: { rules: {
...tsRules ...tsRules,
}, },
}, },
{ {
files: ['*.ts'], files: ["*.ts"],
parser: '@typescript-eslint/parser', parser: "@typescript-eslint/parser",
extends: ['plugin:@typescript-eslint/recommended'], extends: ["plugin:@typescript-eslint/recommended"],
rules: { rules: {
...tsRules ...tsRules,
}, },
}, },
{ {
// Define the configuration for `<script>` tag. // Define the configuration for `<script>` tag.
// Script in `<script>` is assigned a virtual file name with the `.js` extension. // Script in `<script>` is assigned a virtual file name with the `.js` extension.
files: ['**/*.astro/*.js', '*.astro/*.js'], files: ["**/*.astro/*.js", "*.astro/*.js"],
parser: '@typescript-eslint/parser', parser: "@typescript-eslint/parser",
}, },
], ],
}; };

View File

@@ -8,4 +8,4 @@
} }
} }
] ]
} }

View File

@@ -16,8 +16,8 @@ import { rehypeUnicornElementMap } from "./src/utils/markdown/rehype-unicorn-ele
import { rehypeExcerpt } from "./src/utils/markdown/rehype-excerpt"; import { rehypeExcerpt } from "./src/utils/markdown/rehype-excerpt";
import { rehypeUnicornPopulatePost } from "./src/utils/markdown/rehype-unicorn-populate-post"; import { rehypeUnicornPopulatePost } from "./src/utils/markdown/rehype-unicorn-populate-post";
import { rehypeWordCount } from "./src/utils/markdown/rehype-word-count"; import { rehypeWordCount } from "./src/utils/markdown/rehype-word-count";
import {rehypeUnicornGetSuggestedPosts} from "./src/utils/markdown/rehype-unicorn-get-suggested-posts"; import { rehypeUnicornGetSuggestedPosts } from "./src/utils/markdown/rehype-unicorn-get-suggested-posts";
import copy from 'rollup-plugin-copy' import copy from "rollup-plugin-copy";
// TODO: Create types // TODO: Create types
import behead from "remark-behead"; import behead from "remark-behead";
@@ -34,18 +34,18 @@ export default defineConfig({
plugins: [ plugins: [
{ {
...copy({ ...copy({
hook: 'options', hook: "options",
flatten: false, flatten: false,
targets: [ targets: [
{ {
src: 'content/**/*', src: "content/**/*",
dest: 'public/content' dest: "public/content",
} },
] ],
}), }),
enforce: 'pre' enforce: "pre",
} },
] ],
}, },
markdown: { markdown: {
mode: "md", mode: "md",
@@ -103,13 +103,16 @@ export default defineConfig({
{ {
maxHeight: 768, maxHeight: 768,
maxWidth: 768, maxWidth: 768,
} },
], ],
rehypeUnicornElementMap, rehypeUnicornElementMap,
[rehypeExcerpt, { [
maxLength: 150 rehypeExcerpt,
}], {
rehypeWordCount maxLength: 150,
},
],
rehypeWordCount,
], ],
} as AstroUserConfig["markdown"] as never, } as AstroUserConfig["markdown"] as never,
}); });

View File

@@ -13,12 +13,7 @@
"pronouns": "they/themselves", "pronouns": "they/themselves",
"profileImg": "./crutchcorn.png", "profileImg": "./crutchcorn.png",
"color": "#ba68c8", "color": "#ba68c8",
"roles": [ "roles": ["devops", "developer", "author", "community"]
"devops",
"developer",
"author",
"community"
]
}, },
{ {
"id": "fennifith", "id": "fennifith",
@@ -33,11 +28,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./fennifith.jpg", "profileImg": "./fennifith.jpg",
"color": "#0091EA", "color": "#0091EA",
"roles": [ "roles": ["developer", "author", "community"]
"developer",
"author",
"community"
]
}, },
{ {
"id": "evelynhathaway", "id": "evelynhathaway",
@@ -52,11 +43,7 @@
"pronouns": "she", "pronouns": "she",
"profileImg": "proud.png", "profileImg": "proud.png",
"color": "#ef5f17", "color": "#ef5f17",
"roles": [ "roles": ["developer", "devops", "community"]
"developer",
"devops",
"community"
]
}, },
{ {
"id": "adueppen", "id": "adueppen",
@@ -72,10 +59,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./adueppen.png", "profileImg": "./adueppen.png",
"color": "#69ffff", "color": "#69ffff",
"roles": [ "roles": ["developer", "community"]
"developer",
"community"
]
}, },
{ {
"id": "zavukodlak", "id": "zavukodlak",
@@ -91,9 +75,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./vukashin.png", "profileImg": "./vukashin.png",
"color": "#3485FF", "color": "#3485FF",
"roles": [ "roles": ["designer"]
"designer"
]
}, },
{ {
"id": "tommyemo", "id": "tommyemo",
@@ -108,9 +90,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./tommyemo.jpg", "profileImg": "./tommyemo.jpg",
"color": "#8539EB", "color": "#8539EB",
"roles": [ "roles": ["designer"]
"designer"
]
}, },
{ {
"id": "edpratti", "id": "edpratti",
@@ -125,10 +105,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./edpratti.jpg", "profileImg": "./edpratti.jpg",
"color": "#FF3300", "color": "#FF3300",
"roles": [ "roles": ["designer", "author"]
"designer",
"author"
]
}, },
{ {
"id": "sarsamurmu", "id": "sarsamurmu",
@@ -144,9 +121,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./sarsamurmu.png", "profileImg": "./sarsamurmu.png",
"color": "#7C4DFF", "color": "#7C4DFF",
"roles": [ "roles": ["developer"]
"developer"
]
}, },
{ {
"id": "MDutro", "id": "MDutro",
@@ -160,11 +135,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./mdutro.jpg", "profileImg": "./mdutro.jpg",
"color": "#7C4DFF", "color": "#7C4DFF",
"roles": [ "roles": ["developer", "author", "community"]
"developer",
"author",
"community"
]
}, },
{ {
"id": "reikaze", "id": "reikaze",
@@ -179,9 +150,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./reikaze.jpg", "profileImg": "./reikaze.jpg",
"color": "#ba68c8", "color": "#ba68c8",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "thodges314", "id": "thodges314",
@@ -196,9 +165,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./thodges.png", "profileImg": "./thodges.png",
"color": "#ba68c8", "color": "#ba68c8",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "skatcat31", "id": "skatcat31",
@@ -213,10 +180,7 @@
"color": "#ba68c8", "color": "#ba68c8",
"profileImg": "./hello.png", "profileImg": "./hello.png",
"pronouns": "he", "pronouns": "he",
"roles": [ "roles": ["author", "community"]
"author",
"community"
]
}, },
{ {
"id": "seanmiller", "id": "seanmiller",
@@ -233,9 +197,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./seanmiller.jpg", "profileImg": "./seanmiller.jpg",
"color": "#551a8b", "color": "#551a8b",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "pierremtb", "id": "pierremtb",
@@ -252,9 +214,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./pierremtb.jpg", "profileImg": "./pierremtb.jpg",
"color": "#FFEB3B", "color": "#FFEB3B",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "maisydino", "id": "maisydino",
@@ -270,10 +230,7 @@
"pronouns": "she", "pronouns": "she",
"profileImg": "./maisydino.jpg", "profileImg": "./maisydino.jpg",
"color": "#FDF6E3", "color": "#FDF6E3",
"roles": [ "roles": ["author", "community"]
"author",
"community"
]
}, },
{ {
"id": "bobrossrtx", "id": "bobrossrtx",
@@ -289,10 +246,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./bobrossrtx.jpg", "profileImg": "./bobrossrtx.jpg",
"color": "#b7e11e", "color": "#b7e11e",
"roles": [ "roles": ["developer", "author"]
"developer",
"author"
]
}, },
{ {
"id": "ljtech", "id": "ljtech",
@@ -309,9 +263,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./ljtechdotca.png", "profileImg": "./ljtechdotca.png",
"color": "#7b61ff", "color": "#7b61ff",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "SkyHawk_0", "id": "SkyHawk_0",
@@ -323,9 +275,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./goofy.png", "profileImg": "./goofy.png",
"color": "#18BBC9", "color": "#18BBC9",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "splatkillwill", "id": "splatkillwill",
@@ -342,9 +292,7 @@
"pronouns": "they/themselves", "pronouns": "they/themselves",
"profileImg": "./splatkillwill.jpg", "profileImg": "./splatkillwill.jpg",
"color": "#BF00FF", "color": "#BF00FF",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "fmothe", "id": "fmothe",
@@ -360,9 +308,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./fmothe.jpg", "profileImg": "./fmothe.jpg",
"color": "#18BBC9", "color": "#18BBC9",
"roles": [ "roles": ["translator"]
"translator"
]
}, },
{ {
"id": "jahirfiquitiva", "id": "jahirfiquitiva",
@@ -379,9 +325,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./jahirfiquitiva.jpg", "profileImg": "./jahirfiquitiva.jpg",
"color": "#3867d6", "color": "#3867d6",
"roles": [ "roles": ["translator"]
"translator"
]
}, },
{ {
"id": "kaleem", "id": "kaleem",
@@ -397,9 +341,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./kaleem.jpeg", "profileImg": "./kaleem.jpeg",
"color": "#a8b3ba", "color": "#a8b3ba",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "qarnax", "id": "qarnax",
@@ -414,12 +356,7 @@
}, },
"profileImg": "./qarnax.jpg", "profileImg": "./qarnax.jpg",
"color": "", "color": "",
"roles": [ "roles": ["developer", "author", "community", "translator"]
"developer",
"author",
"community",
"translator"
]
}, },
{ {
"id": "alexchadwick", "id": "alexchadwick",
@@ -437,10 +374,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./alexchadwick.jpg", "profileImg": "./alexchadwick.jpg",
"color": "", "color": "",
"roles": [ "roles": ["author", "translator"]
"author",
"translator"
]
}, },
{ {
"id": "williamcook", "id": "williamcook",
@@ -457,9 +391,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./williamcook.jpg", "profileImg": "./williamcook.jpg",
"color": "#AF7AC5", "color": "#AF7AC5",
"roles": [ "roles": ["author"]
"author"
]
}, },
{ {
"id": "rudy", "id": "rudy",
@@ -474,9 +406,7 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./rudy.jpg", "profileImg": "./rudy.jpg",
"color": "#ba68c8", "color": "#ba68c8",
"roles": [ "roles": ["community"]
"community"
]
}, },
{ {
"id": "LayZee", "id": "LayZee",
@@ -492,8 +422,6 @@
"pronouns": "he", "pronouns": "he",
"profileImg": "./lars-gyrup-brink-nielsen.jpg", "profileImg": "./lars-gyrup-brink-nielsen.jpg",
"color": "#1b9bf0", "color": "#1b9bf0",
"roles": [ "roles": ["author"]
"author"
]
} }
] ]

View File

@@ -1,26 +1,26 @@
/* AFTER CHANGING THIS FILE, PLEASE MANUALLY MINIFY IT AND PUT INTO backbtn.min.js */ /* AFTER CHANGING THIS FILE, PLEASE MANUALLY MINIFY IT AND PUT INTO backbtn.min.js */
/* TODO: Add minifier to build script */ /* TODO: Add minifier to build script */
window.onload = () => { window.onload = () => {
const backBtn = document.querySelector('#backbtn'); const backBtn = document.querySelector("#backbtn");
let hasHistory = false; let hasHistory = false;
window.addEventListener('beforeunload', () => { window.addEventListener("beforeunload", () => {
hasHistory = true; hasHistory = true;
}) });
backBtn.addEventListener('click', () => { backBtn.addEventListener("click", () => {
if (!document.referrer) { if (!document.referrer) {
// This is the first page the user has visited on the site in this session // This is the first page the user has visited on the site in this session
window.location.href = '/'; window.location.href = "/";
return; return;
} }
history.back(); history.back();
// User cannot go back, meaning that we're at the first page of the site session // User cannot go back, meaning that we're at the first page of the site session
setTimeout(() => { setTimeout(() => {
if (!hasHistory){ if (!hasHistory) {
window.location.href = "/"; window.location.href = "/";
} }
}, 200); }, 200);
}) });
} };

View File

@@ -2,10 +2,10 @@
const LOCAL_STORAGE_KEY = "tabs-selection"; const LOCAL_STORAGE_KEY = "tabs-selection";
window.addEventListener('DOMContentLoaded', () => { window.addEventListener("DOMContentLoaded", () => {
const tabLists = document.querySelectorAll('[role="tablist"]'); const tabLists = document.querySelectorAll('[role="tablist"]');
tabLists.forEach(tabList => { tabLists.forEach((tabList) => {
/** /**
* @type {NodeListOf<HTMLElement>} * @type {NodeListOf<HTMLElement>}
*/ */
@@ -13,7 +13,7 @@ window.addEventListener('DOMContentLoaded', () => {
// Add a click event handler to each tab // Add a click event handler to each tab
tabs.forEach((tab) => { tabs.forEach((tab) => {
tab.addEventListener('click', e => { tab.addEventListener("click", (e) => {
/** /**
* @type {HTMLElement} * @type {HTMLElement}
*/ */
@@ -26,21 +26,21 @@ window.addEventListener('DOMContentLoaded', () => {
inline: "center", inline: "center",
}); });
}, 0); }, 0);
changeTabs({ target }) changeTabs({ target });
}); });
}); });
// Enable arrow navigation between tabs in the tab list // Enable arrow navigation between tabs in the tab list
let tabFocus = 0; let tabFocus = 0;
tabList.addEventListener('keydown', (_e) => { tabList.addEventListener("keydown", (_e) => {
/** /**
* @type {KeyboardEvent} * @type {KeyboardEvent}
*/ */
const e = _e; const e = _e;
// Move right // Move right
if (e.keyCode === 39 || e.keyCode === 37) { if (e.keyCode === 39 || e.keyCode === 37) {
tabs[tabFocus].setAttribute('tabindex', `-1`); tabs[tabFocus].setAttribute("tabindex", `-1`);
if (e.keyCode === 39) { if (e.keyCode === 39) {
tabFocus++; tabFocus++;
// If we're at the end, go to the start // If we're at the end, go to the start
@@ -56,7 +56,7 @@ window.addEventListener('DOMContentLoaded', () => {
} }
} }
tabs[tabFocus].setAttribute('tabindex', `0`); tabs[tabFocus].setAttribute("tabindex", `0`);
tabs[tabFocus].focus(); tabs[tabFocus].focus();
tabs[tabFocus].click(); tabs[tabFocus].click();
} }
@@ -84,16 +84,18 @@ window.addEventListener('DOMContentLoaded', () => {
// Remove all current selected tabs // Remove all current selected tabs
parent parent
.querySelectorAll('[aria-selected="true"]') .querySelectorAll('[aria-selected="true"]')
.forEach((t) => t.setAttribute('aria-selected', `false`)); .forEach((t) => t.setAttribute("aria-selected", `false`));
// Set this tab as selected // Set this tab as selected
target.setAttribute('aria-selected', `true`); target.setAttribute("aria-selected", `true`);
const tabName = target.dataset.tabname; const tabName = target.dataset.tabname;
/** /**
* @type {NodeListOf<HTMLElement>} * @type {NodeListOf<HTMLElement>}
*/ */
const relatedTabs = document.querySelectorAll(`[role="tab"][data-tabname="${target.dataset.tabname}"]`); const relatedTabs = document.querySelectorAll(
`[role="tab"][data-tabname="${target.dataset.tabname}"]`
);
localStorage.setItem(LOCAL_STORAGE_KEY, tabName); localStorage.setItem(LOCAL_STORAGE_KEY, tabName);
@@ -105,12 +107,12 @@ window.addEventListener('DOMContentLoaded', () => {
// Hide all tab panels // Hide all tab panels
grandparent grandparent
.querySelectorAll('[role="tabpanel"]') .querySelectorAll('[role="tabpanel"]')
.forEach((p) => p.setAttribute('hidden', `true`)); .forEach((p) => p.setAttribute("hidden", `true`));
// Show the selected panel // Show the selected panel
grandparent.parentNode grandparent.parentNode
.querySelector(`#${target.getAttribute('aria-controls')}`) .querySelector(`#${target.getAttribute("aria-controls")}`)
.removeAttribute('hidden'); .removeAttribute("hidden");
} }
/* -------------------- */ /* -------------------- */
@@ -137,18 +139,20 @@ window.addEventListener('DOMContentLoaded', () => {
// If user has linked to a heading that's inside of a tab // If user has linked to a heading that's inside of a tab
const hash = window.location.hash; const hash = window.location.hash;
if (!hash) return; if (!hash) return;
const heading = document.querySelector < HTMLElement > (hash); const heading = document.querySelector < HTMLElement > hash;
if (!heading) return; if (!heading) return;
const isHidden = checkElementsParents(heading, el => const isHidden = checkElementsParents(
el.hasAttribute('hidden') && el.getAttribute('hidden') !== "false" heading,
) (el) => el.hasAttribute("hidden") && el.getAttribute("hidden") !== "false"
);
// If it's not hidden, then we can assume that the browser will auto-scroll to it // If it's not hidden, then we can assume that the browser will auto-scroll to it
if (!isHidden) return; if (!isHidden) return;
const partialHash = hash.slice(1); const partialHash = hash.slice(1);
try { try {
const matchingTab = document.querySelector < HTMLElement > ( const matchingTab =
`[data-headers*="${partialHash}"` document.querySelector <
); HTMLElement >
`[data-headers*="${partialHash}"`;
if (!matchingTab) return; if (!matchingTab) return;
// If header is not in a tab // If header is not in a tab
const tabName = matchingTab.getAttribute("data-tabname"); const tabName = matchingTab.getAttribute("data-tabname");
@@ -162,6 +166,5 @@ window.addEventListener('DOMContentLoaded', () => {
} catch (e) { } catch (e) {
console.error("Error finding matching tab", e); console.error("Error finding matching tab", e);
} }
})() })();
}); });

View File

@@ -1,16 +1,16 @@
/* AFTER CHANGING THIS FILE, PLEASE MANUALLY MINIFY IT AND PUT INTO tabs.min.js */ /* AFTER CHANGING THIS FILE, PLEASE MANUALLY MINIFY IT AND PUT INTO tabs.min.js */
const COLOR_MODE_STORAGE_KEY = "currentTheme"; const COLOR_MODE_STORAGE_KEY = "currentTheme";
const themeToggleBtn = document.querySelector('#theme-toggle-button'); const themeToggleBtn = document.querySelector("#theme-toggle-button");
const darkIconEl = document.querySelector('#dark-icon'); const darkIconEl = document.querySelector("#dark-icon");
const lightIconEl = document.querySelector('#light-icon'); const lightIconEl = document.querySelector("#light-icon");
function toggleButton(theme) { function toggleButton(theme) {
themeToggleBtn.ariaPressed = `${theme === 'dark'}`; themeToggleBtn.ariaPressed = `${theme === "dark"}`;
if (theme === 'light') { if (theme === "light") {
lightIconEl.style.display = null; lightIconEl.style.display = null;
darkIconEl.style.display = 'none'; darkIconEl.style.display = "none";
} else { } else {
lightIconEl.style.display = 'none'; lightIconEl.style.display = "none";
darkIconEl.style.display = null; darkIconEl.style.display = null;
} }
} }
@@ -18,11 +18,12 @@ function toggleButton(theme) {
// TODO: Migrate to `classList` // TODO: Migrate to `classList`
const initialTheme = document.documentElement.className; const initialTheme = document.documentElement.className;
toggleButton(initialTheme); toggleButton(initialTheme);
themeToggleBtn.addEventListener('click', () => { themeToggleBtn.addEventListener("click", () => {
const currentTheme = document.documentElement.className; const currentTheme = document.documentElement.className;
document.documentElement.className = currentTheme === 'light' ? 'dark' : 'light'; document.documentElement.className =
currentTheme === "light" ? "dark" : "light";
// TODO: Persist new setting // TODO: Persist new setting
const newTheme = document.documentElement.className; const newTheme = document.documentElement.className;
toggleButton(newTheme); toggleButton(newTheme);
localStorage.setItem(COLOR_MODE_STORAGE_KEY, newTheme) localStorage.setItem(COLOR_MODE_STORAGE_KEY, newTheme);
}) });

View File

@@ -1,10 +1,10 @@
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker navigator.serviceWorker
.register("/sw.js") .register("/sw.js")
.then(serviceWorker => { .then((serviceWorker) => {
console.log("Service Worker registered: ", serviceWorker); console.log("Service Worker registered: ", serviceWorker);
}) })
.catch(error => { .catch((error) => {
console.error("Error registering the Service Worker: ", error); console.error("Error registering the Service Worker: ", error);
}); });
} }

View File

@@ -4,12 +4,12 @@ import layoutStyles from "./blog-post-layout.module.scss";
<div class={layoutStyles.blogPostLayoutContainer}> <div class={layoutStyles.blogPostLayoutContainer}>
<div class={layoutStyles.leftContainer}> <div class={layoutStyles.leftContainer}>
<slot name="left"/> <slot name="left" />
</div> </div>
<div class={layoutStyles.centerContainer}> <div class={layoutStyles.centerContainer}>
<slot/> <slot />
</div> </div>
<div class={layoutStyles.rightContainer}> <div class={layoutStyles.rightContainer}>
<slot name="right"/> <slot name="right" />
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
--- ---
import { Icon } from 'astro-icon'; import { Icon } from "astro-icon";
import btnStyles from "./dark-light-button.module.scss"; import btnStyles from "./dark-light-button.module.scss";
--- ---
@@ -9,8 +9,20 @@ import btnStyles from "./dark-light-button.module.scss";
aria-pressed="false" aria-pressed="false"
aria-label={"Is dark mode enabled?"} aria-label={"Is dark mode enabled?"}
> >
<Icon name="dark" height="36" width="36" id="dark-icon" style="display: none;"/> <Icon
<Icon name="light" height="36" width="36" id="light-icon" style="display: none;"/> name="dark"
height="36"
width="36"
id="dark-icon"
style="display: none;"
/>
<Icon
name="light"
height="36"
width="36"
id="light-icon"
style="display: none;"
/>
</button> </button>
<script defer is:inline src="/scripts/themetoggle.min.js"/> <script defer is:inline src="/scripts/themetoggle.min.js"></script>

View File

@@ -1,7 +1,7 @@
--- ---
import layoutStyles from "./layout.module.scss"; import layoutStyles from "./layout.module.scss";
import DarkLightButton from "components/dark-light-button/dark-light-button.astro"; import DarkLightButton from "components/dark-light-button/dark-light-button.astro";
import { Icon } from 'astro-icon'; import { Icon } from "astro-icon";
// const { back } = useHistory(); // const { back } = useHistory();
@@ -10,20 +10,30 @@ const rootPath = `/`;
const isBase = Astro.url.pathname === rootPath; const isBase = Astro.url.pathname === rootPath;
const isBlogPost = Astro.url.pathname.startsWith(`${rootPath}posts`); const isBlogPost = Astro.url.pathname.startsWith(`${rootPath}posts`);
const isCollection = Astro.url.pathname.startsWith(`${rootPath}collections`); const isCollection = Astro.url.pathname.startsWith(`${rootPath}collections`);
--- ---
<div class={layoutStyles.horizCenter}> <div class={layoutStyles.horizCenter}>
<header class={layoutStyles.header} aria-label={"Toolbar for primary action buttons"}> <header
class={layoutStyles.header}
aria-label={"Toolbar for primary action buttons"}
>
<div class={layoutStyles.headerInsideContainer}> <div class={layoutStyles.headerInsideContainer}>
{!isBase ? (<> {
<button id="backbtn" class={`${layoutStyles.backBtn} baseBtn`} aria-label="Go back"> !isBase ? (
<>
<button
id="backbtn"
class={`${layoutStyles.backBtn} baseBtn`}
aria-label="Go back"
>
<Icon height="36" width="36" name="back" /> <Icon height="36" width="36" name="back" />
</button> </button>
<script is:inline defer src="/scripts/backbtn.min.js"/> <script is:inline defer src="/scripts/backbtn.min.js" />
</> </>
) : ( ) : (
<div /> <div />
)} )
}
<div class={layoutStyles.iconList}> <div class={layoutStyles.iconList}>
<!-- <AnalyticsLink category={"outbound"} href="https://discord.gg/FMcvc6T" className={"baseBtn"} <!-- <AnalyticsLink category={"outbound"} href="https://discord.gg/FMcvc6T" className={"baseBtn"}
aria-label={"Join the Discord"}> --> aria-label={"Join the Discord"}> -->
@@ -33,7 +43,13 @@ const isCollection = Astro.url.pathname.startsWith(`${rootPath}collections`);
</div> </div>
</div> </div>
</header> </header>
<div class={ isCollection ? "" : !isBlogPost ? "listViewContent" : "postViewContent" }> <div
class={isCollection
? ""
: !isBlogPost
? "listViewContent"
: "postViewContent"}
>
<slot /> <slot />
</div> </div>
</div> </div>

View File

@@ -6,23 +6,23 @@ export interface Page {
export const DR = { export const DR = {
ariaLabel: "Go to the next set of pages", ariaLabel: "Go to the next set of pages",
display: '...' display: "...",
}; };
export const DL = { export const DL = {
ariaLabel: "Go to previous set of pages", ariaLabel: "Go to previous set of pages",
display: '...' display: "...",
}; };
const range = (start: number, end: number): Page[] => { const range = (start: number, end: number): Page[] => {
let length = end - start + 1; const length = end - start + 1;
return Array.from({ length }, (_, idx) => { return Array.from({ length }, (_, idx) => {
const page = idx + start; const page = idx + start;
return { return {
display: String(page), display: String(page),
pageNumber: page, pageNumber: page,
ariaLabel: `Goto page ${page}` ariaLabel: `Goto page ${page}`,
} };
}); });
}; };
@@ -37,14 +37,14 @@ export const getPaginationRange = ({
totalCount, totalCount,
pageSize, pageSize,
siblingCount = 1, siblingCount = 1,
currentPage currentPage,
}: GetPaginationRangeProps): Page[] => { }: GetPaginationRangeProps): Page[] => {
const totalPageCount = Math.ceil(totalCount / pageSize); const totalPageCount = Math.ceil(totalCount / pageSize);
const totalPageCountPage: Page = { const totalPageCountPage: Page = {
display: `${totalPageCount}`, display: `${totalPageCount}`,
pageNumber: totalPageCount pageNumber: totalPageCount,
} };
const totalPageNumbers = siblingCount + 5; const totalPageNumbers = siblingCount + 5;
@@ -63,48 +63,48 @@ export const getPaginationRange = ({
const firstPageIndex: Page = { const firstPageIndex: Page = {
display: "1", display: "1",
pageNumber: 1 pageNumber: 1,
}; };
const lastPageIndex: Page = { const lastPageIndex: Page = {
display: `${totalPageCount}`, display: `${totalPageCount}`,
pageNumber: totalPageCount pageNumber: totalPageCount,
}; };
if (!shouldShowLeftDots && shouldShowRightDots) { if (!shouldShowLeftDots && shouldShowRightDots) {
let leftItemCount = 3 + 2 * siblingCount; const leftItemCount = 3 + 2 * siblingCount;
let leftRange = range(1, leftItemCount); const leftRange = range(1, leftItemCount);
const lastPage = leftRange[leftRange.length - 1] const lastPage = leftRange[leftRange.length - 1];
const DR_Page: Page = { const DR_Page: Page = {
...DR, ...DR,
pageNumber: currentPage + 2 pageNumber: currentPage + 2,
} };
return [...leftRange, DR_Page, totalPageCountPage]; return [...leftRange, DR_Page, totalPageCountPage];
} }
if (shouldShowLeftDots && !shouldShowRightDots) { if (shouldShowLeftDots && !shouldShowRightDots) {
let rightItemCount = 3 + 2 * siblingCount; const rightItemCount = 3 + 2 * siblingCount;
let rightRange = range( const rightRange = range(
totalPageCount - rightItemCount + 1, totalPageCount - rightItemCount + 1,
totalPageCount totalPageCount
); );
const DL_Page: Page = { const DL_Page: Page = {
...DL, ...DL,
pageNumber: currentPage - 2 pageNumber: currentPage - 2,
} };
return [firstPageIndex, DL_Page, ...rightRange]; return [firstPageIndex, DL_Page, ...rightRange];
} }
if (shouldShowLeftDots && shouldShowRightDots) { if (shouldShowLeftDots && shouldShowRightDots) {
let middleRange = range(leftSiblingIndex, rightSiblingIndex); const middleRange = range(leftSiblingIndex, rightSiblingIndex);
const DL_Page: Page = { const DL_Page: Page = {
...DL, ...DL,
pageNumber: currentPage - 2 pageNumber: currentPage - 2,
} };
const DR_Page: Page = { const DR_Page: Page = {
...DR, ...DR,
pageNumber: currentPage + 2 pageNumber: currentPage + 2,
} };
return [firstPageIndex, DL_Page, ...middleRange, DR_Page, lastPageIndex]; return [firstPageIndex, DL_Page, ...middleRange, DR_Page, lastPageIndex];
} }

View File

@@ -1,31 +1,31 @@
--- ---
import { getPaginationRange } from './pagination-logic'; import { getPaginationRange } from "./pagination-logic";
import { Page } from 'astro'; import { Page } from "astro";
import { PostInfo } from 'types/PostInfo'; import { PostInfo } from "types/PostInfo";
import styles from './pagination.module.scss'; import styles from "./pagination.module.scss";
interface PaginationProps { interface PaginationProps {
page: Pick<Page<PostInfo>, 'total' | 'currentPage' | 'size' | 'lastPage' | 'url'>; page: Pick<
Page<PostInfo>,
"total" | "currentPage" | "size" | "lastPage" | "url"
>;
class: string; class: string;
rootURL: string; rootURL: string;
} }
const { const { page, rootURL, class: className = "" } = Astro.props as PaginationProps;
page,
rootURL,
class: className = ""
} = Astro.props as PaginationProps;
const paginationRange = getPaginationRange({ const paginationRange = getPaginationRange({
currentPage: page.currentPage, currentPage: page.currentPage,
totalCount: page.total, totalCount: page.total,
siblingCount: 0, siblingCount: 0,
pageSize: page.size pageSize: page.size,
}); });
const dontShowAnything = page.currentPage === 0 || paginationRange.length < 2; const dontShowAnything = page.currentPage === 0 || paginationRange.length < 2;
const getPageHref = (pageNum: number) => pageNum === 0 || pageNum === 1 ? rootURL : `${rootURL}page/${pageNum}`; const getPageHref = (pageNum: number) =>
pageNum === 0 || pageNum === 1 ? rootURL : `${rootURL}page/${pageNum}`;
const lastPage = paginationRange[paginationRange.length - 1]; const lastPage = paginationRange[paginationRange.length - 1];
const firstPage = paginationRange[0]; const firstPage = paginationRange[0];
@@ -34,27 +34,47 @@ const disablePrevious = !firstPage || page.currentPage === firstPage.pageNumber;
const disableNext = !lastPage || page.currentPage === lastPage.pageNumber; const disableNext = !lastPage || page.currentPage === lastPage.pageNumber;
--- ---
{dontShowAnything ? null : <ul role="navigation" aria-label="Pagination Navigation" class={`${styles.pagination} ${className}`}> {
{!disablePrevious && <li class={`${styles.paginationItem} ${styles.previous}`}> dontShowAnything ? null : (
<ul
role="navigation"
aria-label="Pagination Navigation"
class={`${styles.pagination} ${className}`}
>
{!disablePrevious && (
<li class={`${styles.paginationItem} ${styles.previous}`}>
<a href={getPageHref(page.currentPage - 1)} aria-label="Previous"> <a href={getPageHref(page.currentPage - 1)} aria-label="Previous">
{"<"} {"<"}
</a> </a>
</li>} </li>
)}
{paginationRange.map(pageItem => { {paginationRange.map((pageItem) => {
const isSelected = pageItem.pageNumber === page.currentPage; const isSelected = pageItem.pageNumber === page.currentPage;
return ( return (
<li class={`${styles.paginationItem} ${ isSelected ? styles.active : '' }`}> <li
<a href={getPageHref(pageItem.pageNumber)} aria-label={pageItem.ariaLabel} aria-current={isSelected || undefined}> class={`${styles.paginationItem} ${
isSelected ? styles.active : ""
}`}
>
<a
href={getPageHref(pageItem.pageNumber)}
aria-label={pageItem.ariaLabel}
aria-current={isSelected || undefined}
>
{pageItem.display} {pageItem.display}
</a> </a>
</li> </li>
); );
})} })}
{!disableNext && <li class={`${styles.paginationItem} ${styles.next}`}> {!disableNext && (
<li class={`${styles.paginationItem} ${styles.next}`}>
<a href={getPageHref(page.currentPage + 1)} aria-label="Next"> <a href={getPageHref(page.currentPage + 1)} aria-label="Next">
{">"} {">"}
</a> </a>
</li>} </li>
</ul>} )}
</ul>
)
}

View File

@@ -8,9 +8,9 @@ export interface PostListProps {
showWordCount?: boolean; showWordCount?: boolean;
numberOfArticles?: number; numberOfArticles?: number;
wordCount?: number; wordCount?: number;
unicornData?: PostInfo['authorsMeta']; unicornData?: PostInfo["authorsMeta"];
listAriaLabel: string; listAriaLabel: string;
postsToDisplay: PostInfo[] postsToDisplay: PostInfo[];
} }
/** /**
* unicornData - The data with the associated post. If present - you're on profile page * unicornData - The data with the associated post. If present - you're on profile page
@@ -18,12 +18,7 @@ export interface PostListProps {
const { listAriaLabel, postsToDisplay } = Astro.props as PostListProps; const { listAriaLabel, postsToDisplay } = Astro.props as PostListProps;
--- ---
<ul
class={listStyle.postsListContainer} <ul class={listStyle.postsListContainer} aria-label={listAriaLabel} role="list">
aria-label={listAriaLabel} {postsToDisplay.map((post) => <PostCard post={post} />)}
role="list"
>
{postsToDisplay.map((post) => (
<PostCard post={post}/>
))}
</ul> </ul>

View File

@@ -11,20 +11,10 @@ interface PostCardProps {
class?: string; // class to pass to the post card element class?: string; // class to pass to the post card element
} }
const { const { post, class: className = "" } = Astro.props as PostCardProps;
post,
class: className = ""
} = Astro.props as PostCardProps;
const { const { published, slug, title, authorsMeta, tags, description, excerpt } =
published, post;
slug,
title,
authorsMeta,
tags,
description,
excerpt
} = post;
const publishedStr = dayjs(published).format("MMMM D, YYYY"); const publishedStr = dayjs(published).format("MMMM D, YYYY");
--- ---
@@ -42,7 +32,8 @@ const publishedStr = dayjs(published).format("MMMM D, YYYY");
{authorsMeta[0].name} {authorsMeta[0].name}
</a> </a>
<!-- To avoid having commas on the first author name, we did this --> <!-- To avoid having commas on the first author name, we did this -->
{authorsMeta.slice(1).map((author, i) => { {
authorsMeta.slice(1).map((author, i) => {
return ( return (
<> <>
<span>, </span> <span>, </span>
@@ -51,17 +42,17 @@ const publishedStr = dayjs(published).format("MMMM D, YYYY");
</a> </a>
</> </>
); );
})} })
}
</p> </p>
<div class={cardStyles.dateTagSubheader}> <div class={cardStyles.dateTagSubheader}>
<p class={cardStyles.date}>{publishedStr}</p> <p class={cardStyles.date}>{publishedStr}</p>
{tags.map((tag) => ( {tags.map((tag) => <span class={cardStyles.tag}>{tag}</span>)}
<span class={cardStyles.tag}>
{tag}
</span>
))}
</div> </div>
<p class={cardStyles.excerpt} set:html={description || excerpt} /> <p class={cardStyles.excerpt} set:html={description || excerpt}></p>
</div> </div>
<UserProfilePic authors={authorsMeta} className={cardStyles.authorImagesContainer} /> <UserProfilePic
authors={authorsMeta}
className={cardStyles.authorImagesContainer}
/>
</li> </li>

View File

@@ -1,19 +1,33 @@
--- ---
import {SEOProps} from './shared'; import { SEOProps } from "./shared";
type Props = Pick<SEOProps, 'editedTime' | 'publishedTime' | 'keywords' | 'unicornsData'>; type Props = Pick<
const { keywords, editedTime, publishedTime, unicornsData } = Astro.props as Props; SEOProps,
const author = unicornsData.map(uni => uni.name).join(","); "editedTime" | "publishedTime" | "keywords" | "unicornsData"
>;
const { keywords, editedTime, publishedTime, unicornsData } =
Astro.props as Props;
const author = unicornsData.map((uni) => uni.name).join(",");
--- ---
<>{keywords?.length ? keywords.map(keyword => <>
<meta property="article:tag" content={keyword} />) : null} {
keywords?.length
? keywords.map((keyword) => (
<meta property="article:tag" content={keyword} />
))
: null
}
</> </>
<meta property="article:section" content="Technology" /> <meta property="article:section" content="Technology" />
<meta property="article:author" content={author}/> <meta property="article:author" content={author} />
<>{editedTime && <>
<meta property="article:modified_time" content={editedTime} />} {editedTime && <meta property="article:modified_time" content={editedTime} />}
</> </>
<>{publishedTime && <>
<meta property="article:published_time" content={publishedTime} />} {
publishedTime && (
<meta property="article:published_time" content={publishedTime} />
)
}
</> </>

View File

@@ -1,15 +1,11 @@
--- ---
import {SEOProps} from './shared'; import { SEOProps } from "./shared";
type Props = Pick<SEOProps, 'publishedTime' | 'isbn' | 'unicornsData'>; type Props = Pick<SEOProps, "publishedTime" | "isbn" | "unicornsData">;
const { const { publishedTime, unicornsData, isbn } = Astro.props as Props;
publishedTime,
unicornsData,
isbn
} = Astro.props as Props;
const author = unicornsData!.map((uni)=> uni.name).join(","); const author = unicornsData!.map((uni) => uni.name).join(",");
--- ---
<meta property="book:release_date" content={publishedTime} /> <meta property="book:release_date" content={publishedTime} />

View File

@@ -1,16 +1,18 @@
--- ---
import { removePrefixLanguageFromPath } from "utils/translations";
import { SEOProps } from "./shared";
import { removePrefixLanguageFromPath } from 'utils/translations'; type Props = Pick<SEOProps, "langData"> & {
import {SEOProps} from './shared';
type Props = Pick<SEOProps, 'langData'> & {
pathName: string; pathName: string;
siteMetadata: {siteUrl: string} siteMetadata: { siteUrl: string };
}; };
const {langData, siteMetadata, pathName} = Astro.props as Props; const { langData, siteMetadata, pathName } = Astro.props as Props;
--- ---
{langData?.currentLang && (
<>
{
langData?.currentLang && (
<link <link
rel="alternate" rel="alternate"
href={ href={
@@ -18,9 +20,13 @@ const {langData, siteMetadata, pathName} = Astro.props as Props;
} }
href-lang="x-default" href-lang="x-default"
/> />
)} )
{langData?.otherLangs?.length ? }
langData.otherLangs.map((lang) => ( </>
<>
{
langData?.otherLangs?.length
? langData.otherLangs.map((lang) => (
<link <link
rel="alternate" rel="alternate"
href={ href={
@@ -30,5 +36,7 @@ const {langData, siteMetadata, pathName} = Astro.props as Props;
} }
href-lang={lang} href-lang={lang}
/> />
)) : null} ))
: null
}
</>

View File

@@ -1,14 +1,14 @@
--- ---
import { fileToOpenGraphConverter } from "utils/translations"; import { fileToOpenGraphConverter } from "utils/translations";
import {SEOProps} from './shared'; import { SEOProps } from "./shared";
type Props = Pick<SEOProps, 'title' | 'langData' | 'unicornsData'> & { type Props = Pick<SEOProps, "title" | "langData" | "unicornsData"> & {
currentPath: string; currentPath: string;
metaDescription: string; metaDescription: string;
metaImage: string; metaImage: string;
ogType: string; ogType: string;
siteMetadata: {title: string} siteMetadata: { title: string };
}; };
const { const {
@@ -21,6 +21,7 @@ const {
ogType, ogType,
} = Astro.props as Props; } = Astro.props as Props;
--- ---
{/* Open Graph SEO */} {/* Open Graph SEO */}
<meta property="og:url" content={currentPath} /> <meta property="og:url" content={currentPath} />
<meta property="og:site_name" content={siteMetadata.title} /> <meta property="og:site_name" content={siteMetadata.title} />
@@ -28,17 +29,20 @@ const {
<meta <meta
property="og:locale" property="og:locale"
content={ content={langData ? fileToOpenGraphConverter(langData.currentLang) : "en"}
langData ? fileToOpenGraphConverter(langData.currentLang) : "en"
}
/> />
{langData?.otherLangs?.length ? <>
langData.otherLangs.map((lang) => ( {
langData?.otherLangs?.length
? langData.otherLangs.map((lang) => (
<meta <meta
property="og:locale:alternate" property="og:locale:alternate"
content={fileToOpenGraphConverter(lang)} content={fileToOpenGraphConverter(lang)}
/> />
)) : null} ))
: null
}
</>
<meta property="og:description" content={metaDescription} /> <meta property="og:description" content={metaDescription} />
<meta property="og:image" content={metaImage} /> <meta property="og:image" content={metaImage} />
<meta property="og:type" content={ogType} /> <meta property="og:type" content={ogType} />

View File

@@ -1,7 +1,7 @@
--- ---
import {SEOProps} from './shared'; import { SEOProps } from "./shared";
type Props = Pick<SEOProps, 'unicornsData'>; type Props = Pick<SEOProps, "unicornsData">;
const { unicornsData } = Astro.props as Props; const { unicornsData } = Astro.props as Props;
--- ---

View File

@@ -40,21 +40,54 @@ const currentPath = siteMetadata.siteUrl + (pathName || "");
<title> <title>
{title ? `${title} | ${siteMetadata.title}` : siteMetadata.title} {title ? `${title} | ${siteMetadata.title}` : siteMetadata.title}
</title> </title>
{canonical ? <>{canonical ? <link rel="canonical" href={canonical} /> : null}</>
<link rel="canonical" href={canonical} /> : null}
<meta property="name" content={siteMetadata.title} /> <meta property="name" content={siteMetadata.title} />
<meta name="description" content={metaDescription} /> <meta name="description" content={metaDescription} />
<meta property="keywords" content={metaKeywords} /> <meta property="keywords" content={metaKeywords} />
<Analytics /> <Analytics />
<Twitter title={title} metaDescription={metaDescription} siteMetadata={siteMetadata} metaImage={metaImage} <Twitter
unicornsData={unicornsData} uniTwitter={uniTwitter} type={type} /> title={title}
<OpenGraph currentPath={currentPath} siteMetadata={siteMetadata} title={title} langData={langData} metaDescription={metaDescription}
metaDescription={metaDescription} metaImage={metaImage} ogType={ogType} /> siteMetadata={siteMetadata}
metaImage={metaImage}
unicornsData={unicornsData}
uniTwitter={uniTwitter}
type={type}
/>
<OpenGraph
currentPath={currentPath}
siteMetadata={siteMetadata}
title={title}
langData={langData}
metaDescription={metaDescription}
metaImage={metaImage}
ogType={ogType}
/>
<Locale langData={langData} siteMetadata={siteMetadata} pathName={pathName} /> <Locale langData={langData} siteMetadata={siteMetadata} pathName={pathName} />
{type === 'article' && <>
<Article keywords={keywords} editedTime={editedTime} publishedTime={publishedTime} unicornsData={unicornsData} />} {
{type === 'book' && type === "article" && (
<Book publishedTime={publishedTime} unicornsData={unicornsData} isbn={isbn} />} <Article
{type === 'profile' && keywords={keywords}
<Profile unicornsData={unicornsData} />} editedTime={editedTime}
publishedTime={publishedTime}
unicornsData={unicornsData}
/>
)
}
</>
<>
{
type === "book" && (
<Book
publishedTime={publishedTime}
unicornsData={unicornsData}
isbn={isbn}
/>
)
}
</>
<>
{type === "profile" && <Profile unicornsData={unicornsData} />}
</>
<slot /> <slot />

View File

@@ -1,10 +1,10 @@
--- ---
import {SEOProps} from './shared'; import { SEOProps } from "./shared";
type Props = Pick<SEOProps, 'title' | 'unicornsData' | 'type'> & { type Props = Pick<SEOProps, "title" | "unicornsData" | "type"> & {
metaDescription: string; metaDescription: string;
metaImage: string; metaImage: string;
siteMetadata: {twitterHandle: string} siteMetadata: { twitterHandle: string };
uniTwitter?: string; uniTwitter?: string;
}; };
@@ -15,15 +15,20 @@ const {
metaImage, metaImage,
unicornsData, unicornsData,
uniTwitter, uniTwitter,
type type,
} = Astro.props as Props; } = Astro.props as Props;
--- ---
{/* Twitter SEO */} {/* Twitter SEO */}
<meta name="twitter:title" content={title} /> <meta name="twitter:title" content={title} />
<meta name="twitter:description" content={metaDescription} /> <meta name="twitter:description" content={metaDescription} />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content={siteMetadata.twitterHandle} /> <meta name="twitter:site" content={siteMetadata.twitterHandle} />
<meta name="twitter:image" content={metaImage} /> <meta name="twitter:image" content={metaImage} />
{type === "article" && unicornsData?.length === 1 && uniTwitter ? ( <>
{
type === "article" && unicornsData?.length === 1 && uniTwitter ? (
<meta property="twitter:creator" content={`@${uniTwitter}`} /> <meta property="twitter:creator" content={`@${uniTwitter}`} />
) : null} ) : null
}
</>

View File

@@ -1,20 +1,23 @@
--- ---
const {headingsToDisplaySlugs} = Astro.props as {headingsToDisplaySlugs: Array<string>} const { headingsToDisplaySlugs } = Astro.props as {
headingsToDisplaySlugs: Array<string>;
};
--- ---
<script define:vars={{headingsToDisplaySlugs}}> <script define:vars={{ headingsToDisplaySlugs }}>
window.onload = () => { window.onload = () => {
const tocListRef = document.querySelector('#tocList'); const tocListRef = document.querySelector("#tocList");
const linkRefs = [...document.querySelectorAll('[data-headingitem="true"]')] const linkRefs = [
...document.querySelectorAll('[data-headingitem="true"]'),
];
let previousSection = {current: ""} let previousSection = { current: "" };
const handleObserver = (entries) => { const handleObserver = (entries) => {
const highlightFirstActive = () => { const highlightFirstActive = () => {
if (!tocListRef) return; if (!tocListRef) return;
let firstVisibleLink = let firstVisibleLink = tocListRef.querySelector(".toc-is-visible");
tocListRef.querySelector(".toc-is-visible");
linkRefs.forEach((linkRef) => { linkRefs.forEach((linkRef) => {
linkRef.classList.remove("toc-is-active"); linkRef.classList.remove("toc-is-active");
@@ -64,5 +67,5 @@ const {headingsToDisplaySlugs} = Astro.props as {headingsToDisplaySlugs: Array<s
.forEach((heading) => { .forEach((heading) => {
observer.observe(heading); observer.observe(heading);
}); });
} };
</script> </script>

View File

@@ -21,30 +21,27 @@ const headingsToDisplay = headings
.map((h) => Object.assign({}, h, { depth: h.depth - minDepth + 1 })) .map((h) => Object.assign({}, h, { depth: h.depth - minDepth + 1 }))
.filter((headingInfo) => headingInfo.depth <= 3); .filter((headingInfo) => headingInfo.depth <= 3);
const headingsToDisplaySlugs = headingsToDisplay.map(item => item.slug); const headingsToDisplaySlugs = headingsToDisplay.map((item) => item.slug);
--- ---
<aside aria-label={"Table of Contents"}> <aside aria-label={"Table of Contents"}>
<ol <ol class={tableOfContentsStyle.tableList} role="list" id="tocList">
class={tableOfContentsStyle.tableList} {
role="list" headingsToDisplay.map((headingInfo, i) => {
id="tocList"
>
{headingsToDisplay.map((headingInfo, i) => {
const liClassNames = classnames(tableOfContentsStyle.tocLi, { const liClassNames = classnames(tableOfContentsStyle.tocLi, {
[tableOfContentsStyle.tocH1]: headingInfo.depth === 1, [tableOfContentsStyle.tocH1]: headingInfo.depth === 1,
[tableOfContentsStyle.tocH2]: headingInfo.depth === 2, [tableOfContentsStyle.tocH2]: headingInfo.depth === 2,
[tableOfContentsStyle.tocH3]: headingInfo.depth === 3, [tableOfContentsStyle.tocH3]: headingInfo.depth === 3,
}); });
return ( return (
<li <li class={liClassNames} data-headingitem="true">
class={liClassNames}
data-headingitem="true"
>
<a href={`#${headingInfo.slug}`}>{headingInfo.value}</a> <a href={`#${headingInfo.slug}`}>{headingInfo.value}</a>
</li> </li>
); );
})} })
}
</ol> </ol>
</aside> </aside>
<HeadingIntersectionObserverScript headingsToDisplaySlugs={headingsToDisplaySlugs} /> <HeadingIntersectionObserverScript
headingsToDisplaySlugs={headingsToDisplaySlugs}
/>

View File

@@ -60,7 +60,9 @@
@extend %headline-uniwidth-2; @extend %headline-uniwidth-2;
} }
.tocH2, .tocH3, .tocH4 { .tocH2,
.tocH3,
.tocH4 {
a { a {
font-style: italic; font-style: italic;
} }

View File

@@ -1,7 +1,7 @@
--- ---
import styles from "./user-profile-pic.module.scss"; import styles from "./user-profile-pic.module.scss";
import { UnicornInfo } from "uu-types"; import { UnicornInfo } from "uu-types";
import {Image} from '@astrojs/image/components'; import { Image } from "@astrojs/image/components";
// TODO: Fix image loading and image 'onClick' // TODO: Fix image loading and image 'onClick'
@@ -10,12 +10,14 @@ interface UserProfilePicProps {
className: string; className: string;
} }
const { authors, className } = Astro.props as UserProfilePicProps const { authors, className } = Astro.props as UserProfilePicProps;
const hasTwoAuthors = authors.length !== 1; const hasTwoAuthors = authors.length !== 1;
--- ---
<div class={`${styles.container} ${className || ""}`}> <div class={`${styles.container} ${className || ""}`}>
{authors.map((unicorn, i) => { {
authors.map((unicorn, i) => {
const classesToApply = hasTwoAuthors ? styles.twoAuthor : ""; const classesToApply = hasTwoAuthors ? styles.twoAuthor : "";
return ( return (
@@ -30,10 +32,11 @@ const hasTwoAuthors = authors.length !== 1;
sizes={"85px"} sizes={"85px"}
height={85} height={85}
width={85} width={85}
format={'png'} format={"png"}
class={`circleImg ${styles.profilePicImage} ${styles.width50} ${classesToApply}`} class={`circleImg ${styles.profilePicImage} ${styles.width50} ${classesToApply}`}
/> />
</div> </div>
); );
})} })
}
</div> </div>

View File

@@ -137,15 +137,15 @@
outline: none; outline: none;
} }
.formkit-form .formkit-button:hover>span, .formkit-form .formkit-button:hover > span,
.formkit-form .formkit-submit:hover>span, .formkit-form .formkit-submit:hover > span,
.formkit-form .formkit-button:focus>span, .formkit-form .formkit-button:focus > span,
.formkit-form .formkit-submit:focus>span { .formkit-form .formkit-submit:focus > span {
background-color: rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.1);
} }
.formkit-form .formkit-button>span, .formkit-form .formkit-button > span,
.formkit-form .formkit-submit>span { .formkit-form .formkit-submit > span {
display: block; display: block;
-webkit-transition: all 300ms ease-in-out; -webkit-transition: all 300ms ease-in-out;
transition: all 300ms ease-in-out; transition: all 300ms ease-in-out;
@@ -252,20 +252,35 @@
margin-bottom: 0; margin-bottom: 0;
} }
.formkit-form [data-group="checkboxes"] [data-group="checkbox"] input[type="checkbox"] { .formkit-form
[data-group="checkboxes"]
[data-group="checkbox"]
input[type="checkbox"] {
display: none; display: none;
} }
.formkit-form [data-group="checkboxes"] [data-group="checkbox"] input[type="checkbox"]+label::after { .formkit-form
[data-group="checkboxes"]
[data-group="checkbox"]
input[type="checkbox"]
+ label::after {
content: none; content: none;
} }
.formkit-form [data-group="checkboxes"] [data-group="checkbox"] input[type="checkbox"]:checked+label::after { .formkit-form
[data-group="checkboxes"]
[data-group="checkbox"]
input[type="checkbox"]:checked
+ label::after {
border-color: #ffffff; border-color: #ffffff;
content: ""; content: "";
} }
.formkit-form [data-group="checkboxes"] [data-group="checkbox"] input[type="checkbox"]:checked+label::before { .formkit-form
[data-group="checkboxes"]
[data-group="checkbox"]
input[type="checkbox"]:checked
+ label::before {
background: #10bf7a; background: #10bf7a;
border-color: #10bf7a; border-color: #10bf7a;
} }
@@ -353,7 +368,7 @@
transition: all 300ms ease-in-out; transition: all 300ms ease-in-out;
} }
.formkit-form .formkit-spinner>div { .formkit-form .formkit-spinner > div {
margin: auto; margin: auto;
width: 12px; width: 12px;
height: 12px; height: 12px;
@@ -361,16 +376,18 @@
opacity: 0.3; opacity: 0.3;
border-radius: 100%; border-radius: 100%;
display: inline-block; display: inline-block;
-webkit-animation: formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- 1.4s infinite ease-in-out both; -webkit-animation: formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- 1.4s
animation: formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- 1.4s infinite ease-in-out both; infinite ease-in-out both;
animation: formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- 1.4s infinite
ease-in-out both;
} }
.formkit-form .formkit-spinner>div:nth-child(1) { .formkit-form .formkit-spinner > div:nth-child(1) {
-webkit-animation-delay: -0.32s; -webkit-animation-delay: -0.32s;
animation-delay: -0.32s; animation-delay: -0.32s;
} }
.formkit-form .formkit-spinner>div:nth-child(2) { .formkit-form .formkit-spinner > div:nth-child(2) {
-webkit-animation-delay: -0.16s; -webkit-animation-delay: -0.16s;
animation-delay: -0.16s; animation-delay: -0.16s;
} }
@@ -381,7 +398,7 @@
width: 50px; width: 50px;
} }
.formkit-form .formkit-submit[data-active] .formkit-spinner~span { .formkit-form .formkit-submit[data-active] .formkit-spinner ~ span {
opacity: 0; opacity: 0;
} }
@@ -458,7 +475,6 @@
} }
@-webkit-keyframes formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- { @-webkit-keyframes formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- {
0%, 0%,
80%, 80%,
100% { 100% {
@@ -475,7 +491,6 @@
} }
@keyframes formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- { @keyframes formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- {
0%, 0%,
80%, 80%,
100% { 100% {
@@ -550,22 +565,38 @@
margin-right: -5px; margin-right: -5px;
} }
.formkit-form[min-width~="700"] .formkit-fields[data-stacked="false"] .formkit-field, .formkit-form[min-width~="700"]
.formkit-form[min-width~="800"] .formkit-fields[data-stacked="false"] .formkit-field, .formkit-fields[data-stacked="false"]
.formkit-form[min-width~="700"] .formkit-fields[data-stacked="false"] .formkit-submit, .formkit-field,
.formkit-form[min-width~="800"] .formkit-fields[data-stacked="false"] .formkit-submit { .formkit-form[min-width~="800"]
.formkit-fields[data-stacked="false"]
.formkit-field,
.formkit-form[min-width~="700"]
.formkit-fields[data-stacked="false"]
.formkit-submit,
.formkit-form[min-width~="800"]
.formkit-fields[data-stacked="false"]
.formkit-submit {
margin: 0 5px 15px 5px; margin: 0 5px 15px 5px;
} }
.formkit-form[min-width~="700"] .formkit-fields[data-stacked="false"] .formkit-field, .formkit-form[min-width~="700"]
.formkit-form[min-width~="800"] .formkit-fields[data-stacked="false"] .formkit-field { .formkit-fields[data-stacked="false"]
.formkit-field,
.formkit-form[min-width~="800"]
.formkit-fields[data-stacked="false"]
.formkit-field {
-webkit-flex: 100 1 auto; -webkit-flex: 100 1 auto;
-ms-flex: 100 1 auto; -ms-flex: 100 1 auto;
flex: 100 1 auto; flex: 100 1 auto;
} }
.formkit-form[min-width~="700"] .formkit-fields[data-stacked="false"] .formkit-submit, .formkit-form[min-width~="700"]
.formkit-form[min-width~="800"] .formkit-fields[data-stacked="false"] .formkit-submit { .formkit-fields[data-stacked="false"]
.formkit-submit,
.formkit-form[min-width~="800"]
.formkit-fields[data-stacked="false"]
.formkit-submit {
-webkit-flex: 1 1 auto; -webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto; -ms-flex: 1 1 auto;
flex: 1 1 auto; flex: 1 1 auto;

View File

@@ -310,7 +310,7 @@ $pendIconMarg: #{$baseUnit}px;
margin-bottom: 1em; margin-bottom: 1em;
summary { summary {
padding: .5rem 1.5rem; padding: 0.5rem 1.5rem;
background: var(--darkPrimary); background: var(--darkPrimary);
color: var(--backgroundColor); color: var(--backgroundColor);
cursor: pointer; cursor: pointer;

View File

@@ -1,29 +1,28 @@
--- ---
import {v4 as uuidV4} from 'uuid'; import { v4 as uuidV4 } from "uuid";
const id = uuidV4(); const id = uuidV4();
const clipId = `path-1-inside-1${id}`; const clipId = `path-1-inside-1${id}`;
const props = Astro.props; const props = Astro.props;
--- ---
<svg <svg
width="36" width="36"
height="36" height="36"
viewBox="0 0 36 36" viewBox="0 0 36 36"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
{...props} {...props}
> >
<g clip-path={`url(#${clipId})`}> <g clip-path={`url(#${clipId})`}>
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
d="M24.6893 7.31065C23.0659 5.6873 20.4339 5.6873 18.8106 7.31066L8.06059 18.0607C5.33267 20.7886 5.33267 25.2114 8.06059 27.9393C10.7885 30.6673 15.2113 30.6673 17.9393 27.9393L30.4393 15.4393C31.0251 14.8535 31.9748 14.8535 32.5606 15.4393C33.1464 16.0251 33.1464 16.9749 32.5606 17.5607L20.0606 30.0607C16.1611 33.9602 9.83876 33.9601 5.93927 30.0607C2.03978 26.1612 2.03977 19.8388 5.93927 15.9393L16.6893 5.18934C19.4842 2.39441 24.0157 2.39441 26.8106 5.18933C29.6055 7.98426 29.6055 12.5157 26.8106 15.3107L16.0606 26.0606C14.3702 27.751 11.6296 27.751 9.93927 26.0607C8.24891 24.3703 8.24891 21.6297 9.93927 19.9393L19.9393 9.93933C20.5251 9.35355 21.4748 9.35355 22.0606 9.93933C22.6464 10.5251 22.6464 11.4749 22.0606 12.0607L12.0606 22.0607C11.5418 22.5794 11.5418 23.4205 12.0606 23.9393C12.5794 24.4581 13.4205 24.4581 13.9393 23.9393L24.6893 13.1893C26.3126 11.566 26.3126 8.93401 24.6893 7.31065Z" d="M24.6893 7.31065C23.0659 5.6873 20.4339 5.6873 18.8106 7.31066L8.06059 18.0607C5.33267 20.7886 5.33267 25.2114 8.06059 27.9393C10.7885 30.6673 15.2113 30.6673 17.9393 27.9393L30.4393 15.4393C31.0251 14.8535 31.9748 14.8535 32.5606 15.4393C33.1464 16.0251 33.1464 16.9749 32.5606 17.5607L20.0606 30.0607C16.1611 33.9602 9.83876 33.9601 5.93927 30.0607C2.03978 26.1612 2.03977 19.8388 5.93927 15.9393L16.6893 5.18934C19.4842 2.39441 24.0157 2.39441 26.8106 5.18933C29.6055 7.98426 29.6055 12.5157 26.8106 15.3107L16.0606 26.0606C14.3702 27.751 11.6296 27.751 9.93927 26.0607C8.24891 24.3703 8.24891 21.6297 9.93927 19.9393L19.9393 9.93933C20.5251 9.35355 21.4748 9.35355 22.0606 9.93933C22.6464 10.5251 22.6464 11.4749 22.0606 12.0607L12.0606 22.0607C11.5418 22.5794 11.5418 23.4205 12.0606 23.9393C12.5794 24.4581 13.4205 24.4581 13.9393 23.9393L24.6893 13.1893C26.3126 11.566 26.3126 8.93401 24.6893 7.31065Z"
fill="#153E67" fill="#153E67"></path>
/>
</g> </g>
<defs> <defs>
<clipPath id={clipId}> <clip-path id={clipId}>
<rect width="36" height="36" fill="white" /> <rect width="36" height="36" fill="white"></rect>
</clipPath> </clip-path>
</defs> </defs>
</svg> </svg>

View File

@@ -1,5 +1,5 @@
--- ---
import {v4 as uuidV4} from 'uuid'; import { v4 as uuidV4 } from "uuid";
const id = uuidV4(); const id = uuidV4();
const clipId = `path-1-inside-1${id}`; const clipId = `path-1-inside-1${id}`;
const props = Astro.props; const props = Astro.props;
@@ -13,15 +13,14 @@ const props = Astro.props;
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
{...props} {...props}
> >
<g clipPath={`url(#${clipId.current})`}> <g clip-path={`url(#${clipId})`}>
<path <path
d="M1.43934 32.4393C0.853553 33.0251 0.853553 33.9749 1.43934 34.5607C2.02513 35.1464 2.97487 35.1464 3.56066 34.5607L1.43934 32.4393ZM24.0607 14.0607C24.6464 13.4749 24.6464 12.5251 24.0607 11.9393C23.4749 11.3536 22.5251 11.3536 21.9393 11.9393L24.0607 14.0607ZM8.67157 14.3284L7.61091 13.2678L8.67157 14.3284ZM14.5607 23.5607L24.0607 14.0607L21.9393 11.9393L12.4393 21.4393L14.5607 23.5607ZM26.5 21H13.5V24H26.5V21ZM3.56066 34.5607L8.56066 29.5607L6.43934 27.4393L1.43934 32.4393L3.56066 34.5607ZM8.56066 29.5607L14.5607 23.5607L12.4393 21.4393L6.43934 27.4393L8.56066 29.5607ZM7.5 30H18.8431V27H7.5V30ZM22.7322 28.3891L31.5607 19.5607L29.4393 17.4393L20.6109 26.2678L22.7322 28.3891ZM9 28.5V17.1569H6V28.5H9ZM9.73223 15.3891L18.5607 6.56066L16.4393 4.43934L7.61091 13.2678L9.73223 15.3891ZM18.5607 6.56066C21.5647 3.55659 26.4353 3.5566 29.4393 6.56066L31.5607 4.43934C27.385 0.263705 20.615 0.263698 16.4393 4.43934L18.5607 6.56066ZM9 17.1569C9 16.4938 9.26339 15.8579 9.73223 15.3891L7.61091 13.2678C6.57946 14.2992 6 15.6982 6 17.1569H9ZM31.5607 19.5607C35.7363 15.385 35.7363 8.61497 31.5607 4.43934L29.4393 6.56066C32.4434 9.56472 32.4434 14.4353 29.4393 17.4393L31.5607 19.5607ZM18.8431 30C20.3018 30 21.7008 29.4205 22.7322 28.3891L20.6109 26.2678C20.1421 26.7366 19.5062 27 18.8431 27V30Z" d="M1.43934 32.4393C0.853553 33.0251 0.853553 33.9749 1.43934 34.5607C2.02513 35.1464 2.97487 35.1464 3.56066 34.5607L1.43934 32.4393ZM24.0607 14.0607C24.6464 13.4749 24.6464 12.5251 24.0607 11.9393C23.4749 11.3536 22.5251 11.3536 21.9393 11.9393L24.0607 14.0607ZM8.67157 14.3284L7.61091 13.2678L8.67157 14.3284ZM14.5607 23.5607L24.0607 14.0607L21.9393 11.9393L12.4393 21.4393L14.5607 23.5607ZM26.5 21H13.5V24H26.5V21ZM3.56066 34.5607L8.56066 29.5607L6.43934 27.4393L1.43934 32.4393L3.56066 34.5607ZM8.56066 29.5607L14.5607 23.5607L12.4393 21.4393L6.43934 27.4393L8.56066 29.5607ZM7.5 30H18.8431V27H7.5V30ZM22.7322 28.3891L31.5607 19.5607L29.4393 17.4393L20.6109 26.2678L22.7322 28.3891ZM9 28.5V17.1569H6V28.5H9ZM9.73223 15.3891L18.5607 6.56066L16.4393 4.43934L7.61091 13.2678L9.73223 15.3891ZM18.5607 6.56066C21.5647 3.55659 26.4353 3.5566 29.4393 6.56066L31.5607 4.43934C27.385 0.263705 20.615 0.263698 16.4393 4.43934L18.5607 6.56066ZM9 17.1569C9 16.4938 9.26339 15.8579 9.73223 15.3891L7.61091 13.2678C6.57946 14.2992 6 15.6982 6 17.1569H9ZM31.5607 19.5607C35.7363 15.385 35.7363 8.61497 31.5607 4.43934L29.4393 6.56066C32.4434 9.56472 32.4434 14.4353 29.4393 17.4393L31.5607 19.5607ZM18.8431 30C20.3018 30 21.7008 29.4205 22.7322 28.3891L20.6109 26.2678C20.1421 26.7366 19.5062 27 18.8431 27V30Z"
fill="#153E67" fill="#153E67"></path>
/>
</g> </g>
<defs> <defs>
<clipPath id={clipId.current}> <clip-path id={clipId}>
<rect width="36" height="36" fill="white" /> <rect width="36" height="36" fill="white"></rect>
</clipPath> </clip-path>
</defs> </defs>
</svg> </svg>

View File

@@ -2,13 +2,13 @@
import ThemeStyle from "../page-components/layouts/theme-style.astro"; import ThemeStyle from "../page-components/layouts/theme-style.astro";
import BlockingThemeChangerScript from "../page-components/layouts/blocking-theme-changer-script.astro"; import BlockingThemeChangerScript from "../page-components/layouts/blocking-theme-changer-script.astro";
import Layout from "../components/layout/layout.astro"; import Layout from "../components/layout/layout.astro";
import '../global.scss'; import "../global.scss";
--- ---
<html class="light"> <html class="light">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width" />
<meta charset="utf-8"/> <meta charset="utf-8" />
<link <link
rel="preload" rel="preload"
as="style" as="style"
@@ -57,12 +57,12 @@ import '../global.scss';
sizes="512x512" sizes="512x512"
href="/icons/icon-512x512.png?v=2a" href="/icons/icon-512x512.png?v=2a"
/> />
<script defer src="/uninstall-sw.js" is:inline/> <script defer src="/uninstall-sw.js" is:inline></script>
<ThemeStyle/> <ThemeStyle />
<slot name="head"/> <slot name="head" />
</head> </head>
<body> <body>
<BlockingThemeChangerScript/> <BlockingThemeChangerScript />
<Layout> <Layout>
<slot /> <slot />
</Layout> </Layout>

View File

@@ -24,11 +24,15 @@ const publishedStr = dayjs(post.published).format("MMMM D, YYYY");
--- ---
<div class={styles.container}> <div class={styles.container}>
<UserProfilePic authors={authorsMeta} class={styles.postMetadataAuthorImagesContainer} /> <UserProfilePic
authors={authorsMeta}
class={styles.postMetadataAuthorImagesContainer}
/>
<div class={styles.textDiv}> <div class={styles.textDiv}>
<h2 class={styles.authorName} data-testid="post-meta-author-name"> <h2 class={styles.authorName} data-testid="post-meta-author-name">
<span>by </span> <span>by</span>
{authorsMeta.map((author, i) => { {
authorsMeta.map((author, i) => {
return ( return (
<> <>
{i !== 0 && <span>{", "}</span>} {i !== 0 && <span>{", "}</span>}
@@ -37,19 +41,26 @@ const publishedStr = dayjs(post.published).format("MMMM D, YYYY");
</a> </a>
</> </>
); );
})} })
}
</h2> </h2>
<div class={styles.belowName}> <div class={styles.belowName}>
<p>{publishedStr}</p> <p>{publishedStr}</p>
<p>{post.wordCount} words</p> <p>{post.wordCount} words</p>
</div> </div>
</div> </div>
{!!post.originalLink && ( {
!!post.originalLink && (
<p class={styles.originalLink}> <p class={styles.originalLink}>
Originally posted at&nbsp; Originally posted at&nbsp;
<a href={post.originalLink} target="_blank" rel="nofollow noopener noreferrer"> <a
href={post.originalLink}
target="_blank"
rel="nofollow noopener noreferrer"
>
{originalHost} {originalHost}
</a> </a>
</p> </p>
)} )
}
</div> </div>

View File

@@ -5,17 +5,13 @@ import { PostInfo } from "types/index";
interface PostTitleHeaderProps { interface PostTitleHeaderProps {
post: PostInfo; post: PostInfo;
} }
const {post} = Astro.props as PostTitleHeaderProps; const { post } = Astro.props as PostTitleHeaderProps;
const { title, tags } = post; const { title, tags } = post;
--- ---
<div class={styles.container}> <div class={styles.container}>
<h1 class={styles.title}>{title}</h1> <h1 class={styles.title}>{title}</h1>
<ul aria-label="Post tags" role="list" class={styles.tags}> <ul aria-label="Post tags" role="list" class={styles.tags}>
{tags.map((tag) => ( {tags.map((tag) => <li role="listitem">{tag}</li>)}
<li role="listitem">
{tag}
</li>
))}
</ul> </ul>
</div> </div>

View File

@@ -7,13 +7,12 @@ interface TableOfContentsProps {
lang?: Languages; lang?: Languages;
} }
const { const { suggestedArticles, lang } = Astro.props as TableOfContentsProps;
suggestedArticles,
lang,
} = Astro.props as TableOfContentsProps;
--- ---
{!suggestedArticles.length ? null : <aside aria-label={"Suggested Articles"}> {
!suggestedArticles.length ? null : (
<aside aria-label={"Suggested Articles"}>
<ol role={"list"} class={suggestedStyle.list}> <ol role={"list"} class={suggestedStyle.list}>
<h2 class={suggestedStyle.header}>Related posts</h2> <h2 class={suggestedStyle.header}>Related posts</h2>
{suggestedArticles.map((suggestedArticle, i) => { {suggestedArticles.map((suggestedArticle, i) => {
@@ -21,9 +20,7 @@ const {
.map((author) => author.name) .map((author) => author.name)
.join(", "); .join(", ");
return ( return (
<li <li class={`${suggestedStyle.card} ${suggestedStyle.localCard}`}>
class={`${suggestedStyle.card} ${suggestedStyle.localCard}`}
>
<a <a
href={`/${lang !== "en" ? `${lang}/` : ""}posts/${ href={`/${lang !== "en" ? `${lang}/` : ""}posts/${
suggestedArticle.slug suggestedArticle.slug
@@ -43,4 +40,6 @@ const {
); );
})} })}
</ol> </ol>
</aside>} </aside>
)
}

View File

@@ -1,4 +1,4 @@
--- ---
--- ---
<script is:inline defer src="/scripts/tabs.min.js"/> <script is:inline defer src="/scripts/tabs.min.js"></script>

View File

@@ -1,9 +1,7 @@
--- ---
import * as Terser from "terser"; import * as Terser from "terser";
import { import { COLOR_MODE_STORAGE_KEY } from "../../constants";
COLOR_MODE_STORAGE_KEY,
} from "../../constants";
/** /**
* Much of this code deals with dark mode. It's ripped straight from: * Much of this code deals with dark mode. It's ripped straight from:
@@ -45,12 +43,14 @@ function setColorsByTheme() {
root.className = colorMode; root.className = colorMode;
} }
const boundFn = String(setColorsByTheme) const boundFn = String(setColorsByTheme).replace(
.replace("COLOR_MODE_STORAGE_KEY", COLOR_MODE_STORAGE_KEY); "COLOR_MODE_STORAGE_KEY",
COLOR_MODE_STORAGE_KEY
);
let calledFunction = `(${boundFn})()`; let calledFunction = `(${boundFn})()`;
calledFunction = (await Terser.minify(calledFunction)).code!; calledFunction = (await Terser.minify(calledFunction)).code!;
--- ---
<script set:html={calledFunction} /> <script set:html={calledFunction}></script>

View File

@@ -1,9 +1,7 @@
--- ---
import { import { COLORS } from "../../constants";
COLORS
} from "../../constants";
function getThemeStyling(theme: 'light' | 'dark') { function getThemeStyling(theme: "light" | "dark") {
const CSS_THEME = Object.entries(COLORS).reduce((prev, [key, val]) => { const CSS_THEME = Object.entries(COLORS).reduce((prev, [key, val]) => {
prev += `\n--${key}: ${val[theme]};`; prev += `\n--${key}: ${val[theme]};`;
return prev; return prev;
@@ -14,12 +12,14 @@ function getThemeStyling(theme: 'light' | 'dark') {
const rawStylesCSS = ` const rawStylesCSS = `
html.light, body.light { html.light, body.light {
${getThemeStyling('light')} ${getThemeStyling("light")}
} }
html.dark, body.dark { html.dark, body.dark {
${getThemeStyling('dark')} ${getThemeStyling("dark")}
} }
` `;
--- ---
<style set:html={rawStylesCSS} /> <style set:html={rawStylesCSS}>
</style>

View File

@@ -1,6 +1,6 @@
--- ---
import styles from "./post-list-header.module.scss"; import styles from "./post-list-header.module.scss";
import {Image} from '@astrojs/image/components'; import { Image } from "@astrojs/image/components";
import unicornLogo from "../../../assets/unicorn_utterances_logo_512.png"; import unicornLogo from "../../../assets/unicorn_utterances_logo_512.png";
interface PostListHeaderProps { interface PostListHeaderProps {
@@ -10,10 +10,22 @@ interface PostListHeaderProps {
const { siteDescription } = Astro.props as PostListHeaderProps; const { siteDescription } = Astro.props as PostListHeaderProps;
--- ---
<div class={styles.container} role="banner" aria-label={`Banner for Unicorn Utterances`}> <div
class={styles.container}
role="banner"
aria-label={`Banner for Unicorn Utterances`}
>
<div class={styles.headerPic}> <div class={styles.headerPic}>
<Image format={'png'} loading={"eager"} sizes={"300px"} alt={`Smiling cartoon unicorn with a <Image
bowtie`} src={unicornLogo} height={300} width={300} /> format={"png"}
loading={"eager"}
sizes={"300px"}
alt={`Smiling cartoon unicorn with a
bowtie`}
src={unicornLogo}
height={300}
width={300}
/>
</div> </div>
<div class={styles.noMgContainer}> <div class={styles.noMgContainer}>
<h1 class={styles.title}>Unicorn Utterances</h1> <h1 class={styles.title}>Unicorn Utterances</h1>

View File

@@ -14,14 +14,13 @@ import Pagination from "components/pagination/pagination.astro";
export interface PostListTemplateProps { export interface PostListTemplateProps {
posts: PostInfo[]; posts: PostInfo[];
rootURL: string; rootURL: string;
page: Pick<Page<PostInfo>, 'total' | 'currentPage' | 'size' | 'lastPage' | 'url'> page: Pick<
Page<PostInfo>,
"total" | "currentPage" | "size" | "lastPage" | "url"
>;
} }
const { const { posts, page, rootURL } = Astro.props as PostListTemplateProps;
posts,
page,
rootURL
} = Astro.props as PostListTemplateProps;
--- ---
<div> <div>

View File

@@ -1,8 +1,8 @@
--- ---
import styles from "./profile-header.module.scss"; import styles from "./profile-header.module.scss";
import { UnicornInfo } from "uu-types"; import { UnicornInfo } from "uu-types";
import {Image} from '@astrojs/image/components'; import { Image } from "@astrojs/image/components";
import { Icon } from 'astro-icon'; import { Icon } from "astro-icon";
import SocialBtn from "./social-button.astro"; import SocialBtn from "./social-button.astro";
@@ -17,14 +17,13 @@ interface PicTitleHeaderProps {
const { unicornData } = Astro.props as PicTitleHeaderProps; const { unicornData } = Astro.props as PicTitleHeaderProps;
const possessiveName = getNamePossessive(unicornData.name); const possessiveName = getNamePossessive(unicornData.name);
--- ---
<div <div
class={styles.container} class={styles.container}
role="banner" role="banner"
aria-label={`Banner for ${unicornData.name}`} aria-label={`Banner for ${unicornData.name}`}
> >
<div <div
class={styles.headerPic} class={styles.headerPic}
style={{ style={{
@@ -39,7 +38,7 @@ const possessiveName = getNamePossessive(unicornData.name);
height={192} height={192}
width={192} width={192}
sizes={"192px"} sizes={"192px"}
format={'png'} format={"png"}
loading={"eager"} loading={"eager"}
alt={`${possessiveName} profile picture`} alt={`${possessiveName} profile picture`}
/> />
@@ -57,7 +56,8 @@ const possessiveName = getNamePossessive(unicornData.name);
> >
{unicornData.description} {unicornData.description}
</div> </div>
{unicornData.socials && ( {
unicornData.socials && (
<ul <ul
class={styles.socialsContainer} class={styles.socialsContainer}
aria-label={`${possessiveName} social media links`} aria-label={`${possessiveName} social media links`}
@@ -68,7 +68,7 @@ const possessiveName = getNamePossessive(unicornData.name);
text={"Twitter"} text={"Twitter"}
url={`https://twitter.com/${unicornData.socials.twitter}`} url={`https://twitter.com/${unicornData.socials.twitter}`}
> >
<Icon name="twitter" height="36" width="36" slot="icon"/> <Icon name="twitter" height="36" width="36" slot="icon" />
</SocialBtn> </SocialBtn>
)} )}
{unicornData.socials.github && ( {unicornData.socials.github && (
@@ -76,7 +76,7 @@ const possessiveName = getNamePossessive(unicornData.name);
text={"GitHub"} text={"GitHub"}
url={`https://github.com/${unicornData.socials.github}`} url={`https://github.com/${unicornData.socials.github}`}
> >
<Icon name="github" height="36" width="36" slot="icon"/> <Icon name="github" height="36" width="36" slot="icon" />
</SocialBtn> </SocialBtn>
)} )}
{unicornData.socials.linkedIn && ( {unicornData.socials.linkedIn && (
@@ -84,7 +84,7 @@ const possessiveName = getNamePossessive(unicornData.name);
text={"LinkedIn"} text={"LinkedIn"}
url={`https://www.linkedin.com/in/${unicornData.socials.linkedIn}`} url={`https://www.linkedin.com/in/${unicornData.socials.linkedIn}`}
> >
<Icon name="linkedin" height="36" width="36" slot="icon"/> <Icon name="linkedin" height="36" width="36" slot="icon" />
</SocialBtn> </SocialBtn>
)} )}
{unicornData.socials.twitch && ( {unicornData.socials.twitch && (
@@ -92,7 +92,7 @@ const possessiveName = getNamePossessive(unicornData.name);
text={"Twitch"} text={"Twitch"}
url={`https://twitch.tv/${unicornData.socials.twitch}`} url={`https://twitch.tv/${unicornData.socials.twitch}`}
> >
<Icon name="twitch" height="36" width="36" slot="icon"/> <Icon name="twitch" height="36" width="36" slot="icon" />
</SocialBtn> </SocialBtn>
)} )}
{unicornData.socials.dribbble && ( {unicornData.socials.dribbble && (
@@ -100,18 +100,16 @@ const possessiveName = getNamePossessive(unicornData.name);
text={"Dribbble"} text={"Dribbble"}
url={`https://dribbble.com/${unicornData.socials.dribbble}`} url={`https://dribbble.com/${unicornData.socials.dribbble}`}
> >
<Icon name="dribbble" height="36" width="36" slot="icon"/> <Icon name="dribbble" height="36" width="36" slot="icon" />
</SocialBtn> </SocialBtn>
)} )}
{unicornData.socials.website && ( {unicornData.socials.website && (
<SocialBtn <SocialBtn text={"Website"} url={unicornData.socials.website}>
text={"Website"} <Icon name="site" height="36" width="36" slot="icon" />
url={unicornData.socials.website}
>
<Icon name="site" height="36" width="36" slot="icon"/>
</SocialBtn> </SocialBtn>
)} )}
</ul> </ul>
)} )
</div> }
</div> </div>
</div>

View File

@@ -4,7 +4,7 @@ interface SocialBtnProps {
url: string; url: string;
} }
const { text, url } = Astro.props as SocialBtnProps; const { text, url } = Astro.props as SocialBtnProps;
import styles from './social-button.module.scss'; import styles from "./social-button.module.scss";
--- ---
<li class={`baseBtn ${styles.socialBtnLink}`} role="listitem"> <li class={`baseBtn ${styles.socialBtnLink}`} role="listitem">
@@ -15,14 +15,9 @@ import styles from './social-button.module.scss';
rel="noreferrer" rel="noreferrer"
href={url} href={url}
> --> > -->
<a <a class="unlink" target="_blank" rel="noreferrer" href={url}>
class="unlink"
target="_blank"
rel="noreferrer"
href={url}
>
<span class={styles.svgContainer} aria-hidden={true}> <span class={styles.svgContainer} aria-hidden={true}>
<slot name="icon"/> <slot name="icon" />
</span> </span>
<span class={styles.socialText}>{text}</span> <span class={styles.socialText}>{text}</span>
</a> </a>

View File

@@ -8,7 +8,6 @@
align-items: center; align-items: center;
} }
.socialBtnLink { .socialBtnLink {
&:not(:last-of-type) { &:not(:last-of-type) {
margin-right: 32px; margin-right: 32px;

View File

@@ -3,24 +3,21 @@ import PostList from "components/post-card-list/post-card-list.astro";
import Pagination from "components/pagination/pagination.astro"; import Pagination from "components/pagination/pagination.astro";
import { Page } from "astro"; import { Page } from "astro";
import { PostInfo } from "types/PostInfo"; import { PostInfo } from "types/PostInfo";
import ProfileHeader from './profile-header/profile-header.astro'; import ProfileHeader from "./profile-header/profile-header.astro";
export interface UnicornTemplateProps { export interface UnicornTemplateProps {
unicorn: any; unicorn: any;
posts: PostInfo[]; posts: PostInfo[];
rootURL: string; rootURL: string;
page: Pick<Page<PostInfo>, 'total' | 'currentPage' | 'size' | 'lastPage' | 'url'> page: Pick<
Page<PostInfo>,
"total" | "currentPage" | "size" | "lastPage" | "url"
>;
} }
const { const { unicorn, page, rootURL, posts } = Astro.props as UnicornTemplateProps;
unicorn,
page,
rootURL,
posts
} = Astro.props as UnicornTemplateProps;
--- ---
<!-- <PostListProvider <!-- <PostListProvider
posts={authoredPosts} posts={authoredPosts}
numberOfPages={numberOfPages} numberOfPages={numberOfPages}

View File

@@ -1,13 +1,15 @@
--- ---
import Document from '../layouts/document.astro'; import Document from "../layouts/document.astro";
import PostListTemplate, { PostListTemplateProps } from '../page-components/post-list/post-list.astro'; import PostListTemplate, {
import { getAllPostsForListView } from 'utils/api'; PostListTemplateProps,
import {PostInfo} from 'types/PostInfo'; } from "../page-components/post-list/post-list.astro";
import { getAllPostsForListView } from "utils/api";
import { PostInfo } from "types/PostInfo";
import SEO from "components/seo/seo.astro"; import SEO from "components/seo/seo.astro";
const posts = await Astro.glob<PostInfo>('../../content/blog/**/*.md') const posts = await Astro.glob<PostInfo>("../../content/blog/**/*.md");
const enPosts = getAllPostsForListView(posts, 'en'); const enPosts = getAllPostsForListView(posts, "en");
const postsToDisplay = enPosts.slice(0, 8); const postsToDisplay = enPosts.slice(0, 8);
const page = { const page = {
@@ -17,12 +19,12 @@ const page = {
lastPage: Math.floor(enPosts.length / postsToDisplay.length), lastPage: Math.floor(enPosts.length / postsToDisplay.length),
url: { url: {
current: Astro.url.href, current: Astro.url.href,
next: '/page/2' next: "/page/2",
} },
} as PostListTemplateProps['page']; } as PostListTemplateProps["page"];
--- ---
<Document> <Document>
<SEO slot="head" title="Homepage" /> <SEO slot="head" title="Homepage" />
<PostListTemplate posts={postsToDisplay} page={page} rootURL={'/'}/> <PostListTemplate posts={postsToDisplay} page={page} rootURL={"/"} />
</Document> </Document>

View File

@@ -1,20 +1,20 @@
--- ---
import Document from '../../layouts/document.astro'; import Document from "../../layouts/document.astro";
import PostListTemplate from '../../page-components/post-list/post-list.astro'; import PostListTemplate from "../../page-components/post-list/post-list.astro";
import { getAllPostsForListView } from 'utils/api'; import { getAllPostsForListView } from "utils/api";
import {PostInfo} from 'types/PostInfo'; import { PostInfo } from "types/PostInfo";
import SEO from "components/seo/seo.astro"; import SEO from "components/seo/seo.astro";
import { Page } from 'astro'; import { Page } from "astro";
export async function getStaticPaths({ paginate }) { export async function getStaticPaths({ paginate }) {
const posts = await Astro.glob<PostInfo>('../../../content/blog/**/*.md') const posts = await Astro.glob<PostInfo>("../../../content/blog/**/*.md");
const postsToDisplay = getAllPostsForListView(posts, 'en'); const postsToDisplay = getAllPostsForListView(posts, "en");
return paginate(postsToDisplay, { pageSize: 8 }); return paginate(postsToDisplay, { pageSize: 8 });
} }
const { page } = Astro.props as {page: Page<PostInfo>}; const { page } = Astro.props as { page: Page<PostInfo> };
const pageIndex = page.currentPage; const pageIndex = page.currentPage;
@@ -23,5 +23,5 @@ const SEOTitle = `Post page ${pageIndex}`;
<Document> <Document>
<SEO slot="head" title={SEOTitle} /> <SEO slot="head" title={SEOTitle} />
<PostListTemplate posts={page.data} page={page} rootURL={'/'}/> <PostListTemplate posts={page.data} page={page} rootURL={"/"} />
</Document> </Document>

View File

@@ -1,51 +1,49 @@
--- ---
import Document from '../../layouts/document.astro'; import Document from "../../layouts/document.astro";
import SEO from "components/seo/seo.astro"; import SEO from "components/seo/seo.astro";
import BlogPostLayout from 'components/blog-post-layout/blog-post-layout.astro'; import BlogPostLayout from "components/blog-post-layout/blog-post-layout.astro";
import PostTitleHeader from 'src/page-components/blog-post/post-title-header/post-title-header.astro'; import PostTitleHeader from "src/page-components/blog-post/post-title-header/post-title-header.astro";
import PostMetadata from 'src/page-components/blog-post/post-metadata/post-metadata.astro'; import PostMetadata from "src/page-components/blog-post/post-metadata/post-metadata.astro";
import TabsScript from 'src/page-components/blog-post/tabs-script/tabs-script.astro'; import TabsScript from "src/page-components/blog-post/tabs-script/tabs-script.astro";
import SuggestedArticles from 'src/page-components/blog-post/suggested-articles/suggested-articles.astro'; import SuggestedArticles from "src/page-components/blog-post/suggested-articles/suggested-articles.astro";
import TableOfContents from 'components/table-of-contents/table-of-contents.astro'; import TableOfContents from "components/table-of-contents/table-of-contents.astro";
import type { MarkdownInstance } from 'astro'; import type { MarkdownInstance } from "astro";
import {PostInfo} from 'types/PostInfo'; import { PostInfo } from "types/PostInfo";
import { Languages } from 'types/index'; import { Languages } from "types/index";
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await Astro.glob<PostInfo>('../../../content/blog/**/*.md') const posts = await Astro.glob<PostInfo>("../../../content/blog/**/*.md");
return posts.map(post => { return posts.map((post) => {
return { return {
params: { params: {
// TODO: Pass locale // TODO: Pass locale
postid: post.frontmatter.slug postid: post.frontmatter.slug,
}, },
props: { props: {
Content: post.Content, Content: post.Content,
post: post.frontmatter post: post.frontmatter,
} },
} };
}) });
} }
const { Content, post } = Astro.props as { const { Content, post } = Astro.props as {
post: PostInfo, post: PostInfo;
Content: MarkdownInstance<any>['Content'] Content: MarkdownInstance<any>["Content"];
} };
const translations = post?.translations || []; const translations = post?.translations || [];
const otherLangs = translations const otherLangs = translations
? (Object.keys(translations).filter( ? (Object.keys(translations).filter((t) => t !== post.locale) as Languages[])
(t) => t !== post.locale
) as Languages[])
: []; : [];
--- ---
<Document> <Document>
<TabsScript slot="head"/> <TabsScript slot="head" />
<!-- TODO: Add this --> <!-- TODO: Add this -->
<!-- pathName={router.asPath} --> <!-- pathName={router.asPath} -->
<SEO <SEO
@@ -70,7 +68,10 @@ const otherLangs = translations
<TableOfContents headingsWithId={post.headingsWithId} /> <TableOfContents headingsWithId={post.headingsWithId} />
</div> </div>
<div slot="right"> <div slot="right">
<SuggestedArticles suggestedArticles={post.suggestedArticles} lang={"en"} /> <SuggestedArticles
suggestedArticles={post.suggestedArticles}
lang={"en"}
/>
</div> </div>
<header role="banner" class="marginZeroAutoChild"> <header role="banner" class="marginZeroAutoChild">
<PostTitleHeader post={post} /> <PostTitleHeader post={post} />
@@ -87,7 +88,7 @@ const otherLangs = translations
{post.translations && Object.keys(post.translations).length ? ( {post.translations && Object.keys(post.translations).length ? (
<TranslationsHeader post={post} /> <TranslationsHeader post={post} />
) : null} --> ) : null} -->
<Content/> <Content />
<!-- {post.series ? ( <!-- {post.series ? (
<SeriesNav post={post} postSeries={seriesPosts} /> <SeriesNav post={post} postSeries={seriesPosts} />
) : null} --> ) : null} -->

View File

@@ -1,22 +1,22 @@
--- ---
import Document from '../../../layouts/document.astro'; import Document from "../../../layouts/document.astro";
import SEO from "components/seo/seo.astro"; import SEO from "components/seo/seo.astro";
import UnicornsPage from '../../../page-components/unicorns/unicorn-page.astro'; import UnicornsPage from "../../../page-components/unicorns/unicorn-page.astro";
import { getAllPostsForUnicornListView } from 'utils/api'; import { getAllPostsForUnicornListView } from "utils/api";
import {PostInfo} from 'types/PostInfo'; import { PostInfo } from "types/PostInfo";
import { unicorns } from "utils/data"; import { unicorns } from "utils/data";
import { Page } from 'astro'; import { Page } from "astro";
export async function getStaticPaths() { export async function getStaticPaths() {
return unicorns.map(unicorn => ({ params: {unicornid: unicorn.id}})) return unicorns.map((unicorn) => ({ params: { unicornid: unicorn.id } }));
} }
const params = Astro.params as {unicornid: string}; const params = Astro.params as { unicornid: string };
const unicorn = unicorns.find(unicorn => unicorn.id === params.unicornid); const unicorn = unicorns.find((unicorn) => unicorn.id === params.unicornid);
const posts = await Astro.glob<PostInfo>('../../../../content/blog/**/*.md') const posts = await Astro.glob<PostInfo>("../../../../content/blog/**/*.md");
const enPosts = getAllPostsForUnicornListView(unicorn.id, posts, 'en'); const enPosts = getAllPostsForUnicornListView(unicorn.id, posts, "en");
const postsToDisplay = enPosts.slice(0, 8); const postsToDisplay = enPosts.slice(0, 8);
const page = { const page = {
@@ -26,8 +26,8 @@ const page = {
lastPage: Math.floor(enPosts.length / postsToDisplay.length), lastPage: Math.floor(enPosts.length / postsToDisplay.length),
url: { url: {
current: `/unicorns/${unicorn.id}`, current: `/unicorns/${unicorn.id}`,
next: `/unicorns/${unicorn.id}/page/2` next: `/unicorns/${unicorn.id}/page/2`,
} },
} as Page<PostInfo>; } as Page<PostInfo>;
const rootURL = `/unicorns/${unicorn.id}/`; const rootURL = `/unicorns/${unicorn.id}/`;
@@ -41,5 +41,10 @@ const rootURL = `/unicorns/${unicorn.id}/`;
unicornsData={[unicorn]} unicornsData={[unicorn]}
type="profile" type="profile"
/> />
<UnicornsPage unicorn={unicorn} page={page} posts={postsToDisplay} rootURL={rootURL}/> <UnicornsPage
unicorn={unicorn}
page={page}
posts={postsToDisplay}
rootURL={rootURL}
/>
</Document> </Document>

View File

@@ -1,24 +1,33 @@
--- ---
import Document from '../../../../layouts/document.astro'; import Document from "../../../../layouts/document.astro";
import SEO from "components/seo/seo.astro"; import SEO from "components/seo/seo.astro";
import UnicornsPage from '../../../../page-components/unicorns/unicorn-page.astro'; import UnicornsPage from "../../../../page-components/unicorns/unicorn-page.astro";
import { getAllPostsForUnicornListView } from 'utils/api'; import { getAllPostsForUnicornListView } from "utils/api";
import {PostInfo} from 'types/PostInfo'; import { PostInfo } from "types/PostInfo";
import { unicorns } from "utils/data"; import { unicorns } from "utils/data";
import { Page } from 'astro'; import { Page } from "astro";
export async function getStaticPaths({ paginate }) { export async function getStaticPaths({ paginate }) {
const posts = await Astro.glob<PostInfo>('../../../../../content/blog/**/*.md') const posts = await Astro.glob<PostInfo>(
return unicorns.map(unicorn => { "../../../../../content/blog/**/*.md"
const postsToDisplay = getAllPostsForUnicornListView(unicorn.id, posts, 'en'); );
return paginate(postsToDisplay, { params: {unicornid: unicorn.id}, pageSize: 8 }); return unicorns.map((unicorn) => {
}) const postsToDisplay = getAllPostsForUnicornListView(
unicorn.id,
posts,
"en"
);
return paginate(postsToDisplay, {
params: { unicornid: unicorn.id },
pageSize: 8,
});
});
} }
const { page } = Astro.props as {page: Page<PostInfo>}; const { page } = Astro.props as { page: Page<PostInfo> };
const params = Astro.params as {unicornid: string}; const params = Astro.params as { unicornid: string };
const unicorn = unicorns.find(unicorn => unicorn.id === params.unicornid); const unicorn = unicorns.find((unicorn) => unicorn.id === params.unicornid);
const rootURL = `/unicorns/${unicorn.id}/`; const rootURL = `/unicorns/${unicorn.id}/`;
--- ---
@@ -30,5 +39,10 @@ const rootURL = `/unicorns/${unicorn.id}/`;
unicornsData={[unicorn]} unicornsData={[unicorn]}
type="profile" type="profile"
/> />
<UnicornsPage unicorn={unicorn} page={page} posts={page.data} rootURL={rootURL}/> <UnicornsPage
unicorn={unicorn}
page={page}
posts={page.data}
rootURL={rootURL}
/>
</Document> </Document>

View File

@@ -15,7 +15,7 @@ code .line::before {
margin-right: 1.5rem; margin-right: 1.5rem;
display: inline-block; display: inline-block;
text-align: right; text-align: right;
color: rgba(115,138,148,.4) color: rgba(115, 138, 148, 0.4);
} }
/* Start of Shiki Twoslash CSS: /* Start of Shiki Twoslash CSS:
@@ -77,7 +77,8 @@ pre.shiki:hover .dim {
pre.shiki div.dim { pre.shiki div.dim {
opacity: 0.5; opacity: 0.5;
} }
pre.shiki div.dim, pre.shiki div.highlight { pre.shiki div.dim,
pre.shiki div.highlight {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@@ -90,7 +91,7 @@ pre.shiki div.line {
} }
/** Don't show the language identifiers */ /** Don't show the language identifiers */
pre.shiki .language-id{ pre.shiki .language-id {
display: none; display: none;
} }
@@ -163,7 +164,8 @@ pre code a {
} }
pre data-err { pre data-err {
/* Extracted from VS Code */ /* Extracted from VS Code */
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left; background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
repeat-x bottom left;
padding-bottom: 3px; padding-bottom: 3px;
} }
pre .query { pre .query {
@@ -178,7 +180,8 @@ pre .query {
/* This sections keeps both of those two in in sync */ /* This sections keeps both of those two in in sync */
pre .error, pre .error-behind { pre .error,
pre .error-behind {
margin-left: -14px; margin-left: -14px;
margin-top: 8px; margin-top: 8px;
margin-bottom: 4px; margin-bottom: 4px;
@@ -346,7 +349,6 @@ pre .logger.log-log svg {
margin-right: 9px; margin-right: 9px;
} }
blockquote { blockquote {
position: relative; position: relative;
margin-left: 1em; margin-left: 1em;

View File

@@ -132,7 +132,6 @@ $robotoMono: "Roboto Mono", monospace;
} }
} }
%headline-uniwidth-2 { %headline-uniwidth-2 {
font-family: $asap; font-family: $asap;
font-weight: 400; font-weight: 400;

View File

@@ -20,7 +20,7 @@ export interface RawPostInfo {
export interface PostInfo extends RawPostInfo { export interface PostInfo extends RawPostInfo {
slug: string; slug: string;
locale: Languages; locale: Languages;
Content: MarkdownInstance<never>['Content']; Content: MarkdownInstance<never>["Content"];
authorsMeta: UnicornInfo[]; authorsMeta: UnicornInfo[];
licenseMeta: LicenseInfo; licenseMeta: LicenseInfo;
excerpt: string; excerpt: string;

View File

@@ -18,7 +18,7 @@ export interface RawUnicornInfo {
pronouns: string; pronouns: string;
profileImg: string; profileImg: string;
color: string; color: string;
roles: Array<RolesEnum['id']>; roles: Array<RolesEnum["id"]>;
} }
export interface UnicornInfo extends RawUnicornInfo { export interface UnicornInfo extends RawUnicornInfo {

View File

@@ -2,7 +2,7 @@ import { PostInfo } from "types/PostInfo";
import { Languages } from "types/index"; import { Languages } from "types/index";
import { MarkdownInstance } from "astro"; import { MarkdownInstance } from "astro";
let allPostsCache = new WeakMap<object, MarkdownInstance<PostInfo>[]>(); const allPostsCache = new WeakMap<object, MarkdownInstance<PostInfo>[]>();
export function getAllPosts( export function getAllPosts(
posts: MarkdownInstance<PostInfo>[], posts: MarkdownInstance<PostInfo>[],
@@ -16,15 +16,14 @@ export function getAllPosts(
if (cacheString) allPostsCache.set(cacheString, posts); if (cacheString) allPostsCache.set(cacheString, posts);
return posts return posts.filter((post) => post.frontmatter.locale === language);
.filter(post => post.frontmatter.locale === language);
} }
const listViewCache = {}; const listViewCache = {};
export const getAllPostsForListView = ( export const getAllPostsForListView = (
posts: MarkdownInstance<PostInfo>[], posts: MarkdownInstance<PostInfo>[],
language: Languages, language: Languages
): PostInfo[] => { ): PostInfo[] => {
let allPosts = getAllPosts(posts, language, listViewCache); let allPosts = getAllPosts(posts, language, listViewCache);
@@ -35,16 +34,17 @@ export const getAllPostsForListView = (
return date1 > date2 ? -1 : 1; return date1 > date2 ? -1 : 1;
}); });
return allPosts.map(post => post.frontmatter).filter(post => post.locale === language); return allPosts
.map((post) => post.frontmatter)
.filter((post) => post.locale === language);
}; };
export const getAllPostsForUnicornListView = ( export const getAllPostsForUnicornListView = (
authorId: string, authorId: string,
posts: MarkdownInstance<PostInfo>[], posts: MarkdownInstance<PostInfo>[],
language: Languages, language: Languages
): PostInfo[] => { ): PostInfo[] => {
return getAllPostsForListView(posts, language) return getAllPostsForListView(posts, language).filter((post) =>
.filter(post => post.authorsMeta.find((postAuthor) => postAuthor.id === authorId)
post.authorsMeta.find(postAuthor => postAuthor.id === authorId)
); );
}; };

View File

@@ -37,10 +37,8 @@ const fullUnicorns: UnicornInfo[] = unicornsRaw.map((unicorn) => {
/** /**
* `getFullRelativePath` strips all prefixing `/`, so we must add one manually * `getFullRelativePath` strips all prefixing `/`, so we must add one manually
*/ */
const relativeServerPath = '/' + getFullRelativePath( const relativeServerPath =
"content/data/", "/" + getFullRelativePath("content/data/", unicorn.profileImg);
unicorn.profileImg
);
const profileImgSize = getImageSize(unicorn.profileImg, dataDirectory); const profileImgSize = getImageSize(unicorn.profileImg, dataDirectory);
// Mutation go BRR // Mutation go BRR

View File

@@ -28,7 +28,7 @@ export function getPostSlugs(lang: Languages) {
export const getAllPosts = (lang: Languages): PostInfo[] => { export const getAllPosts = (lang: Languages): PostInfo[] => {
const slugs = getPostSlugs(lang); const slugs = getPostSlugs(lang);
return slugs.map(slug => { return slugs.map((slug) => {
const file = { const file = {
path: path.join(postsDirectory, slug, getIndexPath(lang)), path: path.join(postsDirectory, slug, getIndexPath(lang)),
data: { data: {
@@ -41,8 +41,8 @@ export const getAllPosts = (lang: Languages): PostInfo[] => {
(rehypeUnicornPopulatePost as any)()(undefined, file); (rehypeUnicornPopulatePost as any)()(undefined, file);
return { return {
...(file.data.astro.frontmatter as any || {}).frontmatterBackup, ...((file.data.astro.frontmatter as any) || {}).frontmatterBackup,
...file.data.astro.frontmatter ...file.data.astro.frontmatter,
}; };
}) });
} };

View File

@@ -56,7 +56,7 @@ export type OrderSuggestPosts = ReturnType<
*/ */
const howManySimilarBetween = <T>(arr1: T[], arr2: T[]): number => { const howManySimilarBetween = <T>(arr1: T[], arr2: T[]): number => {
let match = 0; let match = 0;
for (let item of arr1) { for (const item of arr1) {
if (arr2.includes(item)) match++; if (arr2.includes(item)) match++;
} }
return match; return match;
@@ -89,19 +89,16 @@ const getOrderRange = (arr: OrderSuggestPosts) => {
}; };
}; };
export const getSuggestedArticles = ( export const getSuggestedArticles = (postNode: PostInfo, lang: Languages) => {
postNode: PostInfo,
lang: Languages
) => {
const { suggestedPosts, dateSorted } = getAllPostsByLang(lang); const { suggestedPosts, dateSorted } = getAllPostsByLang(lang);
let extraSuggestedArticles: OrderSuggestPosts = []; const extraSuggestedArticles: OrderSuggestPosts = [];
let suggestedArticles: OrderSuggestPosts = []; const suggestedArticles: OrderSuggestPosts = [];
let similarTags: Array<{ const similarTags: Array<{
post: OrderSuggestPosts[number]; post: OrderSuggestPosts[number];
howManyTagsSimilar: number; howManyTagsSimilar: number;
}> = []; }> = [];
for (let post of suggestedPosts) { for (const post of suggestedPosts) {
// Early "return" for value // Early "return" for value
if (suggestedArticles.length >= 3) break; if (suggestedArticles.length >= 3) break;
// Don't return the same article // Don't return the same article
@@ -133,7 +130,7 @@ export const getSuggestedArticles = (
if (suggestedArticles.length >= 3) break; if (suggestedArticles.length >= 3) break;
if (extraSuggestedArticles.length === 0) break; if (extraSuggestedArticles.length === 0) break;
const { largest, smallest } = getOrderRange(suggestedArticles) || {}; const { largest, smallest } = getOrderRange(suggestedArticles) || {};
for (let suggestedPost of extraSuggestedArticles) { for (const suggestedPost of extraSuggestedArticles) {
if ( if (
suggestedPost.order === smallest.order! - 1 || suggestedPost.order === smallest.order! - 1 ||
suggestedPost.order === largest.order! + 1 suggestedPost.order === largest.order! + 1

View File

@@ -10,8 +10,8 @@ import path from "path";
*/ */
import { getImage } from "../../../node_modules/@astrojs/image"; import { getImage } from "../../../node_modules/@astrojs/image";
import sharp_service from "../../../node_modules/@astrojs/image/dist/loaders/sharp.js"; import sharp_service from "../../../node_modules/@astrojs/image/dist/loaders/sharp.js";
import {getImageSize} from "../get-image-size"; import { getImageSize } from "../get-image-size";
import {fileURLToPath} from "url"; import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -30,7 +30,7 @@ export const rehypeAstroImageMd: Plugin<
loader: sharp_service ?? globalThis.astroImage?.loader, loader: sharp_service ?? globalThis.astroImage?.loader,
}; };
let imgNodes: any[] = []; const imgNodes: any[] = [];
visit(tree, (node: any) => { visit(tree, (node: any) => {
if (node.tagName === "img") { if (node.tagName === "img") {
imgNodes.push(node); imgNodes.push(node);
@@ -39,9 +39,13 @@ export const rehypeAstroImageMd: Plugin<
await Promise.all( await Promise.all(
imgNodes.map(async (node) => { imgNodes.map(async (node) => {
const slug = path.dirname(file.path).split('/').at(-1); const slug = path.dirname(file.path).split("/").at(-1);
const filePathDir = path.resolve(__dirname, '../../../public/content/blog', slug) const filePathDir = path.resolve(
__dirname,
"../../../public/content/blog",
slug
);
// TODO: How should remote images be handled? // TODO: How should remote images be handled?
const dimensions = getImageSize(node.properties.src, filePathDir) || { const dimensions = getImageSize(node.properties.src, filePathDir) || {

View File

@@ -1,36 +1,36 @@
import { Root } from "hast"; import { Root } from "hast";
import { Plugin } from "unified"; import { Plugin } from "unified";
import {visit} from 'unist-util-visit' import { visit } from "unist-util-visit";
interface RehypeExcerptProps { interface RehypeExcerptProps {
maxLength: number; maxLength: number;
} }
export const rehypeExcerpt: Plugin< export const rehypeExcerpt: Plugin<[RehypeExcerptProps | never], Root> = ({
[RehypeExcerptProps | never], maxLength,
Root }) => {
> = ({maxLength}) => {
return (tree, file) => { return (tree, file) => {
const getFileExcerpt = () => (file?.data?.astro as any)?.frontmatter?.excerpt as string; const getFileExcerpt = () =>
(file?.data?.astro as any)?.frontmatter?.excerpt as string;
const setFileExcerpt = (val) => { const setFileExcerpt = (val) => {
(file.data.astro as any).frontmatter.excerpt = val; (file.data.astro as any).frontmatter.excerpt = val;
} };
if (!getFileExcerpt()) { if (!getFileExcerpt()) {
setFileExcerpt(""); setFileExcerpt("");
} }
visit(tree, 'element', node => { visit(tree, "element", (node) => {
const fileExcerpt = getFileExcerpt(); const fileExcerpt = getFileExcerpt();
if (fileExcerpt.length >= maxLength) return; if (fileExcerpt.length >= maxLength) return;
// Don't get headers or anything other than text // Don't get headers or anything other than text
if (node.tagName === 'p') { if (node.tagName === "p") {
visit(node, 'text', textNode => { visit(node, "text", (textNode) => {
let newVal = fileExcerpt + textNode.value; let newVal = fileExcerpt + textNode.value;
if (newVal.length > maxLength) { if (newVal.length > maxLength) {
newVal = newVal.slice(0, maxLength - 3) + "..."; newVal = newVal.slice(0, maxLength - 3) + "...";
} }
setFileExcerpt(newVal); setFileExcerpt(newVal);
}) });
} }
}); });
}; };

View File

@@ -2,7 +2,7 @@ import { headingRank } from "hast-util-heading-rank";
import { hasProperty } from "hast-util-has-property"; import { hasProperty } from "hast-util-has-property";
import { toString } from "hast-util-to-string"; import { toString } from "hast-util-to-string";
import { Root, Parent } from "hast"; import { Root, Parent } from "hast";
import {visit} from "unist-util-visit"; import { visit } from "unist-util-visit";
/** /**
* Plugin to add `data-header-text`s to headings. * Plugin to add `data-header-text`s to headings.
@@ -26,11 +26,11 @@ export const rehypeHeaderText = () => {
}; };
if (file.data.astro.frontmatter.headingsWithId) { if (file.data.astro.frontmatter.headingsWithId) {
file.data.astro.frontmatter.headingsWithId.push(headingWithID) file.data.astro.frontmatter.headingsWithId.push(headingWithID);
} else { } else {
file.data.astro.frontmatter.headingsWithId = [headingWithID]; file.data.astro.frontmatter.headingsWithId = [headingWithID];
} }
} }
}); });
}; };
}; };

View File

@@ -9,8 +9,7 @@ import { fromHtml } from "hast-util-from-html";
import path from "path"; import path from "path";
interface RehypeUnicornElementMapProps { interface RehypeUnicornElementMapProps {}
}
function escapeHTML(s) { function escapeHTML(s) {
if (!s) return s; if (!s) return s;
@@ -85,7 +84,9 @@ export const rehypeUnicornElementMap: Plugin<
const headerLinkHTML = ` const headerLinkHTML = `
<a <a
href="#${id}" href="#${id}"
aria-label="Permalink for &quot;${escapeHTML(headerText)}&quot;" aria-label="Permalink for &quot;${escapeHTML(
headerText
)}&quot;"
class="anchor before" class="anchor before"
> >
<svg <svg

View File

@@ -1,9 +1,8 @@
import { Root } from "hast"; import { Root } from "hast";
import { Plugin } from "unified"; import { Plugin } from "unified";
import {getSuggestedArticles} from "../get-suggested-articles"; import { getSuggestedArticles } from "../get-suggested-articles";
interface RehypeUnicornGetSuggestedPostsProps { interface RehypeUnicornGetSuggestedPostsProps {}
}
export const rehypeUnicornGetSuggestedPosts: Plugin< export const rehypeUnicornGetSuggestedPosts: Plugin<
[RehypeUnicornGetSuggestedPostsProps | never], [RehypeUnicornGetSuggestedPostsProps | never],
@@ -16,10 +15,10 @@ export const rehypeUnicornGetSuggestedPosts: Plugin<
const post = { const post = {
...(file.data.astro as any).frontmatter.frontmatterBackup, ...(file.data.astro as any).frontmatter.frontmatterBackup,
...(file.data.astro as any).frontmatter ...(file.data.astro as any).frontmatter,
} };
const suggestedArticles = getSuggestedArticles(post, 'en'); const suggestedArticles = getSuggestedArticles(post, "en");
setData("suggestedArticles", suggestedArticles); setData("suggestedArticles", suggestedArticles);
}; };
}; };

View File

@@ -5,15 +5,13 @@ import { readFileSync } from "fs";
import * as path from "path"; import * as path from "path";
import { licenses, unicorns } from "../data"; import { licenses, unicorns } from "../data";
interface RehypeUnicornPopulatePostProps { interface RehypeUnicornPopulatePostProps {}
}
export const rehypeUnicornPopulatePost: Plugin< export const rehypeUnicornPopulatePost: Plugin<
[RehypeUnicornPopulatePostProps | never], [RehypeUnicornPopulatePostProps | never],
Root Root
> = () => { > = () => {
return (_, file) => { return (_, file) => {
function setData(key: string, val: any) { function setData(key: string, val: any) {
(file.data.astro as any).frontmatter[key] = val; (file.data.astro as any).frontmatter[key] = val;
} }
@@ -29,10 +27,10 @@ export const rehypeUnicornPopulatePost: Plugin<
// Calculate post locale // Calculate post locale
// index.md or index.es.md // index.md or index.es.md
const indexName = directorySplit.at(-1); const indexName = directorySplit.at(-1);
const indexSplit = indexName.split('.'); const indexSplit = indexName.split(".");
let locale = indexSplit.at(-2); let locale = indexSplit.at(-2);
if (locale === 'index') { if (locale === "index") {
locale = 'en'; locale = "en";
} }
// // TODO: Add translations // // TODO: Add translations
@@ -66,16 +64,14 @@ export const rehypeUnicornPopulatePost: Plugin<
let license; let license;
if (frontmatter.license) { if (frontmatter.license) {
license = licenses.find( license = licenses.find((l) => l.id === frontmatter.license);
(l) => l.id === frontmatter.license
);
} }
if (!license) license = null; if (!license) license = null;
setData('slug', slug); setData("slug", slug);
setData('locale', locale); setData("locale", locale);
setData('authorsMeta', authorsMeta); setData("authorsMeta", authorsMeta);
setData('license', license); setData("license", license);
setData('frontmatterBackup', frontmatter); setData("frontmatterBackup", frontmatter);
}; };
}; };

View File

@@ -27,7 +27,7 @@ import { visit } from "unist-util-visit";
import { unified } from "unified"; import { unified } from "unified";
import english from "retext-english"; import english from "retext-english";
import rehypeRetext from 'rehype-retext'; import rehypeRetext from "rehype-retext";
import { validateConfig } from "astro/dist/types/core/config"; import { validateConfig } from "astro/dist/types/core/config";
interface RemarkCountProps {} interface RemarkCountProps {}
@@ -39,9 +39,12 @@ function count(counts: Record<string, number>) {
visit(tree, visitor); visit(tree, visitor);
function visitor(node: Node) { function visitor(node: Node) {
if (node.type === 'SourceNode') { if (node.type === "SourceNode") {
const inlineCount = (node as never as {value: string}).value.split(/\b/g).length; const inlineCount = (node as never as { value: string }).value.split(
counts["InlineCodeWords"] = (counts["InlineCodeWords"] || 0) + inlineCount; /\b/g
).length;
counts["InlineCodeWords"] =
(counts["InlineCodeWords"] || 0) + inlineCount;
} }
counts[node.type] = (counts[node.type] || 0) + 1; counts[node.type] = (counts[node.type] || 0) + 1;
} }
@@ -67,6 +70,7 @@ export const rehypeWordCount: Plugin<[RemarkCountProps | never], Root> = () => {
.use(rehypeRetext, unified().use(english).use(count(counts))) .use(rehypeRetext, unified().use(english).use(count(counts)))
.run(tree); .run(tree);
(file.data.astro as any).frontmatter.wordCount = (counts.InlineCodeWords || 0) + (counts.TextNode || 0); (file.data.astro as any).frontmatter.wordCount =
(counts.InlineCodeWords || 0) + (counts.TextNode || 0);
}; };
}; };

View File

@@ -1 +1 @@
export * from './tabs'; export * from "./tabs";

View File

@@ -14,7 +14,7 @@ const isNodeHeading = (n: ElementNode) =>
const findLargestHeading = (nodes: ElementNode[]) => { const findLargestHeading = (nodes: ElementNode[]) => {
let largestSize = Infinity; let largestSize = Infinity;
for (let node of nodes) { for (const node of nodes) {
if (!isNodeHeading(node)) continue; if (!isNodeHeading(node)) continue;
const size = parseInt(node.tagName.substr(1), 10); const size = parseInt(node.tagName.substr(1), 10);
largestSize = Math.min(largestSize, size); largestSize = Math.min(largestSize, size);
@@ -61,15 +61,15 @@ export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
type: "element", type: "element",
tagName: "div", tagName: "div",
properties: { properties: {
class: "tabs" class: "tabs",
}, },
children: [ children: [
{ {
type: "element", type: "element",
tagName: "ul", tagName: "ul",
properties: { properties: {
role: 'tablist', role: "tablist",
class: "tabs__tab-list" class: "tabs__tab-list",
}, },
children: [] as ElementNode[], children: [] as ElementNode[],
}, },
@@ -98,13 +98,13 @@ export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
tagName: "li", tagName: "li",
children: localNode.children, children: localNode.children,
properties: { properties: {
role: 'tab', role: "tab",
class: "tabs__tab", class: "tabs__tab",
"data-tabname": headerSlug, "data-tabname": headerSlug,
"aria-selected": idx === 0 ? "true" : 'false', "aria-selected": idx === 0 ? "true" : "false",
"aria-controls": `panel-${idx}`, "aria-controls": `panel-${idx}`,
"id": `tab-${idx}`, id: `tab-${idx}`,
tabIndex: idx === 0 ? "0" : "-1" tabIndex: idx === 0 ? "0" : "-1",
}, },
}; };
@@ -118,7 +118,7 @@ export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
class: "tabs__tab-panel", class: "tabs__tab-panel",
tabindex: 0, tabindex: 0,
"aria-labelledby": `tab-${idx}`, "aria-labelledby": `tab-${idx}`,
...(idx === 0 ? {} : {hidden: "true"}) ...(idx === 0 ? {} : { hidden: "true" }),
}, },
}; };

View File

@@ -14,20 +14,22 @@ export const isRelativePath = (str: string) => {
return true; return true;
}; };
var pathJoin = function(...pathArr){ const pathJoin = function (...pathArr) {
return pathArr.map(function(path){ return pathArr
if(path[0] === "/"){ .map(function (path) {
if (path[0] === "/") {
path = path.slice(1); path = path.slice(1);
} }
if (path.startsWith('./')) { if (path.startsWith("./")) {
path = path.slice(2); path = path.slice(2);
} }
if(path[path.length - 1] === "/"){ if (path[path.length - 1] === "/") {
path = path.slice(0, path.length - 1); path = path.slice(0, path.length - 1);
} }
return path; return path;
}).join("/"); })
} .join("/");
};
export const getFullRelativePath = (...paths: string[]) => { export const getFullRelativePath = (...paths: string[]) => {
return isRelativePath(paths[paths.length - 1]) return isRelativePath(paths[paths.length - 1])

View File

@@ -24,7 +24,7 @@
"components/*": ["./src/components/*"], "components/*": ["./src/components/*"],
"utils/*": ["./src/utils/*"], "utils/*": ["./src/utils/*"],
"uu-utils": ["./src/utils"], "uu-utils": ["./src/utils"],
"assets/*": ["./src/assets/*"], "assets/*": ["./src/assets/*"]
} }
} }
} }