Simplify frontend assests and build steps

We just write the files directly in the public dir
Then change the npm scripts to lint and compress them directly
This commit is contained in:
Jonny Barnes 2023-12-21 17:57:48 +00:00
parent 1671323012
commit ec17f65107
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
38 changed files with 333 additions and 11358 deletions

View file

@ -1,7 +0,0 @@
@import url('variables.css');
@import url('fonts.css');
@import url('layout.css');
@import url('colours.css');
@import url('code.css');
@import url('content.css');
@import url('notes.css');

View file

@ -1,3 +0,0 @@
.hljs {
border-radius: .5rem;
}

View file

@ -1,28 +0,0 @@
body {
background-color: var(--color-secondary);
color: var(--color-primary);
}
a {
color: var(--color-link);
&:visited {
color: var(--color-link-visited);
}
&.auth:visited {
color: var(--color-link);
}
}
#site-header {
& a:visited {
color: var(--color-link);
}
& .rss-icon {
& svg {
color: var(--rss-color-link);
}
}
}

View file

@ -1,64 +0,0 @@
@import url('h-card.css');
.h-entry {
border-inline-start: 1px solid var(--color-primary);
padding-inline-start: .5rem;
& .reply-to {
font-style: italic;
}
& .post-info {
& a {
text-decoration: none;
}
}
& .note-metadata {
display: flex;
flex-direction: row;
gap: 1rem;
& .replies,
& .likes,
& .reposts {
display: inline-flex;
flex-direction: row;
gap: .5rem;
align-items: center;
}
& .syndication-links {
flex-flow: row wrap;
& a {
text-decoration: none;
& svg {
width: 1rem;
height: 1rem;
}
}
}
}
}
.feather {
width: 24px;
height: 24px;
stroke: currentcolor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
.sr-only {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}

View file

@ -1,17 +0,0 @@
body {
font-family: var(--font-family-body);
font-size: var(--font-size-md);
}
code {
font-family: var(--font-family-monospace);
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-family-headings);
}

View file

@ -1,32 +0,0 @@
.h-card {
& .hovercard {
display: none;
position: absolute;
z-index: 100;
padding: 1rem;
border-radius: 1rem;
box-shadow: 0 .5rem .5rem .5rem var(--color-primary-shadow);
background-color: var(--color-secondary);
width: fit-content;
transition: opacity 0.5s ease-in-out;
opacity: 0;
flex-direction: column;
gap: .5rem;
& .u-photo {
max-width: 6rem;
}
& .social-icon {
width: 1rem;
height: 1rem;
}
}
&:hover {
& .hovercard {
display: flex;
opacity: 1;
}
}
}

View file

