mirror of
https://github.com/donavon04/DocuCenter.git
synced 2025-01-18 09:40:56 -07:00
added full CRUD capabilities
This commit is contained in:
parent
06fd32caa9
commit
08b58ee729
@ -42,7 +42,7 @@ app.use((err, req, res, next) => {
|
||||
|
||||
// Create a new document
|
||||
app.post('/documents', async (req, res) => {
|
||||
const { title, body, tags } = req.body;
|
||||
const { title, body, tags, created_by } = req.body;
|
||||
|
||||
// Validate input
|
||||
if (!title || !body) {
|
||||
@ -53,12 +53,15 @@ app.post('/documents', async (req, res) => {
|
||||
return res.status(400).json({ error: 'Tags must be an array' });
|
||||
}
|
||||
|
||||
console.log(created_by);
|
||||
|
||||
try {
|
||||
// Insert the document into the database
|
||||
const result = await db.collection('documents').insertOne({
|
||||
title,
|
||||
body,
|
||||
tags,
|
||||
created_by,
|
||||
created_at: new Date()
|
||||
});
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
AUTH_SECRET="txvHsSGbEuQ6j3I3YZEyXkVJnPTP/s5vdK4+OJ8sxQo=" # Added by `npx auth`. Read more: https://cli.authjs.dev
|
||||
|
||||
AUTH_MICROSOFT_ENTRA_ID_ID='3cdfac60-e7fb-4648-89d3-67966c497d35'
|
||||
AUTH_MICROSOFT_ENTRA_ID_SECRET='5Gi8Q~_pmDtvN3.Jwqt85kiI.uiyAAC7Z.4iFayY'
|
||||
AUTH_MICROSOFT_ENTRA_ID_ISSUER='538b9b1c-23fa-4102-b36e-a4d83fc9c4c1'
|
||||
POTATO='77863dd1-f8ab-4f6e-b94a-0f7a0340b320'
|
@ -13,6 +13,11 @@
|
||||
let element;
|
||||
let editor;
|
||||
|
||||
let initialHTML;
|
||||
|
||||
export function setHTML(html){
|
||||
editor.commands.insertContent(html);
|
||||
}
|
||||
|
||||
export function getHTML(){
|
||||
const html = editor.getHTML(); // Get the editor content as HTML
|
||||
@ -25,6 +30,7 @@
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Image.configure({ block: true }),
|
||||
Dropcursor,
|
||||
CodeBlock.configure({
|
||||
exitOnTripleEnter: true,
|
||||
@ -34,7 +40,6 @@
|
||||
class: 'bg-gray-900 border-2 border-gray-500 rounded-xl p-2 text-white',
|
||||
},
|
||||
}),
|
||||
Image,
|
||||
TextAlign.configure({
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
@ -79,7 +84,11 @@
|
||||
},
|
||||
}),
|
||||
],
|
||||
// place the cursor in the editor after initialization
|
||||
autofocus: true,
|
||||
// make the text editable (but that’s the default anyway)
|
||||
editable: true,
|
||||
// disable the loading of the default CSS (which is not much anyway)
|
||||
injectCSS: false,
|
||||
onTransaction: () => {
|
||||
// force re-render so `editor.isActive` works as expected
|
||||
@ -90,6 +99,7 @@
|
||||
class: 'prose-base m-3 focus:outline-none',
|
||||
},
|
||||
},
|
||||
content: initialHTML,
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,12 +1,59 @@
|
||||
<script lang="ts">
|
||||
import '../app.postcss';
|
||||
import { signIn, signOut } from "@auth/sveltekit/client"
|
||||
import { page } from "$app/stores"
|
||||
|
||||
let dropDown = false;
|
||||
|
||||
</script>
|
||||
|
||||
<header class="bg-blue-700 flex flex-row p-4 place-content-between items-center font-roboto shadow-xl">
|
||||
<a href="/home">
|
||||
<img class="max-w-36 mr-5 ml-4 aspect-auto" src="/branding/mpe_logo.png" alt="MPE Logo">
|
||||
</a>
|
||||
|
||||
<header class="bg-blue-700 flex flex-row p-4 place-content-between items-center">
|
||||
<a href="/home"><img class="max-w-36 mr-5 ml-4 aspect-auto" src="/branding/mpe_logo.png" alt="MPE Logo"></a>
|
||||
<a href="/add" class="rounded-3xl bg-blue-500 text-white p-2 px-10 font-roboto shadow m-1 h-10">Add</a>
|
||||
<div class="relative dropdown-container">
|
||||
<button class="rounded-3xl bg-blue-500 p-2 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
|
||||
on:click={() => dropDown = !dropDown}>
|
||||
{#if $page.data.session}
|
||||
{#if $page.data.session.user?.image}
|
||||
<img
|
||||
src={$page.data.session.user.image}
|
||||
class="avatar rounded-full size-8"
|
||||
alt="User Avatar"
|
||||
/>
|
||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
||||
{/if}
|
||||
{:else}
|
||||
<p class="font-bold text-white ml-1">Not Signed In</p>
|
||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if dropDown}
|
||||
<!-- Dropdown menu -->
|
||||
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md p-4 z-50 text-nowrap">
|
||||
{#if $page.data.session}
|
||||
<span class="signedInText block mb-2 text-sm">
|
||||
<small>Signed in as</small><br/>
|
||||
<strong>{$page.data.session.user?.name ?? "User"}</strong>
|
||||
</span>
|
||||
<button
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
||||
on:click={() => { signOut(); dropDown = false;}}>
|
||||
Sign Out
|
||||
</button>
|
||||
{:else}
|
||||
<span class="notSignedInText block mb-2">You are not signed in</span>
|
||||
<button
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
||||
on:click={() => { signIn('microsoft-entra-id'); dropDown = false;}}>
|
||||
Sign In
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<slot />
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
<script>
|
||||
import { signIn, signOut } from "@auth/sveltekit/client"
|
||||
import { page } from "$app/stores"
|
||||
</script>
|
||||
|
||||
<h1>SvelteKit Auth Example</h1>
|
||||
<div>
|
||||
{#if $page.data.session}
|
||||
{#if $page.data.session.user?.image}
|
||||
<img
|
||||
src={$page.data.session.user.image}
|
||||
class="avatar"
|
||||
alt="User Avatar"
|
||||
/>
|
||||
{/if}
|
||||
<span class="signedInText">
|
||||
<small>Signed in as</small><br />
|
||||
<strong>{$page.data.session.user?.name ?? "User"}</strong>
|
||||
<button on:click={() => signOut()}>Sign Out</button>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="notSignedInText">You are not signed in</span>
|
||||
<div class="wrapper-form">
|
||||
<button on:click={() => signIn('microsoft-entra-id')}>Sign In with M365</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
@ -3,6 +3,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<script>
|
||||
import { page } from "$app/stores"
|
||||
import Tiptap from '$lib/components/Tiptap.svelte'
|
||||
|
||||
let editorRef; //This is our reference to the tiptap editor
|
||||
@ -13,6 +14,9 @@
|
||||
const url = "http://localhost:3000/documents";
|
||||
|
||||
console.log("Title value:", title);
|
||||
|
||||
let created_by = $page.data.session.user?.name ?? "User";
|
||||
|
||||
// Get the HTML from the tiptap editor
|
||||
if (editorRef) {
|
||||
// Get the HTML content from the Tiptap editor
|
||||
@ -26,7 +30,8 @@
|
||||
const payload = {
|
||||
title,
|
||||
body,
|
||||
tags
|
||||
tags,
|
||||
created_by
|
||||
};
|
||||
|
||||
console.log(payload);
|
||||
@ -72,31 +77,57 @@
|
||||
}
|
||||
tagInput.value = ''; // Clear the input field
|
||||
}
|
||||
|
||||
let dropMenu = false;
|
||||
let visibility = 'Public';
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center h-full flex-col">
|
||||
<div class="w-full flex-col justify-center w-11/12 md:w-8/12 xl:w-3/4 mt-20">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type your title here..."
|
||||
name="title"
|
||||
id="title"
|
||||
class="rounded-xl w-full p-2 font-roboto mb-1 px-4 focus:outline-none"
|
||||
bind:value={title}
|
||||
required
|
||||
/>
|
||||
<div class="flex items-center justify-center h-full flex-col mb-10">
|
||||
<form class="flex-col justify-center w-11/12 md:w-10/12 xl:w-8/12 mt-20">
|
||||
<div class="flex flex-row w-full">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type your title here..."
|
||||
name="title"
|
||||
id="title"
|
||||
class="rounded-xl w-full p-2 font-roboto mb-1 px-4 focus:outline-none"
|
||||
bind:value={title}
|
||||
required
|
||||
/>
|
||||
<!-- Dropdown Button -->
|
||||
<div class="relative mb-1">
|
||||
<button
|
||||
class="bg-white text-blue-500 rounded-xl focus:outline-none flex flex-row justify-center items-center px-2 ml-1 font-roboto border-2 border-blue-500 h-full text-nowrap"
|
||||
on:click={() => dropMenu = !dropMenu}>
|
||||
{visibility}
|
||||
<i class="fa-solid fa-chevron-down ml-1"></i>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
{#if dropMenu}
|
||||
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md z-50 text-nowrap">
|
||||
<button
|
||||
class="block w-full text-left px-8 py-2 hover:bg-gray-100 text-gray-700" on:click={() => { visibility = 'Public'; dropMenu = false;}}>
|
||||
Public
|
||||
</button>
|
||||
<button
|
||||
class="block w-full text-left px-8 py-2 hover:bg-gray-100 text-gray-700" on:click={() => { visibility = 'Private'; dropMenu = false;}}>
|
||||
Private
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Tiptap bind:this={editorRef}/> <!-- This is the text editor itself -->
|
||||
<div class="w-full flex flex-col justify-between mt-1"> <!-- Bottom Container -->
|
||||
|
||||
<div class="rounded-xl bg-white flex flex-col p-1"> <!-- This is where the tags will go -->
|
||||
<div class="w-full flex flex-row p-1">
|
||||
<input type="text" name="tag" id="tag" placeholder="Type a tag..." class="w-full outline-none p-2 rounded-3xl "/>
|
||||
<button class="rounded-xl bg-blue-500 text-white shadow px-4 font-roboto" on:click={addTag}>Add</button>
|
||||
<button class="rounded-full bg-blue-500 text-white shadow px-4 font-roboto hover:bg-blue-400" on:click={addTag}>Add</button>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
{#each tags as tag, i}
|
||||
<button class="inline-flex rounded-xl p-1 px-3 items-center bg-blue-500 text-white space-x-2 mr-1 border-dashed border-blue-100 border-2" on:click={removeTag(tag)}>
|
||||
<button class="inline-flex rounded-xl p-1 px-3 items-center bg-blue-500 text-white space-x-2 mr-1 border-dashed border-blue-100 border-2 font-roboto" on:click={removeTag(tag)}>
|
||||
<p>{tag}</p>
|
||||
<i class="fa-solid fa-x fa-xs cursor-pointer"></i>
|
||||
</button>
|
||||
@ -104,8 +135,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row w-full justify-end mt-1">
|
||||
<button class="rounded-3xl bg-blue-500 text-white p-2 px-10 font-roboto shadow" on:click={save}>Save</button>
|
||||
<button type="submit" class="rounded-full bg-blue-500 hover:bg-blue-400 text-white p-2 px-6 font-roboto shadow" on:click={save}>
|
||||
Save
|
||||
<i class="fa-regular fa-floppy-disk fa-lg ml-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -1,60 +1,162 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let document = null; // To store fetched document data
|
||||
let error = null; // To store any error that occurs during fetch
|
||||
|
||||
// Fetch the data when the component mounts
|
||||
onMount(async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id'); // Get the `id` from the query string
|
||||
|
||||
if (!id) {
|
||||
error = 'ID is required';
|
||||
<script lang="js">
|
||||
import { page } from "$app/stores"
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let document = null; // To store fetched document data
|
||||
let error = null; // To store any error that occurs during fetch
|
||||
|
||||
let id;
|
||||
// Fetch the data when the component mounts
|
||||
onMount(async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
id = urlParams.get('id'); // Get the `id` from the query string
|
||||
|
||||
if (!id) {
|
||||
error = 'ID is required';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Make the GET request to your API
|
||||
const response = await fetch(`http://localhost:3000/documents/${id}`);
|
||||
if (response.ok) {
|
||||
document = await response.json(); // Store document data
|
||||
} else {
|
||||
error = `Failed to fetch document: ${response.status}`;
|
||||
}
|
||||
} catch (err) {
|
||||
error = `Error: ${err.message}`;
|
||||
}
|
||||
});
|
||||
|
||||
let alertPopup = false;
|
||||
let agreeToDelete = false;
|
||||
// Function to delete a document by ID
|
||||
async function deleteDocument(id) {
|
||||
const url = `http://localhost:3000/documents/${id}`; // Update with your API base URL if different
|
||||
|
||||
// Show the confirmation popup
|
||||
alertPopup = true;
|
||||
|
||||
// Wait until the user makes a decision
|
||||
const interval = setInterval(() => {
|
||||
if (agreeToDelete || !alertPopup) {
|
||||
clearInterval(interval); // Stop checking once the decision is made
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Continue only if the user agrees
|
||||
while (!agreeToDelete && alertPopup) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100)); // Wait for decision
|
||||
}
|
||||
|
||||
// Proceed with deletion if agreed
|
||||
if (agreeToDelete) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error('Failed to delete document:', errorData.error);
|
||||
alert(`Error: ${errorData.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Make the GET request to your API
|
||||
const response = await fetch(`http://localhost:3000/documents/${id}`);
|
||||
if (response.ok) {
|
||||
document = await response.json(); // Store document data
|
||||
} else {
|
||||
error = `Failed to fetch document: ${response.status}`;
|
||||
}
|
||||
} catch (err) {
|
||||
error = `Error: ${err.message}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<p>{error}</p>
|
||||
{:else if document}
|
||||
<div class="prose-base flex bg-white m-auto p-4 w-11/12 md:w-8/12 xl:w-3/4 leading-3">
|
||||
<div class="prose-base w-full">
|
||||
<div class="flex flex-col justify-between">
|
||||
<h1>{document.title}</h1>
|
||||
<div class="flex flex-row h-8">
|
||||
{#each document.tags as tag}
|
||||
<div class="inline-flex rounded-xl px-3 items-center bg-blue-500 text-white space-x-2 mr-1 border-dashed border-blue-100 border-2">
|
||||
<p>{tag}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if document.author}
|
||||
<p>By: {document.author}</p>
|
||||
{/if}
|
||||
{#if document.created_at}
|
||||
<p>{new Date(document.created_at).toLocaleString()}</p>
|
||||
{/if}
|
||||
<hr class="w-full">
|
||||
</div>
|
||||
|
||||
{@html document.body}
|
||||
</div>
|
||||
|
||||
const result = await response.json();
|
||||
console.log(result.message);
|
||||
|
||||
// Redirect after successful deletion
|
||||
window.location.href = "/home";
|
||||
} catch (error) {
|
||||
console.error('Error deleting document:', error);
|
||||
alert('An unexpected error occurred while deleting the document.');
|
||||
}
|
||||
}
|
||||
|
||||
// Reset popup and decision
|
||||
alertPopup = false;
|
||||
agreeToDelete = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if alertPopup}
|
||||
<!-- Full-screen overlay to darken the background -->
|
||||
<div
|
||||
class="fixed inset-0 bg-gray-600 opacity-40 z-40"
|
||||
></div>
|
||||
|
||||
<!-- Centered popup -->
|
||||
<div
|
||||
class="fixed bg-white rounded-xl p-6 shadow-lg text-center z-50"
|
||||
style="top: 50%; left: 50%; transform: translate(-50%, -50%);"
|
||||
>
|
||||
<p class="mb-4 font-roboto">Do you agree to delete this document forever?</p>
|
||||
<div class="flex justify-center space-x-4">
|
||||
<button
|
||||
class="bg-gray-200 text-black px-4 py-2 rounded-lg hover:bg-gray-300 font-roboto"
|
||||
on:click={() => alertPopup = false}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 font-roboto"
|
||||
on:click={() => agreeToDelete = true}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Loading...</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<p>{error}</p>
|
||||
{:else if document}
|
||||
<div class="prose-base flex bg-white m-auto p-4 w-11/12 md:w-10/12 xl:w-8/12 leading-3 mt-6 mb-24 rounded-lg shadow">
|
||||
<div class="prose-base w-full">
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<h1 class="text-2xl">{document.title}</h1>
|
||||
{#if $page.data.session}
|
||||
<div>
|
||||
<a href="/edit?id={id}" class="bg-yellow-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto"><i class="fa-solid fa-pencil fa-lg"></i></a>
|
||||
<button class="bg-red-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto" on:click={() => { deleteDocument(documentId)}}><i class="fa-solid fa-trash fa-lg"></i></button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="leading-3">
|
||||
<div class="flex flex-row h-8">
|
||||
{#each document.tags as tag}
|
||||
<div class="inline-flex rounded-xl px-3 items-center bg-blue-500 text-white space-x-2 mr-1 border-dashed border-blue-100 border-2">
|
||||
<p>{tag}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div>
|
||||
{#if document.created_by}
|
||||
<p class="font-semibold">By: {document.created_by}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="">
|
||||
{#if document.created_at}
|
||||
<p class="font-semibold">{new Date(document.created_at).toLocaleString()}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<hr class="w-full m-0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{@html document.body}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Loading animation could go here -->
|
||||
{/if}
|
||||
|
189
frontend/src/routes/edit/+page.svelte
Normal file
189
frontend/src/routes/edit/+page.svelte
Normal file
@ -0,0 +1,189 @@
|
||||
<svelte:head>
|
||||
<title>Add</title>
|
||||
</svelte:head>
|
||||
|
||||
<script>
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { page } from "$app/stores"
|
||||
import Tiptap from '$lib/components/Tiptap.svelte'
|
||||
|
||||
let editorRef ; //This is our reference to the tiptap editor
|
||||
|
||||
|
||||
let title = "";
|
||||
|
||||
let document = null; // To store fetched document data
|
||||
let error = null; // To store any error that occurs during fetch
|
||||
let documentId;
|
||||
|
||||
// Function to fetch the document by ID
|
||||
async function fetchDocumentById(id) {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/documents/${id}`);
|
||||
if (response.ok) {
|
||||
const fetchedDocument = await response.json();
|
||||
document = fetchedDocument; // Update document data
|
||||
console.log(document.body);
|
||||
editorRef.setHTML(document.body); // Update the editor with the HTML
|
||||
} else {
|
||||
error = `Failed to fetch document: ${response.status}`;
|
||||
}
|
||||
} catch (err) {
|
||||
error = `Error: ${err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up polling on mount
|
||||
onMount(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id'); // Get the `id` from the query string
|
||||
documentId = id;
|
||||
|
||||
if (!id) {
|
||||
error = 'ID is required';
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the document immediately
|
||||
fetchDocumentById(id);
|
||||
|
||||
});
|
||||
|
||||
|
||||
async function save() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id'); // Get the `id` from the query string
|
||||
const url = "http://localhost:3000/documents/"+id;
|
||||
|
||||
console.log("Title value:", title);
|
||||
|
||||
let created_by = $page.data.session.user?.name ?? "User";
|
||||
|
||||
// Get the HTML from the tiptap editor
|
||||
if (editorRef) {
|
||||
// Get the HTML content from the Tiptap editor
|
||||
const body = editorRef ? editorRef.getHTML() : "";
|
||||
|
||||
|
||||
|
||||
if (!title.trim() || !body.trim()) {
|
||||
console.error("Title and body are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title,
|
||||
body,
|
||||
tags,
|
||||
created_by
|
||||
};
|
||||
|
||||
console.log(payload);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Redirect to /home after successful completion
|
||||
window.location.href = "/home"; // Redirecting to /home
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let tags = [];
|
||||
|
||||
// Function to remove a tag
|
||||
function removeTag(tag) {
|
||||
tags = tags.filter(t => t !== tag); // Filter out the tag from the array
|
||||
}
|
||||
|
||||
// Function to add a tag
|
||||
function addTag() {
|
||||
const tagInput = document.querySelector('#tag');
|
||||
const tag = tagInput.value.trim(); // Get input value and trim whitespace
|
||||
if (tag && !tags.includes(tag)) { // Ensure non-empty and no duplicates
|
||||
tags = [...tags, tag]; // Create a new array to trigger reactivity
|
||||
}
|
||||
tagInput.value = ''; // Clear the input field
|
||||
}
|
||||
let dropMenu = false;
|
||||
let visibility = 'Public';
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center h-full flex-col mb-10">
|
||||
<form class="flex-col justify-center w-11/12 md:w-10/12 xl:w-8/12 mt-20">
|
||||
<div class="flex flex-row w-full">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type your title here..."
|
||||
name="title"
|
||||
id="title"
|
||||
class="rounded-xl w-full p-2 font-roboto mb-1 px-4 focus:outline-none"
|
||||
bind:value={title}
|
||||
required
|
||||
/>
|
||||
<!-- Dropdown Button -->
|
||||
<div class="relative mb-1">
|
||||
<button
|
||||
class="bg-white text-blue-500 rounded-xl focus:outline-none flex flex-row justify-center items-center px-2 ml-1 font-roboto border-2 border-blue-500 h-full text-nowrap"
|
||||
on:click={() => dropMenu = !dropMenu}>
|
||||
{visibility}
|
||||
<i class="fa-solid fa-chevron-down ml-1"></i>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
{#if dropMenu}
|
||||
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md z-50 text-nowrap">
|
||||
<button
|
||||
class="block w-full text-left px-8 py-2 hover:bg-gray-100 text-gray-700" on:click={() => { visibility = 'Public'; dropMenu = false;}}>
|
||||
Public
|
||||
</button>
|
||||
<button
|
||||
class="block w-full text-left px-8 py-2 hover:bg-gray-100 text-gray-700" on:click={() => { visibility = 'Private'; dropMenu = false;}}>
|
||||
Private
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Tiptap bind:this={editorRef}/> <!-- This is the text editor itself -->
|
||||
<div class="w-full flex flex-col justify-between mt-1"> <!-- Bottom Container -->
|
||||
|
||||
<div class="rounded-xl bg-white flex flex-col p-1"> <!-- This is where the tags will go -->
|
||||
<div class="w-full flex flex-row p-1">
|
||||
<input type="text" name="tag" id="tag" placeholder="Type a tag..." class="w-full outline-none p-2 rounded-3xl "/>
|
||||
<button class="rounded-full bg-blue-500 text-white shadow px-4 font-roboto hover:bg-blue-400" on:click={addTag}>Add</button>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
{#each tags as tag, i}
|
||||
<button class="inline-flex rounded-xl p-1 px-3 items-center bg-blue-500 text-white space-x-2 mr-1 border-dashed border-blue-100 border-2 font-roboto" on:click={removeTag(tag)}>
|
||||
<p>{tag}</p>
|
||||
<i class="fa-solid fa-x fa-xs cursor-pointer"></i>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row w-full justify-end mt-1">
|
||||
<button type="submit" class="rounded-full bg-blue-500 hover:bg-blue-400 text-white p-2 px-6 font-roboto shadow" on:click={save}>
|
||||
Save
|
||||
<i class="fa-regular fa-floppy-disk fa-lg ml-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from "$app/stores"
|
||||
|
||||
let query = false;
|
||||
let searchQuery = "";
|
||||
@ -72,6 +73,13 @@
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center h-full flex-col">
|
||||
{#if $page.data.session}
|
||||
<div class="flex flex-row justify-end w-full">
|
||||
<a href="/add" class="bg-green-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 mr-4 mt-3 text-white font-roboto">
|
||||
<i class="fa-solid fa-folder-plus fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-col justify-center w-11/12 md:w-8/12 xl:w-1/2 max-w-7xl mt-20">
|
||||
<!-- This is where the search box goes -->
|
||||
<input
|
||||
@ -118,6 +126,7 @@
|
||||
<div class="bg-white rounded-2xl p-4 font-roboto border mt-1 mb-1 hover:bg-slate-200 truncate h-48 shadow-lg">
|
||||
<h3 class="text-lg font-semibold">{@html document.title}</h3>
|
||||
<p class="text-sm text-gray-600">{@html truncateBody(document.body)}</p>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
|
@ -32,25 +32,6 @@
|
||||
<button type="submit" class="flex w-full justify-center rounded-md bg-blue-500 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
{#if $page.data.session}
|
||||
{#if $page.data.session.user?.image}
|
||||
<img
|
||||
src={$page.data.session.user.image}
|
||||
class="avatar"
|
||||
alt="User Avatar"
|
||||
/>
|
||||
{/if}
|
||||
<span class="signedInText">
|
||||
<small>Signed in as</small><br />
|
||||
<strong>{$page.data.session.user?.name ?? "User"}</strong>
|
||||
<button on:click={() => signOut()}>Sign Out</button>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="notSignedInText">You are not signed in</span>
|
||||
<div class="wrapper-form">
|
||||
<button on:click={() => signIn('microsoft-entra-id')}>Sign In with M365</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<p class="mt-10 text-center text-sm/6 text-gray-500">
|
||||
Not a member?
|
||||
|
Loading…
Reference in New Issue
Block a user