From b255ec93469684a05f0399e5bf1921bc58fb0db9 Mon Sep 17 00:00:00 2001 From: lucas <282139159+lucaslus@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:03:32 +0800 Subject: [PATCH] feat(omarchy): integrate desktop theme, shortcuts and Linux packaging --- .github/workflows/ci.yml | 23 +++- .gitignore | 5 + README.md | 31 ++++- README.zh-CN.md | 31 ++++- packaging/omarchy/PKGBUILD | 29 +++++ packaging/omarchy/README.md | 112 ++++++++++++++++++ packaging/omarchy/floatick.desktop | 9 ++ packaging/omarchy/floatick.lua | 9 ++ src-tauri/Cargo.lock | 94 ++++++++++++++- src-tauri/Cargo.toml | 6 +- src-tauri/src/commands.rs | 5 + src-tauri/src/desktop_theme.rs | 61 ++++++++++ src-tauri/src/lib.rs | 44 ++++++- src-tauri/src/panel.rs | 6 + src-tauri/src/tray.rs | 31 +++-- src-tauri/src/updates.rs | 11 +- src/App.tsx | 15 ++- src/components/common/ActionBar.tsx | 2 +- .../common/FloatickTiptapEditor.tsx | 27 +++-- src/components/notes/NoteEditorDrawer.tsx | 13 +- src/components/settings/SettingsDrawer.tsx | 3 +- .../settings/UpdateSettingsSection.tsx | 3 +- src/components/todos/TodoDeadlinePicker.tsx | 2 +- src/components/todos/TodoEditorDrawer.tsx | 13 +- src/components/todos/TodoItemRow.tsx | 2 +- src/i18n/locales/en.json | 1 + src/i18n/locales/zh.json | 1 + src/index.css | 32 ++++- src/lib/api.ts | 9 ++ src/lib/omarchyTheme.ts | 56 +++++++++ src/lib/platform.ts | 13 ++ src/main.tsx | 24 +++- src/stores/useSettingsStore.ts | 44 +++++-- 33 files changed, 688 insertions(+), 79 deletions(-) create mode 100644 packaging/omarchy/PKGBUILD create mode 100644 packaging/omarchy/README.md create mode 100644 packaging/omarchy/floatick.desktop create mode 100644 packaging/omarchy/floatick.lua create mode 100644 src-tauri/src/desktop_theme.rs create mode 100644 src/lib/omarchyTheme.ts create mode 100644 src/lib/platform.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0107a0f..716f9b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ permissions: jobs: required-check: name: Analyze and test - needs: [frontend, backend, tauri-build] + needs: [frontend, backend, tauri-build, linux] if: always() runs-on: ubuntu-latest steps: @@ -116,3 +116,24 @@ jobs: run: | pnpm tauri build --bundles app python3 scripts/verify-update-bundle.py src-tauri/target/release/bundle/macos/Floatick.app + + linux: + name: Linux check, test & build + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - uses: dtolnay/rust-toolchain@stable + - name: Install native dependencies + run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf + - uses: swatinem/rust-cache@v2 + with: + workspaces: src-tauri -> target + - run: pnpm install --frozen-lockfile + - run: cargo test --locked --manifest-path src-tauri/Cargo.toml + - run: pnpm tauri build --no-bundle diff --git a/.gitignore b/.gitignore index ce28f14..15c116f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,8 @@ xcuserdata/ *.keychain-db *.mobileprovision *.provisionprofile + +# Local Arch package outputs +packaging/omarchy/pkg/ +packaging/omarchy/src/ +packaging/omarchy/*.pkg.tar.* diff --git a/README.md b/README.md index 92dddb6..14615a7 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,28 @@ Floatick Menu Bar Todos, TipTap Markdown Editor, and Quick Notes

