keyboard shortcuts, searchbar
This commit is contained in:
parent
c4de94d395
commit
012e86a155
|
@ -33,6 +33,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
|
<div class="searchbar-container">
|
||||||
|
<input type="search" name="note-search" id="note-searchbar" placeholder="Search...">
|
||||||
|
</div>
|
||||||
<div class="note-sidebar-container" id="note-sidebar-container">
|
<div class="note-sidebar-container" id="note-sidebar-container">
|
||||||
<!-- This is how the generated notes will look like:
|
<!-- This is how the generated notes will look like:
|
||||||
<div class="sidebar-note rightclick-element">
|
<div class="sidebar-note rightclick-element">
|
||||||
|
|
|
@ -136,6 +136,48 @@ pub fn edit_specific_note(id: i32, tag: &str, content: &str) -> Result<(), Strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Looks for matches in both content and tag.
|
||||||
|
pub fn search_notes(query: &str) -> Result<String, String> {
|
||||||
|
let home = home_dir().unwrap().join(".snotes.db");
|
||||||
|
let connection = Connection::open(home).map_err(|e| format!("Database Error: {}", e))?;
|
||||||
|
|
||||||
|
let query = format!(
|
||||||
|
"SELECT * FROM notes WHERE content LIKE '%{}%' OR tag LIKE '%{}%'",
|
||||||
|
query, query
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut prepare = connection.prepare(&query).map_err(|e| format!("Query Error: {}", e))?;
|
||||||
|
|
||||||
|
let notes = prepare
|
||||||
|
.query_map([], |row| {
|
||||||
|
Ok(Note {
|
||||||
|
id: row.get(0)?,
|
||||||
|
content: row.get(1)?,
|
||||||
|
date: row.get(2)?,
|
||||||
|
tag: row.get(3)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map_err(|e| format!("Mapping Error: {}", e))?;
|
||||||
|
|
||||||
|
let mut json_array = Vec::new();
|
||||||
|
|
||||||
|
for note in notes {
|
||||||
|
let unwrapped = note.map_err(|e| format!("Note Error: {}", e))?;
|
||||||
|
let note_json = json!({
|
||||||
|
"id": unwrapped.id,
|
||||||
|
"date": unwrapped.date,
|
||||||
|
"content": unwrapped.content,
|
||||||
|
"tag": unwrapped.tag
|
||||||
|
});
|
||||||
|
json_array.push(note_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
let json_string = serde_json::to_string(&json_array).map_err(|e| format!("JSON Error: {}", e))?;
|
||||||
|
|
||||||
|
Ok(json_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -17,24 +17,24 @@ fn get_notes_list() -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn create_note(content: &str, tag: &str) -> bool {
|
fn search_notes(query: &str) -> String {
|
||||||
println!("reached");
|
let results = libsnotes::search_notes(query).unwrap();
|
||||||
libsnotes::create_note(&content.to_string(), &tag.to_string()).unwrap();
|
results.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn create_note(content: &str, tag: &str) -> bool {
|
||||||
|
libsnotes::create_note(&content.to_string(), &tag.to_string()).unwrap();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn delete_specific_note(id: u32) -> bool {
|
fn delete_specific_note(id: u32) -> bool {
|
||||||
println!("reched Delete");
|
|
||||||
|
|
||||||
libsnotes::delete_specific_note(id.try_into().unwrap()).is_ok()
|
libsnotes::delete_specific_note(id.try_into().unwrap()).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn update_specific_note(id: u32, content: &str, tag: &str) -> bool {
|
fn update_specific_note(id: u32, content: &str, tag: &str) -> bool {
|
||||||
println!("update specific note");
|
|
||||||
|
|
||||||
libsnotes::edit_specific_note(id.try_into().unwrap(), tag, content).is_ok()
|
libsnotes::edit_specific_note(id.try_into().unwrap(), tag, content).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ fn main() {
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
greet,
|
greet,
|
||||||
get_notes_list,
|
get_notes_list,
|
||||||
|
search_notes,
|
||||||
create_note,
|
create_note,
|
||||||
delete_specific_note,
|
delete_specific_note,
|
||||||
update_specific_note
|
update_specific_note
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "snotes-deck",
|
"productName": "snotes-deck",
|
||||||
"version": "0.0.3"
|
"version": "0.0.4"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
|
108
src/main.ts
108
src/main.ts
|
@ -7,6 +7,7 @@ let notesMsgEl: HTMLElement | null;
|
||||||
let createNoteContentEl: HTMLTextAreaElement | null;
|
let createNoteContentEl: HTMLTextAreaElement | null;
|
||||||
let createNoteTagEl: HTMLInputElement | null;
|
let createNoteTagEl: HTMLInputElement | null;
|
||||||
|
|
||||||
|
let searchbarEl: HTMLInputElement | null;
|
||||||
let noteSidebarContainerEl: HTMLDivElement | null;
|
let noteSidebarContainerEl: HTMLDivElement | null;
|
||||||
|
|
||||||
let noteArray: Note[] = []
|
let noteArray: Note[] = []
|
||||||
|
@ -46,13 +47,14 @@ async function saveNote() {
|
||||||
content: createNoteContentEl.value,
|
content: createNoteContentEl.value,
|
||||||
tag: createNoteTagEl.value
|
tag: createNoteTagEl.value
|
||||||
});
|
});
|
||||||
clearEditor();
|
// do not clear the editor
|
||||||
|
//clearEditor();
|
||||||
} else {
|
} else {
|
||||||
console.error("No note is currently being edited");
|
console.error("No note is currently being edited");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
showNotes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,10 +85,15 @@ async function retrieveNotes(): Promise<Array<JSON>> {
|
||||||
return notesJson;
|
return notesJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle even listeners on load.
|
||||||
|
* This does not handle listeners for generated fields like
|
||||||
|
* the Notes in the sidebar.
|
||||||
|
*/
|
||||||
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");
|
||||||
// createMsgEl = document.querySelector("#create-msg");
|
searchbarEl = document.querySelector("#note-searchbar");
|
||||||
notesMsgEl = document.querySelector("#notes-list");
|
notesMsgEl = document.querySelector("#notes-list");
|
||||||
showNotes();
|
showNotes();
|
||||||
document.querySelector("#save-button")?.addEventListener("click", (e) => {
|
document.querySelector("#save-button")?.addEventListener("click", (e) => {
|
||||||
|
@ -98,10 +105,38 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
clearEditor();
|
clearEditor();
|
||||||
showNotes();
|
showNotes();
|
||||||
})
|
});
|
||||||
document.querySelector("#show-notes-button")?.addEventListener("click", (e) => {
|
document.querySelector("#show-notes-button")?.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showNotes();
|
showNotes();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pressing TAB should insert intends in the editor.
|
||||||
|
// This could potentially cause issues later...
|
||||||
|
document.querySelector("#create-input")?.addEventListener("keydown", (event: Event) => {
|
||||||
|
const e = event as KeyboardEvent;
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
const target = e.target as HTMLTextAreaElement;
|
||||||
|
const start = target.selectionStart;
|
||||||
|
const end = target.selectionEnd;
|
||||||
|
|
||||||
|
const newValue = target.value.substring(0, start) +
|
||||||
|
"\t" + target.value.substring(end);
|
||||||
|
|
||||||
|
target.value = newValue;
|
||||||
|
|
||||||
|
target.setSelectionRange(start + 1, start + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// searchbar event listener
|
||||||
|
|
||||||
|
document.querySelector("#note-searchbar")?.addEventListener("input", (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
const input = target.value;
|
||||||
|
|
||||||
|
searchNote(input);
|
||||||
})
|
})
|
||||||
|
|
||||||
refreshContextMenuElements();
|
refreshContextMenuElements();
|
||||||
|
@ -274,3 +309,68 @@ function clearEditor() {
|
||||||
editorState = EditorState.NEW;
|
editorState = EditorState.NEW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for global key presses
|
||||||
|
document.addEventListener('keydown', (e) => handleKeyboardShortcuts(e));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle global keyboard shortcuts like save, search, new
|
||||||
|
*/
|
||||||
|
function handleKeyboardShortcuts(event: KeyboardEvent) {
|
||||||
|
// save
|
||||||
|
if (event.ctrlKey && event.key === 's') {
|
||||||
|
event.preventDefault();
|
||||||
|
saveNote();
|
||||||
|
}
|
||||||
|
// new
|
||||||
|
if (event.ctrlKey && event.key === 'n') {
|
||||||
|
event.preventDefault();
|
||||||
|
clearEditor();
|
||||||
|
}
|
||||||
|
// refresh
|
||||||
|
if (event.ctrlKey && event.key === 'r') {
|
||||||
|
event.preventDefault();
|
||||||
|
showNotes();
|
||||||
|
}
|
||||||
|
// focus searchbox
|
||||||
|
if (event.ctrlKey && event.key === 'f') {
|
||||||
|
event.preventDefault();
|
||||||
|
if (searchbarEl) {
|
||||||
|
searchbarEl.focus();
|
||||||
|
} else {
|
||||||
|
console.error("failed to focus on searchbar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// open by id
|
||||||
|
// quick switch note 1-9
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for note and displays the results accordingly
|
||||||
|
*/
|
||||||
|
async function searchNote(input: string) {
|
||||||
|
if (notesMsgEl) {
|
||||||
|
const array: Array<any> = await getSearchResults(input);
|
||||||
|
|
||||||
|
noteArray = array.map((jsonObj) => ({
|
||||||
|
id: jsonObj.id,
|
||||||
|
content: jsonObj.content,
|
||||||
|
date: jsonObj.date,
|
||||||
|
tag: jsonObj.tag
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(noteArray[0])
|
||||||
|
|
||||||
|
fillNoteSidebar(noteArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSearchResults(input: string): Promise<Array<JSON>> {
|
||||||
|
const resultsString: string = await invoke("search_notes", {
|
||||||
|
query: input
|
||||||
|
});
|
||||||
|
const resultsJson = JSON.parse(resultsString);
|
||||||
|
console.log(resultsJson);
|
||||||
|
return resultsJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
|
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#button-row {
|
#button-row {
|
||||||
|
@ -31,7 +33,7 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
height: 80vh;
|
height: 90vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
|
@ -42,6 +44,7 @@
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
|
@ -104,7 +107,7 @@ button:active {
|
||||||
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #3b3b3b;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
@ -112,7 +115,9 @@ button:active {
|
||||||
|
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||||
background-color: #252525;
|
background-color: #252525;
|
||||||
color: #f0f0f0
|
color: #f0f0f0;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
#create-input:focus {
|
#create-input:focus {
|
||||||
|
@ -145,7 +150,7 @@ button {
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-sidebar-container {
|
.note-sidebar-container {
|
||||||
max-height: 100%;
|
max-height: 90%;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,6 +208,24 @@ button {
|
||||||
background-color: #770079;
|
background-color: #770079;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Searchbar */
|
||||||
|
|
||||||
|
.searchbar-container {
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#note-searchbar {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#note-searchbar::placeholder {}
|
||||||
|
|
||||||
/* MISC */
|
/* MISC */
|
||||||
|
|
||||||
/* Fancier Scrollbar */
|
/* Fancier Scrollbar */
|
||||||
|
|
Loading…
Reference in New Issue