about summary refs log tree commit diff
path: root/client/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/lib')
-rw-r--r--client/src/lib/actions/shorten.ts50
-rw-r--r--client/src/lib/components/Form.svelte64
-rw-r--r--client/src/lib/components/Response.svelte33
-rw-r--r--client/src/lib/components/Responses.svelte24
-rw-r--r--client/src/lib/components/Title.svelte22
-rw-r--r--client/src/lib/components/icons/ArrowIcon.svelte10
-rw-r--r--client/src/lib/components/icons/CrossIcon.svelte10
-rw-r--r--client/src/lib/data/links.ts18
-rw-r--r--client/src/lib/utils/addProtocol.ts6
-rw-r--r--client/src/lib/utils/checkUrl.ts10
-rw-r--r--client/src/lib/utils/debounce.ts12
11 files changed, 259 insertions, 0 deletions
diff --git a/client/src/lib/actions/shorten.ts b/client/src/lib/actions/shorten.ts
new file mode 100644
index 0000000..ca685c5
--- /dev/null
+++ b/client/src/lib/actions/shorten.ts
@@ -0,0 +1,50 @@
+interface ShortenResponse {
+    hash: string;
+}
+
+export interface ShortenRequest {
+    url: string;
+    nonce: string;
+    response: Promise<ShortenResponse>;
+}
+
+async function makeRequest(url: string): Promise<ShortenResponse> {
+    let body;
+
+    try {
+        const response = await fetch("/", {
+            headers: {
+                Accept: "application/json",
+                "Content-Type": "application/json",
+            },
+            method: "post",
+            body: JSON.stringify({ url }),
+        });
+
+        body = await response.json();
+    } catch (err) {
+        throw {
+            error: "Error!",
+        };
+    }
+
+    if (body.hash) {
+        return {
+            hash: body.hash,
+        };
+    } else {
+        throw {
+            message: body.error || "Error!",
+        };
+    }
+}
+
+export default function shorten(url: string): ShortenRequest {
+    const nonce = Math.random().toString(36).substr(2, 5);
+
+    return {
+        url,
+        nonce,
+        response: makeRequest(url),
+    };
+}
diff --git a/client/src/lib/components/Form.svelte b/client/src/lib/components/Form.svelte
new file mode 100644
index 0000000..a05e868
--- /dev/null
+++ b/client/src/lib/components/Form.svelte
@@ -0,0 +1,64 @@
+<script lang="ts">
+    import shorten from "$lib/actions/shorten";
+    import { links } from "$lib/data/links";
+    import checkUrl from "$lib/utils/checkUrl";
+    import debounce from "$lib/utils/debounce";
+    import ArrowIcon from "./icons/ArrowIcon.svelte";
+    import CrossIcon from "./icons/CrossIcon.svelte";
+
+    let value = "";
+    let valid = false;
+
+    function submit() {
+        const url = checkUrl(value);
+        if (url !== null) {
+            links.add(shorten(url));
+        }
+    }
+
+    const check = debounce(() => valid = !!checkUrl(value), 100);
+
+    // @ts-ignore: Value is a dependency
+    $: value, check();
+</script>
+
+<style>
+    form {
+        position: relative;
+        border: 1px solid #aaaabb;
+        box-shadow: 0 4px 6px #aaaabb30;
+        box-sizing: border-box;
+        border-radius: 5px;
+    }
+
+    .field {
+        box-sizing: border-box;
+        width: 100%;
+        border: none;
+        padding: 15px 50px 15px 20px;
+        background: transparent;
+        font-size: 1rem;
+    }
+
+    .button {
+        position: absolute;
+        right: 10px;
+        margin: auto;
+        top: 0;
+        bottom: 0;
+        background: transparent;
+        border: none;
+        cursor: pointer;
+    }
+</style>
+
+<form on:submit|preventDefault={submit}>
+    <input class="field" bind:value type="text"/>
+    <button class="button" type="submit">
+        {#if valid}
+            <ArrowIcon/>
+        {:else}
+            <CrossIcon/>
+        {/if}
+    </button>
+</form>
diff --git a/client/src/lib/components/Response.svelte b/client/src/lib/components/Response.svelte
new file mode 100644
index 0000000..0ed8cc9
--- /dev/null
+++ b/client/src/lib/components/Response.svelte
@@ -0,0 +1,33 @@
+<script lang="ts">
+    import type { ShortenRequest } from "$lib/actions/shorten";
+
+    export let info: ShortenRequest;
+</script>
+
+<style>
+    div {
+        display: flex;
+        justify-content: space-between;
+    }
+
+    .url {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+    .output {
+        margin-left: 50px;
+    }
+</style>
+
+<div>
+    <span class="url">{info.url}</span>
+    {#await info.response}
+        <span class="output">Loading...</span>
+    {:then { hash }} 
+        <a class="output" href="https://sho.rest/{hash}">sho.rest/{hash}</a>
+    {:catch { error }}
+        <span class="output">{error}</span>
+    {/await}
+</div>
\ No newline at end of file
diff --git a/client/src/lib/components/Responses.svelte b/client/src/lib/components/Responses.svelte
new file mode 100644
index 0000000..6e83658
--- /dev/null
+++ b/client/src/lib/components/Responses.svelte
@@ -0,0 +1,24 @@
+<script lang="ts">
+    import Response from "./Response.svelte"
+    import { slide } from 'svelte/transition';
+    import { links } from "$lib/data/links";
+</script>
+
+<style>
+    ul {
+        list-style: none;
+        padding: 0 10px;
+    }
+
+    li {
+        margin-bottom: 10px;
+    }
+</style>
+
+<ul>
+    {#each $links as info (info.nonce)}
+        <li transition:slide >
+            <Response {info}/>
+        </li>
+    {/each}
+</ul>
\ No newline at end of file
diff --git a/client/src/lib/components/Title.svelte b/client/src/lib/components/Title.svelte
new file mode 100644
index 0000000..4266eb1
--- /dev/null
+++ b/client/src/lib/components/Title.svelte
@@ -0,0 +1,22 @@
+<style>
+    p {
+        color: #212121;
+        margin: 0 0 5px 0;
+    }
+
+    .text {
+        margin: 10px 0 15px 0;
+    }
+
+    img {
+        width: 25px;
+    }
+</style>
+
+<div>
+    <img src="/shorest.svg" alt=""/>
+    <div class="text">
+        <p><b>sho.rest</b></p>
+        <p>Made with ❤ by <b>Mel</b></p>
+    </div>
+</div>
diff --git a/client/src/lib/components/icons/ArrowIcon.svelte b/client/src/lib/components/icons/ArrowIcon.svelte
new file mode 100644
index 0000000..52c79ae
--- /dev/null
+++ b/client/src/lib/components/icons/ArrowIcon.svelte
@@ -0,0 +1,10 @@
+<style>
+    svg {
+        width: 20px;
+        color: #212121;
+    }
+</style>
+
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
+</svg>
\ No newline at end of file
diff --git a/client/src/lib/components/icons/CrossIcon.svelte b/client/src/lib/components/icons/CrossIcon.svelte
new file mode 100644
index 0000000..55525d8
--- /dev/null
+++ b/client/src/lib/components/icons/CrossIcon.svelte
@@ -0,0 +1,10 @@
+<style>
+    svg {
+        width: 20px;
+        color: #212121;
+    }
+</style>
+
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
+</svg>
\ No newline at end of file
diff --git a/client/src/lib/data/links.ts b/client/src/lib/data/links.ts
new file mode 100644
index 0000000..0f0a9ce
--- /dev/null
+++ b/client/src/lib/data/links.ts
@@ -0,0 +1,18 @@
+import type { ShortenRequest } from "$lib/actions/shorten";
+import type { Writable } from "svelte/store";
+import { writable } from "svelte/store";
+
+function createLinks() {
+    const { subscribe, update }: Writable<ShortenRequest[]> = writable([]);
+
+    function add(request: ShortenRequest) {
+        update((l) => [request, ...l.slice(0, 2)]);
+    }
+
+    return {
+        subscribe,
+        add,
+    };
+}
+
+export const links = createLinks();
diff --git a/client/src/lib/utils/addProtocol.ts b/client/src/lib/utils/addProtocol.ts
new file mode 100644
index 0000000..75c6214
--- /dev/null
+++ b/client/src/lib/utils/addProtocol.ts
@@ -0,0 +1,6 @@
+export default function (url: string) {
+    if (!/^https?:\/\//.test(url)) {
+        url = "https://" + url;
+    }
+    return url;
+}
diff --git a/client/src/lib/utils/checkUrl.ts b/client/src/lib/utils/checkUrl.ts
new file mode 100644
index 0000000..8ed747f
--- /dev/null
+++ b/client/src/lib/utils/checkUrl.ts
@@ -0,0 +1,10 @@
+import addProtocol from "./addProtocol";
+
+export default function (url: string): string | null {
+    try {
+        const normalizedUrl = new URL(addProtocol(url));
+        return normalizedUrl.toString();
+    } catch (e) {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/client/src/lib/utils/debounce.ts b/client/src/lib/utils/debounce.ts
new file mode 100644
index 0000000..86ef3db
--- /dev/null
+++ b/client/src/lib/utils/debounce.ts
@@ -0,0 +1,12 @@
+type Procedure = (...args: any[]) => any;
+
+export default function <F extends Procedure>(f: F, duration: number) {
+    let timeout: ReturnType<typeof setTimeout> | null = null;
+    return function (...args: Parameters<F>) {
+        if (timeout !== null) {
+            clearTimeout(timeout);
+            timeout = null;
+        }
+        timeout = setTimeout(() => f(...args), duration);
+    };
+}