@ -1,55 +0,0 @@
.grid {
display: grid;
grid-template-columns: 5vw 1fr 5vw;
grid-template-rows: min-content 1fr min-content;
row-gap: 1rem;
}
#site-header {
grid-column: 2 / 3;
grid-row: 1 / 2;
& .rss-icon {
& svg {
width: auto;
height: 1rem;
}
}
}
main {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
.h-feed {
display: flex;
flex-direction: column;
gap: 2rem;
}
.h-entry {
& p:first-of-type,
& h1:first-of-type {
margin-block-start: 0;
}
}
.pagination {
margin-block-start: 1rem;
}
footer {
grid-column: 2 / 3;
grid-row: 3 / 4;
& .iwc-logo {
max-width: 85vw;
}
& .footer-actions {
display: flex;
flex-direction: row;
gap: 1rem;
}
}

View file

@ -1,38 +0,0 @@
main {
& > .u-comment {
margin-block-start: 2rem;
margin-inline-start: 2rem;
border-inline-start: 1px solid var(--color-primary);
padding-inline-start: .5rem;
& .mini-h-card {
display: inline-flex;
flex-direction: row;
align-items: baseline;
& .u-photo {
width: 2rem;
height: 2rem;
border-radius: 50%;
margin-block-end: 0.5rem;
}
}
}
& .notes-subtitle {
font-size: 1.2rem;
font-weight: 600;
}
& .webmentions-author-list {
display: flex;
flex-flow: row wrap;
gap: 1rem;
& img {
width: 4rem;
height: 4rem;
border-radius: 50%;
}
}
}

View file

@ -1,23 +0,0 @@
:root {
/* Font Family */
--font-family-headings: "Archer SSm A", "Archer SSm B", serif;
--font-family-body: "Verlag A", "Verlag B", sans-serif;
--font-family-monospace: "Operator Mono SSm A", "Operator Mono SSm B", monospace;
/* Font Size */
--font-size-sm: 0.75rem; /* 12px */
--font-size-base: 1rem; /* 16px, base */
--font-size-md: 1.25rem; /* 20px */
--font-size-lg: 1.5rem; /* 24px */
--font-size-xl: 1.75rem; /* 28px */
--font-size-xxl: 2rem; /* 32px */
--font-size-xxxl: 2.25rem; /* 36px */
/* Colours */
--color-primary: oklch(36.8% 0.1 125.505deg);
--color-secondary: oklch(96.3% 0.1 125.505deg);
--color-link: oklch(48.09% 0.146 241.41deg);
--color-link-visited: oklch(70.44% 0.21 304.41deg);
--color-primary-shadow: oklch(19.56% 0.054 125.505deg / 40%);
--rss-color-link: oklch(67.59% 0.189 42.04deg);
}

View file

@ -1,17 +0,0 @@
import '../css/app.css';
import { Auth } from './auth.js';
let auth = new Auth();
document.querySelectorAll('.add-passkey').forEach((el) => {
el.addEventListener('click', () => {
auth.register();
});
});
document.querySelectorAll('.login-passkey').forEach((el) => {
el.addEventListener('click', () => {
auth.login();
});
});

View file

@ -1,167 +0,0 @@
class Auth {
constructor() {}
async register() {
const createOptions = await this.getCreateOptions();
const publicKeyCredentialCreationOptions = {
challenge: this.base64URLStringToBuffer(createOptions.challenge),
rp: {
id: createOptions.rp.id,
name: createOptions.rp.name,
},
user: {
id: new TextEncoder().encode(window.atob(createOptions.user.id)),
name: createOptions.user.name,
displayName: createOptions.user.displayName,
},
pubKeyCredParams: createOptions.pubKeyCredParams,
excludeCredentials: [],
authenticatorSelection: createOptions.authenticatorSelection,
timeout: 60000,
};
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
});
if (!credential) {
throw new Error('Error generating a passkey');
}
const authenticatorAttestationResponse = {
id: credential.id ? credential.id : null,
type: credential.type ? credential.type : null,
rawId: credential.rawId ? this.bufferToBase64URLString(credential.rawId) : null,
response: {
attestationObject: credential.response.attestationObject ? this.bufferToBase64URLString(credential.response.attestationObject) : null,
clientDataJSON: credential.response.clientDataJSON ? this.bufferToBase64URLString(credential.response.clientDataJSON) : null,
}
};
const registerCredential = await window.fetch('/admin/passkeys/register', {
method: 'POST',
body: JSON.stringify(authenticatorAttestationResponse),
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
},
});
if (!registerCredential.ok) {
throw new Error('Error saving the passkey');
}
window.location.reload();
}
async getCreateOptions() {
const response = await fetch('/admin/passkeys/register', {
method: 'GET',
});
return await response.json();
}
async login() {
const loginData = await this.getLoginData();
const publicKeyCredential = await navigator.credentials.get({
publicKey: {
challenge: this.base64URLStringToBuffer(loginData.challenge),
userVerification: loginData.userVerification,
timeout: 60000,
}
});
if (!publicKeyCredential) {
throw new Error('Authentication failed');
}
const authenticatorAttestationResponse = {
id: publicKeyCredential.id ? publicKeyCredential.id : '',
type: publicKeyCredential.type ? publicKeyCredential.type : '',
rawId: publicKeyCredential.rawId ? this.bufferToBase64URLString(publicKeyCredential.rawId) : '',
response: {
authenticatorData: publicKeyCredential.response.authenticatorData ? this.bufferToBase64URLString(publicKeyCredential.response.authenticatorData) : '',
clientDataJSON: publicKeyCredential.response.clientDataJSON ? this.bufferToBase64URLString(publicKeyCredential.response.clientDataJSON) : '',
signature: publicKeyCredential.response.signature ? this.bufferToBase64URLString(publicKeyCredential.response.signature) : '',
userHandle: publicKeyCredential.response.userHandle ? this.bufferToBase64URLString(publicKeyCredential.response.userHandle) : '',
},
};
const loginAttempt = await window.fetch('/login/passkey', {
method: 'POST',
body: JSON.stringify(authenticatorAttestationResponse),
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
},
});
if (!loginAttempt.ok) {
throw new Error('Login failed');
}
window.location.assign('/admin');
}
async getLoginData() {
const response = await fetch('/login/passkey', {
method: 'GET',
});
return await response.json();
}
/**
* Convert a base64 URL string to a buffer.
*
* Sourced from https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/browser/src/helpers/base64URLStringToBuffer.ts#L8
*
* @param {string} base64URLString
* @returns {ArrayBuffer}
*/
base64URLStringToBuffer(base64URLString) {
// Convert from Base64URL to Base64
const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
/**
* Pad with '=' until it's a multiple of four
* (4 - (85 % 4 = 1) = 3) % 4 = 3 padding
* (4 - (86 % 4 = 2) = 2) % 4 = 2 padding
* (4 - (87 % 4 = 3) = 1) % 4 = 1 padding
* (4 - (88 % 4 = 0) = 4) % 4 = 0 padding
*/
const padLength = (4 - (base64.length % 4)) % 4;
const padded = base64.padEnd(base64.length + padLength, '=');
// Convert to a binary string
const binary = window.atob(padded);
// Convert binary string to buffer
const buffer = new ArrayBuffer(binary.length);
const bytes = new Uint8Array(buffer);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return buffer;
}
/**
* Convert a buffer to a base64 URL string.
*
* Sourced from https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/browser/src/helpers/bufferToBase64URLString.ts#L7
*
* @param {ArrayBuffer} buffer
* @returns {string}
*/
bufferToBase64URLString(buffer) {
const bytes = new Uint8Array(buffer);
let str = '';
for (const charCode of bytes) {
str += String.fromCharCode(charCode);
}
const base64String = btoa(str);
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
}
export { Auth };

View file

@ -4,14 +4,11 @@
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title'){{ config('app.name') }}</title>
<meta name="viewport" content="width=device-width">
@if (!empty(config('app.font_link')))
<link rel="stylesheet" href="{{ config('app.font_link') }}">
@endif
<link rel="stylesheet" href="/assets/highlight/zenburn.css">
@production
<link rel="stylesheet" href="/assets/app.css">
@endproduction
<link rel="stylesheet" href="/assets/css/app.css">
<link rel="alternate" type="application/rss+xml" title="Blog RSS Feed" href="/blog/feed.rss">
<link rel="alternate" type="application/atom+xml" title="Blog Atom Feed" href="/blog/feed.atom">
<link rel="alternate" type="application/json" title="Blog JSON Feed" href="/blog/feed.json">
@ -81,7 +78,7 @@
@section('scripts')
<script type="module" src="/assets/frontend/is-land.js"></script>
<script type="module" src="/assets/frontend/snow-fall.js"></script>
<script src="/assets/app.js"></script>
<script type="module" src="/assets/js/app.js"></script>
@show
</body>
</html>