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
|
// Create a new document
|
||||||
app.post('/documents', async (req, res) => {
|
app.post('/documents', async (req, res) => {
|
||||||
const { title, body, tags } = req.body;
|
const { title, body, tags, created_by } = req.body;
|
||||||
|
|
||||||
// Validate input
|
// Validate input
|
||||||
if (!title || !body) {
|
if (!title || !body) {
|
||||||
@ -53,12 +53,15 @@ app.post('/documents', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'Tags must be an array' });
|
return res.status(400).json({ error: 'Tags must be an array' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(created_by);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Insert the document into the database
|
// Insert the document into the database
|
||||||
const result = await db.collection('documents').insertOne({
|
const result = await db.collection('documents').insertOne({
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
tags,
|
tags,
|
||||||
|
created_by,
|
||||||
created_at: new Date()
|
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 element;
|
||||||
let editor;
|
let editor;
|
||||||
|
|
||||||
|
let initialHTML;
|
||||||
|
|
||||||
|
export function setHTML(html){
|
||||||
|
editor.commands.insertContent(html);
|
||||||
|
}
|
||||||
|
|
||||||
export function getHTML(){
|
export function getHTML(){
|
||||||
const html = editor.getHTML(); // Get the editor content as HTML
|
const html = editor.getHTML(); // Get the editor content as HTML
|
||||||
@ -25,6 +30,7 @@
|
|||||||
extensions: [
|
extensions: [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
|
Image.configure({ block: true }),
|
||||||
Dropcursor,
|
Dropcursor,
|
||||||
CodeBlock.configure({
|
CodeBlock.configure({
|
||||||
exitOnTripleEnter: true,
|
exitOnTripleEnter: true,
|
||||||
@ -34,7 +40,6 @@
|
|||||||
class: 'bg-gray-900 border-2 border-gray-500 rounded-xl p-2 text-white',
|
class: 'bg-gray-900 border-2 border-gray-500 rounded-xl p-2 text-white',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Image,
|
|
||||||
TextAlign.configure({
|
TextAlign.configure({
|
||||||
types: ['heading', 'paragraph'],
|
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,
|
editable: true,
|
||||||
|
// disable the loading of the default CSS (which is not much anyway)
|
||||||
injectCSS: false,
|
injectCSS: false,
|
||||||
onTransaction: () => {
|
onTransaction: () => {
|
||||||
// force re-render so `editor.isActive` works as expected
|
// force re-render so `editor.isActive` works as expected
|
||||||
@ -90,6 +99,7 @@
|
|||||||
class: 'prose-base m-3 focus:outline-none',
|
class: 'prose-base m-3 focus:outline-none',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
content: initialHTML,
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,59 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '../app.postcss';
|
import '../app.postcss';
|
||||||
|
import { signIn, signOut } from "@auth/sveltekit/client"
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
|
||||||
|
let dropDown = false;
|
||||||
|
|
||||||
</script>
|
</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">
|
<div class="relative dropdown-container">
|
||||||
<a href="/home"><img class="max-w-36 mr-5 ml-4 aspect-auto" src="/branding/mpe_logo.png" alt="MPE Logo"></a>
|
<button class="rounded-3xl bg-blue-500 p-2 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
|
||||||
<a href="/add" class="rounded-3xl bg-blue-500 text-white p-2 px-10 font-roboto shadow m-1 h-10">Add</a>
|
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>
|
</header>
|
||||||
|
|
||||||
<slot />
|
<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>
|
</svelte:head>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { page } from "$app/stores"
|
||||||
import Tiptap from '$lib/components/Tiptap.svelte'
|
import Tiptap from '$lib/components/Tiptap.svelte'
|
||||||
|
|
||||||
let editorRef; //This is our reference to the tiptap editor
|
let editorRef; //This is our reference to the tiptap editor
|
||||||
@ -13,6 +14,9 @@
|
|||||||
const url = "http://localhost:3000/documents";
|
const url = "http://localhost:3000/documents";
|
||||||
|
|
||||||
console.log("Title value:", title);
|
console.log("Title value:", title);
|
||||||
|
|
||||||
|
let created_by = $page.data.session.user?.name ?? "User";
|
||||||
|
|
||||||
// Get the HTML from the tiptap editor
|
// Get the HTML from the tiptap editor
|
||||||
if (editorRef) {
|
if (editorRef) {
|
||||||
// Get the HTML content from the Tiptap editor
|
// Get the HTML content from the Tiptap editor
|
||||||
@ -26,7 +30,8 @@
|
|||||||
const payload = {
|
const payload = {
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
tags
|
tags,
|
||||||
|
created_by
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(payload);
|
console.log(payload);
|
||||||
@ -72,11 +77,13 @@
|
|||||||
}
|
}
|
||||||
tagInput.value = ''; // Clear the input field
|
tagInput.value = ''; // Clear the input field
|
||||||
}
|
}
|
||||||
|
let dropMenu = false;
|
||||||
|
let visibility = 'Public';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center justify-center h-full flex-col">
|
<div class="flex items-center justify-center h-full flex-col mb-10">
|
||||||
<div class="w-full flex-col justify-center w-11/12 md:w-8/12 xl:w-3/4 mt-20">
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type your title here..."
|
placeholder="Type your title here..."
|
||||||
@ -86,17 +93,41 @@
|
|||||||
bind:value={title}
|
bind:value={title}
|
||||||
required
|
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 -->
|
<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="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="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">
|
<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 "/>
|
<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>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
{#each tags as tag, i}
|
{#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>
|
<p>{tag}</p>
|
||||||
<i class="fa-solid fa-x fa-xs cursor-pointer"></i>
|
<i class="fa-solid fa-x fa-xs cursor-pointer"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -104,8 +135,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row w-full justify-end mt-1">
|
<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}>
|
||||||
</div>
|
Save
|
||||||
|
<i class="fa-regular fa-floppy-disk fa-lg ml-2"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
@ -1,13 +1,15 @@
|
|||||||
<script>
|
<script lang="js">
|
||||||
|
import { page } from "$app/stores"
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let document = null; // To store fetched document data
|
let document = null; // To store fetched document data
|
||||||
let error = null; // To store any error that occurs during fetch
|
let error = null; // To store any error that occurs during fetch
|
||||||
|
|
||||||
|
let id;
|
||||||
// Fetch the data when the component mounts
|
// Fetch the data when the component mounts
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const id = urlParams.get('id'); // Get the `id` from the query string
|
id = urlParams.get('id'); // Get the `id` from the query string
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
error = 'ID is required';
|
error = 'ID is required';
|
||||||
@ -26,15 +28,108 @@
|
|||||||
error = `Error: ${err.message}`;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
</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>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
<p>{error}</p>
|
<p>{error}</p>
|
||||||
{:else if document}
|
{: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 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="prose-base w-full">
|
||||||
<div class="flex flex-col justify-between">
|
<div class="flex flex-col justify-between">
|
||||||
<h1>{document.title}</h1>
|
<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">
|
<div class="flex flex-row h-8">
|
||||||
{#each document.tags as tag}
|
{#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">
|
<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">
|
||||||
@ -42,19 +137,26 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if document.author}
|
<div class="flex flex-row justify-between">
|
||||||
<p>By: {document.author}</p>
|
<div>
|
||||||
|
{#if document.created_by}
|
||||||
|
<p class="font-semibold">By: {document.created_by}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
{#if document.created_at}
|
{#if document.created_at}
|
||||||
<p>{new Date(document.created_at).toLocaleString()}</p>
|
<p class="font-semibold">{new Date(document.created_at).toLocaleString()}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<hr class="w-full">
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="w-full m-0">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{@html document.body}
|
{@html document.body}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p>Loading...</p>
|
<!-- Loading animation could go here -->
|
||||||
{/if}
|
{/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>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
|
||||||
let query = false;
|
let query = false;
|
||||||
let searchQuery = "";
|
let searchQuery = "";
|
||||||
@ -72,6 +73,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center justify-center h-full flex-col">
|
<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">
|
<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 -->
|
<!-- This is where the search box goes -->
|
||||||
<input
|
<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">
|
<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>
|
<h3 class="text-lg font-semibold">{@html document.title}</h3>
|
||||||
<p class="text-sm text-gray-600">{@html truncateBody(document.body)}</p>
|
<p class="text-sm text-gray-600">{@html truncateBody(document.body)}</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/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>
|
<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>
|
</div>
|
||||||
</form>
|
</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">
|
<p class="mt-10 text-center text-sm/6 text-gray-500">
|
||||||
Not a member?
|
Not a member?
|
||||||
|
Loading…
Reference in New Issue
Block a user