Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
- **Sprints in one note.** Every sprint and a Backlog live in one Sprints note: a points donut, goals per sprint and per month, drag-and-drop tasks and a rich-text note for each task.
- **Keyboard first.** Every button has a shortcut, and you can change any of them.
- **Back and forward.** The mouse's side buttons and Alt+← / Alt+→ step through screens.
- **A keystroke away.** `Ctrl+Alt+N` shows or hides Notelet from any app; it waits in the system tray.
- **Private.** Notes stay in one file on your PC. Export a backup file whenever you like.


Expand Down Expand Up @@ -75,7 +74,7 @@ Notelet is an independent project and is not affiliated with or endorsed by Appl
2. Run it. Windows may warn about an unknown publisher the first time, because the installer is not code-signed: click **More info**, then **Run anyway**. It installs for your account only, so no admin rights are needed.
3. Notelet opens, adds an icon to the system tray and starts with Windows from then on.

Press `Ctrl+Alt+N` from any app to show or hide Notelet. Closing the window (X) only hides it; to quit, right-click the tray icon → **Quit**. Turn off **Start with Windows** in the same menu if you don't want it at login. The window remembers its size and place; `Alt+Enter` or the ⤢ button maximizes and restores it.
Closing the window (X) only hides it; click the tray icon to bring it back. To quit, right-click the tray icon → **Quit**. Turn off **Start with Windows** in the same menu if you don't want it at login. The window remembers its size and place; `Alt+Enter` or the ⤢ button maximizes and restores it.

## Update

Expand Down Expand Up @@ -129,11 +128,12 @@ Press `Ctrl+/` (`⌘/` on Mac) for the full list, grouped by topic. Every button
| Change case: lower, UPPER, Title | `Shift+F3` | `⇧F3` |
| Back / Forward | mouse side buttons, `Alt+←` / `Alt+→` | mouse side buttons, `⌘[` / `⌘]` |
| All shortcuts | `Ctrl+/` | `⌘/` |
| Show / hide Notelet from any app | `Ctrl+Alt+N` | — |

`Ctrl+click` (`⌘`-click on Mac) opens a link in a note; a plain click only places the caret.

Text styles follow Notes for Mac with Ctrl in place of Cmd; where Chrome kept a key for itself (Ctrl+N, Ctrl+Shift+T) Notelet uses Alt instead, the same keys as the extension had.

**To change a shortcut**, click its key in the list and press the new one. Esc, Tab or a click elsewhere cancels; Backspace removes the key. Keys Windows keeps, and those Chrome kept, are refused; the show / hide key must use Ctrl or Alt, and a key another app holds is refused with a message; and a key another shortcut uses asks before taking it over. ↺ resets one shortcut; **Reset All to Default** resets them all.
**To change a shortcut**, click its key in the list and press the new one. Esc, Tab or a click elsewhere cancels; Backspace removes the key. Keys Windows keeps, and those Chrome kept, are refused; a key another shortcut uses asks before taking it over. ↺ resets one shortcut; **Reset All to Default** resets them all.

## Development

Expand Down
3 changes: 1 addition & 2 deletions desktop/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "notelet"
version = "0.7.1"
version = "0.8.0"
description = "Lightweight notes with sprint tracking"
edition = "2021"
publish = false
Expand All @@ -15,7 +15,6 @@ tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "~2.12", features = ["tray-icon"] }
tauri-plugin-single-instance = "2"
tauri-plugin-global-shortcut = "2"
tauri-plugin-autostart = "2"
tauri-plugin-window-state = "2"
tauri-plugin-updater = "2"
Expand Down
44 changes: 0 additions & 44 deletions desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}
use tauri::{AppHandle, Emitter, Manager, State, WebviewWindow, WindowEvent};
use tauri_plugin_autostart::{MacosLauncher, ManagerExt as _};
use tauri_plugin_dialog::DialogExt;
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState};
use tauri_plugin_updater::{Update, UpdaterExt};
use tauri_plugin_window_state::{AppHandleExt, StateFlags};

