mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-06 04:21:55 +00:00
chore: apply automatic linting
This commit is contained in:
34
.eslintrc.js
34
.eslintrc.js
@@ -1,46 +1,46 @@
|
||||
const tsRules = {
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off"
|
||||
}
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:astro/recommended'],
|
||||
extends: ["eslint:recommended", "plugin:astro/recommended"],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": "off"
|
||||
"no-unused-vars": "off",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.astro'],
|
||||
parser: 'astro-eslint-parser',
|
||||
files: ["*.astro"],
|
||||
parser: "astro-eslint-parser",
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
extraFileExtensions: ['.astro'],
|
||||
parser: "@typescript-eslint/parser",
|
||||
extraFileExtensions: [".astro"],
|
||||
},
|
||||
rules: {
|
||||
...tsRules
|
||||
...tsRules,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['plugin:@typescript-eslint/recommended'],
|
||||
files: ["*.ts"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: ["plugin:@typescript-eslint/recommended"],
|
||||
rules: {
|
||||
...tsRules
|
||||
...tsRules,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Define the configuration for `<script>` tag.
|
||||
// Script in `<script>` is assigned a virtual file name with the `.js` extension.
|
||||
files: ['**/*.astro/*.js', '*.astro/*.js'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
files: ["**/*.astro/*.js", "*.astro/*.js"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -17,7 +17,7 @@ import { rehypeExcerpt } from "./src/utils/markdown/rehype-excerpt";
|
||||
import { rehypeUnicornPopulatePost } from "./src/utils/markdown/rehype-unicorn-populate-post";
|
||||
import { rehypeWordCount } from "./src/utils/markdown/rehype-word-count";
|
||||
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
|
||||
import behead from "remark-behead";
|
||||
@@ -34,18 +34,18 @@ export default defineConfig({
|
||||
plugins: [
|
||||
{
|
||||
...copy({
|
||||
hook: 'options',
|
||||
hook: "options",
|
||||
flatten: false,
|
||||
targets: [
|
||||
{
|
||||
src: 'content/**/*',
|
||||
dest: 'public/content'
|
||||
}
|
||||
]
|
||||
src: "content/**/*",
|
||||
dest: "public/content",
|
||||
},
|
||||
],
|
||||
}),
|
||||
enforce: 'pre'
|
||||
}
|
||||
]
|
||||
enforce: "pre",
|
||||
},
|
||||
],
|
||||
},
|
||||
markdown: {
|
||||
mode: "md",
|
||||
@@ -103,13 +103,16 @@ export default defineConfig({
|
||||
{
|
||||
maxHeight: 768,
|
||||
maxWidth: 768,
|
||||
}
|
||||
},
|
||||
],
|
||||
rehypeUnicornElementMap,
|
||||
[rehypeExcerpt, {
|
||||
maxLength: 150
|
||||
}],
|
||||
rehypeWordCount
|
||||
[
|
||||
rehypeExcerpt,
|
||||
{
|
||||
maxLength: 150,
|
||||
},
|
||||
],
|
||||
rehypeWordCount,
|
||||
],
|
||||
} as AstroUserConfig["markdown"] as never,
|
||||
});
|
||||
|
||||
@@ -13,12 +13,7 @@
|
||||
"pronouns": "they/themselves",
|
||||
"profileImg": "./crutchcorn.png",
|
||||
"color": "#ba68c8",
|
||||
"roles": [
|
||||
"devops",
|
||||
"developer",
|
||||
"author",
|
||||
"community"
|
||||
]
|
||||
"roles": ["devops", "developer", "author", "community"]
|
||||
},
|
||||
{
|
||||
"id": "fennifith",
|
||||
@@ -33,11 +28,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./fennifith.jpg",
|
||||
"color": "#0091EA",
|
||||
"roles": [
|
||||
"developer",
|
||||
"author",
|
||||
"community"
|
||||
]
|
||||
"roles": ["developer", "author", "community"]
|
||||
},
|
||||
{
|
||||
"id": "evelynhathaway",
|
||||
@@ -52,11 +43,7 @@
|
||||
"pronouns": "she",
|
||||
"profileImg": "proud.png",
|
||||
"color": "#ef5f17",
|
||||
"roles": [
|
||||
"developer",
|
||||
"devops",
|
||||
"community"
|
||||
]
|
||||
"roles": ["developer", "devops", "community"]
|
||||
},
|
||||
{
|
||||
"id": "adueppen",
|
||||
@@ -72,10 +59,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./adueppen.png",
|
||||
"color": "#69ffff",
|
||||
"roles": [
|
||||
"developer",
|
||||
"community"
|
||||
]
|
||||
"roles": ["developer", "community"]
|
||||
},
|
||||
{
|
||||
"id": "zavukodlak",
|
||||
@@ -91,9 +75,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./vukashin.png",
|
||||
"color": "#3485FF",
|
||||
"roles": [
|
||||
"designer"
|
||||
]
|
||||
"roles": ["designer"]
|
||||
},
|
||||
{
|
||||
"id": "tommyemo",
|
||||
@@ -108,9 +90,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./tommyemo.jpg",
|
||||
"color": "#8539EB",
|
||||
"roles": [
|
||||
"designer"
|
||||
]
|
||||
"roles": ["designer"]
|
||||
},
|
||||
{
|
||||
"id": "edpratti",
|
||||
@@ -125,10 +105,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./edpratti.jpg",
|
||||
"color": "#FF3300",
|
||||
"roles": [
|
||||
"designer",
|
||||
"author"
|
||||
]
|
||||
"roles": ["designer", "author"]
|
||||
},
|
||||
{
|
||||
"id": "sarsamurmu",
|
||||
@@ -144,9 +121,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./sarsamurmu.png",
|
||||
"color": "#7C4DFF",
|
||||
"roles": [
|
||||
"developer"
|
||||
]
|
||||
"roles": ["developer"]
|
||||
},
|
||||
{
|
||||
"id": "MDutro",
|
||||
@@ -160,11 +135,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./mdutro.jpg",
|
||||
"color": "#7C4DFF",
|
||||
"roles": [
|
||||
"developer",
|
||||
"author",
|
||||
"community"
|
||||
]
|
||||
"roles": ["developer", "author", "community"]
|
||||
},
|
||||
{
|
||||
"id": "reikaze",
|
||||
@@ -179,9 +150,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./reikaze.jpg",
|
||||
"color": "#ba68c8",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "thodges314",
|
||||
@@ -196,9 +165,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./thodges.png",
|
||||
"color": "#ba68c8",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "skatcat31",
|
||||
@@ -213,10 +180,7 @@
|
||||
"color": "#ba68c8",
|
||||
"profileImg": "./hello.png",
|
||||
"pronouns": "he",
|
||||
"roles": [
|
||||
"author",
|
||||
"community"
|
||||
]
|
||||
"roles": ["author", "community"]
|
||||
},
|
||||
{
|
||||
"id": "seanmiller",
|
||||
@@ -233,9 +197,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./seanmiller.jpg",
|
||||
"color": "#551a8b",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "pierremtb",
|
||||
@@ -252,9 +214,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./pierremtb.jpg",
|
||||
"color": "#FFEB3B",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "maisydino",
|
||||
@@ -270,10 +230,7 @@
|
||||
"pronouns": "she",
|
||||
"profileImg": "./maisydino.jpg",
|
||||
"color": "#FDF6E3",
|
||||
"roles": [
|
||||
"author",
|
||||
"community"
|
||||
]
|
||||
"roles": ["author", "community"]
|
||||
},
|
||||
{
|
||||
"id": "bobrossrtx",
|
||||
@@ -289,10 +246,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./bobrossrtx.jpg",
|
||||
"color": "#b7e11e",
|
||||
"roles": [
|
||||
"developer",
|
||||
"author"
|
||||
]
|
||||
"roles": ["developer", "author"]
|
||||
},
|
||||
{
|
||||
"id": "ljtech",
|
||||
@@ -309,9 +263,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./ljtechdotca.png",
|
||||
"color": "#7b61ff",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "SkyHawk_0",
|
||||
@@ -323,9 +275,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./goofy.png",
|
||||
"color": "#18BBC9",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "splatkillwill",
|
||||
@@ -342,9 +292,7 @@
|
||||
"pronouns": "they/themselves",
|
||||
"profileImg": "./splatkillwill.jpg",
|
||||
"color": "#BF00FF",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "fmothe",
|
||||
@@ -360,9 +308,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./fmothe.jpg",
|
||||
"color": "#18BBC9",
|
||||
"roles": [
|
||||
"translator"
|
||||
]
|
||||
"roles": ["translator"]
|
||||
},
|
||||
{
|
||||
"id": "jahirfiquitiva",
|
||||
@@ -379,9 +325,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./jahirfiquitiva.jpg",
|
||||
"color": "#3867d6",
|
||||
"roles": [
|
||||
"translator"
|
||||
]
|
||||
"roles": ["translator"]
|
||||
},
|
||||
{
|
||||
"id": "kaleem",
|
||||
@@ -397,9 +341,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./kaleem.jpeg",
|
||||
"color": "#a8b3ba",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "qarnax",
|
||||
@@ -414,12 +356,7 @@
|
||||
},
|
||||
"profileImg": "./qarnax.jpg",
|
||||
"color": "",
|
||||
"roles": [
|
||||
"developer",
|
||||
"author",
|
||||
"community",
|
||||
"translator"
|
||||
]
|
||||
"roles": ["developer", "author", "community", "translator"]
|
||||
},
|
||||
{
|
||||
"id": "alexchadwick",
|
||||
@@ -437,10 +374,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./alexchadwick.jpg",
|
||||
"color": "",
|
||||
"roles": [
|
||||
"author",
|
||||
"translator"
|
||||
]
|
||||
"roles": ["author", "translator"]
|
||||
},
|
||||
{
|
||||
"id": "williamcook",
|
||||
@@ -457,9 +391,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./williamcook.jpg",
|
||||
"color": "#AF7AC5",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
},
|
||||
{
|
||||
"id": "rudy",
|
||||
@@ -474,9 +406,7 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./rudy.jpg",
|
||||
"color": "#ba68c8",
|
||||
"roles": [
|
||||
"community"
|
||||
]
|
||||
"roles": ["community"]
|
||||
},
|
||||
{
|
||||
"id": "LayZee",
|
||||
@@ -492,8 +422,6 @@
|
||||
"pronouns": "he",
|
||||
"profileImg": "./lars-gyrup-brink-nielsen.jpg",
|
||||
"color": "#1b9bf0",
|
||||
"roles": [
|
||||
"author"
|
||||
]
|
||||
"roles": ["author"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/* AFTER CHANGING THIS FILE, PLEASE MANUALLY MINIFY IT AND PUT INTO backbtn.min.js */
|
||||
/* TODO: Add minifier to build script */
|
||||
window.onload = () => {
|
||||
const backBtn = document.querySelector('#backbtn');
|
||||
const backBtn = document.querySelector("#backbtn");
|
||||
|
||||
let hasHistory = false;
|
||||
window.addEventListener('beforeunload', () => {
|
||||
window.addEventListener("beforeunload", () => {
|
||||
hasHistory = true;
|
||||
})
|
||||
});
|
||||
|
||||
backBtn.addEventListener('click', () => {
|
||||
backBtn.addEventListener("click", () => {
|
||||
if (!document.referrer) {
|
||||
// This is the first page the user has visited on the site in this session
|
||||
window.location.href = '/';
|
||||
window.location.href = "/";
|
||||
return;
|
||||
}
|
||||
history.back();
|
||||
@@ -22,5 +22,5 @@ window.onload = () => {
|
||||
window.location.href = "/";
|
||||
}
|
||||
}, 200);
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
const LOCAL_STORAGE_KEY = "tabs-selection";
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const tabLists = document.querySelectorAll('[role="tablist"]');
|
||||
|
||||
tabLists.forEach(tabList => {
|
||||
tabLists.forEach((tabList) => {
|
||||
/**
|
||||
* @type {NodeListOf<HTMLElement>}
|
||||
*/
|
||||
@@ -13,7 +13,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Add a click event handler to each tab
|
||||
tabs.forEach((tab) => {
|
||||
tab.addEventListener('click', e => {
|
||||
tab.addEventListener("click", (e) => {
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
@@ -26,21 +26,21 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
inline: "center",
|
||||
});
|
||||
}, 0);
|
||||
changeTabs({ target })
|
||||
changeTabs({ target });
|
||||
});
|
||||
});
|
||||
|
||||
// Enable arrow navigation between tabs in the tab list
|
||||
let tabFocus = 0;
|
||||
|
||||
tabList.addEventListener('keydown', (_e) => {
|
||||
tabList.addEventListener("keydown", (_e) => {
|
||||
/**
|
||||
* @type {KeyboardEvent}
|
||||
*/
|
||||
const e = _e;
|
||||
// Move right
|
||||
if (e.keyCode === 39 || e.keyCode === 37) {
|
||||
tabs[tabFocus].setAttribute('tabindex', `-1`);
|
||||
tabs[tabFocus].setAttribute("tabindex", `-1`);
|
||||
if (e.keyCode === 39) {
|
||||
tabFocus++;
|
||||
// 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].click();
|
||||
}
|
||||
@@ -84,16 +84,18 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
// Remove all current selected tabs
|
||||
parent
|
||||
.querySelectorAll('[aria-selected="true"]')
|
||||
.forEach((t) => t.setAttribute('aria-selected', `false`));
|
||||
.forEach((t) => t.setAttribute("aria-selected", `false`));
|
||||
|
||||
// Set this tab as selected
|
||||
target.setAttribute('aria-selected', `true`);
|
||||
target.setAttribute("aria-selected", `true`);
|
||||
|
||||
const tabName = target.dataset.tabname;
|
||||
/**
|
||||
* @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);
|
||||
|
||||
@@ -105,12 +107,12 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
// Hide all tab panels
|
||||
grandparent
|
||||
.querySelectorAll('[role="tabpanel"]')
|
||||
.forEach((p) => p.setAttribute('hidden', `true`));
|
||||
.forEach((p) => p.setAttribute("hidden", `true`));
|
||||
|
||||
// Show the selected panel
|
||||
grandparent.parentNode
|
||||
.querySelector(`#${target.getAttribute('aria-controls')}`)
|
||||
.removeAttribute('hidden');
|
||||
.querySelector(`#${target.getAttribute("aria-controls")}`)
|
||||
.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
/* -------------------- */
|
||||
@@ -137,18 +139,20 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
// If user has linked to a heading that's inside of a tab
|
||||
const hash = window.location.hash;
|
||||
if (!hash) return;
|
||||
const heading = document.querySelector < HTMLElement > (hash);
|
||||
const heading = document.querySelector < HTMLElement > hash;
|
||||
if (!heading) return;
|
||||
const isHidden = checkElementsParents(heading, el =>
|
||||
el.hasAttribute('hidden') && el.getAttribute('hidden') !== "false"
|
||||
)
|
||||
const isHidden = checkElementsParents(
|
||||
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 (!isHidden) return;
|
||||
const partialHash = hash.slice(1);
|
||||
try {
|
||||
const matchingTab = document.querySelector < HTMLElement > (
|
||||
`[data-headers*="${partialHash}"`
|
||||
);
|
||||
const matchingTab =
|
||||
document.querySelector <
|
||||
HTMLElement >
|
||||
`[data-headers*="${partialHash}"`;
|
||||
if (!matchingTab) return;
|
||||
// If header is not in a tab
|
||||
const tabName = matchingTab.getAttribute("data-tabname");
|
||||
@@ -162,6 +166,5 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
} catch (e) {
|
||||
console.error("Error finding matching tab", e);
|
||||
}
|
||||
})()
|
||||
|
||||
})();
|
||||
});
|
||||
@@ -1,16 +1,16 @@
|
||||
/* AFTER CHANGING THIS FILE, PLEASE MANUALLY MINIFY IT AND PUT INTO tabs.min.js */
|
||||
const COLOR_MODE_STORAGE_KEY = "currentTheme";
|
||||
|
||||
const themeToggleBtn = document.querySelector('#theme-toggle-button');
|
||||
const darkIconEl = document.querySelector('#dark-icon');
|
||||
const lightIconEl = document.querySelector('#light-icon');
|
||||
const themeToggleBtn = document.querySelector("#theme-toggle-button");
|
||||
const darkIconEl = document.querySelector("#dark-icon");
|
||||
const lightIconEl = document.querySelector("#light-icon");
|
||||
function toggleButton(theme) {
|
||||
themeToggleBtn.ariaPressed = `${theme === 'dark'}`;
|
||||
if (theme === 'light') {
|
||||
themeToggleBtn.ariaPressed = `${theme === "dark"}`;
|
||||
if (theme === "light") {
|
||||
lightIconEl.style.display = null;
|
||||
darkIconEl.style.display = 'none';
|
||||
darkIconEl.style.display = "none";
|
||||
} else {
|
||||
lightIconEl.style.display = 'none';
|
||||
lightIconEl.style.display = "none";
|
||||
darkIconEl.style.display = null;
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,12 @@ function toggleButton(theme) {
|
||||
// TODO: Migrate to `classList`
|
||||
const initialTheme = document.documentElement.className;
|
||||
toggleButton(initialTheme);
|
||||
themeToggleBtn.addEventListener('click', () => {
|
||||
themeToggleBtn.addEventListener("click", () => {
|
||||
const currentTheme = document.documentElement.className;
|
||||
document.documentElement.className = currentTheme === 'light' ? 'dark' : 'light';
|
||||
document.documentElement.className =
|
||||
currentTheme === "light" ? "dark" : "light";
|
||||
// TODO: Persist new setting
|
||||
const newTheme = document.documentElement.className;
|
||||
toggleButton(newTheme);
|
||||
localStorage.setItem(COLOR_MODE_STORAGE_KEY, newTheme)
|
||||
})
|
||||
localStorage.setItem(COLOR_MODE_STORAGE_KEY, newTheme);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register("/sw.js")
|
||||
.then(serviceWorker => {
|
||||
.then((serviceWorker) => {
|
||||
console.log("Service Worker registered: ", serviceWorker);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error("Error registering the Service Worker: ", error);
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon';
|
||||
import { Icon } from "astro-icon";
|
||||
import btnStyles from "./dark-light-button.module.scss";
|
||||
---
|
||||
|
||||
@@ -9,8 +9,20 @@ import btnStyles from "./dark-light-button.module.scss";
|
||||
aria-pressed="false"
|
||||
aria-label={"Is dark mode enabled?"}
|
||||
>
|
||||
<Icon 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;"/>
|
||||
<Icon
|
||||
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>
|
||||
|
||||
<script defer is:inline src="/scripts/themetoggle.min.js"/>
|
||||
<script defer is:inline src="/scripts/themetoggle.min.js"></script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import layoutStyles from "./layout.module.scss";
|
||||
import DarkLightButton from "components/dark-light-button/dark-light-button.astro";
|
||||
import { Icon } from 'astro-icon';
|
||||
import { Icon } from "astro-icon";
|
||||
|
||||
// const { back } = useHistory();
|
||||
|
||||
@@ -10,20 +10,30 @@ const rootPath = `/`;
|
||||
const isBase = Astro.url.pathname === rootPath;
|
||||
const isBlogPost = Astro.url.pathname.startsWith(`${rootPath}posts`);
|
||||
const isCollection = Astro.url.pathname.startsWith(`${rootPath}collections`);
|
||||
|
||||
---
|
||||
|
||||
<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}>
|
||||
{!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" />
|
||||
</button>
|
||||
<script is:inline defer src="/scripts/backbtn.min.js" />
|
||||
</>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
)
|
||||
}
|
||||
<div class={layoutStyles.iconList}>
|
||||
<!-- <AnalyticsLink category={"outbound"} href="https://discord.gg/FMcvc6T" className={"baseBtn"}
|
||||
aria-label={"Join the Discord"}> -->
|
||||
@@ -33,7 +43,13 @@ const isCollection = Astro.url.pathname.startsWith(`${rootPath}collections`);
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class={ isCollection ? "" : !isBlogPost ? "listViewContent" : "postViewContent" }>
|
||||
<div
|
||||
class={isCollection
|
||||
? ""
|
||||
: !isBlogPost
|
||||
? "listViewContent"
|
||||
: "postViewContent"}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,23 +6,23 @@ export interface Page {
|
||||
|
||||
export const DR = {
|
||||
ariaLabel: "Go to the next set of pages",
|
||||
display: '...'
|
||||
display: "...",
|
||||
};
|
||||
|
||||
export const DL = {
|
||||
ariaLabel: "Go to previous set of pages",
|
||||
display: '...'
|
||||
display: "...",
|
||||
};
|
||||
|
||||
const range = (start: number, end: number): Page[] => {
|
||||
let length = end - start + 1;
|
||||
const length = end - start + 1;
|
||||
return Array.from({ length }, (_, idx) => {
|
||||
const page = idx + start;
|
||||
return {
|
||||
display: String(page),
|
||||
pageNumber: page,
|
||||
ariaLabel: `Goto page ${page}`
|
||||
}
|
||||
ariaLabel: `Goto page ${page}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -37,14 +37,14 @@ export const getPaginationRange = ({
|
||||
totalCount,
|
||||
pageSize,
|
||||
siblingCount = 1,
|
||||
currentPage
|
||||
currentPage,
|
||||
}: GetPaginationRangeProps): Page[] => {
|
||||
const totalPageCount = Math.ceil(totalCount / pageSize);
|
||||
|
||||
const totalPageCountPage: Page = {
|
||||
display: `${totalPageCount}`,
|
||||
pageNumber: totalPageCount
|
||||
}
|
||||
pageNumber: totalPageCount,
|
||||
};
|
||||
|
||||
const totalPageNumbers = siblingCount + 5;
|
||||
|
||||
@@ -63,48 +63,48 @@ export const getPaginationRange = ({
|
||||
|
||||
const firstPageIndex: Page = {
|
||||
display: "1",
|
||||
pageNumber: 1
|
||||
pageNumber: 1,
|
||||
};
|
||||
const lastPageIndex: Page = {
|
||||
display: `${totalPageCount}`,
|
||||
pageNumber: totalPageCount
|
||||
pageNumber: totalPageCount,
|
||||
};
|
||||
|
||||
if (!shouldShowLeftDots && shouldShowRightDots) {
|
||||
let leftItemCount = 3 + 2 * siblingCount;
|
||||
let leftRange = range(1, leftItemCount);
|
||||
const lastPage = leftRange[leftRange.length - 1]
|
||||
const leftItemCount = 3 + 2 * siblingCount;
|
||||
const leftRange = range(1, leftItemCount);
|
||||
const lastPage = leftRange[leftRange.length - 1];
|
||||
const DR_Page: Page = {
|
||||
...DR,
|
||||
pageNumber: currentPage + 2
|
||||
}
|
||||
pageNumber: currentPage + 2,
|
||||
};
|
||||
|
||||
return [...leftRange, DR_Page, totalPageCountPage];
|
||||
}
|
||||
|
||||
if (shouldShowLeftDots && !shouldShowRightDots) {
|
||||
let rightItemCount = 3 + 2 * siblingCount;
|
||||
let rightRange = range(
|
||||
const rightItemCount = 3 + 2 * siblingCount;
|
||||
const rightRange = range(
|
||||
totalPageCount - rightItemCount + 1,
|
||||
totalPageCount
|
||||
);
|
||||
const DL_Page: Page = {
|
||||
...DL,
|
||||
pageNumber: currentPage - 2
|
||||
}
|
||||
pageNumber: currentPage - 2,
|
||||
};
|
||||
return [firstPageIndex, DL_Page, ...rightRange];
|
||||
}
|
||||
|
||||
if (shouldShowLeftDots && shouldShowRightDots) {
|
||||
let middleRange = range(leftSiblingIndex, rightSiblingIndex);
|
||||
const middleRange = range(leftSiblingIndex, rightSiblingIndex);
|
||||
const DL_Page: Page = {
|
||||
...DL,
|
||||
pageNumber: currentPage - 2
|
||||
}
|
||||
pageNumber: currentPage - 2,
|
||||
};
|
||||
const DR_Page: Page = {
|
||||
...DR,
|
||||
pageNumber: currentPage + 2
|
||||
}
|
||||
pageNumber: currentPage + 2,
|
||||
};
|
||||
return [firstPageIndex, DL_Page, ...middleRange, DR_Page, lastPageIndex];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
---
|
||||
import { getPaginationRange } from './pagination-logic';
|
||||
import { Page } from 'astro';
|
||||
import { PostInfo } from 'types/PostInfo';
|
||||
import styles from './pagination.module.scss';
|
||||
import { getPaginationRange } from "./pagination-logic";
|
||||
import { Page } from "astro";
|
||||
import { PostInfo } from "types/PostInfo";
|
||||
import styles from "./pagination.module.scss";
|
||||
|
||||
interface PaginationProps {
|
||||
page: Pick<Page<PostInfo>, 'total' | 'currentPage' | 'size' | 'lastPage' | 'url'>;
|
||||
page: Pick<
|
||||
Page<PostInfo>,
|
||||
"total" | "currentPage" | "size" | "lastPage" | "url"
|
||||
>;
|
||||
class: string;
|
||||
rootURL: string;
|
||||
}
|
||||
|
||||
const {
|
||||
page,
|
||||
rootURL,
|
||||
class: className = ""
|
||||
} = Astro.props as PaginationProps;
|
||||
const { page, rootURL, class: className = "" } = Astro.props as PaginationProps;
|
||||
|
||||
const paginationRange = getPaginationRange({
|
||||
currentPage: page.currentPage,
|
||||
totalCount: page.total,
|
||||
siblingCount: 0,
|
||||
pageSize: page.size
|
||||
pageSize: page.size,
|
||||
});
|
||||
|
||||
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 firstPage = paginationRange[0];
|
||||
@@ -34,27 +34,47 @@ const disablePrevious = !firstPage || page.currentPage === firstPage.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>
|
||||
</li>}
|
||||
</li>
|
||||
)}
|
||||
|
||||
{paginationRange.map(pageItem => {
|
||||
{paginationRange.map((pageItem) => {
|
||||
const isSelected = pageItem.pageNumber === page.currentPage;
|
||||
return (
|
||||
<li class={`${styles.paginationItem} ${ isSelected ? styles.active : '' }`}>
|
||||
<a href={getPageHref(pageItem.pageNumber)} aria-label={pageItem.ariaLabel} aria-current={isSelected || undefined}>
|
||||
<li
|
||||
class={`${styles.paginationItem} ${
|
||||
isSelected ? styles.active : ""
|
||||
}`}
|
||||
>
|
||||
<a
|
||||
href={getPageHref(pageItem.pageNumber)}
|
||||
aria-label={pageItem.ariaLabel}
|
||||
aria-current={isSelected || undefined}
|
||||
>
|
||||
{pageItem.display}
|
||||
</a>
|
||||
</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>
|
||||
</li>}
|
||||
</ul>}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ export interface PostListProps {
|
||||
showWordCount?: boolean;
|
||||
numberOfArticles?: number;
|
||||
wordCount?: number;
|
||||
unicornData?: PostInfo['authorsMeta'];
|
||||
unicornData?: PostInfo["authorsMeta"];
|
||||
listAriaLabel: string;
|
||||
postsToDisplay: PostInfo[]
|
||||
postsToDisplay: PostInfo[];
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
---
|
||||
<ul
|
||||
class={listStyle.postsListContainer}
|
||||
aria-label={listAriaLabel}
|
||||
role="list"
|
||||
>
|
||||
{postsToDisplay.map((post) => (
|
||||
<PostCard post={post}/>
|
||||
))}
|
||||
|
||||
<ul class={listStyle.postsListContainer} aria-label={listAriaLabel} role="list">
|
||||
{postsToDisplay.map((post) => <PostCard post={post} />)}
|
||||
</ul>
|
||||
@@ -11,20 +11,10 @@ interface PostCardProps {
|
||||
class?: string; // class to pass to the post card element
|
||||
}
|
||||
|
||||
const {
|
||||
post,
|
||||
class: className = ""
|
||||
} = Astro.props as PostCardProps;
|
||||
const { post, class: className = "" } = Astro.props as PostCardProps;
|
||||
|
||||
const {
|
||||
published,
|
||||
slug,
|
||||
title,
|
||||
authorsMeta,
|
||||
tags,
|
||||
description,
|
||||
excerpt
|
||||
} = post;
|
||||
const { published, slug, title, authorsMeta, tags, description, excerpt } =
|
||||
post;
|
||||
|
||||
const publishedStr = dayjs(published).format("MMMM D, YYYY");
|
||||
---
|
||||
@@ -42,7 +32,8 @@ const publishedStr = dayjs(published).format("MMMM D, YYYY");
|
||||
{authorsMeta[0].name}
|
||||
</a>
|
||||
<!-- 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 (
|
||||
<>
|
||||
<span>, </span>
|
||||
@@ -51,17 +42,17 @@ const publishedStr = dayjs(published).format("MMMM D, YYYY");
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
})
|
||||
}
|
||||
</p>
|
||||
<div class={cardStyles.dateTagSubheader}>
|
||||
<p class={cardStyles.date}>{publishedStr}</p>
|
||||
{tags.map((tag) => (
|
||||
<span class={cardStyles.tag}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{tags.map((tag) => <span class={cardStyles.tag}>{tag}</span>)}
|
||||
</div>
|
||||
<p class={cardStyles.excerpt} set:html={description || excerpt} />
|
||||
<p class={cardStyles.excerpt} set:html={description || excerpt}></p>
|
||||
</div>
|
||||
<UserProfilePic authors={authorsMeta} className={cardStyles.authorImagesContainer} />
|
||||
<UserProfilePic
|
||||
authors={authorsMeta}
|
||||
className={cardStyles.authorImagesContainer}
|
||||
/>
|
||||
</li>
|
||||
@@ -1,19 +1,33 @@
|
||||
---
|
||||
import {SEOProps} from './shared';
|
||||
import { SEOProps } from "./shared";
|
||||
|
||||
type Props = Pick<SEOProps, 'editedTime' | 'publishedTime' | 'keywords' | 'unicornsData'>;
|
||||
const { keywords, editedTime, publishedTime, unicornsData } = Astro.props as Props;
|
||||
const author = unicornsData.map(uni => uni.name).join(",");
|
||||
type Props = Pick<
|
||||
SEOProps,
|
||||
"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: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} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
@@ -1,13 +1,9 @@
|
||||
---
|
||||
import {SEOProps} from './shared';
|
||||
import { SEOProps } from "./shared";
|
||||
|
||||
type Props = Pick<SEOProps, 'publishedTime' | 'isbn' | 'unicornsData'>;
|
||||
type Props = Pick<SEOProps, "publishedTime" | "isbn" | "unicornsData">;
|
||||
|
||||
const {
|
||||
publishedTime,
|
||||
unicornsData,
|
||||
isbn
|
||||
} = Astro.props as Props;
|
||||
const { publishedTime, unicornsData, isbn } = Astro.props as Props;
|
||||
|
||||
const author = unicornsData!.map((uni) => uni.name).join(",");
|
||||
---
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
---
|
||||
import { removePrefixLanguageFromPath } from "utils/translations";
|
||||
import { SEOProps } from "./shared";
|
||||
|
||||
import { removePrefixLanguageFromPath } from 'utils/translations';
|
||||
import {SEOProps} from './shared';
|
||||
|
||||
type Props = Pick<SEOProps, 'langData'> & {
|
||||
type Props = Pick<SEOProps, "langData"> & {
|
||||
pathName: string;
|
||||
siteMetadata: {siteUrl: string}
|
||||
siteMetadata: { siteUrl: string };
|
||||
};
|
||||
|
||||
const { langData, siteMetadata, pathName } = Astro.props as Props;
|
||||
---
|
||||
{langData?.currentLang && (
|
||||
|
||||
<>
|
||||
{
|
||||
langData?.currentLang && (
|
||||
<link
|
||||
rel="alternate"
|
||||
href={
|
||||
@@ -18,9 +20,13 @@ const {langData, siteMetadata, pathName} = Astro.props as Props;
|
||||
}
|
||||
href-lang="x-default"
|
||||
/>
|
||||
)}
|
||||
{langData?.otherLangs?.length ?
|
||||
langData.otherLangs.map((lang) => (
|
||||
)
|
||||
}
|
||||
</>
|
||||
<>
|
||||
{
|
||||
langData?.otherLangs?.length
|
||||
? langData.otherLangs.map((lang) => (
|
||||
<link
|
||||
rel="alternate"
|
||||
href={
|
||||
@@ -30,5 +36,7 @@ const {langData, siteMetadata, pathName} = Astro.props as Props;
|
||||
}
|
||||
href-lang={lang}
|
||||
/>
|
||||
)) : null}
|
||||
|
||||
))
|
||||
: null
|
||||
}
|
||||
</>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
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;
|
||||
metaDescription: string;
|
||||
metaImage: string;
|
||||
ogType: string;
|
||||
siteMetadata: {title: string}
|
||||
siteMetadata: { title: string };
|
||||
};
|
||||
|
||||
const {
|
||||
@@ -21,6 +21,7 @@ const {
|
||||
ogType,
|
||||
} = Astro.props as Props;
|
||||
---
|
||||
|
||||
{/* Open Graph SEO */}
|
||||
<meta property="og:url" content={currentPath} />
|
||||
<meta property="og:site_name" content={siteMetadata.title} />
|
||||
@@ -28,17 +29,20 @@ const {
|
||||
|
||||
<meta
|
||||
property="og:locale"
|
||||
content={
|
||||
langData ? fileToOpenGraphConverter(langData.currentLang) : "en"
|
||||
}
|
||||
content={langData ? fileToOpenGraphConverter(langData.currentLang) : "en"}
|
||||
/>
|
||||
{langData?.otherLangs?.length ?
|
||||
langData.otherLangs.map((lang) => (
|
||||
<>
|
||||
{
|
||||
langData?.otherLangs?.length
|
||||
? langData.otherLangs.map((lang) => (
|
||||
<meta
|
||||
property="og:locale:alternate"
|
||||
content={fileToOpenGraphConverter(lang)}
|
||||
/>
|
||||
)) : null}
|
||||
))
|
||||
: null
|
||||
}
|
||||
</>
|
||||
<meta property="og:description" content={metaDescription} />
|
||||
<meta property="og:image" content={metaImage} />
|
||||
<meta property="og:type" content={ogType} />
|
||||
@@ -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;
|
||||
---
|
||||
|
||||
|
||||
@@ -40,21 +40,54 @@ const currentPath = siteMetadata.siteUrl + (pathName || "");
|
||||
<title>
|
||||
{title ? `${title} | ${siteMetadata.title}` : siteMetadata.title}
|
||||
</title>
|
||||
{canonical ?
|
||||
<link rel="canonical" href={canonical} /> : null}
|
||||
<>{canonical ? <link rel="canonical" href={canonical} /> : null}</>
|
||||
<meta property="name" content={siteMetadata.title} />
|
||||
<meta name="description" content={metaDescription} />
|
||||
<meta property="keywords" content={metaKeywords} />
|
||||
<Analytics />
|
||||
<Twitter title={title} metaDescription={metaDescription} 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} />
|
||||
<Twitter
|
||||
title={title}
|
||||
metaDescription={metaDescription}
|
||||
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} />
|
||||
{type === 'article' &&
|
||||
<Article keywords={keywords} editedTime={editedTime} publishedTime={publishedTime} unicornsData={unicornsData} />}
|
||||
{type === 'book' &&
|
||||
<Book publishedTime={publishedTime} unicornsData={unicornsData} isbn={isbn} />}
|
||||
{type === 'profile' &&
|
||||
<Profile unicornsData={unicornsData} />}
|
||||
<>
|
||||
{
|
||||
type === "article" && (
|
||||
<Article
|
||||
keywords={keywords}
|
||||
editedTime={editedTime}
|
||||
publishedTime={publishedTime}
|
||||
unicornsData={unicornsData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
<>
|
||||
{
|
||||
type === "book" && (
|
||||
<Book
|
||||
publishedTime={publishedTime}
|
||||
unicornsData={unicornsData}
|
||||
isbn={isbn}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
<>
|
||||
{type === "profile" && <Profile unicornsData={unicornsData} />}
|
||||
</>
|
||||
<slot />
|
||||
@@ -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;
|
||||
metaImage: string;
|
||||
siteMetadata: {twitterHandle: string}
|
||||
siteMetadata: { twitterHandle: string };
|
||||
uniTwitter?: string;
|
||||
};
|
||||
|
||||
@@ -15,15 +15,20 @@ const {
|
||||
metaImage,
|
||||
unicornsData,
|
||||
uniTwitter,
|
||||
type
|
||||
type,
|
||||
} = Astro.props as Props;
|
||||
---
|
||||
|
||||
{/* Twitter SEO */}
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={metaDescription} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content={siteMetadata.twitterHandle} />
|
||||
<meta name="twitter:image" content={metaImage} />
|
||||
{type === "article" && unicornsData?.length === 1 && uniTwitter ? (
|
||||
<>
|
||||
{
|
||||
type === "article" && unicornsData?.length === 1 && uniTwitter ? (
|
||||
<meta property="twitter:creator" content={`@${uniTwitter}`} />
|
||||
) : null}
|
||||
) : null
|
||||
}
|
||||
</>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
---
|
||||
const {headingsToDisplaySlugs} = Astro.props as {headingsToDisplaySlugs: Array<string>}
|
||||
const { headingsToDisplaySlugs } = Astro.props as {
|
||||
headingsToDisplaySlugs: Array<string>;
|
||||
};
|
||||
---
|
||||
|
||||
<script define:vars={{ headingsToDisplaySlugs }}>
|
||||
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 highlightFirstActive = () => {
|
||||
if (!tocListRef) return;
|
||||
let firstVisibleLink =
|
||||
tocListRef.querySelector(".toc-is-visible");
|
||||
let firstVisibleLink = tocListRef.querySelector(".toc-is-visible");
|
||||
|
||||
linkRefs.forEach((linkRef) => {
|
||||
linkRef.classList.remove("toc-is-active");
|
||||
@@ -64,5 +67,5 @@ const {headingsToDisplaySlugs} = Astro.props as {headingsToDisplaySlugs: Array<s
|
||||
.forEach((heading) => {
|
||||
observer.observe(heading);
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -21,30 +21,27 @@ const headingsToDisplay = headings
|
||||
.map((h) => Object.assign({}, h, { depth: h.depth - minDepth + 1 }))
|
||||
.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"}>
|
||||
<ol
|
||||
class={tableOfContentsStyle.tableList}
|
||||
role="list"
|
||||
id="tocList"
|
||||
>
|
||||
{headingsToDisplay.map((headingInfo, i) => {
|
||||
<ol class={tableOfContentsStyle.tableList} role="list" id="tocList">
|
||||
{
|
||||
headingsToDisplay.map((headingInfo, i) => {
|
||||
const liClassNames = classnames(tableOfContentsStyle.tocLi, {
|
||||
[tableOfContentsStyle.tocH1]: headingInfo.depth === 1,
|
||||
[tableOfContentsStyle.tocH2]: headingInfo.depth === 2,
|
||||
[tableOfContentsStyle.tocH3]: headingInfo.depth === 3,
|
||||
});
|
||||
return (
|
||||
<li
|
||||
class={liClassNames}
|
||||
data-headingitem="true"
|
||||
>
|
||||
<li class={liClassNames} data-headingitem="true">
|
||||
<a href={`#${headingInfo.slug}`}>{headingInfo.value}</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
})
|
||||
}
|
||||
</ol>
|
||||
</aside>
|
||||
<HeadingIntersectionObserverScript headingsToDisplaySlugs={headingsToDisplaySlugs} />
|
||||
<HeadingIntersectionObserverScript
|
||||
headingsToDisplaySlugs={headingsToDisplaySlugs}
|
||||
/>
|
||||
|
||||
@@ -60,7 +60,9 @@
|
||||
@extend %headline-uniwidth-2;
|
||||
}
|
||||
|
||||
.tocH2, .tocH3, .tocH4 {
|
||||
.tocH2,
|
||||
.tocH3,
|
||||
.tocH4 {
|
||||
a {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import styles from "./user-profile-pic.module.scss";
|
||||
import { UnicornInfo } from "uu-types";
|
||||
import {Image} from '@astrojs/image/components';
|
||||
import { Image } from "@astrojs/image/components";
|
||||
|
||||
// TODO: Fix image loading and image 'onClick'
|
||||
|
||||
@@ -10,12 +10,14 @@ interface UserProfilePicProps {
|
||||
className: string;
|
||||
}
|
||||
|
||||
const { authors, className } = Astro.props as UserProfilePicProps
|
||||
const { authors, className } = Astro.props as UserProfilePicProps;
|
||||
|
||||
const hasTwoAuthors = authors.length !== 1;
|
||||
---
|
||||
|
||||
<div class={`${styles.container} ${className || ""}`}>
|
||||
{authors.map((unicorn, i) => {
|
||||
{
|
||||
authors.map((unicorn, i) => {
|
||||
const classesToApply = hasTwoAuthors ? styles.twoAuthor : "";
|
||||
|
||||
return (
|
||||
@@ -30,10 +32,11 @@ const hasTwoAuthors = authors.length !== 1;
|
||||
sizes={"85px"}
|
||||
height={85}
|
||||
width={85}
|
||||
format={'png'}
|
||||
format={"png"}
|
||||
class={`circleImg ${styles.profilePicImage} ${styles.width50} ${classesToApply}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -252,20 +252,35 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
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;
|
||||
border-color: #10bf7a;
|
||||
}
|
||||
@@ -361,8 +376,10 @@
|
||||
opacity: 0.3;
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- 1.4s infinite ease-in-out both;
|
||||
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
|
||||
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) {
|
||||
@@ -458,7 +475,6 @@
|
||||
}
|
||||
|
||||
@-webkit-keyframes formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
@@ -475,7 +491,6 @@
|
||||
}
|
||||
|
||||
@keyframes formkit-bouncedelay-formkit-form-data-uid-882d42bb6f- {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
@@ -550,22 +565,38 @@
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
.formkit-form[min-width~="700"] .formkit-fields[data-stacked="false"] .formkit-field,
|
||||
.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 {
|
||||
.formkit-form[min-width~="700"]
|
||||
.formkit-fields[data-stacked="false"]
|
||||
.formkit-field,
|
||||
.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;
|
||||
}
|
||||
|
||||
.formkit-form[min-width~="700"] .formkit-fields[data-stacked="false"] .formkit-field,
|
||||
.formkit-form[min-width~="800"] .formkit-fields[data-stacked="false"] .formkit-field {
|
||||
.formkit-form[min-width~="700"]
|
||||
.formkit-fields[data-stacked="false"]
|
||||
.formkit-field,
|
||||
.formkit-form[min-width~="800"]
|
||||
.formkit-fields[data-stacked="false"]
|
||||
.formkit-field {
|
||||
-webkit-flex: 100 1 auto;
|
||||
-ms-flex: 100 1 auto;
|
||||
flex: 100 1 auto;
|
||||
}
|
||||
|
||||
.formkit-form[min-width~="700"] .formkit-fields[data-stacked="false"] .formkit-submit,
|
||||
.formkit-form[min-width~="800"] .formkit-fields[data-stacked="false"] .formkit-submit {
|
||||
.formkit-form[min-width~="700"]
|
||||
.formkit-fields[data-stacked="false"]
|
||||
.formkit-submit,
|
||||
.formkit-form[min-width~="800"]
|
||||
.formkit-fields[data-stacked="false"]
|
||||
.formkit-submit {
|
||||
-webkit-flex: 1 1 auto;
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
|
||||
@@ -310,7 +310,7 @@ $pendIconMarg: #{$baseUnit}px;
|
||||
margin-bottom: 1em;
|
||||
|
||||
summary {
|
||||
padding: .5rem 1.5rem;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background: var(--darkPrimary);
|
||||
color: var(--backgroundColor);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import {v4 as uuidV4} from 'uuid';
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
const id = uuidV4();
|
||||
const clipId = `path-1-inside-1${id}`;
|
||||
const props = Astro.props;
|
||||
@@ -18,12 +18,11 @@ const props = Astro.props;
|
||||
fill-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"
|
||||
fill="#153E67"
|
||||
/>
|
||||
fill="#153E67"></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id={clipId}>
|
||||
<rect width="36" height="36" fill="white" />
|
||||
</clipPath>
|
||||
<clip-path id={clipId}>
|
||||
<rect width="36" height="36" fill="white"></rect>
|
||||
</clip-path>
|
||||
</defs>
|
||||
</svg>
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import {v4 as uuidV4} from 'uuid';
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
const id = uuidV4();
|
||||
const clipId = `path-1-inside-1${id}`;
|
||||
const props = Astro.props;
|
||||
@@ -13,15 +13,14 @@ const props = Astro.props;
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath={`url(#${clipId.current})`}>
|
||||
<g clip-path={`url(#${clipId})`}>
|
||||
<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"
|
||||
fill="#153E67"
|
||||
/>
|
||||
fill="#153E67"></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id={clipId.current}>
|
||||
<rect width="36" height="36" fill="white" />
|
||||
</clipPath>
|
||||
<clip-path id={clipId}>
|
||||
<rect width="36" height="36" fill="white"></rect>
|
||||
</clip-path>
|
||||
</defs>
|
||||
</svg>
|
||||
@@ -2,12 +2,12 @@
|
||||
import ThemeStyle from "../page-components/layouts/theme-style.astro";
|
||||
import BlockingThemeChangerScript from "../page-components/layouts/blocking-theme-changer-script.astro";
|
||||
import Layout from "../components/layout/layout.astro";
|
||||
import '../global.scss';
|
||||
import "../global.scss";
|
||||
---
|
||||
|
||||
<html class="light">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta charset="utf-8" />
|
||||
<link
|
||||
rel="preload"
|
||||
@@ -57,7 +57,7 @@ import '../global.scss';
|
||||
sizes="512x512"
|
||||
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 />
|
||||
<slot name="head" />
|
||||
</head>
|
||||
|
||||
@@ -24,11 +24,15 @@ const publishedStr = dayjs(post.published).format("MMMM D, YYYY");
|
||||
---
|
||||
|
||||
<div class={styles.container}>
|
||||
<UserProfilePic authors={authorsMeta} class={styles.postMetadataAuthorImagesContainer} />
|
||||
<UserProfilePic
|
||||
authors={authorsMeta}
|
||||
class={styles.postMetadataAuthorImagesContainer}
|
||||
/>
|
||||
<div class={styles.textDiv}>
|
||||
<h2 class={styles.authorName} data-testid="post-meta-author-name">
|
||||
<span>by</span>
|
||||
{authorsMeta.map((author, i) => {
|
||||
{
|
||||
authorsMeta.map((author, i) => {
|
||||
return (
|
||||
<>
|
||||
{i !== 0 && <span>{", "}</span>}
|
||||
@@ -37,19 +41,26 @@ const publishedStr = dayjs(post.published).format("MMMM D, YYYY");
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
})
|
||||
}
|
||||
</h2>
|
||||
<div class={styles.belowName}>
|
||||
<p>{publishedStr}</p>
|
||||
<p>{post.wordCount} words</p>
|
||||
</div>
|
||||
</div>
|
||||
{!!post.originalLink && (
|
||||
{
|
||||
!!post.originalLink && (
|
||||
<p class={styles.originalLink}>
|
||||
Originally posted at
|
||||
<a href={post.originalLink} target="_blank" rel="nofollow noopener noreferrer">
|
||||
<a
|
||||
href={post.originalLink}
|
||||
target="_blank"
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
{originalHost}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
@@ -12,10 +12,6 @@ const { title, tags } = post;
|
||||
<div class={styles.container}>
|
||||
<h1 class={styles.title}>{title}</h1>
|
||||
<ul aria-label="Post tags" role="list" class={styles.tags}>
|
||||
{tags.map((tag) => (
|
||||
<li role="listitem">
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
{tags.map((tag) => <li role="listitem">{tag}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -7,13 +7,12 @@ interface TableOfContentsProps {
|
||||
lang?: Languages;
|
||||
}
|
||||
|
||||
const {
|
||||
suggestedArticles,
|
||||
lang,
|
||||
} = Astro.props as TableOfContentsProps;
|
||||
const { 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}>
|
||||
<h2 class={suggestedStyle.header}>Related posts</h2>
|
||||
{suggestedArticles.map((suggestedArticle, i) => {
|
||||
@@ -21,9 +20,7 @@ const {
|
||||
.map((author) => author.name)
|
||||
.join(", ");
|
||||
return (
|
||||
<li
|
||||
class={`${suggestedStyle.card} ${suggestedStyle.localCard}`}
|
||||
>
|
||||
<li class={`${suggestedStyle.card} ${suggestedStyle.localCard}`}>
|
||||
<a
|
||||
href={`/${lang !== "en" ? `${lang}/` : ""}posts/${
|
||||
suggestedArticle.slug
|
||||
@@ -43,4 +40,6 @@ const {
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</aside>}
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
---
|
||||
---
|
||||
|
||||
<script is:inline defer src="/scripts/tabs.min.js"/>
|
||||
<script is:inline defer src="/scripts/tabs.min.js"></script>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
---
|
||||
import * as Terser from "terser";
|
||||
|
||||
import {
|
||||
COLOR_MODE_STORAGE_KEY,
|
||||
} from "../../constants";
|
||||
import { COLOR_MODE_STORAGE_KEY } from "../../constants";
|
||||
|
||||
/**
|
||||
* Much of this code deals with dark mode. It's ripped straight from:
|
||||
@@ -45,12 +43,14 @@ function setColorsByTheme() {
|
||||
root.className = colorMode;
|
||||
}
|
||||
|
||||
const boundFn = String(setColorsByTheme)
|
||||
.replace("COLOR_MODE_STORAGE_KEY", COLOR_MODE_STORAGE_KEY);
|
||||
const boundFn = String(setColorsByTheme).replace(
|
||||
"COLOR_MODE_STORAGE_KEY",
|
||||
COLOR_MODE_STORAGE_KEY
|
||||
);
|
||||
|
||||
let calledFunction = `(${boundFn})()`;
|
||||
|
||||
calledFunction = (await Terser.minify(calledFunction)).code!;
|
||||
---
|
||||
|
||||
<script set:html={calledFunction} />
|
||||
<script set:html={calledFunction}></script>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
---
|
||||
import {
|
||||
COLORS
|
||||
} from "../../constants";
|
||||
import { COLORS } from "../../constants";
|
||||
|
||||
function getThemeStyling(theme: 'light' | 'dark') {
|
||||
function getThemeStyling(theme: "light" | "dark") {
|
||||
const CSS_THEME = Object.entries(COLORS).reduce((prev, [key, val]) => {
|
||||
prev += `\n--${key}: ${val[theme]};`;
|
||||
return prev;
|
||||
@@ -14,12 +12,14 @@ function getThemeStyling(theme: 'light' | 'dark') {
|
||||
|
||||
const rawStylesCSS = `
|
||||
html.light, body.light {
|
||||
${getThemeStyling('light')}
|
||||
${getThemeStyling("light")}
|
||||
}
|
||||
html.dark, body.dark {
|
||||
${getThemeStyling('dark')}
|
||||
${getThemeStyling("dark")}
|
||||
}
|
||||
`
|
||||
`;
|
||||
---
|
||||
|
||||
<style set:html={rawStylesCSS} />
|
||||
<style set:html={rawStylesCSS}>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
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";
|
||||
|
||||
interface PostListHeaderProps {
|
||||
@@ -10,10 +10,22 @@ interface 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}>
|
||||
<Image format={'png'} loading={"eager"} sizes={"300px"} alt={`Smiling cartoon unicorn with a
|
||||
bowtie`} src={unicornLogo} height={300} width={300} />
|
||||
<Image
|
||||
format={"png"}
|
||||
loading={"eager"}
|
||||
sizes={"300px"}
|
||||
alt={`Smiling cartoon unicorn with a
|
||||
bowtie`}
|
||||
src={unicornLogo}
|
||||
height={300}
|
||||
width={300}
|
||||
/>
|
||||
</div>
|
||||
<div class={styles.noMgContainer}>
|
||||
<h1 class={styles.title}>Unicorn Utterances</h1>
|
||||
|
||||
@@ -14,14 +14,13 @@ import Pagination from "components/pagination/pagination.astro";
|
||||
export interface PostListTemplateProps {
|
||||
posts: PostInfo[];
|
||||
rootURL: string;
|
||||
page: Pick<Page<PostInfo>, 'total' | 'currentPage' | 'size' | 'lastPage' | 'url'>
|
||||
page: Pick<
|
||||
Page<PostInfo>,
|
||||
"total" | "currentPage" | "size" | "lastPage" | "url"
|
||||
>;
|
||||
}
|
||||
|
||||
const {
|
||||
posts,
|
||||
page,
|
||||
rootURL
|
||||
} = Astro.props as PostListTemplateProps;
|
||||
const { posts, page, rootURL } = Astro.props as PostListTemplateProps;
|
||||
---
|
||||
|
||||
<div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
import styles from "./profile-header.module.scss";
|
||||
import { UnicornInfo } from "uu-types";
|
||||
import {Image} from '@astrojs/image/components';
|
||||
import { Icon } from 'astro-icon';
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import { Icon } from "astro-icon";
|
||||
|
||||
import SocialBtn from "./social-button.astro";
|
||||
|
||||
@@ -17,7 +17,6 @@ interface PicTitleHeaderProps {
|
||||
const { unicornData } = Astro.props as PicTitleHeaderProps;
|
||||
|
||||
const possessiveName = getNamePossessive(unicornData.name);
|
||||
|
||||
---
|
||||
|
||||
<div
|
||||
@@ -39,7 +38,7 @@ const possessiveName = getNamePossessive(unicornData.name);
|
||||
height={192}
|
||||
width={192}
|
||||
sizes={"192px"}
|
||||
format={'png'}
|
||||
format={"png"}
|
||||
loading={"eager"}
|
||||
alt={`${possessiveName} profile picture`}
|
||||
/>
|
||||
@@ -57,7 +56,8 @@ const possessiveName = getNamePossessive(unicornData.name);
|
||||
>
|
||||
{unicornData.description}
|
||||
</div>
|
||||
{unicornData.socials && (
|
||||
{
|
||||
unicornData.socials && (
|
||||
<ul
|
||||
class={styles.socialsContainer}
|
||||
aria-label={`${possessiveName} social media links`}
|
||||
@@ -104,14 +104,12 @@ const possessiveName = getNamePossessive(unicornData.name);
|
||||
</SocialBtn>
|
||||
)}
|
||||
{unicornData.socials.website && (
|
||||
<SocialBtn
|
||||
text={"Website"}
|
||||
url={unicornData.socials.website}
|
||||
>
|
||||
<SocialBtn text={"Website"} url={unicornData.socials.website}>
|
||||
<Icon name="site" height="36" width="36" slot="icon" />
|
||||
</SocialBtn>
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,7 +4,7 @@ interface SocialBtnProps {
|
||||
url: string;
|
||||
}
|
||||
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">
|
||||
@@ -15,12 +15,7 @@ import styles from './social-button.module.scss';
|
||||
rel="noreferrer"
|
||||
href={url}
|
||||
> -->
|
||||
<a
|
||||
class="unlink"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={url}
|
||||
>
|
||||
<a class="unlink" target="_blank" rel="noreferrer" href={url}>
|
||||
<span class={styles.svgContainer} aria-hidden={true}>
|
||||
<slot name="icon" />
|
||||
</span>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.socialBtnLink {
|
||||
&:not(:last-of-type) {
|
||||
margin-right: 32px;
|
||||
|
||||
@@ -3,24 +3,21 @@ import PostList from "components/post-card-list/post-card-list.astro";
|
||||
import Pagination from "components/pagination/pagination.astro";
|
||||
import { Page } from "astro";
|
||||
import { PostInfo } from "types/PostInfo";
|
||||
import ProfileHeader from './profile-header/profile-header.astro';
|
||||
import ProfileHeader from "./profile-header/profile-header.astro";
|
||||
|
||||
export interface UnicornTemplateProps {
|
||||
unicorn: any;
|
||||
posts: PostInfo[];
|
||||
rootURL: string;
|
||||
page: Pick<Page<PostInfo>, 'total' | 'currentPage' | 'size' | 'lastPage' | 'url'>
|
||||
page: Pick<
|
||||
Page<PostInfo>,
|
||||
"total" | "currentPage" | "size" | "lastPage" | "url"
|
||||
>;
|
||||
}
|
||||
|
||||
const {
|
||||
unicorn,
|
||||
page,
|
||||
rootURL,
|
||||
posts
|
||||
} = Astro.props as UnicornTemplateProps;
|
||||
const { unicorn, page, rootURL, posts } = Astro.props as UnicornTemplateProps;
|
||||
---
|
||||
|
||||
|
||||
<!-- <PostListProvider
|
||||
posts={authoredPosts}
|
||||
numberOfPages={numberOfPages}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
---
|
||||
import Document from '../layouts/document.astro';
|
||||
import PostListTemplate, { PostListTemplateProps } from '../page-components/post-list/post-list.astro';
|
||||
import { getAllPostsForListView } from 'utils/api';
|
||||
import {PostInfo} from 'types/PostInfo';
|
||||
import Document from "../layouts/document.astro";
|
||||
import PostListTemplate, {
|
||||
PostListTemplateProps,
|
||||
} 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";
|
||||
|
||||
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 page = {
|
||||
@@ -17,12 +19,12 @@ const page = {
|
||||
lastPage: Math.floor(enPosts.length / postsToDisplay.length),
|
||||
url: {
|
||||
current: Astro.url.href,
|
||||
next: '/page/2'
|
||||
}
|
||||
} as PostListTemplateProps['page'];
|
||||
next: "/page/2",
|
||||
},
|
||||
} as PostListTemplateProps["page"];
|
||||
---
|
||||
|
||||
<Document>
|
||||
<SEO slot="head" title="Homepage" />
|
||||
<PostListTemplate posts={postsToDisplay} page={page} rootURL={'/'}/>
|
||||
<PostListTemplate posts={postsToDisplay} page={page} rootURL={"/"} />
|
||||
</Document>
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
import Document from '../../layouts/document.astro';
|
||||
import PostListTemplate from '../../page-components/post-list/post-list.astro';
|
||||
import { getAllPostsForListView } from 'utils/api';
|
||||
import {PostInfo} from 'types/PostInfo';
|
||||
import Document from "../../layouts/document.astro";
|
||||
import PostListTemplate 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 { Page } from 'astro';
|
||||
import { Page } from "astro";
|
||||
|
||||
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 });
|
||||
}
|
||||
@@ -23,5 +23,5 @@ const SEOTitle = `Post page ${pageIndex}`;
|
||||
|
||||
<Document>
|
||||
<SEO slot="head" title={SEOTitle} />
|
||||
<PostListTemplate posts={page.data} page={page} rootURL={'/'}/>
|
||||
<PostListTemplate posts={page.data} page={page} rootURL={"/"} />
|
||||
</Document>
|
||||
@@ -1,46 +1,44 @@
|
||||
---
|
||||
import Document from '../../layouts/document.astro';
|
||||
import Document from "../../layouts/document.astro";
|
||||
|
||||
import SEO from "components/seo/seo.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 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 SuggestedArticles from 'src/page-components/blog-post/suggested-articles/suggested-articles.astro';
|
||||
import TableOfContents from 'components/table-of-contents/table-of-contents.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 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 SuggestedArticles from "src/page-components/blog-post/suggested-articles/suggested-articles.astro";
|
||||
import TableOfContents from "components/table-of-contents/table-of-contents.astro";
|
||||
|
||||
import type { MarkdownInstance } from 'astro';
|
||||
import {PostInfo} from 'types/PostInfo';
|
||||
import { Languages } from 'types/index';
|
||||
import type { MarkdownInstance } from "astro";
|
||||
import { PostInfo } from "types/PostInfo";
|
||||
import { Languages } from "types/index";
|
||||
|
||||
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 {
|
||||
params: {
|
||||
// TODO: Pass locale
|
||||
postid: post.frontmatter.slug
|
||||
postid: post.frontmatter.slug,
|
||||
},
|
||||
props: {
|
||||
Content: post.Content,
|
||||
post: post.frontmatter
|
||||
}
|
||||
}
|
||||
})
|
||||
post: post.frontmatter,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { Content, post } = Astro.props as {
|
||||
post: PostInfo,
|
||||
Content: MarkdownInstance<any>['Content']
|
||||
}
|
||||
post: PostInfo;
|
||||
Content: MarkdownInstance<any>["Content"];
|
||||
};
|
||||
|
||||
const translations = post?.translations || [];
|
||||
|
||||
const otherLangs = translations
|
||||
? (Object.keys(translations).filter(
|
||||
(t) => t !== post.locale
|
||||
) as Languages[])
|
||||
? (Object.keys(translations).filter((t) => t !== post.locale) as Languages[])
|
||||
: [];
|
||||
---
|
||||
|
||||
@@ -70,7 +68,10 @@ const otherLangs = translations
|
||||
<TableOfContents headingsWithId={post.headingsWithId} />
|
||||
</div>
|
||||
<div slot="right">
|
||||
<SuggestedArticles suggestedArticles={post.suggestedArticles} lang={"en"} />
|
||||
<SuggestedArticles
|
||||
suggestedArticles={post.suggestedArticles}
|
||||
lang={"en"}
|
||||
/>
|
||||
</div>
|
||||
<header role="banner" class="marginZeroAutoChild">
|
||||
<PostTitleHeader post={post} />
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
---
|
||||
import Document from '../../../layouts/document.astro';
|
||||
import Document from "../../../layouts/document.astro";
|
||||
import SEO from "components/seo/seo.astro";
|
||||
import UnicornsPage from '../../../page-components/unicorns/unicorn-page.astro';
|
||||
import { getAllPostsForUnicornListView } from 'utils/api';
|
||||
import {PostInfo} from 'types/PostInfo';
|
||||
import UnicornsPage from "../../../page-components/unicorns/unicorn-page.astro";
|
||||
import { getAllPostsForUnicornListView } from "utils/api";
|
||||
import { PostInfo } from "types/PostInfo";
|
||||
import { unicorns } from "utils/data";
|
||||
import { Page } from 'astro';
|
||||
import { Page } from "astro";
|
||||
|
||||
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 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 enPosts = getAllPostsForUnicornListView(unicorn.id, posts, 'en');
|
||||
const posts = await Astro.glob<PostInfo>("../../../../content/blog/**/*.md");
|
||||
const enPosts = getAllPostsForUnicornListView(unicorn.id, posts, "en");
|
||||
const postsToDisplay = enPosts.slice(0, 8);
|
||||
|
||||
const page = {
|
||||
@@ -26,8 +26,8 @@ const page = {
|
||||
lastPage: Math.floor(enPosts.length / postsToDisplay.length),
|
||||
url: {
|
||||
current: `/unicorns/${unicorn.id}`,
|
||||
next: `/unicorns/${unicorn.id}/page/2`
|
||||
}
|
||||
next: `/unicorns/${unicorn.id}/page/2`,
|
||||
},
|
||||
} as Page<PostInfo>;
|
||||
|
||||
const rootURL = `/unicorns/${unicorn.id}/`;
|
||||
@@ -41,5 +41,10 @@ const rootURL = `/unicorns/${unicorn.id}/`;
|
||||
unicornsData={[unicorn]}
|
||||
type="profile"
|
||||
/>
|
||||
<UnicornsPage unicorn={unicorn} page={page} posts={postsToDisplay} rootURL={rootURL}/>
|
||||
<UnicornsPage
|
||||
unicorn={unicorn}
|
||||
page={page}
|
||||
posts={postsToDisplay}
|
||||
rootURL={rootURL}
|
||||
/>
|
||||
</Document>
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
---
|
||||
import Document from '../../../../layouts/document.astro';
|
||||
import Document from "../../../../layouts/document.astro";
|
||||
import SEO from "components/seo/seo.astro";
|
||||
import UnicornsPage from '../../../../page-components/unicorns/unicorn-page.astro';
|
||||
import { getAllPostsForUnicornListView } from 'utils/api';
|
||||
import {PostInfo} from 'types/PostInfo';
|
||||
import UnicornsPage from "../../../../page-components/unicorns/unicorn-page.astro";
|
||||
import { getAllPostsForUnicornListView } from "utils/api";
|
||||
import { PostInfo } from "types/PostInfo";
|
||||
import { unicorns } from "utils/data";
|
||||
import { Page } from 'astro';
|
||||
import { Page } from "astro";
|
||||
|
||||
export async function getStaticPaths({ paginate }) {
|
||||
const posts = await Astro.glob<PostInfo>('../../../../../content/blog/**/*.md')
|
||||
return unicorns.map(unicorn => {
|
||||
const postsToDisplay = getAllPostsForUnicornListView(unicorn.id, posts, 'en');
|
||||
return paginate(postsToDisplay, { params: {unicornid: unicorn.id}, pageSize: 8 });
|
||||
})
|
||||
const posts = await Astro.glob<PostInfo>(
|
||||
"../../../../../content/blog/**/*.md"
|
||||
);
|
||||
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 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}/`;
|
||||
---
|
||||
|
||||
@@ -30,5 +39,10 @@ const rootURL = `/unicorns/${unicorn.id}/`;
|
||||
unicornsData={[unicorn]}
|
||||
type="profile"
|
||||
/>
|
||||
<UnicornsPage unicorn={unicorn} page={page} posts={page.data} rootURL={rootURL}/>
|
||||
<UnicornsPage
|
||||
unicorn={unicorn}
|
||||
page={page}
|
||||
posts={page.data}
|
||||
rootURL={rootURL}
|
||||
/>
|
||||
</Document>
|
||||
|
||||
@@ -15,7 +15,7 @@ code .line::before {
|
||||
margin-right: 1.5rem;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
color: rgba(115,138,148,.4)
|
||||
color: rgba(115, 138, 148, 0.4);
|
||||
}
|
||||
|
||||
/* Start of Shiki Twoslash CSS:
|
||||
@@ -77,7 +77,8 @@ pre.shiki:hover .dim {
|
||||
pre.shiki div.dim {
|
||||
opacity: 0.5;
|
||||
}
|
||||
pre.shiki div.dim, pre.shiki div.highlight {
|
||||
pre.shiki div.dim,
|
||||
pre.shiki div.highlight {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -163,7 +164,8 @@ pre code a {
|
||||
}
|
||||
pre data-err {
|
||||
/* 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;
|
||||
}
|
||||
pre .query {
|
||||
@@ -178,7 +180,8 @@ pre .query {
|
||||
|
||||
/* This sections keeps both of those two in in sync */
|
||||
|
||||
pre .error, pre .error-behind {
|
||||
pre .error,
|
||||
pre .error-behind {
|
||||
margin-left: -14px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 4px;
|
||||
@@ -346,7 +349,6 @@ pre .logger.log-log svg {
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
|
||||
blockquote {
|
||||
position: relative;
|
||||
margin-left: 1em;
|
||||
|
||||
@@ -132,7 +132,6 @@ $robotoMono: "Roboto Mono", monospace;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
%headline-uniwidth-2 {
|
||||
font-family: $asap;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface RawPostInfo {
|
||||
export interface PostInfo extends RawPostInfo {
|
||||
slug: string;
|
||||
locale: Languages;
|
||||
Content: MarkdownInstance<never>['Content'];
|
||||
Content: MarkdownInstance<never>["Content"];
|
||||
authorsMeta: UnicornInfo[];
|
||||
licenseMeta: LicenseInfo;
|
||||
excerpt: string;
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface RawUnicornInfo {
|
||||
pronouns: string;
|
||||
profileImg: string;
|
||||
color: string;
|
||||
roles: Array<RolesEnum['id']>;
|
||||
roles: Array<RolesEnum["id"]>;
|
||||
}
|
||||
|
||||
export interface UnicornInfo extends RawUnicornInfo {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { PostInfo } from "types/PostInfo";
|
||||
import { Languages } from "types/index";
|
||||
import { MarkdownInstance } from "astro";
|
||||
|
||||
let allPostsCache = new WeakMap<object, MarkdownInstance<PostInfo>[]>();
|
||||
const allPostsCache = new WeakMap<object, MarkdownInstance<PostInfo>[]>();
|
||||
|
||||
export function getAllPosts(
|
||||
posts: MarkdownInstance<PostInfo>[],
|
||||
@@ -16,15 +16,14 @@ export function getAllPosts(
|
||||
|
||||
if (cacheString) allPostsCache.set(cacheString, posts);
|
||||
|
||||
return posts
|
||||
.filter(post => post.frontmatter.locale === language);
|
||||
return posts.filter((post) => post.frontmatter.locale === language);
|
||||
}
|
||||
|
||||
const listViewCache = {};
|
||||
|
||||
export const getAllPostsForListView = (
|
||||
posts: MarkdownInstance<PostInfo>[],
|
||||
language: Languages,
|
||||
language: Languages
|
||||
): PostInfo[] => {
|
||||
let allPosts = getAllPosts(posts, language, listViewCache);
|
||||
|
||||
@@ -35,16 +34,17 @@ export const getAllPostsForListView = (
|
||||
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 = (
|
||||
authorId: string,
|
||||
posts: MarkdownInstance<PostInfo>[],
|
||||
language: Languages,
|
||||
language: Languages
|
||||
): PostInfo[] => {
|
||||
return getAllPostsForListView(posts, language)
|
||||
.filter(post =>
|
||||
post.authorsMeta.find(postAuthor => postAuthor.id === authorId)
|
||||
return getAllPostsForListView(posts, language).filter((post) =>
|
||||
post.authorsMeta.find((postAuthor) => postAuthor.id === authorId)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,10 +37,8 @@ const fullUnicorns: UnicornInfo[] = unicornsRaw.map((unicorn) => {
|
||||
/**
|
||||
* `getFullRelativePath` strips all prefixing `/`, so we must add one manually
|
||||
*/
|
||||
const relativeServerPath = '/' + getFullRelativePath(
|
||||
"content/data/",
|
||||
unicorn.profileImg
|
||||
);
|
||||
const relativeServerPath =
|
||||
"/" + getFullRelativePath("content/data/", unicorn.profileImg);
|
||||
const profileImgSize = getImageSize(unicorn.profileImg, dataDirectory);
|
||||
|
||||
// Mutation go BRR
|
||||
|
||||
@@ -28,7 +28,7 @@ export function getPostSlugs(lang: Languages) {
|
||||
|
||||
export const getAllPosts = (lang: Languages): PostInfo[] => {
|
||||
const slugs = getPostSlugs(lang);
|
||||
return slugs.map(slug => {
|
||||
return slugs.map((slug) => {
|
||||
const file = {
|
||||
path: path.join(postsDirectory, slug, getIndexPath(lang)),
|
||||
data: {
|
||||
@@ -41,8 +41,8 @@ export const getAllPosts = (lang: Languages): PostInfo[] => {
|
||||
(rehypeUnicornPopulatePost as any)()(undefined, file);
|
||||
|
||||
return {
|
||||
...(file.data.astro.frontmatter as any || {}).frontmatterBackup,
|
||||
...file.data.astro.frontmatter
|
||||
...((file.data.astro.frontmatter as any) || {}).frontmatterBackup,
|
||||
...file.data.astro.frontmatter,
|
||||
};
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export type OrderSuggestPosts = ReturnType<
|
||||
*/
|
||||
const howManySimilarBetween = <T>(arr1: T[], arr2: T[]): number => {
|
||||
let match = 0;
|
||||
for (let item of arr1) {
|
||||
for (const item of arr1) {
|
||||
if (arr2.includes(item)) match++;
|
||||
}
|
||||
return match;
|
||||
@@ -89,19 +89,16 @@ const getOrderRange = (arr: OrderSuggestPosts) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const getSuggestedArticles = (
|
||||
postNode: PostInfo,
|
||||
lang: Languages
|
||||
) => {
|
||||
export const getSuggestedArticles = (postNode: PostInfo, lang: Languages) => {
|
||||
const { suggestedPosts, dateSorted } = getAllPostsByLang(lang);
|
||||
|
||||
let extraSuggestedArticles: OrderSuggestPosts = [];
|
||||
let suggestedArticles: OrderSuggestPosts = [];
|
||||
let similarTags: Array<{
|
||||
const extraSuggestedArticles: OrderSuggestPosts = [];
|
||||
const suggestedArticles: OrderSuggestPosts = [];
|
||||
const similarTags: Array<{
|
||||
post: OrderSuggestPosts[number];
|
||||
howManyTagsSimilar: number;
|
||||
}> = [];
|
||||
for (let post of suggestedPosts) {
|
||||
for (const post of suggestedPosts) {
|
||||
// Early "return" for value
|
||||
if (suggestedArticles.length >= 3) break;
|
||||
// Don't return the same article
|
||||
@@ -133,7 +130,7 @@ export const getSuggestedArticles = (
|
||||
if (suggestedArticles.length >= 3) break;
|
||||
if (extraSuggestedArticles.length === 0) break;
|
||||
const { largest, smallest } = getOrderRange(suggestedArticles) || {};
|
||||
for (let suggestedPost of extraSuggestedArticles) {
|
||||
for (const suggestedPost of extraSuggestedArticles) {
|
||||
if (
|
||||
suggestedPost.order === smallest.order! - 1 ||
|
||||
suggestedPost.order === largest.order! + 1
|
||||
|
||||
@@ -30,7 +30,7 @@ export const rehypeAstroImageMd: Plugin<
|
||||
loader: sharp_service ?? globalThis.astroImage?.loader,
|
||||
};
|
||||
|
||||
let imgNodes: any[] = [];
|
||||
const imgNodes: any[] = [];
|
||||
visit(tree, (node: any) => {
|
||||
if (node.tagName === "img") {
|
||||
imgNodes.push(node);
|
||||
@@ -39,9 +39,13 @@ export const rehypeAstroImageMd: Plugin<
|
||||
|
||||
await Promise.all(
|
||||
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?
|
||||
const dimensions = getImageSize(node.properties.src, filePathDir) || {
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { Root } from "hast";
|
||||
import { Plugin } from "unified";
|
||||
import {visit} from 'unist-util-visit'
|
||||
import { visit } from "unist-util-visit";
|
||||
|
||||
interface RehypeExcerptProps {
|
||||
maxLength: number;
|
||||
}
|
||||
|
||||
export const rehypeExcerpt: Plugin<
|
||||
[RehypeExcerptProps | never],
|
||||
Root
|
||||
> = ({maxLength}) => {
|
||||
export const rehypeExcerpt: Plugin<[RehypeExcerptProps | never], Root> = ({
|
||||
maxLength,
|
||||
}) => {
|
||||
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) => {
|
||||
(file.data.astro as any).frontmatter.excerpt = val;
|
||||
}
|
||||
};
|
||||
if (!getFileExcerpt()) {
|
||||
setFileExcerpt("");
|
||||
}
|
||||
|
||||
visit(tree, 'element', node => {
|
||||
visit(tree, "element", (node) => {
|
||||
const fileExcerpt = getFileExcerpt();
|
||||
if (fileExcerpt.length >= maxLength) return;
|
||||
// Don't get headers or anything other than text
|
||||
if (node.tagName === 'p') {
|
||||
visit(node, 'text', textNode => {
|
||||
if (node.tagName === "p") {
|
||||
visit(node, "text", (textNode) => {
|
||||
let newVal = fileExcerpt + textNode.value;
|
||||
if (newVal.length > maxLength) {
|
||||
newVal = newVal.slice(0, maxLength - 3) + "...";
|
||||
}
|
||||
setFileExcerpt(newVal);
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ export const rehypeHeaderText = () => {
|
||||
};
|
||||
|
||||
if (file.data.astro.frontmatter.headingsWithId) {
|
||||
file.data.astro.frontmatter.headingsWithId.push(headingWithID)
|
||||
file.data.astro.frontmatter.headingsWithId.push(headingWithID);
|
||||
} else {
|
||||
file.data.astro.frontmatter.headingsWithId = [headingWithID];
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ import { fromHtml } from "hast-util-from-html";
|
||||
|
||||
import path from "path";
|
||||
|
||||
interface RehypeUnicornElementMapProps {
|
||||
}
|
||||
interface RehypeUnicornElementMapProps {}
|
||||
|
||||
function escapeHTML(s) {
|
||||
if (!s) return s;
|
||||
@@ -85,7 +84,9 @@ export const rehypeUnicornElementMap: Plugin<
|
||||
const headerLinkHTML = `
|
||||
<a
|
||||
href="#${id}"
|
||||
aria-label="Permalink for "${escapeHTML(headerText)}""
|
||||
aria-label="Permalink for "${escapeHTML(
|
||||
headerText
|
||||
)}""
|
||||
class="anchor before"
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -2,8 +2,7 @@ import { Root } from "hast";
|
||||
import { Plugin } from "unified";
|
||||
import { getSuggestedArticles } from "../get-suggested-articles";
|
||||
|
||||
interface RehypeUnicornGetSuggestedPostsProps {
|
||||
}
|
||||
interface RehypeUnicornGetSuggestedPostsProps {}
|
||||
|
||||
export const rehypeUnicornGetSuggestedPosts: Plugin<
|
||||
[RehypeUnicornGetSuggestedPostsProps | never],
|
||||
@@ -16,10 +15,10 @@ export const rehypeUnicornGetSuggestedPosts: Plugin<
|
||||
|
||||
const post = {
|
||||
...(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);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,15 +5,13 @@ import { readFileSync } from "fs";
|
||||
import * as path from "path";
|
||||
import { licenses, unicorns } from "../data";
|
||||
|
||||
interface RehypeUnicornPopulatePostProps {
|
||||
}
|
||||
interface RehypeUnicornPopulatePostProps {}
|
||||
|
||||
export const rehypeUnicornPopulatePost: Plugin<
|
||||
[RehypeUnicornPopulatePostProps | never],
|
||||
Root
|
||||
> = () => {
|
||||
return (_, file) => {
|
||||
|
||||
function setData(key: string, val: any) {
|
||||
(file.data.astro as any).frontmatter[key] = val;
|
||||
}
|
||||
@@ -29,10 +27,10 @@ export const rehypeUnicornPopulatePost: Plugin<
|
||||
// Calculate post locale
|
||||
// index.md or index.es.md
|
||||
const indexName = directorySplit.at(-1);
|
||||
const indexSplit = indexName.split('.');
|
||||
const indexSplit = indexName.split(".");
|
||||
let locale = indexSplit.at(-2);
|
||||
if (locale === 'index') {
|
||||
locale = 'en';
|
||||
if (locale === "index") {
|
||||
locale = "en";
|
||||
}
|
||||
|
||||
// // TODO: Add translations
|
||||
@@ -66,16 +64,14 @@ export const rehypeUnicornPopulatePost: Plugin<
|
||||
|
||||
let license;
|
||||
if (frontmatter.license) {
|
||||
license = licenses.find(
|
||||
(l) => l.id === frontmatter.license
|
||||
);
|
||||
license = licenses.find((l) => l.id === frontmatter.license);
|
||||
}
|
||||
if (!license) license = null;
|
||||
|
||||
setData('slug', slug);
|
||||
setData('locale', locale);
|
||||
setData('authorsMeta', authorsMeta);
|
||||
setData('license', license);
|
||||
setData('frontmatterBackup', frontmatter);
|
||||
setData("slug", slug);
|
||||
setData("locale", locale);
|
||||
setData("authorsMeta", authorsMeta);
|
||||
setData("license", license);
|
||||
setData("frontmatterBackup", frontmatter);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ import { visit } from "unist-util-visit";
|
||||
|
||||
import { unified } from "unified";
|
||||
import english from "retext-english";
|
||||
import rehypeRetext from 'rehype-retext';
|
||||
import rehypeRetext from "rehype-retext";
|
||||
import { validateConfig } from "astro/dist/types/core/config";
|
||||
|
||||
interface RemarkCountProps {}
|
||||
@@ -39,9 +39,12 @@ function count(counts: Record<string, number>) {
|
||||
visit(tree, visitor);
|
||||
|
||||
function visitor(node: Node) {
|
||||
if (node.type === 'SourceNode') {
|
||||
const inlineCount = (node as never as {value: string}).value.split(/\b/g).length;
|
||||
counts["InlineCodeWords"] = (counts["InlineCodeWords"] || 0) + inlineCount;
|
||||
if (node.type === "SourceNode") {
|
||||
const inlineCount = (node as never as { value: string }).value.split(
|
||||
/\b/g
|
||||
).length;
|
||||
counts["InlineCodeWords"] =
|
||||
(counts["InlineCodeWords"] || 0) + inlineCount;
|
||||
}
|
||||
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)))
|
||||
.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);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './tabs';
|
||||
export * from "./tabs";
|
||||
|
||||
@@ -14,7 +14,7 @@ const isNodeHeading = (n: ElementNode) =>
|
||||
|
||||
const findLargestHeading = (nodes: ElementNode[]) => {
|
||||
let largestSize = Infinity;
|
||||
for (let node of nodes) {
|
||||
for (const node of nodes) {
|
||||
if (!isNodeHeading(node)) continue;
|
||||
const size = parseInt(node.tagName.substr(1), 10);
|
||||
largestSize = Math.min(largestSize, size);
|
||||
@@ -61,15 +61,15 @@ export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
|
||||
type: "element",
|
||||
tagName: "div",
|
||||
properties: {
|
||||
class: "tabs"
|
||||
class: "tabs",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: "element",
|
||||
tagName: "ul",
|
||||
properties: {
|
||||
role: 'tablist',
|
||||
class: "tabs__tab-list"
|
||||
role: "tablist",
|
||||
class: "tabs__tab-list",
|
||||
},
|
||||
children: [] as ElementNode[],
|
||||
},
|
||||
@@ -98,13 +98,13 @@ export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
|
||||
tagName: "li",
|
||||
children: localNode.children,
|
||||
properties: {
|
||||
role: 'tab',
|
||||
role: "tab",
|
||||
class: "tabs__tab",
|
||||
"data-tabname": headerSlug,
|
||||
"aria-selected": idx === 0 ? "true" : 'false',
|
||||
"aria-selected": idx === 0 ? "true" : "false",
|
||||
"aria-controls": `panel-${idx}`,
|
||||
"id": `tab-${idx}`,
|
||||
tabIndex: idx === 0 ? "0" : "-1"
|
||||
id: `tab-${idx}`,
|
||||
tabIndex: idx === 0 ? "0" : "-1",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
|
||||
class: "tabs__tab-panel",
|
||||
tabindex: 0,
|
||||
"aria-labelledby": `tab-${idx}`,
|
||||
...(idx === 0 ? {} : {hidden: "true"})
|
||||
...(idx === 0 ? {} : { hidden: "true" }),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -14,20 +14,22 @@ export const isRelativePath = (str: string) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
var pathJoin = function(...pathArr){
|
||||
return pathArr.map(function(path){
|
||||
const pathJoin = function (...pathArr) {
|
||||
return pathArr
|
||||
.map(function (path) {
|
||||
if (path[0] === "/") {
|
||||
path = path.slice(1);
|
||||
}
|
||||
if (path.startsWith('./')) {
|
||||
if (path.startsWith("./")) {
|
||||
path = path.slice(2);
|
||||
}
|
||||
if (path[path.length - 1] === "/") {
|
||||
path = path.slice(0, path.length - 1);
|
||||
}
|
||||
return path;
|
||||
}).join("/");
|
||||
}
|
||||
})
|
||||
.join("/");
|
||||
};
|
||||
|
||||
export const getFullRelativePath = (...paths: string[]) => {
|
||||
return isRelativePath(paths[paths.length - 1])
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"components/*": ["./src/components/*"],
|
||||
"utils/*": ["./src/utils/*"],
|
||||
"uu-utils": ["./src/utils"],
|
||||
"assets/*": ["./src/assets/*"],
|
||||
"assets/*": ["./src/assets/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user