-Floatick is a lightweight, local-first productivity app crafted natively for macOS. It rests quietly in your Menu Bar with a live counter badge for pending tasks. One click slides open a focused workspace directly beneath the icon. No heavy setups, no cloud sign-ups—just fast capture and effortless follow-through. +Floatick is a lightweight, local-first productivity app for macOS, with Omarchy / Hyprland support. On macOS, it lives in the Menu Bar and opens a floating panel when you click its icon. Omarchy uses the tray interaction and window styling described below. The screenshot above shows the macOS interface. + +## Omarchy / Linux + +Build the Omarchy edition as an Arch package from source. Follow the [Omarchy setup guide](packaging/omarchy/README.md) to install it and load the bundled Hyprland window rules; the window behavior below assumes those rules are loaded. The supplied rules require **Hyprland 0.55+ with Lua configuration**. + +| Area | Omarchy behavior | +| --- | --- | +| **Open the panel** | **Right-click the system tray icon → choose “显示 Floatick” (Show Floatick)**. Unlike macOS, do not rely on a left click to toggle the panel. Launching Floatick again from the application launcher also opens the existing instance. | +| **Panel placement** | Hyprland rules center the floating panel on the screen where it opens, independently of the tray icon. The panel floats in the current workspace; cross-workspace pinning is disabled because it can cover input-method candidate windows. | +| **Window appearance** | The outer frame inherits Omarchy’s theme border colors, width, rounding, opacity, and shadow; corners are square by default. Floatick removes its macOS-style rounded shell, custom border, and transparent outer padding. Internal buttons and cards keep their usual styling. | +| **Summon shortcut** | Configure `Super+Ctrl+Shift+F` in Hyprland (see the Omarchy guide). Check personal bindings first. Super+Shift+F opens the system file manager; Super+Enter opens the terminal. Floatick uses Ctrl+Enter to save, and does not use Super/Win for app shortcuts. | +| **Theme** | The system-theme option is labeled **Omarchy** when its palette is available. It follows Omarchy colors and light/dark mode, updating automatically after theme changes. Explicit Light/Dark keeps Floatick colors. | +| **Keyboard shortcuts** | Use `Ctrl` instead of `⌘`: `Ctrl+N` to create, `Ctrl+Enter` to save, `Ctrl+F` to search, `Ctrl+,` for settings, and `Ctrl+1/2` to switch between todos and notes. | +| **Hide or quit** | `Esc` dismisses an editor or settings drawer first, then hides the panel. Closing the window through the window manager also hides it. To exit, choose “退出 Floatick” (Quit Floatick) in the tray menu or quit from the app’s settings. Automatic collapse on focus loss is configurable in settings. | +| **Tray counts and updates** | Pending-count labels depend on status-bar support. Update Linux builds by rebuilding and installing the package; the macOS Sparkle updater is not used. | + +Floatick defaults to XWayland on Hyprland for desktop panel behavior. Fractional scaling may look less sharp than native Wayland applications. Todos, notes, and tags work the same way, with offline data stored in `~/.floatick`. ## Key Features -- **Menu Bar Native**: Sits quietly in your system status bar with a live pending badge. Click to open directly beneath the tray icon without cluttering your Dock. +- **Native macOS Menu Bar**: Sits quietly in your system status bar with a live pending badge. Click to open directly beneath the tray icon without cluttering your Dock. - **Full-Width Cards & Floating Capsules**: Task titles span the full row width without premature truncation. Hovering smoothly reveals frosted action capsules (deadline, edit, copy markdown, delete). - **Separated Browsing & Editing**: Check off tasks instantly in the list; open the full TipTap editor drawer on demand via double-click or `⌘N`. - **TipTap Markdown & Slash Commands**: Press `/` for interactive checklists, headings, code blocks, and quotes with instant formatting. @@ -42,19 +59,23 @@ Floatick is a lightweight, local-first productivity app crafted natively for mac ## Download & Installation -Download the latest DMG installer from [GitHub Releases](https://github.com/lucaslus/floatick/releases) and drag Floatick to your `Applications` folder. The installer is a Universal Binary supporting both Apple silicon (M-series) and Intel Macs. +**macOS:** Download the latest DMG installer from [GitHub Releases](https://github.com/lucaslus/floatick/releases) and drag Floatick to your `Applications` folder. The installer is a Universal Binary supporting both Apple silicon (M-series) and Intel Macs. > **First-Launch Note for macOS**: > If macOS displays an "unidentified developer" warning, open **System Settings → Privacy & Security**, scroll down to **Security**, and click **Open Anyway** (only required on first launch). ## App Updates +The Sparkle instructions below apply to macOS. On Omarchy, rebuild and install the package as described in the [setup guide](packaging/omarchy/README.md). + Open **Settings → Software Updates** (`⌘,`) to check for a new version, read release notes, or enable daily automatic checks. Sparkle displays download progress and verifies Ed25519 signatures before installing and relaunching, with your confirmation. Automatic checks do not automatically install updates. Versions 0.4.0 and 0.4.1 do not contain the updater; install v0.4.2 manually once. Updates require an internet connection; your todos and notes remain local. Update preferences are stored in macOS user defaults. ## Shortcuts & Controls +The table below describes macOS. On Omarchy, replace `⌘` with `Ctrl` and use the tray’s right-click menu to open the panel, as described above. + | Action | Shortcut / Gesture | Description | | --- | --- | --- | | **Toggle Window** | Click Menu Bar Icon | Toggle Floatick panel under the status tray | @@ -87,7 +108,7 @@ Your todos and notes never leave your computer and work entirely offline. Option ### Requirements -- macOS 10.15 or later +- macOS 10.15 or later, or Omarchy with the dependencies in the [setup guide](packaging/omarchy/README.md) - [Node.js](https://nodejs.org/) 18+ and [pnpm](https://pnpm.io/) - [Rust](https://www.rust-lang.org/) (1.80+) and Cargo @@ -101,7 +122,7 @@ pnpm install pnpm tauri:dev ``` -The Tauri commands automatically download the pinned, checksum-verified Sparkle framework. Before running Cargo directly, run `pnpm setup:sparkle` once. The updater is available in a packaged `.app`; browser previews and unbundled `tauri dev` show an unavailable state. Build and launch the `.app` to test native update windows. +On macOS, the Tauri commands automatically download the pinned, checksum-verified Sparkle framework. Before running Cargo directly, run `pnpm setup:sparkle` once. The updater is available in a packaged `.app`; browser previews and unbundled `tauri dev` show an unavailable state. Build and launch the `.app` to test native update windows. ### Production Build diff --git a/README.zh-CN.md b/README.zh-CN.md index e5558fc..c9d0e69 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -27,11 +27,28 @@ Floatick 菜单栏待办、TipTap 富文本写作与随手记便签

-Floatick 是一款专为 macOS 设计的原生轻量效率工具。安静常驻于屏幕顶部的菜单栏中,未完成事项实时显示角标;点击图标即在手边展开浮动面板。无需复杂配置,点开即记,做完即勾,不扰专注。 +Floatick 是一款本地优先的轻量效率工具,支持 macOS,并提供 Omarchy / Hyprland 适配。在 macOS 上,应用常驻菜单栏,点击图标即可展开浮动面板;Omarchy 的托盘操作与窗口外观见下方说明。上方截图展示的是 macOS 界面。 + +## Omarchy / Linux + +Omarchy 版可从源码构建 Arch 软件包。请按 [Omarchy 安装说明](packaging/omarchy/README.md) 安装,并加载随包提供的 Hyprland 浮窗规则;以下窗口行为以加载该规则为前提。目前提供的规则适用于 **Hyprland 0.55+ 的 Lua 配置**。 + +| 项目 | Omarchy 下的操作与表现 | +| --- | --- | +| **打开面板** | **右键系统托盘图标 → 点击「显示 Floatick」**。不同于 macOS 的左键直接展开,不要依赖左键切换面板。也可从应用启动器再次启动 Floatick,唤出已有实例。 | +| **浮窗位置** | 面板在打开时所在屏幕居中悬浮,由 Hyprland 规则定位,不跟随托盘图标;随包规则使其在当前工作区保持悬浮,不启用会遮挡输入法候选框的跨工作区固定(pin)。 | +| **窗口外观** | 外层使用 Omarchy 系统边框,跟随桌面主题的边框颜色、宽度、圆角、透明度和阴影设置;默认是直角。移除 macOS 风格的圆角外壳、自绘边框及外围透明留白,内部按钮和卡片保留原有样式。 | +| **快捷键呼出** | 在 Hyprland 中配置 `Super+Ctrl+Shift+F`(见 Omarchy 安装说明),先检查个人绑定是否冲突。系统的 `Super+Shift+F` 是文件管理器,`Super+Enter` 是终端;Floatick 使用 `Ctrl+Enter` 保存,应用快捷键不使用 Super/Win。 | +| **主题跟随** | 检测到系统配色后,“系统”选项显示为 **Omarchy**,跟随背景、文字、强调色和明暗模式,切换系统主题后自动更新。手动选择浅色/深色仍使用 Floatick 原配色。 | +| **快捷键** | 使用 `Ctrl` 替代 `⌘`:`Ctrl+N` 新建、`Ctrl+Enter` 保存、`Ctrl+F` 搜索、`Ctrl+,` 打开设置、`Ctrl+1/2` 切换待办与便签。 | +| **收起与退出** | `Esc` 先关闭编辑器或设置等抽屉,再收起面板;系统关闭窗口操作也只收起面板。真正退出请在托盘菜单点击「退出 Floatick」,或使用应用设置中的退出操作。失焦自动收起可在设置中控制。 | +| **托盘计数与更新** | 待办计数是否显示取决于状态栏支持。Linux 版通过重新构建并安装软件包更新,不使用 macOS 的 Sparkle 更新器。 | + +Hyprland 下默认使用 XWayland,以适配桌面浮窗行为;分数缩放时,清晰度可能与原生 Wayland 应用不同。待办、便签与标签功能保持一致,数据仍保存在 `~/.floatick`,无需联网即可使用。 ## 特性亮点 -- **原生常驻菜单栏**:常驻右上角状态栏,角标实时显示待办数,点击即开,不占 Dock。 +- **macOS 原生常驻菜单栏**:常驻右上角状态栏,角标实时显示待办数,点击即开,不占 Dock。 - **全宽卡片与悬浮胶囊**:长标题全宽展示无遮挡;鼠标悬浮呼出截止时间、编辑、复制与删除操作胶囊。 - **浏览勾选与编辑抽屉独立**:列表专注纯粹浏览与单键勾销;双击或按 `⌘N` 唤出独立编辑抽屉。 - **TipTap 富文本与斜杠指令**:输入 `/` 快速插入交互式任务清单、多级标题、代码块与引用。 @@ -42,19 +59,23 @@ Floatick 是一款专为 macOS 设计的原生轻量效率工具。安静常驻 ## 下载安装 -前往 [GitHub Releases](https://github.com/lucaslus/floatick/releases) 下载最新 DMG 安装包,打开后将 Floatick 拖入 `Applications` 即可。安装包为 Universal Binary,原生支持 Apple 芯片(M 系列)与 Intel 架构。 +**macOS:**前往 [GitHub Releases](https://github.com/lucaslus/floatick/releases) 下载最新 DMG 安装包,打开后将 Floatick 拖入 `Applications` 即可。安装包为 Universal Binary,原生支持 Apple 芯片(M 系列)与 Intel 架构。 > **macOS 首次打开提示**: > 如打开时提示“来自未受信任的开发者”,请前往 **系统设置 → 隐私与安全**,在“安全性”中点击 **仍要打开** 即可(仅需首次确认一次)。 ## 应用更新 +以下 Sparkle 更新说明适用于 macOS;Omarchy 请按 [安装说明](packaging/omarchy/README.md) 重新构建并安装软件包。 + 打开 **设置 → 软件更新**(`⌘,`),可手动检查新版本、查看更新说明,或开启每天自动检查。Sparkle 原生窗口负责展示下载进度、验证 Ed25519 签名,并在你确认后安装、重启;自动检查不会自动安装。 v0.4.0 和 v0.4.1 尚未内置更新组件,需要先手动安装一次 v0.4.2。检查更新需要联网,待办和便签数据仍保存在本机。更新偏好保存在 macOS 用户偏好中。 ## 快捷键与常用交互 +下表以 macOS 为例;Omarchy 使用 `Ctrl` 替代 `⌘`,打开面板请使用上方所述的托盘右键菜单。 + | 操作 | 快捷键 / 交互 | 说明 | | --- | --- | --- | | **唤出 / 收起** | 点击托盘图标 | 在托盘正下方弹出或收起面板 | @@ -87,7 +108,7 @@ Floatick 首次启动时会在用户目录自动创建数据文件: ### 环境要求 -- macOS 10.15 或更高版本 +- macOS 10.15 或更高版本;Omarchy 的系统依赖见 [安装说明](packaging/omarchy/README.md) - [Node.js](https://nodejs.org/) 18+ 与 [pnpm](https://pnpm.io/) - [Rust](https://www.rust-lang.org/) (1.80+) 与 Cargo 环境 @@ -101,7 +122,7 @@ pnpm install pnpm tauri:dev ``` -Tauri 命令会自动下载固定版本且经过 SHA-256 校验的 Sparkle 框架。直接运行 Cargo 前,请先执行一次 `pnpm setup:sparkle`。更新功能仅在打包后的 `.app` 中启用,浏览器预览及未打包的 `tauri dev` 会显示不可用提示;测试原生更新窗口请构建并启动 `.app`。 +在 macOS 上,Tauri 命令会自动下载固定版本且经过 SHA-256 校验的 Sparkle 框架。直接运行 Cargo 前,请先执行一次 `pnpm setup:sparkle`。更新功能仅在打包后的 `.app` 中启用,浏览器预览及未打包的 `tauri dev` 会显示不可用提示;测试原生更新窗口请构建并启动 `.app`。 ### 生产打包 diff --git a/packaging/omarchy/PKGBUILD b/packaging/omarchy/PKGBUILD new file mode 100644 index 0000000..931a50f --- /dev/null +++ b/packaging/omarchy/PKGBUILD @@ -0,0 +1,29 @@ +# Local package: run makepkg from this directory in a Floatick source checkout. +pkgname=floatick-omarchy +pkgver=0.4.2 +pkgrel=3 +pkgdesc='Floating todos and notes with Omarchy desktop integration' +arch=('x86_64' 'aarch64') +url='https://github.com/lucaslus/floatick' +license=('MIT') +depends=('webkit2gtk-4.1' 'libayatana-appindicator' 'xorg-xwayland') +makedepends=('rust' 'nodejs' 'pnpm') +optdepends=('fcitx5-gtk: Fcitx input method support in GTK') +provides=('floatick') +conflicts=('floatick') +options=('!strip') + +build() { + cd "$startdir/../.." + pnpm install --frozen-lockfile + pnpm tauri build --no-bundle +} + +package() { + cd "$startdir/../.." + install -Dm755 src-tauri/target/release/floatick "$pkgdir/usr/bin/floatick" + install -Dm644 packaging/omarchy/floatick.desktop "$pkgdir/usr/share/applications/floatick.desktop" + install -Dm644 src-tauri/icons/128x128.png "$pkgdir/usr/share/icons/hicolor/128x128/apps/floatick.png" + install -Dm644 packaging/omarchy/floatick.lua "$pkgdir/usr/share/floatick/floatick.lua" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} diff --git a/packaging/omarchy/README.md b/packaging/omarchy/README.md new file mode 100644 index 0000000..4f6534b --- /dev/null +++ b/packaging/omarchy/README.md @@ -0,0 +1,112 @@ +# Omarchy / Arch Linux + +Floatick supports a Linux tray menu and uses XWayland by default on Hyprland +when `DISPLAY` is available. This overrides Omarchy’s global `GDK_BACKEND=wayland`; for troubleshooting, +set the app-specific `FLOATICK_GDK_BACKEND=wayland` to opt out. This keeps +GTK's always-on-top requests available. Hyprland rules own panel placement; native Wayland +cannot provide the same positioning behavior. Linux does not emit Tauri tray +click events or expose tray geometry, so **right-click the system tray icon** +and choose **显示 Floatick** (Show Floatick), or launch `floatick` again to show the existing instance. +The panel opens centered on its screen, independently of the tray icon. +Tray count labels depend on the status bar's support. + +## Build and install + +From this directory in a source checkout: + +```sh +# Install build/runtime dependencies first (Omarchy). +omarchy pkg add base-devel rust nodejs pnpm webkit2gtk-4.1 libayatana-appindicator xorg-xwayland +makepkg -si +``` + +This local PKGBUILD builds the current checkout, including local changes. It is +not an AUR recipe. The result is `floatick-omarchy-*.pkg.tar.zst`. No macOS +Sparkle framework is downloaded on Linux. Updates use a rebuilt package and +`pacman -U`, not Sparkle. The desktop launcher and autostart both run the same +binary; launching twice does not create competing writers to `~/.floatick`. + +For development, run `pnpm install --frozen-lockfile` and `pnpm tauri:dev` from +the repository root. To only build a binary, use `pnpm tauri build --no-bundle`. + +## Omarchy floating panel rules + +For Omarchy with Hyprland **0.55+ and Lua configuration**, add this line after +the Omarchy defaults in `~/.config/hypr/hyprland.lua`: + +```lua +dofile("/usr/share/floatick/floatick.lua") +``` + +Then run `hyprctl reload` and `hyprctl configerrors`. The package provides its +own file under `/usr/share/floatick`; it does not modify Omarchy's files or +rewrite your configuration. The rule centers the floating panel on its screen in the current workspace. It deliberately does not +pin the window across workspaces: Hyprland renders pinned XWayland windows +above Fcitx candidate popups, hiding the candidate list. The outer frame +inherits Omarchy’s border colors, width, rounding, opacity and shadow (square +corners by default). Floatick fills the window without a rounded inset or its +own border; internal controls keep their usual styling. Older Hyprland configurations +need rules matching their version; do not source this Lua file from `.conf`. +Before removing the package, remove the `dofile` line as well. + +Optionally bind an unused shortcut in `~/.config/hypr/bindings.lua`: + +```lua +-- Check existing bindings before assigning this example shortcut. +o.bind("SUPER + CTRL + SHIFT + F", "Floatick", "floatick") +``` + +The recommended summon shortcut is **Super+Ctrl+Shift+F**. Check `hyprctl -j binds` +first for personal conflicts. Do not use Super+Shift+F (Omarchy file manager) +or Super+Enter (terminal). The binding launches Floatick or shows its existing +window in the center of the current screen. For a source checkout without an +installed package, replace `floatick` above with the absolute path to +`src-tauri/target/release/floatick`. + +Use **Ctrl** instead of **⌘** for in-app shortcuts, including **Ctrl+Enter** +to save todos and notes from either the title or body. Linux does not treat +Super/Win as an application shortcut modifier. Escape and window-manager +close hide the panel; use the tray's **退出 Floatick** or the settings quit +button to exit. Autostart is controlled in Floatick's settings. + +## Chinese input (Fcitx5 / Rime) + +Install `fcitx5-gtk` when using Fcitx with this GTK application. Floatick uses +its native input-method module; it does not draw the candidate list itself. +Do not add `pin = true` to the Floatick window rule: on Hyprland this can put +the panel above the Fcitx candidate window. Remove an older pin rule and +reopen the panel after reloading Hyprland. Keep `float = true` enabled. +Candidate colors, fonts and spacing are controlled globally by Fcitx, not by +the Floatick UI theme. + +## Desktop smoke check + +- Launch from the application menu; check the floating panel and tray, with + square outer corners and the system theme border (default content size: 440×700). +- Hide with Escape, then launch again; the same process should reopen. +- Right-click the tray icon and choose **显示 Floatick**; check focus and + click-outside collapse. +- Compose Chinese in search, title and body fields. Check that candidates + appear above the panel, Space/Enter commits a candidate, and Escape cancels + composition without closing the editor. +- Create and save a todo/note using Ctrl+N and Ctrl+Enter, then relaunch. +- Check both monitors and their scaling; XWayland may be less sharp at + fractional scales than a native Wayland application. +- Enable autostart and verify after the next login. + +References: [Tauri tray limitations](https://v2.tauri.app/learn/system-tray/), +[Hyprland window rules](https://wiki.hypr.land/Configuring/Basics/Window-Rules/). + +## Follow the Omarchy theme + +In Settings → Appearance, choose **Omarchy** (the system-theme option on +Omarchy). Floatick reads `~/.local/state/omarchy/current/theme/colors.toml` +(respecting `XDG_STATE_HOME`, with the older config-directory path as a fallback). +It follows background, foreground, accent, selection and light/dark mode. +Controls derive their hover/border colors from this palette, and accent buttons +choose a contrasting label color. The outer frame remains managed by Hyprland. + +Theme changes are detected within about two seconds while the app is active, +and refreshed on focus. No hook, restart, or changes to Omarchy theme files +are required. Explicit Light/Dark choices keep Floatick's original colors. +Missing or invalid palettes fall back to ordinary system light/dark mode. diff --git a/packaging/omarchy/floatick.desktop b/packaging/omarchy/floatick.desktop new file mode 100644 index 0000000..3f6f376 --- /dev/null +++ b/packaging/omarchy/floatick.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=Floatick +Comment=Local-first floating todos and notes +Exec=floatick +Icon=floatick +Terminal=false +Categories=Office; +StartupWMClass=floatick diff --git a/packaging/omarchy/floatick.lua b/packaging/omarchy/floatick.lua new file mode 100644 index 0000000..eeedd9b --- /dev/null +++ b/packaging/omarchy/floatick.lua @@ -0,0 +1,9 @@ +-- Omarchy / Hyprland >= 0.55. Load after Omarchy defaults. +-- The XWayland class is set by GTK to the application executable name. +o.window({ class = "^(floatick|Floatick|io.github.lucaslushuo.floatick)$" }, { + float = true, + -- Pinned XWayland windows cover Fcitx candidate popups on Hyprland. + pin = false, + -- Inherit Omarchy border colors, width, rounding, opacity and shadow. + center = true, +}) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index df14980..05f0730 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1096,7 +1096,9 @@ dependencies = [ "tauri-plugin-autostart", "tauri-plugin-log", "tauri-plugin-opener", + "tauri-plugin-single-instance", "tauri-plugin-sparkle-updater", + "toml 0.8.2", ] [[package]] @@ -3736,6 +3738,22 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-single-instance" +version = "2.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0cb5c412a5071b69bab6a6df1583cbb89460d4a83b6a24769b08d15b6b1e1" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.20", + "tokio", + "tracing", + "windows-sys 0.60.2", + "zbus", +] + [[package]] name = "tauri-plugin-sparkle-updater" version = "0.2.5" @@ -4783,6 +4801,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -4816,13 +4843,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -4853,6 +4897,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4865,6 +4915,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4877,12 +4933,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4895,6 +4963,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4907,6 +4981,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4919,6 +4999,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4931,6 +5017,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 356e4bd..34fbe9c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "floatick" version = "0.4.2" -description = "A focused, local-first floating todo and note app for macOS" +description = "A focused, local-first floating todo and note app for macOS and Linux" authors = ["Lucas"] edition = "2021" rust-version = "1.77.2" @@ -15,6 +15,7 @@ tauri-build = { version = "2.6.3", features = [] } [dependencies] serde_json = "1.0" +toml = "0.8" serde = { version = "1.0", features = ["derive"] } log = "0.4" tauri = { version = "2.11.3", features = ["macos-private-api", "tray-icon", "image-png"] } @@ -26,3 +27,6 @@ chrono = { version = "0.4", features = ["serde"] } [target.'cfg(target_os = "macos")'.dependencies] tauri-plugin-sparkle-updater = "=0.2.5" + +[target.'cfg(target_os = "linux")'.dependencies] +tauri-plugin-single-instance = "2" diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index ad7bf70..12de616 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -105,3 +105,8 @@ pub fn set_autostart_enabled(app_handle: AppHandle, enabled: bool) -> Result<(), pub fn quit_app(app_handle: AppHandle) { app_handle.exit(0); } + +#[tauri::command] +pub fn uses_system_window_frame() -> bool { + crate::panel::uses_system_window_frame() +} diff --git a/src-tauri/src/desktop_theme.rs b/src-tauri/src/desktop_theme.rs new file mode 100644 index 0000000..afa8c1c --- /dev/null +++ b/src-tauri/src/desktop_theme.rs @@ -0,0 +1,61 @@ +use serde::Serialize; +use std::collections::BTreeMap; + +#[derive(Debug, Serialize, PartialEq)] +pub struct DesktopTheme { + colors: BTreeMap, + mode: Option, +} + +fn parse_theme(source: &str) -> Option { + let values: BTreeMap = toml::from_str(source).ok()?; + let mut colors = BTreeMap::new(); + for key in ["background", "foreground", "accent", "selection", "muted"] { + if let Some(value) = values.get(key).and_then(|v| v.as_str()) { + if value.len() == 7 && value.starts_with('#') + && value[1..].bytes().all(|c| c.is_ascii_hexdigit()) { + colors.insert(key.to_string(), value.to_string()); + } + } + } + if !["background", "foreground", "accent"].iter().all(|key| colors.contains_key(*key)) { + return None; + } + let mode = values.get("mode").and_then(|v| v.as_str()) + .filter(|m| matches!(*m, "dark" | "light")).map(String::from); + Some(DesktopTheme { colors, mode }) +} + +/// Read only Omarchy's active palette, including older config-dir installations. +#[tauri::command] +pub fn get_omarchy_theme() -> Option { + #[cfg(target_os = "linux")] + { + for base in [dirs::state_dir(), dirs::config_dir()].into_iter().flatten() { + let path = base.join("omarchy/current/theme/colors.toml"); + if path.is_file() { + return std::fs::read_to_string(path).ok().and_then(|s| parse_theme(&s)); + } + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn supports_dark_light_and_legacy_palettes() { + for mode in ["mode = 'dark'", "mode = 'light'", ""] { + let theme = parse_theme(&format!("{mode}\nbackground='#121212'\nforeground='#bebebe'\naccent='#e68e0d'\nselection='#333333'\n")).unwrap(); + assert_eq!(theme.colors["accent"], "#e68e0d"); + assert_eq!(theme.mode.is_some(), !mode.is_empty()); + } + } + #[test] + fn rejects_missing_invalid_and_css_injection_values() { + for source in ["", "background = 42", "background='#121212'\nforeground='#ffffff'\naccent='red; background: url(x)'", "background='#121212'\nforeground='#ffffff'\naccent='#zzzzzz'"] { + assert!(parse_theme(source).is_none()); + } + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 01d25ee..8f21c69 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,12 +4,32 @@ mod panel; pub mod storage; pub mod tray; mod updates; +mod desktop_theme; use tauri::{Manager, WindowEvent}; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + // GTK must choose its backend before any toolkit or worker threads start. + // Hyprland's native Wayland backend cannot honor absolute panel positions. + #[cfg(target_os = "linux")] + if std::env::var_os("HYPRLAND_INSTANCE_SIGNATURE").is_some() + && std::env::var_os("DISPLAY").is_some() + { + // Omarchy may export GDK_BACKEND=wayland globally. Use an app-specific + // override so launching from the menu and autostart behave identically. + let backend = std::env::var_os("FLOATICK_GDK_BACKEND") + .unwrap_or_else(|| "x11".into()); + std::env::set_var("GDK_BACKEND", backend); + } + let builder = tauri::Builder::default(); + #[cfg(target_os = "linux")] + let builder = builder.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| { + if !args.iter().any(|arg| arg == "--autostart") { + tray::show_window(app); + } + })); #[cfg(target_os = "macos")] let builder = builder.plugin(tauri_plugin_sparkle_updater::init()); builder @@ -27,11 +47,14 @@ pub fn run() { )?; } - // Setup macOS Menu Bar System Tray + // Setup the desktop system tray tray::setup_tray(app.handle())?; // Blur / Click-outside handling & initial window position if let Some(window) = app.get_webview_window("main") { + if panel::uses_system_window_frame() { + window.set_decorations(true)?; + } let is_autostart = std::env::args().any(|arg| arg == "--autostart"); if !is_autostart { let app_handle = app.handle().clone(); @@ -46,8 +69,14 @@ pub fn run() { let w_clone = window.clone(); window.on_window_event(move |event| { - if let WindowEvent::Focused(focused) = event { - panel::on_focus_changed(&w_clone, *focused); + match event { + WindowEvent::Focused(focused) => panel::on_focus_changed(&w_clone, *focused), + #[cfg(target_os = "linux")] + WindowEvent::CloseRequested { api, .. } => { + api.prevent_close(); + let _ = panel::hide_window(&w_clone); + } + _ => {} } }); } @@ -63,6 +92,8 @@ pub fn run() { commands::save_notes, commands::get_settings, commands::save_settings, + commands::uses_system_window_frame, + desktop_theme::get_omarchy_theme, commands::hide_window, commands::toggle_window, commands::set_always_on_top, @@ -76,10 +107,11 @@ pub fn run() { ]) .build(tauri::generate_context!()) .expect("error while building tauri application") - .run(|app_handle, event| { - if let tauri::RunEvent::Reopen { .. } = event { + .run(|_app_handle, _event| { + #[cfg(target_os = "macos")] + if let tauri::RunEvent::Reopen { .. } = _event { log::info!(target: "floatick::panel", "reopen"); - tray::show_window(app_handle); + tray::show_window(_app_handle); } }); } diff --git a/src-tauri/src/panel.rs b/src-tauri/src/panel.rs index 82848b1..6269478 100644 --- a/src-tauri/src/panel.rs +++ b/src-tauri/src/panel.rs @@ -2,6 +2,12 @@ use std::sync::Mutex; use std::time::Duration; use tauri::WebviewWindow; +// Hyprland supplies the outer frame, including Omarchy's square theme border. +pub(crate) fn uses_system_window_frame() -> bool { + cfg!(target_os = "linux") + && std::env::var_os("HYPRLAND_INSTANCE_SIGNATURE").is_some() +} + const BLUR_SETTLE_DELAY: Duration = Duration::from_millis(150); #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index ccdf1c1..5bd28ec 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -4,6 +4,10 @@ use tauri::{ AppHandle, LogicalPosition, Manager, Position, Rect, }; +// Linux tray-icon uses this ID in a shared runtime icon directory. +// Keep it application-specific to avoid collisions with other Tauri apps. +const TRAY_ID: &str = "io.github.lucaslushuo.floatick.tray"; + pub fn setup_tray(app: &AppHandle) -> Result<(), Box> { let show_item = MenuItem::with_id(app, "show", "显示 Floatick", true, None::<&str>)?; let quit_item = MenuItem::with_id(app, "quit", "退出 Floatick", true, None::<&str>)?; @@ -11,7 +15,10 @@ pub fn setup_tray(app: &AppHandle) -> Result<(), Box> { let menu = Menu::with_items(app, &[&show_item, &sep, &quit_item])?; + #[cfg(target_os = "macos")] let tray_icon = tauri::image::Image::from_bytes(include_bytes!("../icons/tray-icon@2x.png"))?; + #[cfg(not(target_os = "macos"))] + let tray_icon = tauri::image::Image::from_bytes(include_bytes!("../icons/32x32.png"))?; let initial_count = crate::storage::load_todos() .map(|todos| { @@ -28,12 +35,12 @@ pub fn setup_tray(app: &AppHandle) -> Result<(), Box> { "".to_string() }; - let _tray = TrayIconBuilder::with_id("main-tray") + let _tray = TrayIconBuilder::with_id(TRAY_ID) .icon(tray_icon) - .icon_as_template(true) + .icon_as_template(cfg!(target_os = "macos")) .title(title_str) .menu(&menu) - .show_menu_on_left_click(false) + .show_menu_on_left_click(cfg!(target_os = "linux")) .tooltip("Floatick") .on_menu_event(|app_handle, event| match event.id.as_ref() { "show" => { @@ -187,12 +194,12 @@ pub fn position_window_at_rect(window: &tauri::WebviewWindow, rect: Rect) { let min_x = mon_pos.x + 8.0; let max_x = mon_pos.x + mon_size.width - window_width - 8.0; - window_x = window_x.clamp(min_x, max_x); + window_x = window_x.clamp(min_x, max_x.max(min_x)); if tray_size.height > 0.0 { let min_y = mon_pos.y + 8.0; let max_y = mon_pos.y + mon_size.height - window_height - 8.0; - window_y = window_y.clamp(min_y, max_y); + window_y = window_y.clamp(min_y, max_y.max(min_y)); } else { window_y = mon_pos.y + 36.0; } @@ -225,8 +232,18 @@ pub fn show_window(app_handle: &AppHandle) { if let Some(window) = app_handle.get_webview_window("main") { crate::panel::cancel_pending_hide(); + #[cfg(target_os = "linux")] + if std::env::var_os("HYPRLAND_INSTANCE_SIGNATURE").is_some() { + // Let the compositor's Floatick rule position the panel. XWayland + // coordinates differ from monitor logical coordinates at fractional + // scales; applying GTK positions here can move it to another screen. + let _ = window.show(); + let _ = window.set_focus(); + return; + } + let mut positioned = false; - if let Some(tray) = app_handle.tray_by_id("main-tray") { + if let Some(tray) = app_handle.tray_by_id(TRAY_ID) { if let Ok(Some(rect)) = tray.rect() { let size = rect.size.to_logical::(1.0); if size.width > 0.0 && size.height > 0.0 { @@ -261,7 +278,7 @@ pub fn show_window(app_handle: &AppHandle) { } pub fn update_tray_todo_count(app: &AppHandle, count: usize) { - if let Some(tray) = app.tray_by_id("main-tray") { + if let Some(tray) = app.tray_by_id(TRAY_ID) { let title_str = if count > 0 { format!(" {}", count) } else { diff --git a/src-tauri/src/updates.rs b/src-tauri/src/updates.rs index 99d4200..0748ffd 100644 --- a/src-tauri/src/updates.rs +++ b/src-tauri/src/updates.rs @@ -15,6 +15,7 @@ pub struct UpdateSettings { #[tauri::command] pub async fn get_update_settings(app: AppHandle) -> Result { + #[allow(unused_mut)] // Only Sparkle mutates this on macOS. let mut settings = UpdateSettings { current_version: app.package_info().version.to_string(), available: false, @@ -37,9 +38,9 @@ pub async fn get_update_settings(app: AppHandle) -> Result Result<(), String> { +pub async fn check_for_updates(_app: AppHandle) -> Result<(), String> { #[cfg(target_os = "macos")] - if let Some(updater) = app.sparkle_updater() { + if let Some(updater) = _app.sparkle_updater() { if !updater.can_check_for_updates().map_err(|e| e.to_string())? { return Err("update_in_progress".into()); } @@ -52,11 +53,13 @@ pub async fn check_for_updates(app: AppHandle) -> Result<(), String> { #[tauri::command] pub async fn set_automatically_checks_for_updates( - app: AppHandle, + _app: AppHandle, enabled: bool, ) -> Result<(), String> { + #[cfg(not(target_os = "macos"))] + let _ = enabled; #[cfg(target_os = "macos")] - if let Some(updater) = app.sparkle_updater() { + if let Some(updater) = _app.sparkle_updater() { return updater .set_automatically_checks_for_updates(enabled) .map_err(|e| e.to_string()); diff --git a/src/App.tsx b/src/App.tsx index 7232410..dfc9a67 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,4 @@ +import { isPrimaryShortcut } from "./lib/platform"; import React, { useEffect, useState } from "react"; import { Header } from "@/components/layout/Header"; import { ContentSwitcher } from "@/components/layout/ContentSwitcher"; @@ -79,6 +80,8 @@ export const App: React.FC = () => { // Global Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { + // Let the IME consume candidate navigation/confirmation before app shortcuts. + if (e.isComposing || e.keyCode === 229) return; // Esc closes drawers first, then hides window if (e.key === "Escape") { if (isSettingsOpen) { @@ -101,30 +104,30 @@ export const App: React.FC = () => { } // Cmd+1 -> Todos, Cmd+2 -> Notes - if ((e.metaKey || e.ctrlKey) && e.key === "1") { + if (isPrimaryShortcut(e) && e.key === "1") { e.preventDefault(); setActiveTab("todos"); } - if ((e.metaKey || e.ctrlKey) && e.key === "2") { + if (isPrimaryShortcut(e) && e.key === "2") { e.preventDefault(); setActiveTab("notes"); } // Cmd+, -> Settings - if ((e.metaKey || e.ctrlKey) && e.key === ",") { + if (isPrimaryShortcut(e) && e.key === ",") { e.preventDefault(); setIsSettingsOpen(true); } // Cmd+F -> Focus Search input - if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "f") { + if (isPrimaryShortcut(e) && e.key.toLowerCase() === "f") { e.preventDefault(); const searchInput = document.querySelector('input[type="text"]') as HTMLInputElement | null; searchInput?.focus(); } // Cmd+N -> New Item - if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "n") { + if (isPrimaryShortcut(e) && e.key.toLowerCase() === "n") { e.preventDefault(); if (isSettingsOpen || isTagDrawerOpen || isTodoEditorOpen || isNoteEditorOpen) return; window.dispatchEvent(new CustomEvent("floatick:new-item")); @@ -141,7 +144,7 @@ export const App: React.FC = () => { return (
diff --git a/src/components/common/ActionBar.tsx b/src/components/common/ActionBar.tsx index 79b23da..6596d7d 100644 --- a/src/components/common/ActionBar.tsx +++ b/src/components/common/ActionBar.tsx @@ -62,7 +62,7 @@ export const ActionBar: React.FC = ({ > 0 ? "fill" : "regular"} /> {selectedTagCount > 0 && ( - + {selectedTagCount} )} diff --git a/src/components/common/FloatickTiptapEditor.tsx b/src/components/common/FloatickTiptapEditor.tsx index f5b6067..eaa3029 100644 --- a/src/components/common/FloatickTiptapEditor.tsx +++ b/src/components/common/FloatickTiptapEditor.tsx @@ -1,3 +1,4 @@ +import { isPrimaryShortcut, shortcutLabel } from "../../lib/platform"; import React, { useEffect, useState, useRef, useMemo } from "react"; import { useEditor, EditorContent, Editor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; @@ -329,7 +330,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = {/* Headings */} editor.chain().focus().toggleHeading({ level: 1 }).run()} > @@ -337,7 +338,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = editor.chain().focus().toggleHeading({ level: 2 }).run()} > @@ -349,7 +350,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = {/* Inline styles */} editor.chain().focus().toggleBold().run()} > @@ -357,7 +358,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = editor.chain().focus().toggleItalic().run()} > @@ -365,7 +366,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = editor.chain().focus().toggleStrike().run()} > @@ -373,7 +374,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = editor.chain().focus().toggleCode().run()} > @@ -385,7 +386,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = {/* Lists */} editor.chain().focus().toggleTaskList().run()} > @@ -393,7 +394,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = editor.chain().focus().toggleBulletList().run()} > @@ -401,7 +402,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = editor.chain().focus().toggleOrderedList().run()} > @@ -541,7 +542,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) =
editor.chain().focus().undo().run()} > @@ -549,7 +550,7 @@ export const EditorToolbar: React.FC<{ editor: Editor | null }> = ({ editor }) = editor.chain().focus().redo().run()} > @@ -700,8 +701,8 @@ export const FloatickTiptapEditor: React.FC = ({ } } - // Cmd+Enter or Ctrl+Enter to trigger save - if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { + // Save with the platform primary modifier (Ctrl on Omarchy). + if (isPrimaryShortcut(event) && event.key === "Enter") { event.preventDefault(); onCmdEnter?.(); return true; diff --git a/src/components/notes/NoteEditorDrawer.tsx b/src/components/notes/NoteEditorDrawer.tsx index 07176cc..59f9aa8 100644 --- a/src/components/notes/NoteEditorDrawer.tsx +++ b/src/components/notes/NoteEditorDrawer.tsx @@ -1,3 +1,4 @@ +import { isPrimaryShortcut, shortcutLabel } from "../../lib/platform"; import React, { useState, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { @@ -94,8 +95,8 @@ export const NoteEditorDrawer: React.FC = ({ } } - // In edit mode: Cmd+Enter to save - if (mode === "edit" && (e.metaKey || e.ctrlKey) && e.key === "Enter") { + // Save with the platform primary modifier (Ctrl on Omarchy). + if (mode === "edit" && isPrimaryShortcut(e) && e.key === "Enter") { e.preventDefault(); handleSave(); } @@ -320,7 +321,7 @@ export const NoteEditorDrawer: React.FC = ({