feat: initial project version (#1)

### Explanation

_No response_

### Issue

_No response_

### Attestations

- [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [ ] I have pinned the dependencies to a specific patch version.

### Style

- [ ] I have run the linter and resolved any errors.
- [ ] My pull request uses an appropriate title, matching the conventional commit standards.
- [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Reviewed-on: https://codeberg.org/nhcarrigan/website-headers/pulls/1
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit is contained in:
Naomi Carrigan 2024-12-20 21:55:13 +00:00 committed by Naomi the Technomancer
parent e0211be69e
commit 55f78cd29d
10 changed files with 4637 additions and 11 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
prod

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["typescript"]
}

View File

@ -1,16 +1,10 @@
# New Repository Template # Website Headers
This template contains all of our basic files for a new GitHub repository. There is also a handy workflow that will create an issue on a new repository made from this template, with a checklist for the steps we usually take in setting up a new repository. This project generates a JavaScript file to be uploaded to our CDN. This file automatically generates metadata, favicons, styles, and scripts for all of our production pages.
If you're starting a Node.JS project with TypeScript, we have a [specific template](https://github.com/naomi-lgbt/nodejs-typescript-template) for that purpose. To work on the file locally, you can serve the `index.html` file using your preferred application. Make changes to the `src/index.ts` file and run `pnpm build`, and the HTML will source the resulting JavaScript automatically.
## Readme When building the file, it is important to use `pnpm build` so that the minification step is run. Running `tsc` directly will bypass this step - while this is perfectly acceptable for debugging locally, the file MUST be minified before uploading to our CDN.
Delete all of the above text (including this line), and uncomment the below text to use our standard readme template.
<!-- # Project Name
Project Description
## Live Version ## Live Version
@ -36,4 +30,4 @@ Copyright held by Naomi Carrigan.
## Contact ## Contact
We may be contacted through our [Chat Server](http://chat.nhcarrigan.com) or via email at `contact@nhcarrigan.com`. --> We may be contacted through our [Chat Server](http://chat.nhcarrigan.com) or via email at `contact@nhcarrigan.com`.

16
build.ts Normal file
View File

@ -0,0 +1,16 @@
import { readFile, writeFile } from "fs/promises";
import { join } from "path";
import { minify } from "terser";
const code = (
await readFile(join(process.cwd(), "prod", "index.js"), "utf-8")
).replace("{{ version }}", process.env.npm_package_version ?? "0.0.0");
const result = await minify(code, {
format: {
comments: /@license|@preserve|@copyright/,
},
});
if (!result.code) {
throw new Error("Failed to minify code.");
}
await writeFile(join(process.cwd(), "prod", "index.js"), result.code);

13
eslint.config.js Normal file
View File

@ -0,0 +1,13 @@
import NaomisConfig from "@nhcarrigan/eslint-config";
import globals from "globals";
export default [
...NaomisConfig,
{
languageOptions: {
globals: {
...globals.browser
}
}
},
];

45
index.html Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Naomi's Website Headers</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Just a script that loads our custom CSS and JS"
/>
<script src="./prod/index.js" defer></script>
</head>
<body>
<main>
<h1>Naomi's Website Headers</h1>
<section>
<p>
This repository generates a script that we serve via our CDN for all
of our websites to load the same styles and third-party scripts.
</p>
<p>
This file allows you to test changes to the script before deploying.
</p>
</section>
<section>
<h2>Links</h2>
<p>
<a href="https://codeberg.org/nhcarrigan/website-headers">
<i class="fa-solid fa-code"></i> Source Code
</a>
</p>
<p>
<a href="https://docs.nhcarrigan.com">
<i class="fa-solid fa-book"></i> Documentation
</a>
</p>
<p>
<a href="https://chat.nhcarrigan.com">
<i class="fa-solid fa-circle-info"></i> Support
</a>
</p>
</section>
</main>
</body>
</html>

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "website-headers",
"version": "0.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"build": "tsc && tsx build.ts",
"lint": "eslint src --max-warnings 0",
"start": "echo \"This is not an executable script. Please review the README for more instructions.\" && exit 1",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@nhcarrigan/eslint-config": "5.0.0",
"@nhcarrigan/typescript-config": "4.0.0",
"@types/node": "22.10.2",
"eslint": "9.17.0",
"globals": "15.14.0",
"terser": "5.37.0",
"tsx": "4.19.2",
"typescript": "5.7.2"
}
}

4250
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

265
src/index.ts Normal file
View File

@ -0,0 +1,265 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
// #region Version
const version = "{{ version }}";
console.log(`
========================================
Loading NHCarrigan library v${version}.
Copyright (c) ${new Date().getFullYear()} NHCarrigan
Changelog: https://codeberg.org/nhcarrigan/website-headers/releases
Licensed under our public license: https://docs.nhcarrigan.com/legal/license
Questions? Contact us at https://docs.nhcarrigan.com/about/contact
========================================
`)
// #endregion
// #region Query Selectors
const head = document.querySelector("head");
const body = document.querySelector("body");
const title = document.querySelector("title");
const description = document.querySelector(`meta[name="description"]`);
const { href: url, hostname } = window.location;
// #endregion
// #region Metadata and Open Graph
/**
* The title and description are set by each website. This should
* only load things like open graph data and favicons.
*/
const openGraphTitle = document.createElement("meta");
openGraphTitle.setAttribute("property", "og:title");
openGraphTitle.setAttribute("content", title?.innerText ?? "NHCarrigan");
const openGraphDescription = document.createElement("meta");
openGraphDescription.setAttribute("property", "og:description");
openGraphDescription.setAttribute(
"content",
description?.getAttribute("content")
// eslint-disable-next-line stylistic/max-len
?? "We are a software engineering and community management consulting firm.",
);
const openGraphImage = document.createElement("meta");
openGraphImage.setAttribute("property", "og:image");
openGraphImage.setAttribute(
"content",
"https://cdn.nhcarrigan.com/og-image.png",
);
const openGraphUrl = document.createElement("meta");
openGraphUrl.setAttribute("property", "og:url");
openGraphUrl.setAttribute("content", url);
const openGraphType = document.createElement("meta");
openGraphType.setAttribute("property", "og:type");
openGraphType.setAttribute("content", "website");
const twitterCard = document.createElement("meta");
twitterCard.setAttribute("name", "twitter:card");
twitterCard.setAttribute("content", "summary_large_image");
const twitterDomain = document.createElement("meta");
twitterDomain.setAttribute("name", "twitter:domain");
twitterDomain.setAttribute("content", hostname);
const twitterUrl = document.createElement("meta");
twitterUrl.setAttribute("name", "twitter:url");
twitterUrl.setAttribute("content", url);
const twitterTitle = document.createElement("meta");
twitterTitle.setAttribute("name", "twitter:title");
twitterTitle.setAttribute("content", title?.innerText ?? "NHCarrigan");
const twitterDescription = document.createElement("meta");
twitterDescription.setAttribute("name", "twitter:description");
twitterDescription.setAttribute(
"content",
description?.getAttribute("content")
// eslint-disable-next-line stylistic/max-len
?? "We are a software engineering and community management consulting firm.",
);
const twitterImage = document.createElement("meta");
twitterImage.setAttribute("name", "twitter:image");
twitterImage.setAttribute("content", "https://cdn.nhcarrigan.com/og-image.png");
// #endregion
// #region Favicon
const favicon = document.createElement("link");
favicon.rel = "icon";
favicon.type = "image/x-icon";
favicon.href = "https://cdn.nhcarrigan.com/favicon/favicon.ico";
const appleTouchIcon = document.createElement("link");
appleTouchIcon.rel = "apple-touch-icon";
appleTouchIcon.href = "https://cdn.nhcarrigan.com/favicon/apple-touch-icon.png";
const smallIcon = document.createElement("link");
smallIcon.rel = "icon";
smallIcon.href = "https://cdn.nhcarrigan.com/favicon/favicon-16x16.png";
const largeIcon = document.createElement("link");
largeIcon.rel = "icon";
largeIcon.href = "https://cdn.nhcarrigan.com/favicon/favicon-32x32.png";
// #endregion
// #region Styles
const styles = document.createElement("style");
styles.innerHTML = `
@font-face {
font-family: 'OpenDyslexic';
src: url('https://cdn.nhcarrigan.com/fonts/OpenDyslexicMono-Regular.otf') format('opentype');
}
:root {
--foreground: #04624f;
--background: #abfcecdd;
}
html {
font-family: 'OpenDyslexic', monospace;
cursor: url('https://cdn.nhcarrigan.com/cursors/cursor.cur'), auto;
min-height: 100vh;
min-width: 100vw;
}
body::before {
background: url(https://cdn.nhcarrigan.com/background.png);
background-size: cover;
background-position: center;
width: 100%;
height: 100%;
z-index: -1;
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 1;
pointer-events: none;
}
main {
color: var(--foreground);
background-color: var(--background);
text-align: center;
border-radius: 10px;
width: 95%;
max-width: 1080px;
margin: auto;
}
footer {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
color: var(--foreground);
background-color: var(--background);
position: fixed;
bottom: 0;
}
a {
color: unset;
cursor: url('https://cdn.nhcarrigan.com/cursors/pointer.cur'), pointer;
}
span[style*="background-image"][style*="crisp.chat/avatar/website"] {
bottom: 100px !important;
}
`;
// #endregion
// #region Components
const footer = document.createElement("footer");
footer.innerHTML = `
<p>&copy; Naomi Carrigan</p>
<a href="https://chat.nhcarrigan.com" target="_blank" rel="noreferrer">
<i class="fa-solid fa-comments"></i>
</a>
<div className="h-4/5" id="tree-nation-offset-website"></div>
`;
const videoOverlay = document.createElement("video");
videoOverlay.autoplay = true;
videoOverlay.loop = true;
videoOverlay.muted = true;
videoOverlay.playsInline = true;
videoOverlay.src = "https://cdn.nhcarrigan.com/overlay.webm";
videoOverlay.style.pointerEvents = "none";
videoOverlay.style.position = "fixed";
videoOverlay.style.top = "0";
videoOverlay.style.left = "0";
videoOverlay.style.opacity = "0.25";
// #endregion
// #region Scripts
const treeNation = document.createElement("script");
treeNation.src
= "https://widgets.tree-nation.com/js/widgets/v1/widgets.min.js?v=1.0";
const treeNationBottom = document.createElement("script");
treeNationBottom.innerHTML = `
TreeNationOffsetWebsite({
code: "a17464e0cd351220",
lang: "en",
theme: "dark",
}).render("#tree-nation-offset-website");
`;
const fontAwesome = document.createElement("script");
fontAwesome.src = "https://kit.fontawesome.com/f949111719.js";
const hubspot = document.createElement("script");
hubspot.src = "https://js.hs-scripts.com/47086586.js";
hubspot.async = true;
hubspot.defer = true;
hubspot.id = "hs-script-loader";
const crisp = document.createElement("script");
crisp.innerHTML = `
window.$crisp=[];window.CRISP_WEBSITE_ID="5398ce41-4ceb-4e31-9049-4c784a70179a";(function(){d=document;s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();
`;
const analytics = document.createElement("script");
analytics.defer = true;
analytics.setAttribute("domain", "nhcarrigan.com");
// eslint-disable-next-line stylistic/max-len
analytics.src = "https://analytics.nhcarrigan.com/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js";
const analytics2 = document.createElement("script");
analytics2.innerHTML = `
window.plausible = window.plausible ??
function() {
(window.plausible.q = window.plausible.q ?? []).push(arguments)
}
`;
// #endregion
// #region Inject Elements
head?.appendChild(openGraphTitle);
head?.appendChild(openGraphDescription);
head?.appendChild(openGraphImage);
head?.appendChild(openGraphUrl);
head?.appendChild(openGraphType);
head?.appendChild(twitterCard);
head?.appendChild(twitterDomain);
head?.appendChild(twitterUrl);
head?.appendChild(twitterTitle);
head?.appendChild(twitterDescription);
head?.appendChild(twitterImage);
head?.appendChild(favicon);
head?.appendChild(appleTouchIcon);
head?.appendChild(smallIcon);
head?.appendChild(largeIcon);
head?.appendChild(styles);
head?.appendChild(treeNation);
head?.appendChild(fontAwesome);
head?.appendChild(hubspot);
head?.appendChild(crisp);
head?.appendChild(analytics);
head?.appendChild(analytics2);
body?.appendChild(footer);
body?.appendChild(videoOverlay);
body?.appendChild(treeNationBottom);
// #endregion

9
tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "@nhcarrigan/typescript-config",
"compilerOptions": {
"outDir": "./prod",
"rootDir": "./src",
"lib": ["DOM", "ESNext"],
},
"exclude": ["build.ts"]
}