feat: add basic working spa mode
This commit is contained in:
parent
4ab42f9543
commit
8033b18a64
5 changed files with 93 additions and 53 deletions
|
@ -234,6 +234,9 @@ const domain = Astro.url.host
|
|||
|
||||
// Initialize with default config
|
||||
initSearch();
|
||||
|
||||
// Re-initialize when Astro's view transitions occur, this provides fix for SPA navigation
|
||||
document.addEventListener('astro:page-load', initSearch);
|
||||
</script>}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -8,13 +8,26 @@ const ArtalkInstanceDomain = siteConfig.comments.artalk.instanceDomain
|
|||
<div id="comments" data-path={Astro.url.pathname} data-server={ArtalkInstanceDomain}></div>
|
||||
<script>
|
||||
import Artalk from "artalk";
|
||||
const atkElement = document.querySelector('#comments');
|
||||
Artalk.init({
|
||||
el: '#comments',
|
||||
pageKey: atkElement?.getAttribute('data-path') || window.location.pathname,
|
||||
server: `https://${atkElement?.getAttribute('data-server')}`,
|
||||
darkMode: "auto",
|
||||
versionCheck: false
|
||||
|
||||
function initArtalk() {
|
||||
const atkElement = document.querySelector('#comments');
|
||||
|
||||
if (!atkElement) return;
|
||||
Artalk.init({
|
||||
el: '#comments',
|
||||
pageKey: atkElement?.getAttribute('data-path') || window.location.pathname,
|
||||
server: `https://${atkElement?.getAttribute('data-server')}`,
|
||||
darkMode: "auto",
|
||||
versionCheck: false
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', initArtalk);
|
||||
|
||||
// Re-initialize on view transitions, fix issues with Astro's ClientRouter
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
initArtalk();
|
||||
});
|
||||
</script>
|
||||
</div>
|
|
@ -54,25 +54,34 @@ const { encryptedData, iv } = encrypt(content, password);
|
|||
</style>
|
||||
|
||||
<script>
|
||||
// Client-side decryption logic
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Define the function that sets up the decryption logic
|
||||
function setupDecryption() {
|
||||
const containers = document.querySelectorAll('.encrypted-content');
|
||||
|
||||
containers.forEach(container => {
|
||||
const encryptedData = container.getAttribute('data-encrypted');
|
||||
const iv = container.getAttribute('data-iv');
|
||||
const passwordInput = container.querySelector('.decrypt-password');
|
||||
const decryptButton = container.querySelector('.decrypt-button');
|
||||
const contentContainer = container.querySelector('.content-container');
|
||||
const passwordForm = container.querySelector('.password-form');
|
||||
const passwordInput = container.querySelector<HTMLInputElement>('.decrypt-password');
|
||||
const decryptButton = container.querySelector<HTMLButtonElement>('.decrypt-button');
|
||||
const contentContainer = container.querySelector<HTMLDivElement>('.content-container');
|
||||
const passwordForm = container.querySelector<HTMLDivElement>('.password-form');
|
||||
|
||||
if (!encryptedData || !iv || !passwordInput || !decryptButton || !contentContainer || !passwordForm) {
|
||||
console.error('Missing required elements for decryption');
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if already initialized to prevent duplicate event listeners
|
||||
if (container.getAttribute("initialized") === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
decryptButton.addEventListener('click', async () => {
|
||||
try {
|
||||
const password = passwordInput.value;
|
||||
if (!password) return;
|
||||
|
||||
const content = await decrypt(encryptedData, iv, password);
|
||||
contentContainer.innerHTML = content;
|
||||
contentContainer.innerHTML = await decrypt(encryptedData, iv, password);
|
||||
contentContainer.classList.remove('hidden');
|
||||
passwordForm.classList.add('hidden');
|
||||
} catch (error) {
|
||||
|
@ -82,52 +91,61 @@ const { encryptedData, iv } = encrypt(content, password);
|
|||
});
|
||||
|
||||
// Allow pressing Enter to decrypt
|
||||
passwordInput.addEventListener('keydown', (e) => {
|
||||
passwordInput.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
decryptButton.click();
|
||||
}
|
||||
});
|
||||
|
||||
// Mark as initialized to prevent duplicate event listeners
|
||||
container.setAttribute("initialized", 'true');
|
||||
});
|
||||
}
|
||||
|
||||
async function decrypt(encryptedData, iv, password) {
|
||||
// Convert base64 to array buffer
|
||||
const encryptedBytes = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
|
||||
const ivBytes = Uint8Array.from(atob(iv), c => c.charCodeAt(0));
|
||||
async function decrypt(encryptedData: string, iv: string, password: string): Promise<string> {
|
||||
// Convert base64 to array buffer
|
||||
const encryptedBytes = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
|
||||
const ivBytes = Uint8Array.from(atob(iv), c => c.charCodeAt(0));
|
||||
|
||||
// Derive key from password
|
||||
const encoder = new TextEncoder();
|
||||
const passwordBytes = encoder.encode(password);
|
||||
const keyMaterial = await window.crypto.subtle.importKey(
|
||||
'raw',
|
||||
passwordBytes,
|
||||
{ name: 'PBKDF2' },
|
||||
false,
|
||||
['deriveBits', 'deriveKey']
|
||||
);
|
||||
// Derive key from password
|
||||
const encoder = new TextEncoder();
|
||||
const passwordBytes = encoder.encode(password);
|
||||
const keyMaterial = await window.crypto.subtle.importKey(
|
||||
'raw',
|
||||
passwordBytes,
|
||||
{ name: 'PBKDF2' },
|
||||
false,
|
||||
['deriveBits', 'deriveKey']
|
||||
);
|
||||
|
||||
const key = await window.crypto.subtle.deriveKey(
|
||||
{
|
||||
name: 'PBKDF2',
|
||||
salt: ivBytes,
|
||||
iterations: 100000,
|
||||
hash: 'SHA-256'
|
||||
},
|
||||
keyMaterial,
|
||||
{ name: 'AES-GCM', length: 256 },
|
||||
false,
|
||||
['decrypt']
|
||||
);
|
||||
const key = await window.crypto.subtle.deriveKey(
|
||||
{
|
||||
name: 'PBKDF2',
|
||||
salt: ivBytes,
|
||||
iterations: 100000,
|
||||
hash: 'SHA-256'
|
||||
},
|
||||
keyMaterial,
|
||||
{ name: 'AES-GCM', length: 256 },
|
||||
false,
|
||||
['decrypt']
|
||||
);
|
||||
|
||||
// Decrypt
|
||||
const decryptedBytes = await window.crypto.subtle.decrypt(
|
||||
{ name: 'AES-GCM', iv: ivBytes },
|
||||
key,
|
||||
encryptedBytes
|
||||
);
|
||||
// Decrypt
|
||||
const decryptedBytes = await window.crypto.subtle.decrypt(
|
||||
{ name: 'AES-GCM', iv: ivBytes },
|
||||
key,
|
||||
encryptedBytes
|
||||
);
|
||||
|
||||
// Convert array buffer to text
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(decryptedBytes);
|
||||
}
|
||||
});
|
||||
// Convert array buffer to text
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(decryptedBytes);
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', setupDecryption);
|
||||
|
||||
// Re-initialize when Astro's view transitions occur, this provides fix for SPA navigation
|
||||
document.addEventListener('astro:page-load', setupDecryption);
|
||||
</script>
|
|
@ -9,6 +9,7 @@ export const siteConfig = {
|
|||
email: 'hi@mercury.info',
|
||||
},
|
||||
// features
|
||||
spa: false, // enable single page application mode, this will enable navigation (with fade transitions) without reloading the page, and enable client-side routing
|
||||
noClientJavaScript: false, // disable client-side javascript, this will:
|
||||
// 1. disable most built-in client-side javascript from rendering (protected content component and umami still needs javascript to function, sorry)
|
||||
// 2. the full text search will be redirected to a search engine
|
||||
|
|
|
@ -19,6 +19,9 @@ interface Props {
|
|||
ogImage?: string;
|
||||
}
|
||||
|
||||
import { ClientRouter } from "astro:transitions";
|
||||
const spaEnabled = siteConfig.spa
|
||||
|
||||
const noscript = siteConfig.noClientJavaScript
|
||||
|
||||
const statisticsEnabled = siteConfig.siteAnalytics.enabled
|
||||
|
@ -44,6 +47,8 @@ const { title = pageTitle, author = siteConfig.defaultAuthor.name,description =
|
|||
<meta name="generator" content={Astro.generator} />
|
||||
<Meta title={pageTitle} author={author} description={description} ogImage={ogImage} />
|
||||
<title>{pageTitle}</title>
|
||||
{spaEnabled && <ClientRouter fallback="animate" />}
|
||||
<!--transitional animation is broken in firefox though-->
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue