Added guestbook
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
2cbc24f293
commit
5997105fed
|
@ -8,6 +8,7 @@
|
|||
"name": "svelte-testing",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"js-sha256": "^0.9.0",
|
||||
"svelte-gestures": "^1.4.1",
|
||||
"svelte-language-server": "^0.14.29"
|
||||
},
|
||||
|
@ -724,6 +725,11 @@
|
|||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/Base64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/Base64/-/Base64-1.1.0.tgz",
|
||||
"integrity": "sha512-qeacf8dvGpf+XAT27ESHMh7z84uRzj/ua2pQdJg483m3bEXv/kVFtDnMgvf70BQGqzbZhR9t6BmASzKvqfJf3Q=="
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -2111,6 +2117,11 @@
|
|||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-sha256": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
||||
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
|
@ -4286,6 +4297,11 @@
|
|||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"Base64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/Base64/-/Base64-1.1.0.tgz",
|
||||
"integrity": "sha512-qeacf8dvGpf+XAT27ESHMh7z84uRzj/ua2pQdJg483m3bEXv/kVFtDnMgvf70BQGqzbZhR9t6BmASzKvqfJf3Q=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -5222,6 +5238,11 @@
|
|||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"js-sha256": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
||||
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"js-sha256": "^0.9.0",
|
||||
"svelte-gestures": "^1.4.1",
|
||||
"svelte-language-server": "^0.14.29"
|
||||
}
|
||||
|
|
|
@ -1,63 +1,78 @@
|
|||
<script>
|
||||
import Guestbook from "../misc/guestbook.svelte";
|
||||
|
||||
</script>
|
||||
|
||||
<h1
|
||||
class="font-roboto_slab text-center text-2xl lg:text-6xl xl:text-7xl w-auto md:w-max mt-10 lg:mt-32 mx-auto font-bold h1-shadow"
|
||||
>Welcome to my website</h1>
|
||||
<h1
|
||||
class="font-roboto_slab text-center text-2xl lg:text-6xl xl:text-7xl w-auto md:w-max mt-10 lg:mt-32 mx-auto font-bold h1-shadow"
|
||||
>
|
||||
Welcome to my website
|
||||
</h1>
|
||||
<!-->
|
||||
<br class="hidden lg:block" />
|
||||
</!-->
|
||||
<p class="font-roboto_slab text-center lg:text-2xl text-md lg:mt-5">The modern web meets 2000</p>
|
||||
<hr class="border-black dark:border-[#ffc0f5]" />
|
||||
<br />
|
||||
<div class="md:text-center lg:px-40">
|
||||
<p class="">
|
||||
Welcome! This is my personal website that I made as a hobby project. You are entirely welcome!
|
||||
I was inspired to make this page thanks to a couple of friends of mine, whose pages you can
|
||||
check out under the "Galaxies" page.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-3 mt-2 md:mt-0 lg:px-20 xl:px-40 xl:mt-10">
|
||||
<img
|
||||
class="h-40 md:h-60 rounded "
|
||||
src="https://dutchellie.nl/DutchEllie/proper-website-2/raw/branch/main/web/static/images/rin-2.gif"
|
||||
alt="Kagamine Rin excitedly jumping! yay!"
|
||||
/>
|
||||
<div class="w-full grid md:grid-cols-2 gap-2">
|
||||
<div class="col-span-1">
|
||||
<h2 class="font-semibold text-left ml-10 mb-2">Tech</h2>
|
||||
<ul class="ml-10 list-disc marker:text-blue-400">
|
||||
<li>Kubernetes</li>
|
||||
<li>Svelte</li>
|
||||
<li>SvelteKit</li>
|
||||
<li>Lot's of Japanese music</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<h2 class="font-semibold text-left ml-10 mb-2">Misc</h2>
|
||||
<ul class="ml-10 list-disc marker:text-blue-400">
|
||||
<li class="underline text-[#002896] dark:text-blue-200 dark:hover:text-blue-50 hover:text-blue-500">
|
||||
<a href="https://dutchellie.nl/DutchEllie/svelte-website">Gitea Repo</a>
|
||||
</li>
|
||||
<li class="underline text-[#002896] dark:text-blue-200 dark:hover:text-blue-50 hover:text-blue-500">
|
||||
<a href="https://drone.dutchellie.nl/DutchEllie/svelte-website"
|
||||
>Drone CI/CD
|
||||
<img
|
||||
class="ml-2 mb-1 inline"
|
||||
src="https://drone.dutchellie.nl/api/badges/DutchEllie/svelte-website/status.svg?ref=refs/heads/main"
|
||||
alt="Drone build status"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<p>
|
||||
This entire project is open source (GPL-3) and the source code is linked above. In
|
||||
addition, a CI/CD pipeline deploys this website on every push.
|
||||
</p>
|
||||
</div>
|
||||
<p class="font-roboto_slab text-center lg:text-2xl text-md lg:mt-5">The modern web meets 2000</p>
|
||||
<hr class="border-black dark:border-[#ffc0f5]" />
|
||||
<br />
|
||||
<div class="md:text-center lg:px-40">
|
||||
<p class="">
|
||||
Welcome! This is my personal website that I made as a hobby project. You are entirely welcome! I
|
||||
was inspired to make this page thanks to a couple of friends of mine, whose pages you can check
|
||||
out under the "Galaxies" page.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-3 mt-2 md:mt-0 lg:px-20 xl:px-40 xl:mt-10">
|
||||
<img
|
||||
class="h-40 md:h-60 rounded "
|
||||
src="https://dutchellie.nl/DutchEllie/proper-website-2/raw/branch/main/web/static/images/rin-2.gif"
|
||||
alt="Kagamine Rin excitedly jumping! yay!"
|
||||
/>
|
||||
<div class="w-full grid md:grid-cols-2 gap-2">
|
||||
<div class="col-span-1">
|
||||
<h2 class="font-semibold text-left ml-10 mb-2">Tech</h2>
|
||||
<ul class="ml-10 list-disc marker:text-blue-400">
|
||||
<li>Kubernetes</li>
|
||||
<li>Svelte</li>
|
||||
<li>SvelteKit</li>
|
||||
<li>Lot's of Japanese music</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<h2 class="font-semibold text-left ml-10 mb-2">Misc</h2>
|
||||
<ul class="ml-10 list-disc marker:text-blue-400">
|
||||
<li
|
||||
class="underline text-[#002896] dark:text-blue-200 dark:hover:text-blue-50 hover:text-blue-500"
|
||||
>
|
||||
<a href="https://dutchellie.nl/DutchEllie/svelte-website">Gitea Repo</a>
|
||||
</li>
|
||||
<li
|
||||
class="underline text-[#002896] dark:text-blue-200 dark:hover:text-blue-50 hover:text-blue-500"
|
||||
>
|
||||
<a href="https://drone.dutchellie.nl/DutchEllie/svelte-website"
|
||||
>Drone CI/CD
|
||||
<img
|
||||
class="ml-2 mb-1 inline"
|
||||
src="https://drone.dutchellie.nl/api/badges/DutchEllie/svelte-website/status.svg?ref=refs/heads/main"
|
||||
alt="Drone build status"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 md:gap-2 mt-3">
|
||||
<div>
|
||||
<h2 class="font-semibold">Details</h2>
|
||||
<p>
|
||||
This entire project is open source (GPL-3) and the source code is linked above. In addition, a
|
||||
CI/CD pipeline deploys this website on every push.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<Guestbook/>
|
||||
</div>
|
||||
|
||||
<!-- How to do the animation and reverse it: https://stackoverflow.com/a/44742399 -->
|
||||
<style>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<script>
|
||||
export let show = false;
|
||||
export let title = 'Error';
|
||||
export let message = 'Error';
|
||||
|
||||
function onClose() {
|
||||
show = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<div
|
||||
on:click={() => {
|
||||
onClose();
|
||||
}}
|
||||
class="h-screen w-screen bg-opacity-60 bg-black fixed top-0 left-0 z-10 py-20 "
|
||||
>
|
||||
<div
|
||||
on:click|stopPropagation
|
||||
class="h-full w-3/4 bg-red-500 dark:bg-red-800 dark:text-slate-200 mx-auto rounded-xl pb-3 overflow-y-auto"
|
||||
>
|
||||
<div class="bg-zinc-800 font-bold text-white dark:bg-slate-700 rounded-t-xl p-3">
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
<div class="p-3">
|
||||
<p class="text-white">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
|
@ -1,3 +1,180 @@
|
|||
<div class="">
|
||||
<script lang="ts" context="module">
|
||||
export type Comment = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
website: string;
|
||||
message: string;
|
||||
time: string;
|
||||
uuid: string;
|
||||
};
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<script lang="ts">
|
||||
import Errormodal from './errormodal.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import GuestbookEntry from './guestbook/guestbook-entry.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { comment } from 'postcss';
|
||||
|
||||
let showError = false;
|
||||
let errorTitle = 'Error';
|
||||
let errorMessage = 'Error message';
|
||||
|
||||
let name = '';
|
||||
let email = '';
|
||||
let website = '';
|
||||
let message = '';
|
||||
|
||||
let DisplayComments: Comment[];
|
||||
let commentPage = 0;
|
||||
let pageSize = 10;
|
||||
async function onSubmit(event: SubmitEvent) {
|
||||
// Validation
|
||||
if (name === '' || message === '') {
|
||||
errorMessage = 'You must enter a name and a message!';
|
||||
showError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
name.length >= 40 ||
|
||||
email.length >= 100 ||
|
||||
website.length >= 200 ||
|
||||
message.length >= 360
|
||||
) {
|
||||
errorMessage =
|
||||
'Your name must be < 40 characters. Email < 100, website < 200 and message < 360.';
|
||||
showError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
name,
|
||||
email,
|
||||
website,
|
||||
message
|
||||
};
|
||||
|
||||
const jsonData = JSON.stringify(data);
|
||||
const res = await fetch('https://api.quenten.nl/api/testing/comment', {
|
||||
method: 'POST',
|
||||
body: jsonData
|
||||
});
|
||||
|
||||
if (res.status >= 300) {
|
||||
errorMessage = await res.text();
|
||||
showError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
clear();
|
||||
getComments();
|
||||
}
|
||||
|
||||
function clear() {
|
||||
name = '';
|
||||
email = '';
|
||||
website = '';
|
||||
message = '';
|
||||
}
|
||||
|
||||
async function getComments() {
|
||||
// Check if in localstorage already
|
||||
let commentLS = localStorage.getItem('comments');
|
||||
let comments: Comment[];
|
||||
if (commentLS === null) {
|
||||
// Not yet in storage
|
||||
comments = await updateComments();
|
||||
DisplayComments = comments;
|
||||
return;
|
||||
}
|
||||
|
||||
let newHash = await fetch('https://api.quenten.nl/api/testing/commenthash')
|
||||
.then((res) => res.text())
|
||||
.then((t) => {
|
||||
return t;
|
||||
});
|
||||
let oldHash = localStorage.getItem('commenthash');
|
||||
if (oldHash != newHash) {
|
||||
DisplayComments = await updateComments();
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayComments = JSON.parse(decodeURIComponent(window.atob(commentLS)));
|
||||
}
|
||||
|
||||
async function fetchComments() {
|
||||
let comments: Comment[];
|
||||
comments = await fetch('https://api.quenten.nl/api/testing/comment')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
return data;
|
||||
});
|
||||
return comments;
|
||||
}
|
||||
|
||||
async function updateComments() {
|
||||
let comments = await fetchComments();
|
||||
localStorage.setItem('comments', window.btoa(encodeURIComponent(JSON.stringify(comments))));
|
||||
localStorage.setItem('commenthash', sha256(JSON.stringify(comments)));
|
||||
return comments;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
getComments();
|
||||
});
|
||||
</script>
|
||||
|
||||
<Errormodal bind:show={showError} bind:title={errorTitle} bind:message={errorMessage} />
|
||||
<div class="">
|
||||
<h2 class="font-semibold">Guestbook</h2>
|
||||
<p>You can leave any comment you want right here in this guestbook! No signing up necessary.</p>
|
||||
<form on:submit|preventDefault={onSubmit} class="mt-2 w-full grid gap-2">
|
||||
<input
|
||||
class="p-1 rounded bg- border-2 focus:border-pink-400 dark:focus:border-pink-700 outline-none"
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="Nickname (max. 40 chars)"
|
||||
bind:value={name}
|
||||
/>
|
||||
<input
|
||||
class="p-1 rounded bg- border-2 focus:border-pink-400 dark:focus:border-pink-700 outline-none"
|
||||
type="text"
|
||||
name="email"
|
||||
placeholder="Email (optional)"
|
||||
bind:value={email}
|
||||
/>
|
||||
<input
|
||||
class="p-1 rounded bg- border-2 focus:border-pink-400 dark:focus:border-pink-700 outline-none"
|
||||
type="text"
|
||||
name="website"
|
||||
placeholder="Website (optional)"
|
||||
bind:value={website}
|
||||
/>
|
||||
<textarea
|
||||
class="p-1 rounded bg- border-2 focus:border-pink-400 dark:focus:border-pink-700 outline-none"
|
||||
type="text"
|
||||
name="message"
|
||||
placeholder="Message (max. 360 chars)"
|
||||
bind:value={message}
|
||||
/>
|
||||
<button
|
||||
class="bg-blue-100 border-slate-400 border-2 rounded-full py-2 w-fit px-10"
|
||||
type="submit">Submit</button
|
||||
>
|
||||
</form>
|
||||
<div>
|
||||
{#if DisplayComments != null}
|
||||
<div class="mt-4">
|
||||
<button class="px-5 py-2 bg-slate-400 rounded-xl" on:click={() => {if (commentPage > 0) commentPage -= 1}}>Previous</button>
|
||||
<button class="px-5 py-2 bg-slate-400 rounded-xl" on:click={() => commentPage += 1}>Next Page</button>
|
||||
<p class="ml-2 mt-1 w-full md:w-max">Current page: {commentPage + 1}</p>
|
||||
</div>
|
||||
{#each DisplayComments.slice(commentPage * pageSize, commentPage * pageSize + pageSize) as c}
|
||||
<GuestbookEntry comment={c} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import type { Comment } from '../guestbook.svelte';
|
||||
export let comment: Comment;
|
||||
</script>
|
||||
|
||||
<div class="rounded bg-red-100 mt-2 break-words">
|
||||
<div class="rounded-t bg-slate-400 grid grid-cols-2 gap-1 min-h-fit px-3 py-1">
|
||||
<p class="text-xs">Author: {comment.name}</p>
|
||||
<p class="text-xs">
|
||||
Time:
|
||||
{#if comment.time != null}
|
||||
{new Date(comment.time).toLocaleString('en-US', {dateStyle: 'long', timeStyle: 'short'})}
|
||||
{:else}
|
||||
brokn
|
||||
{/if}
|
||||
</p>
|
||||
{#if comment.website != null}
|
||||
<p class="text-xs col-span-2">Site: {comment.website}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="px-3 ">
|
||||
<p class="">{comment.message}</p>
|
||||
</div>
|
||||
</div>
|
|
@ -3,7 +3,7 @@
|
|||
import Button from './navbutton.svelte';
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-7 grid-rows-1 sm:grid-rows-1 gap-y-2 mt-2 sm:mt-4 sm:pl-4 align-middle h-24 md:h-16">
|
||||
<div class="grid grid-cols-7 grid-rows-1 sm:grid-rows-1 gap-y-2 mt-2 sm:mt-4 sm:pl-4 align-middle h-10 sm:h-24 md:h-16">
|
||||
<div class="lg:col-start-2 col-span-7 md:col-span-4 lg:col-span-3 flex flex-row items-start">
|
||||
<div class="h-full sm:aspect-square">
|
||||
<img
|
||||
|
|
|
@ -52,7 +52,8 @@
|
|||
<slot />
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div bind:clientHeight={footerHeight} class="bg-slate-100 dark:bg-purple-900 dark:text-pink-50 w-full h-min md:h-10 lg:pl-20 absolute bottom-0">
|
||||
<!-- To make this stick to the bottom, actually make it absolute, instead of fixed -->
|
||||
<div bind:clientHeight={footerHeight} class="bg-slate-100 dark:bg-purple-900 dark:text-pink-50 w-full h-min md:h-10 lg:pl-20 fixed bottom-0">
|
||||
<div class="h-full">
|
||||
<button on:click={onMusicToggle} class="bg-white dark:bg-[#B68BD6] dark:text-[#040033] border-black border rounded h-min w-20 mt-1">
|
||||
{#if musicPaused}
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
<Content />
|
||||
</div>
|
||||
|
||||
<div class="hidden md:block col-start-7 mt-10 mr-2">
|
||||
|
||||
<Rightbar />
|
||||
</div>
|
||||
<div class="hidden md:block col-start-7 mt-10 mr-2">
|
||||
<Rightbar />
|
||||
</div>
|
||||
</Maincontent>
|
||||
|
|
|
@ -86,6 +86,7 @@ async function fetchAndCache(request: Request) {
|
|||
|
||||
worker.addEventListener("fetch", (event) => {
|
||||
if (event.request.method !== "GET") return;
|
||||
if (new URL(event.request.url).hostname === "api.quenten.nl") return;
|
||||
|
||||
//console.log("fetch event intercepted")
|
||||
|
||||
|
|
Loading…
Reference in New Issue