Expand Down Expand Up @@ -38,7 +37,6 @@ impl Pending {
#[derive(Default)]
struct AppState {
storage_lock: Mutex<()>,
global_shortcut: Mutex<Option<Shortcut>>,
pending: Mutex<Option<Pending>>,
}

Expand Down Expand Up @@ -76,28 +74,6 @@ fn storage_save(app: AppHandle, state: State<AppState>, data: Map<String, Value>
storage::save(&data_dir(&app)?, &data).map_err(|error| error.to_string())
}

/// Registers the new key before letting go of the old one, so a key another app holds leaves
/// the old key working. Returns `Err("taken")` in that case; `None` removes the key.
#[tauri::command]
fn set_global_shortcut(app: AppHandle, state: State<AppState>, accelerator: Option<String>) -> Result<(), String> {
let wanted = accelerator
.map(|text| text.parse::<Shortcut>().map_err(|error| error.to_string()))
.transpose()?;
let mut current = state.global_shortcut.lock().map_err(|error| error.to_string())?;
if wanted == *current {
return Ok(());
}
let shortcuts = app.global_shortcut();
if let Some(shortcut) = wanted {
shortcuts.register(shortcut).map_err(|_| "taken".to_string())?;
}
if let Some(old) = current.take() {
let _ = shortcuts.unregister(old);
}
*current = wanted;
Ok(())
}

#[tauri::command]
fn toggle_maximize(window: WebviewWindow) -> Result<(), String> {
let maximized = window.is_maximized().map_err(|error| error.to_string())?;
Expand Down Expand Up @@ -143,16 +119,6 @@ fn hide_main(app: &AppHandle) {
}
}

fn toggle_main(app: &AppHandle) {
let Some(window) = app.get_webview_window(MAIN_WINDOW) else { return };
let in_front = window.is_visible().unwrap_or(false) && window.is_focused().unwrap_or(false);
if in_front {
hide_main(app);
} else {
show_main(app);
}
}

/// Asks the page to write pending edits, then quits or updates once it answers or the timeout
/// passes, whichever comes first.
fn request_after_flush(app: &AppHandle, pending: Pending) {
Expand Down Expand Up @@ -307,22 +273,12 @@ pub fn run() {
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| show_main(app)))
.plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, Some(vec![HIDDEN_ARG])))
.plugin(tauri_plugin_window_state::Builder::default().with_state_flags(window_state_flags()).build())
.plugin(
tauri_plugin_global_shortcut::Builder::new()
.with_handler(|app, _shortcut, event| {
if event.state == ShortcutState::Pressed {
toggle_main(app);
}
})
.build(),
)
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.manage(AppState::default())
.invoke_handler(tauri::generate_handler![
storage_load,
storage_save,
set_global_shortcut,
toggle_maximize,
after_flush,
save_backup
Expand Down
2 changes: 1 addition & 1 deletion desktop/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Notelet",
"version": "0.7.1",
"version": "0.8.0",
"identifier": "com.ttncode.notelet",
"build": {
"frontendDist": "../extension"
Expand Down
19 changes: 2 additions & 17 deletions extension/app.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { backupFileName, createBackup, parseBackup } from "./backup.js";
import { closeOnOutsideClick, pickTextFile, showToast } from "./dialogs.js";
import { createFileArea, historyKey, recoveryMessage, toAccelerator, whenShown } from "./desktop.js";
import { createFileArea, historyKey, recoveryMessage, whenShown } from "./desktop.js";
import { mergeTrackers, newBoardNote } from "./board-merge.js";
import { boardSearchText, findSprint, findTask, mapTasks, removeSprint, updateSprint } from "./board.js";
import { createBoardView } from "./board-view.js";
import { NoteEditor } from "./editor.js";
import { formatEditedDate } from "./format.js";
import { applyShortcutTitles, globalCombo, matchHotkey, setCustomKeys } from "./hotkeys.js";
import { applyShortcutTitles, matchHotkey, setCustomKeys } from "./hotkeys.js";
import { setupLayout } from "./layout.js";
import { EMPTY_BODY_HTML, EMPTY_NOTE_HTML, isBlankNote, isEmptyNote, isExpired, newNote, noteText, noteTitleOf, orderedGroups, placeNote, stepNote } from "./model.js";
import { mergeNote } from "./merge.js";
Expand All @@ -28,7 +28,6 @@ const VIEWPORT_MARGIN_PX = 8;
const IS_MAC = /mac/i.test(navigator.userAgentData?.platform ?? navigator.platform);
const NARROW = matchMedia("(max-width: 699px)");
const MERGE_CONFLICT_MESSAGE = "This note was also edited in another window. A paragraph you both changed now appears in both versions; for anything else, this window's change was kept.";
const SHORTCUT_TAKEN_MESSAGE = "That key is already used by another app, so Notelet kept the previous one.";
const SAVE_FAILED_MESSAGE = "Couldn't save your last change. Your text is still here — keep this tab open and try again, or export a backup.";

const THEME_ICONS = {
Expand Down Expand Up @@ -82,7 +81,6 @@ async function main() {
state.ui = loaded.ui;
state.settings = loaded.settings;
setCustomKeys(state.settings.hotkeys);
registerGlobalShortcut().catch(() => whenShown(document, window, () => showToast(SHORTCUT_TAKEN_MESSAGE)));
showTheme(state.ui.theme);
await removeStaleNotes();
editor = new NoteEditor({ element: byId("editor"), isMac: IS_MAC, onChange: onEditorChange });
Expand All @@ -98,8 +96,6 @@ async function main() {
whenShown(document, window, () => showStartupNotice(recovery, loaded.notes.length === 0));
}

const registerGlobalShortcut = () => tauri.core.invoke("set_global_shortcut", { accelerator: toAccelerator(globalCombo()) });

// shortcut: an edit typed under SAVE_DELAY_MS before Windows shuts down can be lost, since WebView2
// is killed without a page event; fixing it needs a WM_QUERYENDSESSION hook in Rust.
function listenForFlush() {
Expand Down Expand Up @@ -808,19 +804,8 @@ const showHelp = () => openShortcuts({
});

function saveShortcuts(hotkeys) {
const previous = state.settings.hotkeys;
adoptSettings({ ...state.settings, hotkeys });
store.saveSettings(state.settings).catch(reportSaveError);
registerGlobalShortcut().catch(() => keepPreviousGlobalKey(previous));
}

// Windows refused the new global key, so the setting goes back to the key still registered.
function keepPreviousGlobalKey(previousHotkeys) {
const { showHide: _refused, ...hotkeys } = state.settings.hotkeys;
const restored = Object.hasOwn(previousHotkeys, "showHide") ? { ...hotkeys, showHide: previousHotkeys.showHide } : hotkeys;
adoptSettings({ ...state.settings, hotkeys: restored });
store.saveSettings(state.settings).catch(reportSaveError);
showToast(SHORTCUT_TAKEN_MESSAGE);
}

// Changed shortcuts take effect at once, tooltips included.
Expand Down
2 changes: 2 additions & 0 deletions extension/clipboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ function textMarker(list, item, index) {
// characters. data-copy-marker lets a paste back into Notelet drop them again.
export function clipboardHtml(root) {
const copy = root.cloneNode(true);
// Chrome leaves font-size and colour spans in a note when lines merge.
copy.querySelectorAll("[style]").forEach((element) => element.removeAttribute("style"));
for (const list of copy.querySelectorAll("ul.checklist, ul.dashed")) {
list.setAttribute("style", "list-style: none; padding-left: 0");
for (const item of list.children) item.prepend(htmlMarker(list, item));
Expand Down
7 changes: 0 additions & 7 deletions extension/desktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,6 @@ function removeKeys(map, keys) {
return changes;
}

// The accelerator string the global-shortcut plugin parses, e.g. "Ctrl+Alt+KeyN".
export function toAccelerator(combo) {
if (!combo?.code) return null;
const modifiers = [combo.mod && "Ctrl", combo.alt && "Alt", combo.shift && "Shift"].filter(Boolean);
return [...modifiers, combo.code].join("+");
}

export function recoveryMessage({ recovered, damagedPath }) {
if (recovered === "backup") return "Notelet's notes file was damaged, so the last good copy was opened. Your most recent change may be missing.";
if (recovered === "reset") return `Notelet's notes file was damaged and was moved to ${damagedPath}. Import a backup to bring your notes back.`;
Expand Down
Loading
Loading