Navigation, creation and removal of bookmarks/folders

This commit is contained in:
nefrace 2022-12-15 01:57:49 +03:00
parent 5b385a852f
commit 17f6aede91
6 changed files with 2132 additions and 46 deletions

View File

@ -44,7 +44,7 @@ func (s Server) InitHandlers() {
bookmarkRouter.Path("").Methods("GET").HandlerFunc(MakeHTTPHandler(s.handleGetBookmarks)) bookmarkRouter.Path("").Methods("GET").HandlerFunc(MakeHTTPHandler(s.handleGetBookmarks))
bookmarkRouter.Path("").Methods("POST").HandlerFunc(MakeHTTPHandler(s.handleNewBookmark)) bookmarkRouter.Path("").Methods("POST").HandlerFunc(MakeHTTPHandler(s.handleNewBookmark))
bookmarkRouter.Path("").Methods("PUT").HandlerFunc(MakeHTTPHandler(s.handleUpdateBookmark)) bookmarkRouter.Path("").Methods("PUT").HandlerFunc(MakeHTTPHandler(s.handleUpdateBookmark))
bookmarkRouter.Path("{id}").Methods("DELETE").HandlerFunc(MakeHTTPHandler(s.handleDeleteBookmark)) bookmarkRouter.Path("/{id}").Methods("DELETE").HandlerFunc(MakeHTTPHandler(s.handleDeleteBookmark))
bookmarkRouter.Path("/last").HandlerFunc(MakeHTTPHandler(s.handleGetLastBookmarks)) bookmarkRouter.Path("/last").HandlerFunc(MakeHTTPHandler(s.handleGetLastBookmarks))
bookmarkRouter.Path("/last/{count}").HandlerFunc(MakeHTTPHandler(s.handleGetLastBookmarks)) bookmarkRouter.Path("/last/{count}").HandlerFunc(MakeHTTPHandler(s.handleGetLastBookmarks))
bookmarkRouter.Path("/random").HandlerFunc(MakeHTTPHandler(s.handleGetRandomBookmarks)) bookmarkRouter.Path("/random").HandlerFunc(MakeHTTPHandler(s.handleGetRandomBookmarks))
@ -52,11 +52,11 @@ func (s Server) InitHandlers() {
folderRouter := apiRouter.PathPrefix("/f").Subrouter() folderRouter := apiRouter.PathPrefix("/f").Subrouter()
folderRouter.Use(s.AuthorizedOnly) folderRouter.Use(s.AuthorizedOnly)
folderRouter.Path("/{id}").Methods("GET").HandlerFunc(MakeHTTPHandler(s.handleGetFolder))
folderRouter.Path("").Methods("GET").HandlerFunc(MakeHTTPHandler(s.handleGetFolder)) folderRouter.Path("").Methods("GET").HandlerFunc(MakeHTTPHandler(s.handleGetFolder))
folderRouter.Path("").Methods("POST").HandlerFunc(MakeHTTPHandler(s.handleNewFolder)) folderRouter.Path("").Methods("POST").HandlerFunc(MakeHTTPHandler(s.handleNewFolder))
folderRouter.Path("").Methods("PUT").HandlerFunc(MakeHTTPHandler(s.handleUpdateFolder)) folderRouter.Path("").Methods("PUT").HandlerFunc(MakeHTTPHandler(s.handleUpdateFolder))
folderRouter.Path("{id}").Methods("GET").HandlerFunc(MakeHTTPHandler(s.handleGetFolder)) folderRouter.Path("/{id}").Methods("DELETE").HandlerFunc(MakeHTTPHandler(s.handleDeleteFolder))
folderRouter.Path("{id}").Methods("DELETE").HandlerFunc(MakeHTTPHandler(s.handleDeleteFolder))
folderRouter.Path("/{name}").HandlerFunc(MakeHTTPHandler(s.handleGetFolder)) folderRouter.Path("/{name}").HandlerFunc(MakeHTTPHandler(s.handleGetFolder))
r.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/"))) r.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))

View File

@ -15,8 +15,20 @@
<span></span> <span></span>
</template> </template>
<template id="newFolder">
<div class="card hoverable">
<h3><i data-feather-t="folder-plus"></i>New folder</h3>
</div>
</template>
<template id="newBookmark">
<div class="card hoverable">
<h3><i data-feather-t="file-plus"></i>New bookmark</h3>
</div>
</template>
<template id="folder"> <template id="folder">
<div class="card"> <div class="card hoverable">
<div class="xmark"> <div class="xmark">
<i data-feather-t="x"></i> <i data-feather-t="x"></i>
</div> </div>
@ -25,7 +37,7 @@
</template> </template>
<template id="bookmark"> <template id="bookmark">
<div class="card"> <div class="card hoverable">
<div class="xmark"> <div class="xmark">
<i data-feather-t="x"></i> <i data-feather-t="x"></i>
</div> </div>
@ -35,9 +47,42 @@
</div> </div>
</template> </template>
<template id="bookmark_form">
<div class="panel">
<div class="xmark">
<i data-feather="x" onclick="closeModal()"></i>
</div>
<h1>New bookmark</h1>
<form onsubmit="newBookmark(event)">
<input type="text" id="bookmark_name" placeholder="Name" />
<input type="url" id="bookmark_url" placeholder="http://example.com/" />
<input
type="text"
id="bookmark_tags"
placeholder="Tags, comma separated"
/>
<input type="submit" value="Create" />
</form>
</div>
</template>
<template id="folder_form">
<div class="panel">
<div class="xmark" onclick="closeModal()">
<i data-feather="x"></i>
</div>
<h1>New folder</h1>
<form onsubmit="newFolder(event)">
<input type="text" id="folder_name" placeholder="Name" />
<input type="submit" value="Create" />
</form>
</div>
</template>
<body> <body>
<div id="modal" onclick="closeModal(event)" class="modal-base"></div>
<div class="wrapper"> <div class="wrapper">
<h1>Dashboard protorype</h1> <h1 id="title">Dashboard protorype</h1>
<div id="bookmarks"></div> <div id="bookmarks"></div>
</div> </div>
</body> </body>

1832
static/js/lit.all.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,53 +2,73 @@ if (!localStorage.getItem("SessionID")) {
window.location.href = "/login.html"; window.location.href = "/login.html";
} }
const Q = (selector) => document.querySelector(selector);
window.addEventListener("load", async () => { window.addEventListener("load", async () => {
console.log("loading..."); await refreshFolder();
const rootFolder = await getFolder();
const bm = document.getElementById("bookmarks");
if (!rootFolder.success) {
bm.textContent = "Нет закладок";
return;
}
if (!bookmark || !folder) {
return;
}
for (let f of rootFolder.folder.ChildFolders) {
const clone = createFolderElement(f);
bm.appendChild(clone);
}
for (let b of rootFolder.folder.ChildBookmarks) {
const clone = createBookmarkElement(b);
bm.appendChild(clone);
}
feather.replace();
}); });
function mapFolder(folder, element) {
element.innerHTML = "";
if (folder.Folder.Name != "root") {
document.getElementById("title").textContent = folder.Folder.Name;
const uplevel = {
id: folder.Folder.ParentID,
Name: "Go back",
Persistent: true,
};
const clone = createFolderElement(uplevel);
element.appendChild(clone);
} else {
document.getElementById("title").textContent = "Dashboard Prototype";
}
for (let f of folder.ChildFolders) {
const clone = createFolderElement(f);
element.appendChild(clone);
}
for (let b of folder.ChildBookmarks) {
const clone = createBookmarkElement(b);
element.appendChild(clone);
}
element.appendChild(newFolderItem(folder));
element.appendChild(newBookmarkItem(folder));
feather.replace();
}
function createFolderElement(folder) { function createFolderElement(folder) {
const template = document.querySelector("#folder"); const template = Q("#folder");
const clone = template.content.firstElementChild.cloneNode(true); const clone = template.content.firstElementChild.cloneNode(true);
clone.querySelector("#folder_name").textContent = folder.Name; clone.querySelector("#folder_name").textContent = folder.Name;
clone.addEventListener("click", () => { clone.addEventListener("click", async () => {
alert(folder.Name); console.log(folder);
const newFolder = await getFolder(folder.id);
mapFolder(newFolder.folder, document.getElementById("bookmarks"));
}); });
for (let el of clone.querySelectorAll("[data-feather-t]")) { for (let el of clone.querySelectorAll("[data-feather-t]")) {
el.setAttribute("data-feather", el.getAttribute("data-feather-t")); el.setAttribute("data-feather", el.getAttribute("data-feather-t"));
} }
clone.querySelector(".xmark").addEventListener("click", (e) => { if (folder.Persistent) {
alert(`${folder.Name} was deleted`); clone.querySelector(".xmark").remove();
e.stopPropagation(); } else {
}); clone.querySelector(".xmark").addEventListener("click", async (e) => {
e.stopPropagation();
await delFolder(folder);
});
}
return clone; return clone;
} }
function createBookmarkElement(bookmark) { function createBookmarkElement(bookmark) {
const template = document.querySelector("#bookmark"); const template = Q("#bookmark");
const tagtempl = document.querySelector("#booktag"); const tagtempl = Q("#booktag");
const clone = template.content.firstElementChild.cloneNode(true); const clone = template.content.firstElementChild.cloneNode(true);
clone.querySelector("#bookmark_name").textContent = bookmark.Name; clone.querySelector("#bookmark_name").textContent = bookmark.Name;
const link = clone.querySelector("#bookmark_link"); const link = clone.querySelector("#bookmark_link");
link.setAttribute("href", bookmark.Url); link.setAttribute("href", bookmark.Url);
link.textContent = bookmark.Url; link.textContent = bookmark.Url;
clone.addEventListener("click", async () => {
window.location.href = bookmark.Url;
});
for (let tag of bookmark.Tags) { for (let tag of bookmark.Tags) {
const t = tagtempl.content.cloneNode(true); const t = tagtempl.content.cloneNode(true);
const el = t.querySelector("span"); const el = t.querySelector("span");
@ -59,9 +79,111 @@ function createBookmarkElement(bookmark) {
for (let el of clone.querySelectorAll("[data-feather-t]")) { for (let el of clone.querySelectorAll("[data-feather-t]")) {
el.setAttribute("data-feather", el.getAttribute("data-feather-t")); el.setAttribute("data-feather", el.getAttribute("data-feather-t"));
} }
clone.querySelector(".xmark").addEventListener("click", (e) => { clone.querySelector(".xmark").addEventListener("click", async (e) => {
alert(`${bookmark.Name} was deleted`);
e.stopPropagation(); e.stopPropagation();
await delBookmark(bookmark);
}); });
return clone; return clone;
} }
function newFolderItem(folder) {
const template = document.getElementById("newFolder");
const clone = template.content.firstElementChild.cloneNode(true);
clone.addEventListener("click", () => {
const folderForm = document.getElementById("folder_form");
const el = folderForm.content.firstElementChild.cloneNode(true);
el.querySelector("form").setAttribute("folderid", folder.Folder.id);
const m = Q("#modal");
m.appendChild(el);
m.classList.add("show");
// console.log("New folder called for " + folder.Folder.Name);
});
for (let el of clone.querySelectorAll("[data-feather-t]")) {
el.setAttribute("data-feather", el.getAttribute("data-feather-t"));
}
return clone;
}
function newBookmarkItem(folder) {
const template = document.getElementById("newBookmark");
const clone = template.content.firstElementChild.cloneNode(true);
clone.addEventListener("click", () => {
const bookmarkForm = document.getElementById("bookmark_form");
const el = bookmarkForm.content.firstElementChild.cloneNode(true);
el.querySelector("form").setAttribute("folderid", folder.Folder.id);
const m = Q("#modal");
m.appendChild(el);
m.classList.add("show");
});
for (let el of clone.querySelectorAll("[data-feather-t]")) {
el.setAttribute("data-feather", el.getAttribute("data-feather-t"));
}
return clone;
}
function closeModal(event) {
const m = Q("#modal");
if (event) if (event.target != m) return;
m.classList.remove("show");
m.innerHTML = "";
}
async function newFolder(event) {
event.preventDefault();
const form = event.target;
const parentID = form.getAttribute("folderid");
const name = form.querySelector("#folder_name").value;
console.log(name);
const result = await createFolder(parentID, name);
if (result.success) {
await refreshFolder(parentID);
closeModal();
}
}
async function newBookmark(event) {
event.preventDefault();
const form = event.target;
const parentID = form.getAttribute("folderid");
const name = form.querySelector("#bookmark_name").value;
const url = form.querySelector("#bookmark_url").value;
const tags = form.querySelector("#bookmark_tags").value.split(",");
console.log(name);
const result = await createBookmark(parentID, {
Name: name,
Url: url,
FolderID: parentID,
Tags: tags,
});
if (result.success) {
await refreshFolder(parentID);
closeModal();
}
}
async function refreshFolder(id) {
const folder = await getFolder(id);
const bm = document.getElementById("bookmarks");
if (!folder.success) {
bm.textContent = "Нет закладок";
return;
}
mapFolder(folder.folder, bm);
}
async function delFolder(folder) {
if (!confirm(`Delete folder "${folder.Name}"?`)) return;
const result = await deleteFolder(folder.id);
if (result.success) {
await refreshFolder(folder.ParentID);
}
}
async function delBookmark(bookmark) {
if (!confirm(`Delete bookmark "${bookmark.Name}"?`)) return;
const result = await deleteBookmark(bookmark.id);
if (result.success) {
await refreshFolder(bookmark.FolderID);
}
}

View File

@ -44,7 +44,7 @@ async function login(username, password) {
async function getFolder(id) { async function getFolder(id) {
let url = "f"; let url = "f";
if (id) url = `f/{id}`; if (id) url = `f/${id}`;
const r = request(url); const r = request(url);
try { try {
const result = await fetch(r); const result = await fetch(r);
@ -55,3 +55,53 @@ async function getFolder(id) {
return { success: false, err }; return { success: false, err };
} }
} }
async function createFolder(parent, name) {
if (name == "root") {
return { success: false, err: "can't create folder with that name" };
}
const r = request("f", "POST", {
ParentID: parent,
Name: name,
});
try {
const result = await fetch(r);
return { success: true };
} catch (err) {
console.error(err);
return { success: false, err };
}
}
async function createBookmark(parent, bookmark) {
const r = request("bookmark", "POST", bookmark);
try {
const result = await fetch(r);
return { success: true };
} catch (err) {
console.error(err);
return { success: false, err };
}
}
async function deleteFolder(id) {
const r = request(`f/${id}`, "DELETE");
try {
const result = await fetch(r);
return { success: true };
} catch (err) {
console.error(err);
return { success: false, err };
}
}
async function deleteBookmark(id) {
const r = request(`bookmark/${id}`, "DELETE");
try {
const result = await fetch(r);
return { success: true };
} catch (err) {
console.error(err);
return { success: false, err };
}
}

View File

@ -59,12 +59,13 @@ body {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
min-height: 100vh; min-height: 100vh;
max-width: 1400px;
margin: auto;
} }
.panel { .panel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: 1em;
color: var(--col-fg-1); color: var(--col-fg-1);
background-color: var(--col-bg-2); background-color: var(--col-bg-2);
box-shadow: 2px 2px 100px #1115; box-shadow: 2px 2px 100px #1115;
@ -74,7 +75,7 @@ body {
a { a {
text-decoration: none; text-decoration: none;
color: var(--col-acc-1); color: var(--col-acc-2);
} }
#bookmarks { #bookmarks {
@ -82,11 +83,13 @@ a {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
/* overflow-y: auto; */
} }
.card { .card {
display: flex; display: flex;
justify-content: center;
flex-direction: column; flex-direction: column;
border-radius: 1em; color: var(--col-fg-1);
background-color: var(--col-bg-2); background-color: var(--col-bg-2);
box-shadow: 2px 2px 100px #1115; box-shadow: 2px 2px 100px #1115;
text-align: left; text-align: left;
@ -95,6 +98,23 @@ a {
transition: all 200ms ease; transition: all 200ms ease;
position: relative; position: relative;
z-index: 0; z-index: 0;
/* width: 320px; */
height: 120px;
}
.card.hoverable::before {
content: "";
display: block;
position: absolute;
left: 0;
top: 0;
width: 0%;
transition: all 200ms ease;
height: 100%;
background-color: var(--col-acc-0);
z-index: -1;
}
.card.hoverable:hover::before {
width: 100%;
} }
.card .xmark { .card .xmark {
position: absolute; position: absolute;
@ -105,11 +125,7 @@ a {
right: 1em; right: 1em;
top: 1em; top: 1em;
} }
.card:hover {
box-shadow: 2px 2px 50px var(--col-acc-2);
transform: scale(1.1);
z-index: 1;
}
.tags { .tags {
font-size: 0.8em; font-size: 0.8em;
color: var(--col-fg-3); color: var(--col-fg-3);
@ -125,7 +141,6 @@ input {
background-color: var(--col-bg-3); background-color: var(--col-bg-3);
color: var(--col-fg-1); color: var(--col-fg-1);
border: none; border: none;
border-radius: 5px;
padding: 1em 1em; padding: 1em 1em;
font-size: 1em; font-size: 1em;
box-shadow: 2px 2px 50px #1115; box-shadow: 2px 2px 50px #1115;
@ -160,3 +175,25 @@ button:focus,
background-color: var(--col-acc-1); background-color: var(--col-acc-1);
color: white; color: white;
} }
.modal-base {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #0005;
opacity: 0;
backdrop-filter: blur(10px);
transition: all 300ms ease;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
/* display: none; */
pointer-events: none;
}
.modal-base.show {
opacity: 1;
pointer-events: all;
}