Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
catto | b09d1eacbf | |
catto | f4d6b920ab | |
catto | 60339d8f5c |
21
README.md
21
README.md
|
@ -5,3 +5,24 @@ This template should help get you started developing with Tauri in vanilla HTML,
|
||||||
## Recommended IDE Setup
|
## Recommended IDE Setup
|
||||||
|
|
||||||
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||||
|
|
||||||
|
# Android Build
|
||||||
|
https://v2.tauri.app/start/prerequisites/#android
|
||||||
|
|
||||||
|
Use the SDK Manager in Android Studio to install the following:
|
||||||
|
|
||||||
|
Android SDK Platform
|
||||||
|
Android SDK Platform-Tools
|
||||||
|
NDK (Side by side)
|
||||||
|
Android SDK Build-Tools
|
||||||
|
Android SDK Command-line Tools
|
||||||
|
|
||||||
|
|
||||||
|
`export JAVA_HOME=/opt/android-studio/jbr`
|
||||||
|
|
||||||
|
```
|
||||||
|
export ANDROID_HOME="$HOME/Android/Sdk"
|
||||||
|
export NDK_HOME="$ANDROID_HOME/ndk/$(ls -1 $ANDROID_HOME/ndk)"
|
||||||
|
```
|
||||||
|
|
||||||
|
`rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android`
|
206
index.html
206
index.html
|
@ -1,95 +1,143 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="/src/styles.css" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Snotes</title>
|
||||||
|
<script type="module" src="/src/main.ts" defer></script>
|
||||||
|
<style>
|
||||||
|
.logo.vite:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #747bff);
|
||||||
|
}
|
||||||
|
|
||||||
<head>
|
.logo.typescript:hover {
|
||||||
<meta charset="UTF-8" />
|
filter: drop-shadow(0 0 2em #2d79c7);
|
||||||
<link rel="stylesheet" href="/src/styles.css" />
|
}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
</style>
|
||||||
<title>Snotes Deck</title>
|
</head>
|
||||||
<script type="module" src="/src/main.ts" defer></script>
|
|
||||||
<style>
|
|
||||||
.logo.vite:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #747bff);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo.typescript:hover {
|
<body>
|
||||||
filter: drop-shadow(0 0 2em #2d79c7);
|
<div class="menu" id="contextMenu">
|
||||||
}
|
<button id="deleteButton">Delete</button>
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="menu" id="contextMenu">
|
|
||||||
<button id="deleteButton">Delete</button>
|
|
||||||
</div>
|
|
||||||
<div class="top-bar">
|
|
||||||
<div id="button-row">
|
|
||||||
<button class="row" id="show-notes-button">Refresh Notes</button>
|
|
||||||
<button class="row" type="submit" id="save-button">Save</button>
|
|
||||||
<button class="row" id="new-button">New</button>
|
|
||||||
<button class="row" id="image-button">OCR</button>
|
|
||||||
<button class="row" id="export-button">Export</button>
|
|
||||||
<button class="row" id="settings-button">Settings</button>
|
|
||||||
<input type="file" id="fileInput" accept="image/*" style="display: none;" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="top-bar">
|
||||||
<div class="container">
|
<div id="button-row">
|
||||||
<div class="sidebar">
|
<button class="row" id="show-notes-button">Refresh Notes</button>
|
||||||
<div class="resizable-handle"></div>
|
<button class="row" type="submit" id="save-button">Save</button>
|
||||||
<div class="searchbar-container">
|
<button class="row" id="new-button">New</button>
|
||||||
<img id="reverse-icon-asc" src="./src/assets/sort-from-bottom-to-top.svg">
|
<button class="row" id="image-button">OCR</button>
|
||||||
<img id="reverse-icon-desc" src="./src/assets/sort-from-top-to-bottom.svg">
|
<button class="row" id="export-button">Export</button>
|
||||||
<input type="text" name="note-search" id="note-searchbar" placeholder="Search...">
|
<button class="row" id="settings-button">Settings</button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="fileInput"
|
||||||
|
accept="image/*"
|
||||||
|
style="display: none"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="note-sidebar-container unselectable" id="note-sidebar-container">
|
</div>
|
||||||
<!-- This is how the generated notes will look like:
|
<div class="container">
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="resizable-handle"></div>
|
||||||
|
<div class="searchbar-container">
|
||||||
|
<img
|
||||||
|
id="reverse-icon-asc"
|
||||||
|
src="./src/assets/sort-from-bottom-to-top.svg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
id="reverse-icon-desc"
|
||||||
|
src="./src/assets/sort-from-top-to-bottom.svg"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="note-search"
|
||||||
|
id="note-searchbar"
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="note-sidebar-container unselectable"
|
||||||
|
id="note-sidebar-container"
|
||||||
|
>
|
||||||
|
<!-- This is how the generated notes will look like:
|
||||||
<div class="sidebar-note rightclick-element">
|
<div class="sidebar-note rightclick-element">
|
||||||
<span class="sidebar-note-id">1</span>
|
<span class="sidebar-note-id">1</span>
|
||||||
<span class="sidebar-note-content">Lorem ipsum dolor sit amet...</span>
|
<span class="sidebar-note-content">Lorem ipsum dolor sit amet...</span>
|
||||||
<span class="sidebar-note-tag">Tag</span>
|
<span class="sidebar-note-tag">Tag</span>
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="editor">
|
|
||||||
<input autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" id="create-tag"
|
|
||||||
placeholder="Tag..." />
|
|
||||||
|
|
||||||
<textarea autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" id="create-input"
|
|
||||||
placeholder="Type your note here..."></textarea>
|
|
||||||
|
|
||||||
<p id="create-msg"></p>
|
|
||||||
|
|
||||||
<p style="white-space: pre-line" id="notes-list"></p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="id-modal-bg"></div>
|
|
||||||
<div id="id-modal-container">
|
|
||||||
<div class="openbyid-bar-container">
|
|
||||||
<input type="text" name="id-search" id="id-search" placeholder="Note ID...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="settings-modal-container">
|
|
||||||
<div class="settings-flex">
|
|
||||||
<div class="fontsize-setting">
|
|
||||||
<h3>Font Size (in px)</h3>
|
|
||||||
<input type="number" name="fontsize" id="fontsize-setting-input" placeholder="">
|
|
||||||
</div>
|
</div>
|
||||||
<div id="ocrlanguage-setting">
|
</div>
|
||||||
<div>
|
<div class="editor">
|
||||||
<h3>OCR Language</h3> <a style="display: inline;" href="https://tesseract-ocr.github.io/tessdoc/Data-Files"
|
<input
|
||||||
target="_blank">Language
|
autocomplete="off"
|
||||||
Codes</a>
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
spellcheck="false"
|
||||||
|
id="create-tag"
|
||||||
|
placeholder="Tag..."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
spellcheck="false"
|
||||||
|
id="create-input"
|
||||||
|
placeholder="Type your note here..."
|
||||||
|
></textarea>
|
||||||
|
|
||||||
|
<p id="create-msg"></p>
|
||||||
|
|
||||||
|
<p style="white-space: pre-line" id="notes-list"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="id-modal-bg"></div>
|
||||||
|
<div id="id-modal-container">
|
||||||
|
<div class="openbyid-bar-container">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="id-search"
|
||||||
|
id="id-search"
|
||||||
|
placeholder="Note ID..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="settings-modal-container">
|
||||||
|
<div class="settings-flex">
|
||||||
|
<h2 class="row"><code>SNOTES</code></h2>
|
||||||
|
<p><small id="app-version"></small></p>
|
||||||
|
<div class="fontsize-setting">
|
||||||
|
<h3>Font Size (in px)</h3>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="fontsize"
|
||||||
|
id="fontsize-setting-input"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="ocrlanguage" id="ocr-language-setting-input" placeholder="eng">
|
<div id="ocrlanguage-setting">
|
||||||
|
<div>
|
||||||
|
<h3>OCR Language</h3>
|
||||||
|
<a
|
||||||
|
style="display: inline"
|
||||||
|
href="https://tesseract-ocr.github.io/tessdoc/Data-Files"
|
||||||
|
target="_blank"
|
||||||
|
>Language Codes</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="ocrlanguage"
|
||||||
|
id="ocr-language-setting-input"
|
||||||
|
placeholder="eng"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button class="save-button" id="save-settings-button">Save</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="save-button" id="save-settings-button">Save</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -176,6 +176,8 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"home",
|
"home",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -196,6 +198,12 @@ version = "0.4.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.18"
|
version = "0.2.18"
|
||||||
|
@ -219,9 +227,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.79"
|
version = "1.0.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -257,18 +265,18 @@ checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.197"
|
version = "1.0.215"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.197"
|
version = "1.0.215"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -277,11 +285,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.115"
|
version = "1.0.133"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -294,9 +303,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.58"
|
version = "2.0.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -8,6 +8,8 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.37"
|
chrono = "0.4.37"
|
||||||
home = "0.5.9"
|
home = "0.5.9"
|
||||||
|
serde = "1.0.215"
|
||||||
|
serde_derive = "1.0.215"
|
||||||
serde_json = "1.0.115"
|
serde_json = "1.0.115"
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
version = "0.31.0"
|
version = "0.31.0"
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub fn create_note(content: &String, tag: &String) -> Result<()> {
|
||||||
|
|
||||||
let query_insert = "INSERT INTO notes (content, date, tag) VALUES (?1, ?2, ?3)";
|
let query_insert = "INSERT INTO notes (content, date, tag) VALUES (?1, ?2, ?3)";
|
||||||
|
|
||||||
match connection.execute(query_insert, [&content, &date_string, &tag_string]) {
|
match connection.execute(query_insert, [content, &date_string, &tag_string]) {
|
||||||
Ok(v) => println!("CREATE OK {}", v),
|
Ok(v) => println!("CREATE OK {}", v),
|
||||||
Err(e) => println!("CREATE ERR {}", e),
|
Err(e) => println!("CREATE ERR {}", e),
|
||||||
};
|
};
|
||||||
|
@ -239,7 +239,7 @@ pub fn get_note_by_id(id: u32) -> Result<String, String> {
|
||||||
let db = get_db_dir();
|
let db = get_db_dir();
|
||||||
let connection = Connection::open(db).map_err(|e| format!("Database Error: {}", e))?;
|
let connection = Connection::open(db).map_err(|e| format!("Database Error: {}", e))?;
|
||||||
|
|
||||||
let query = format!("SELECT * FROM notes WHERE nid IS {};", id.to_string());
|
let query = format!("SELECT * FROM notes WHERE nid IS {};", id);
|
||||||
let mut prepare = connection
|
let mut prepare = connection
|
||||||
.prepare(&query)
|
.prepare(&query)
|
||||||
.map_err(|e| format!("Query Error: {}", e))?;
|
.map_err(|e| format!("Query Error: {}", e))?;
|
||||||
|
@ -274,6 +274,42 @@ pub fn get_note_by_id(id: u32) -> Result<String, String> {
|
||||||
Ok(json_string)
|
Ok(json_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all notes as a json
|
||||||
|
pub fn export_all() -> Result<String, String> {
|
||||||
|
let db = get_db_dir();
|
||||||
|
let connection = Connection::open(db).map_err(|e| format!("Database Error: {}", e))?;
|
||||||
|
|
||||||
|
let query = "SELECT * FROM notes";
|
||||||
|
let mut prepare = connection.prepare(query).unwrap();
|
||||||
|
|
||||||
|
let notes = prepare
|
||||||
|
.query_map([], |row| {
|
||||||
|
Ok(Note {
|
||||||
|
id: row.get(0)?,
|
||||||
|
content: row.get(1)?,
|
||||||
|
date: row.get(2)?,
|
||||||
|
tag: row.get(3)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut json_array = Vec::new();
|
||||||
|
|
||||||
|
for note in notes {
|
||||||
|
let unwrapped = note.unwrap();
|
||||||
|
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_pretty(&json_array).unwrap();
|
||||||
|
Ok(json_string)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -289,6 +325,5 @@ mod tests {
|
||||||
fn test_id_10() {
|
fn test_id_10() {
|
||||||
let result = get_note_by_id(10).unwrap();
|
let result = get_note_by_id(10).unwrap();
|
||||||
println!("{}", result);
|
println!("{}", result);
|
||||||
assert!(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2052,6 +2052,8 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"home",
|
"home",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3304,9 +3306,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.132"
|
version = "1.0.133"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.11",
|
"itoa 1.0.11",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -3484,7 +3486,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "snotes-deck"
|
name = "snotes-deck"
|
||||||
version = "0.0.0"
|
version = "0.0.15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"home",
|
"home",
|
||||||
"libsnotes",
|
"libsnotes",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "snotes-deck"
|
name = "snotes-deck"
|
||||||
version = "0.0.0"
|
version = "0.0.15"
|
||||||
description = "A simple little Note App"
|
description = "A simple little Note App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
|
@ -5,6 +5,22 @@ use std::fs;
|
||||||
|
|
||||||
use home::home_dir;
|
use home::home_dir;
|
||||||
use libsnotes::show_notes;
|
use libsnotes::show_notes;
|
||||||
|
use tauri::{
|
||||||
|
menu::{AboutMetadata, MenuBuilder, MenuItemBuilder, SubmenuBuilder},
|
||||||
|
Emitter,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn export_all_notes() -> String {
|
||||||
|
let res = libsnotes::export_all().unwrap();
|
||||||
|
println!("{}", &res);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_app_version() -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn init_db() {
|
fn init_db() {
|
||||||
|
@ -103,10 +119,41 @@ fn save_settings(settings: String) {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
|
app.handle();
|
||||||
|
|
||||||
|
let settings = MenuItemBuilder::new("Export All")
|
||||||
|
.id("exportall")
|
||||||
|
//.accelerator("CmdOrCtrl+,")
|
||||||
|
.build(app)?;
|
||||||
|
|
||||||
|
let app_submenu = SubmenuBuilder::new(app, "File")
|
||||||
|
.about(Some(AboutMetadata {
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.separator()
|
||||||
|
.item(&settings)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let menu = MenuBuilder::new(app).items(&[&app_submenu]).build()?;
|
||||||
|
|
||||||
|
app.set_menu(menu)?;
|
||||||
|
|
||||||
|
app.on_menu_event(move |app, event| {
|
||||||
|
if event.id() == settings.id() {
|
||||||
|
// emit a window event to the frontend
|
||||||
|
let _event = app.emit("custom-event", "/exportall");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
export_all_notes,
|
||||||
|
get_app_version,
|
||||||
init_db,
|
init_db,
|
||||||
get_notes_list,
|
get_notes_list,
|
||||||
get_latest_note,
|
get_latest_note,
|
||||||
|
|
|
@ -18,14 +18,14 @@
|
||||||
},
|
},
|
||||||
"productName": "snotes-deck",
|
"productName": "snotes-deck",
|
||||||
"mainBinaryName": "snotes-deck",
|
"mainBinaryName": "snotes-deck",
|
||||||
"version": "0.0.13",
|
"version": "0.0.15",
|
||||||
"identifier": "space.maidsin.snotes-deck",
|
"identifier": "space.maidsin.snotes-deck",
|
||||||
"plugins": {},
|
"plugins": {},
|
||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Snotes Deck",
|
"title": "Snotes",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 600,
|
"height": 600,
|
||||||
"useHttpsScheme": true
|
"useHttpsScheme": true
|
||||||
|
|
277
src/main.ts
277
src/main.ts
|
@ -24,6 +24,8 @@ let idModalActive = false;
|
||||||
|
|
||||||
let typingTimer: number | null = null;
|
let typingTimer: number | null = null;
|
||||||
const AUTOSAVE_DELAY = 5000;
|
const AUTOSAVE_DELAY = 5000;
|
||||||
|
const BACKGROUND_COLOR = "#252525";
|
||||||
|
const BACKGROUND_COLOR_HOVER = "#5C5C5C";
|
||||||
|
|
||||||
enum EditorState {
|
enum EditorState {
|
||||||
NEW,
|
NEW,
|
||||||
|
@ -519,8 +521,24 @@ function handleKeyboardShortcuts(event: KeyboardEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
showNotes();
|
showNotes();
|
||||||
}
|
}
|
||||||
|
// focus editor
|
||||||
|
if (event.ctrlKey && event.key === "h") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (createNoteContentEl) {
|
||||||
|
createNoteContentEl.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// focus tags
|
||||||
|
if (event.ctrlKey && event.key === "j") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (createNoteTagEl) {
|
||||||
|
createNoteTagEl.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: if we're focused the searchbox, arrow down will focus on the first result in the list
|
||||||
|
|
||||||
// focus searchbox
|
// focus searchbox
|
||||||
if (event.ctrlKey && event.key === "f") {
|
if (event.ctrlKey && event.key === "p") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (searchbarEl) {
|
if (searchbarEl) {
|
||||||
searchbarEl.focus();
|
searchbarEl.focus();
|
||||||
|
@ -572,8 +590,229 @@ function handleKeyboardShortcuts(event: KeyboardEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// quick switch note 1-9
|
// quick switch note 1-9
|
||||||
|
if (event.ctrlKey && event.key === "f") {
|
||||||
|
openSearchModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface SearchResult {
|
||||||
|
text: string;
|
||||||
|
startIndex: number;
|
||||||
|
endIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: when clicked should it close the search results?
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
function openSearchModal() {
|
||||||
|
const createNoteContentEl = document.querySelector(
|
||||||
|
"textarea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
if (!createNoteContentEl) return;
|
||||||
|
|
||||||
|
const modalBg = document.createElement("div");
|
||||||
|
modalBg.id = "search-modal-bg";
|
||||||
|
modalBg.style.cssText = `
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const modal = document.createElement("div");
|
||||||
|
modal.id = "search-modal";
|
||||||
|
modal.style.cssText = `
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: ${BACKGROUND_COLOR};
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||||
|
z-index: 1001;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 600px;
|
||||||
|
border-radius: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const searchInput = document.createElement("input");
|
||||||
|
searchInput.type = "text";
|
||||||
|
searchInput.placeholder = "Search this note...";
|
||||||
|
searchInput.style.cssText = `
|
||||||
|
width: 100%;
|
||||||
|
padding: .5rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: .25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resultContainer = document.createElement("div");
|
||||||
|
resultContainer.id = "search-results";
|
||||||
|
resultContainer.style.cssText = `
|
||||||
|
width: 100%;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
background-color: ${BACKGROUND_COLOR};
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Close button
|
||||||
|
const closeButton = document.createElement("button");
|
||||||
|
closeButton.textContent = "×";
|
||||||
|
closeButton.style.cssText = `
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Append elements to the modal
|
||||||
|
modal.appendChild(closeButton);
|
||||||
|
modal.appendChild(searchInput);
|
||||||
|
modal.appendChild(resultContainer);
|
||||||
|
|
||||||
|
// Append the modal to the body
|
||||||
|
document.body.appendChild(modalBg);
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// Add focus
|
||||||
|
searchInput.focus();
|
||||||
|
|
||||||
|
function findSearchResults(searchTerm: string): SearchResult[] {
|
||||||
|
const content = createNoteContentEl.value;
|
||||||
|
const results: SearchResult[] = [];
|
||||||
|
|
||||||
|
if (!searchTerm) return results;
|
||||||
|
|
||||||
|
const lines = content.split("\n");
|
||||||
|
let currentIndex = 0;
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const lowerLine = line.toLowerCase();
|
||||||
|
const searchTermLower = searchTerm.toLowerCase();
|
||||||
|
let position = lowerLine.indexOf(searchTermLower);
|
||||||
|
|
||||||
|
while (position !== -1) {
|
||||||
|
results.push({
|
||||||
|
text: line,
|
||||||
|
startIndex: currentIndex + position,
|
||||||
|
endIndex: currentIndex + position + searchTerm.length,
|
||||||
|
});
|
||||||
|
position = lowerLine.indexOf(searchTermLower, position + 1);
|
||||||
|
}
|
||||||
|
currentIndex += line.length + 1; // +1 for the newline character
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter content
|
||||||
|
searchInput.addEventListener("input", () => {
|
||||||
|
const searchTerm = searchInput.value;
|
||||||
|
const results = findSearchResults(searchTerm);
|
||||||
|
|
||||||
|
// Display results
|
||||||
|
resultContainer.innerHTML = "";
|
||||||
|
results.forEach((result) => {
|
||||||
|
const p = document.createElement("p");
|
||||||
|
|
||||||
|
// Highlight matches
|
||||||
|
const beforeMatch = result.text.substring(
|
||||||
|
0,
|
||||||
|
result.text.toLowerCase().indexOf(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
|
const match = result.text.substring(
|
||||||
|
result.text.toLowerCase().indexOf(searchTerm.toLowerCase()),
|
||||||
|
result.text.toLowerCase().indexOf(searchTerm.toLowerCase()) +
|
||||||
|
searchTerm.length
|
||||||
|
);
|
||||||
|
const afterMatch = result.text.substring(
|
||||||
|
result.text.toLowerCase().indexOf(searchTerm.toLowerCase()) +
|
||||||
|
searchTerm.length
|
||||||
|
);
|
||||||
|
|
||||||
|
p.innerHTML = `${beforeMatch}<mark>${match}</mark>${afterMatch}`;
|
||||||
|
p.style.cssText = `
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
`;
|
||||||
|
p.addEventListener("mouseover", () => {
|
||||||
|
p.style.backgroundColor = BACKGROUND_COLOR_HOVER;
|
||||||
|
});
|
||||||
|
p.addEventListener("mouseout", () => {
|
||||||
|
p.style.backgroundColor = "transparent";
|
||||||
|
});
|
||||||
|
p.addEventListener("click", () => selectResult(result));
|
||||||
|
resultContainer.appendChild(p);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
modal.remove();
|
||||||
|
modalBg.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal
|
||||||
|
modalBg.addEventListener("click", closeModal);
|
||||||
|
closeButton.addEventListener("click", closeModal);
|
||||||
|
modal.addEventListener("click", (e) => e.stopPropagation());
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll to and focus on the clicked result in the Editor
|
||||||
|
*
|
||||||
|
* @param result the search result
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
function selectResult(result: SearchResult) {
|
||||||
|
const createNoteContentEl = document.querySelector(
|
||||||
|
"textarea"
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
if (!createNoteContentEl) return;
|
||||||
|
|
||||||
|
createNoteContentEl.focus();
|
||||||
|
createNoteContentEl.setSelectionRange(result.startIndex, result.endIndex);
|
||||||
|
|
||||||
|
// Calculate the position of the selection
|
||||||
|
const textBeforeSelection = createNoteContentEl.value.substring(
|
||||||
|
0,
|
||||||
|
result.startIndex
|
||||||
|
);
|
||||||
|
const lines = textBeforeSelection.split("\n");
|
||||||
|
const lineNumber = lines.length;
|
||||||
|
|
||||||
|
// Get the line height (fallback to 20 if computation fails)
|
||||||
|
const computedLineHeight =
|
||||||
|
parseInt(getComputedStyle(createNoteContentEl).lineHeight) || 20;
|
||||||
|
|
||||||
|
// Calculate scroll position to center the selection in the viewport
|
||||||
|
const targetPosition = (lineNumber - 1) * computedLineHeight;
|
||||||
|
const textareaHeight = createNoteContentEl.clientHeight;
|
||||||
|
const scrollPosition = Math.max(0, targetPosition - textareaHeight / 2);
|
||||||
|
|
||||||
|
createNoteContentEl.scrollTo({
|
||||||
|
top: scrollPosition,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Searches for note and displays the results accordingly
|
* Searches for note and displays the results accordingly
|
||||||
*/
|
*/
|
||||||
|
@ -670,7 +909,10 @@ async function openNoteById(value: string): Promise<boolean> {
|
||||||
|
|
||||||
async function exportNote(contents: string | null) {
|
async function exportNote(contents: string | null) {
|
||||||
if (contents) {
|
if (contents) {
|
||||||
const title = contents.slice(0, 10).trim();
|
const title =
|
||||||
|
contents.replace(/\n/g, " ").length < 30
|
||||||
|
? contents.replace(/\n/g, " ").trim()
|
||||||
|
: contents.replace(/\n/g, " ").slice(0, 30).trim();
|
||||||
const filePath = await save({
|
const filePath = await save({
|
||||||
defaultPath: (await homeDir()) + "/" + title + ".md",
|
defaultPath: (await homeDir()) + "/" + title + ".md",
|
||||||
filters: [
|
filters: [
|
||||||
|
@ -692,9 +934,23 @@ async function exportNote(contents: string | null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleOpenSettingsModal() {
|
async function exportAllNotes() {
|
||||||
|
let res = await invoke("export_all_notes");
|
||||||
|
console.log(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleOpenSettingsModal() {
|
||||||
|
// insert app version
|
||||||
|
const appVersionEl = document.getElementById("app-version");
|
||||||
|
if (appVersionEl) {
|
||||||
|
const VERSION_STR = await invoke("get_app_version");
|
||||||
|
appVersionEl.textContent = "version " + VERSION_STR;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to get app version element.");
|
||||||
|
}
|
||||||
|
// open modal
|
||||||
const modalBg = document.getElementById("id-modal-bg");
|
const modalBg = document.getElementById("id-modal-bg");
|
||||||
const setingsModalContainer = document.getElementById(
|
const settingsModalContainer = document.getElementById(
|
||||||
"settings-modal-container"
|
"settings-modal-container"
|
||||||
);
|
);
|
||||||
const settingsFontsizeInput = document.getElementById(
|
const settingsFontsizeInput = document.getElementById(
|
||||||
|
@ -704,15 +960,16 @@ function handleOpenSettingsModal() {
|
||||||
"ocr-language-setting-input"
|
"ocr-language-setting-input"
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
const settingsSaveButton = document.getElementById("save-settings-button");
|
const settingsSaveButton = document.getElementById("save-settings-button");
|
||||||
if (modalBg && setingsModalContainer && settingsFontsizeInput) {
|
if (modalBg && settingsModalContainer && settingsFontsizeInput) {
|
||||||
modalBg.style.display = "block";
|
modalBg.style.display = "block";
|
||||||
setingsModalContainer.style.display = "block";
|
settingsModalContainer.style.display = "block";
|
||||||
|
settingsModalContainer.style.backgroundColor = BACKGROUND_COLOR;
|
||||||
settingsFontsizeInput.focus();
|
settingsFontsizeInput.focus();
|
||||||
settingsFontsizeInput.value = settings ? settings.fontSize : "16";
|
settingsFontsizeInput.value = settings ? settings.fontSize : "16";
|
||||||
settingsOcrLanguageInput.value = settings ? settings.ocrLanguage : "eng";
|
settingsOcrLanguageInput.value = settings ? settings.ocrLanguage : "eng";
|
||||||
|
|
||||||
modalBg.addEventListener("click", () => {
|
modalBg.addEventListener("click", () => {
|
||||||
setingsModalContainer.style.display = "none";
|
settingsModalContainer.style.display = "none";
|
||||||
modalBg.style.display = "none";
|
modalBg.style.display = "none";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -737,11 +994,11 @@ function handleOpenSettingsModal() {
|
||||||
} else {
|
} else {
|
||||||
console.error("failed to get createNoteContentEl");
|
console.error("failed to get createNoteContentEl");
|
||||||
}
|
}
|
||||||
setingsModalContainer.style.display = "none";
|
settingsModalContainer.style.display = "none";
|
||||||
modalBg.style.display = "none";
|
modalBg.style.display = "none";
|
||||||
}
|
}
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
setingsModalContainer.style.display = "none";
|
settingsModalContainer.style.display = "none";
|
||||||
modalBg.style.display = "none";
|
modalBg.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -766,7 +1023,7 @@ function handleOpenSettingsModal() {
|
||||||
} else {
|
} else {
|
||||||
console.error("failed to get createNoteContentEl");
|
console.error("failed to get createNoteContentEl");
|
||||||
}
|
}
|
||||||
setingsModalContainer.style.display = "none";
|
settingsModalContainer.style.display = "none";
|
||||||
modalBg.style.display = "none";
|
modalBg.style.display = "none";
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -46,15 +46,24 @@
|
||||||
background-color: #1f1f1f;
|
background-color: #1f1f1f;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: calc(100vh - 1vh);
|
||||||
|
|
||||||
border-radius: 15px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#create-input {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0.5em;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #3b3b3b;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||||
|
resize: none;
|
||||||
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
background-color: #252525;
|
||||||
|
color: #f6f6f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
|
@ -338,9 +347,11 @@ button {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
margin-top: 20%;
|
padding: 2em;
|
||||||
margin-left: 50%;
|
|
||||||
|
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue