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:
parent
1671323012
commit
ec17f65107
38 changed files with 333 additions and 11358 deletions
|
@ -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');
|
|
@ -1,3 +0,0 @@
|
|||
.hljs {
|
||||
border-radius: .5rem;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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 };
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue