delete function and menu, open existing note

This commit is contained in:
catto 2024-04-13 20:51:49 +02:00
parent b4f58c84d6
commit a5e6806aca
6 changed files with 186 additions and 61 deletions

View File

@ -20,6 +20,9 @@
<body> <body>
<div class="top-bar"> <div class="top-bar">
<div class="menu" id="contextMenu">
<button id="deleteButton">Delete</button>
</div>
<h1>Snotes Deck</h1> <h1>Snotes Deck</h1>
<div id="button-row"> <div id="button-row">
@ -39,12 +42,15 @@
</div> </div>
<div class="editor"> <div class="editor">
<input id="create-tag" placeholder="Tag..." /> <input id="create-tag" placeholder="Tag..." />
<textarea id="create-input" placeholder="Note..." ></textarea> <textarea id="create-input" placeholder="Note..."></textarea>
<p id="create-msg"></p> <p id="create-msg"></p>
<p style="white-space: pre-line" id="notes-list"></p>
</div>
<p style="white-space: pre-line" id="notes-list"></p>
</div>
</body> </body>

View File

@ -10,7 +10,6 @@ pub struct Note {
tag: String, tag: String,
} }
pub fn init_db() -> Result<()> { pub fn init_db() -> Result<()> {
let home = home_dir().unwrap().join(".snotes.db"); let home = home_dir().unwrap().join(".snotes.db");
let connection = Connection::open(home)?; let connection = Connection::open(home)?;
@ -64,19 +63,21 @@ pub fn show_notes(all: bool, tag: &str) -> Result<String, String> {
} }
if !tag.is_empty() { if !tag.is_empty() {
query = format!("SELECT * FROM notes WHERE tag IS '{}'", tag); query = format!("SELECT * FROM notes WHERE tag IS '{tag}'");
} }
let mut prepare = connection.prepare(&query).unwrap(); let mut prepare = connection.prepare(&query).unwrap();
let notes = prepare.query_map([], |row| { let notes = prepare
Ok(Note { .query_map([], |row| {
id: row.get(0)?, Ok(Note {
content: row.get(1)?, id: row.get(0)?,
date: row.get(2)?, content: row.get(1)?,
tag: row.get(3)?, date: row.get(2)?,
tag: row.get(3)?,
})
}) })
}).unwrap(); .unwrap();
let mut json_array = Vec::new(); let mut json_array = Vec::new();
@ -96,20 +97,32 @@ pub fn show_notes(all: bool, tag: &str) -> Result<String, String> {
Ok(json_string) Ok(json_string)
} }
pub fn delete_latest_note() -> Result<()> { pub fn delete_latest_note() -> Result<(), String> {
let home = home_dir().unwrap().join(".snotes.db"); let home = home_dir().unwrap().join(".snotes.db");
let connection = Connection::open(home)?; let connection = Connection::open(home).map_err(|e| format!("Database Error: {e}"))?;
let query = String::from("DELETE FROM NOTES WHERE nid = (SELECT MAX(nid) FROM notes)"); let query = String::from("DELETE FROM NOTES WHERE nid = (SELECT MAX(nid) FROM notes)");
match connection.execute(&query, []) { match connection.execute(&query, []) {
Ok(v) => println!("DELETE OK {}", v), Ok(v) => {
Err(e) => println!("DELETE ERR {}", e), println!("DELETE OK {}", v);
Ok(())
}
Err(e) => Err(format!("Delete Error: {e}")),
} }
Ok(())
} }
pub fn delete_specific_note(id: i32) -> Result<(), String> {
let home = home_dir().unwrap().join(".snotes.db");
let connection = Connection::open(home).map_err(|e| format!("Database Error: {e}"))?;
let query = "DELETE FROM notes WHERE nid = ?1";
match connection.execute(query, [id]) {
Ok(1) => Ok(()), // 1 row affected means the note was deleted successfully
Ok(_) => Err("No note with the provided ID found.".to_string()),
Err(e) => Err(format!("Delete Error: {e}")),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -11,8 +11,8 @@ fn greet(name: &str) -> String {
#[tauri::command] #[tauri::command]
fn get_notes_list() -> String { fn get_notes_list() -> String {
let notes = show_notes(false, &String::new()).unwrap(); let notes = show_notes(false, "").unwrap();
format!("{}", notes) notes.to_string()
} }
#[tauri::command] #[tauri::command]
@ -23,9 +23,16 @@ fn create_note(content: &str, tag: &str) -> bool {
true true
} }
#[tauri::command]
fn delete_specific_note(id: u32) -> bool {
println!("reched Delete");
libsnotes::delete_specific_note(id.try_into().unwrap()).is_ok()
}
fn main() { fn main() {
tauri::Builder::default() tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, get_notes_list, create_note]) .invoke_handler(tauri::generate_handler![greet, get_notes_list, create_note, delete_specific_note])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

View File

@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "snotes-deck", "productName": "snotes-deck",
"version": "0.0.1" "version": "0.0.2"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@ -3,13 +3,14 @@ import { Note } from "./model";
let notesMsgEl: HTMLElement | null; let notesMsgEl: HTMLElement | null;
//let createMsgEl: HTMLElement | null;
let createNoteContentEl: HTMLTextAreaElement | null; let createNoteContentEl: HTMLTextAreaElement | null;
let createNoteTagEl: HTMLInputElement | null; let createNoteTagEl: HTMLInputElement | null;
let noteSidebarContainerEl: HTMLDivElement | null; let noteSidebarContainerEl: HTMLDivElement | null;
let noteArray: Note[] = []
// create // create
async function createNote() { async function createNote() {
console.log("reached ssssjs") console.log("reached ssssjs")
@ -25,13 +26,9 @@ async function createNote() {
// read // read
async function showNotes() { async function showNotes() {
if (notesMsgEl) { if (notesMsgEl) {
//const notesJson: string = await invoke("get_notes_list");
//const formattedJson = JSON.stringify(JSON.parse(notesJson), null, 2); // Indentation of 2 spaces
//notesMsgEl.textContent = formattedJson;
const array: Array<any> = await retrieveNotes(); const array: Array<any> = await retrieveNotes();
const noteArray: Note[] = array.map((jsonObj) => ({ noteArray = array.map((jsonObj) => ({
id: jsonObj.id, id: jsonObj.id,
content: jsonObj.content, content: jsonObj.content,
date: jsonObj.date, date: jsonObj.date,
@ -51,31 +48,6 @@ async function retrieveNotes(): Promise<Array<JSON>> {
return notesJson; return notesJson;
} }
// TODO: read better array of note elements with id iterable the whole thing
// update
// async function updateNote() {
// if (true) {
// await invoke("update_note", {
// id: null,
// content: null,
// tag: null
// });
// }
// }
// delete
// async function deleteNote() {
// if (true) {
// await invoke("delete_note", {
// id: null
// }
// )
// }
// }
window.addEventListener("DOMContentLoaded", () => { window.addEventListener("DOMContentLoaded", () => {
createNoteContentEl = document.querySelector("#create-input"); createNoteContentEl = document.querySelector("#create-input");
createNoteTagEl = document.querySelector("#create-tag"); createNoteTagEl = document.querySelector("#create-tag");
@ -91,8 +63,57 @@ window.addEventListener("DOMContentLoaded", () => {
e.preventDefault(); e.preventDefault();
showNotes(); showNotes();
}) })
refreshContextMenuElements();
}); });
/**
* We need to add new event listeners every time we refresh the note list
*/
function refreshContextMenuElements() {
const elements: NodeListOf<HTMLElement> = document.querySelectorAll(".rightclick-element")
const contextMenu = document.getElementById('contextMenu');
if (contextMenu) {
elements.forEach(element => {
element.addEventListener("contextmenu", (e: MouseEvent) => {
e.preventDefault();
// get the position with mouse and everything
const mouseX = e.clientX;
const mouseY = e.clientY;
// Calculate the position of the context menu relative to the mouse cursor
const menuWidth = contextMenu.offsetWidth;
const menuHeight = contextMenu.offsetHeight;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let posX = mouseX + menuWidth > viewportWidth ? mouseX - menuWidth : mouseX;
let posY = mouseY + menuHeight > viewportHeight ? mouseY - menuHeight : mouseY;
contextMenu.style.display = 'block'; // Show the custom context menu
contextMenu.style.left = `${posX}px`;
contextMenu.style.top = `${posY}px`;
const noteIdElement = element.querySelector('.sidebar-note-id');
if (noteIdElement) {
const noteIdStr = noteIdElement.textContent;
//console.log('Right-clicked note id:', noteId);
if (noteIdStr) {
const noteId: Number = parseInt(noteIdStr);
showNoteSidebarContextMenu(noteId);
}
} else {
console.error('.sidebar-note-id element not found within the note.');
}
});
})
}
}
function fillNoteSidebar(noteArray: Note[]) { function fillNoteSidebar(noteArray: Note[]) {
@ -106,6 +127,7 @@ function fillNoteSidebar(noteArray: Note[]) {
// Create HTML elements for each note // Create HTML elements for each note
const noteEl: HTMLDivElement = document.createElement('div'); const noteEl: HTMLDivElement = document.createElement('div');
noteEl.classList.add('sidebar-note'); noteEl.classList.add('sidebar-note');
noteEl.classList.add('rightclick-element');
noteEl.addEventListener("click", () => handleSidebarNoteClick(note.id), false); noteEl.addEventListener("click", () => handleSidebarNoteClick(note.id), false);
const idSpan: HTMLSpanElement = document.createElement('span'); const idSpan: HTMLSpanElement = document.createElement('span');
@ -131,11 +153,61 @@ function fillNoteSidebar(noteArray: Note[]) {
// Append noteEl to the container, if it still exists? // Append noteEl to the container, if it still exists?
noteSidebarContainerEl ? noteSidebarContainerEl.appendChild(noteEl) : null; noteSidebarContainerEl ? noteSidebarContainerEl.appendChild(noteEl) : null;
}); });
refreshContextMenuElements();
} }
} }
function handleSidebarNoteClick(id: Number): any { function handleSidebarNoteClick(id: Number): any {
console.log("huh " + id); console.log("huh " + id);
if (createNoteContentEl && createNoteTagEl) {
// search for note
let n: Note = {
id: 0,
content: "undefined",
date: "undefined",
tag: "undefined"
};
noteArray.forEach(note => {
if (note.id === id) {
n = note;
}
});
if (n) {
createNoteContentEl.value = n.content as string;
createNoteTagEl.value = n.tag as string;
} else {
// don't destory currently editing note if this fails
console.error("Error fetching note");
}
}
}
function showNoteSidebarContextMenu(noteId: Number) {
const contextMenu = document.getElementById('contextMenu');
const deleteButton = document.getElementById('deleteButton');
if (contextMenu && deleteButton) {
deleteButton.addEventListener('click', async function () {
console.log('Deleting...');
await invoke("delete_specific_note", {
id: noteId
});
// hide after delete
contextMenu.style.display = 'none';
showNotes();
});
// hide when clicking outside of it
document.addEventListener('click', function (event) {
if (!contextMenu.contains(event.target as Node)) {
contextMenu.style.display = 'none';
}
});
}
} }

View File

@ -90,11 +90,11 @@ button:active {
background-color: #e8e8e8; background-color: #e8e8e8;
} }
#create-tag{ #create-tag {
margin: 0.5em; margin: 0.5em;
} }
#create-input{ #create-input {
margin: 0.5em; margin: 0.5em;
height: 100%; height: 100%;
@ -114,7 +114,8 @@ button:active {
#create-input:focus { #create-input:focus {
border-color: #cf66e9; border-color: #cf66e9;
box-shadow: 0 0 5px rgba(102, 175, 233, 0.6); box-shadow: 0 0 5px rgba(102, 175, 233, 0.6);
outline: none; /* Remove default focus outline */ outline: none;
/* Remove default focus outline */
} }
input, input,
@ -166,3 +167,29 @@ button {
padding: 2px 6px; padding: 2px 6px;
border-radius: 4px; border-radius: 4px;
} }
/* CONTEXT MENU */
.context-menu {
position: relative;
}
.menu {
display: none;
position: absolute;
z-index: 999;
border: 1px solid #ccc;
padding: 5px;
}
.menu button {
display: block;
background-color: transparent;
border: none;
cursor: pointer;
padding: 5px;
}
.menu button:hover {
background-color: #770079;
}