mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-20 21:39:25 +01:00
Compare commits
8 Commits
v0.7.0
...
add-macos-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7f8f7ef6e | ||
|
|
4709f8c660 | ||
|
|
4696aa1759 | ||
|
|
924fc09516 | ||
|
|
5a700662dd | ||
|
|
8f992bfa92 | ||
|
|
e7dd27c744 | ||
|
|
7914836c3e |
@@ -5,22 +5,36 @@ title: "Release Notes"
|
|||||||
|
|
||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
Information about release notes of Coco Server is provided here.
|
Information about release notes of Coco App is provided here.
|
||||||
|
|
||||||
## Latest (In development)
|
## Latest (In development)
|
||||||
|
|
||||||
### ❌ Breaking changes
|
### ❌ Breaking changes
|
||||||
|
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
||||||
|
- feat: enhance ui for skipped version #834
|
||||||
|
|
||||||
### 🐛 Bug fix
|
### 🐛 Bug fix
|
||||||
|
|
||||||
|
- fix: fix issue with update check failure #833
|
||||||
|
|
||||||
|
### ✈️ Improvements
|
||||||
|
|
||||||
|
## 0.7.1 (2025-07-27)
|
||||||
|
|
||||||
|
### ❌ Breaking changes
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
### 🐛 Bug fix
|
||||||
|
|
||||||
|
- fix: correct enter key behavior #828
|
||||||
|
|
||||||
### ✈️ Improvements
|
### ✈️ Improvements
|
||||||
|
|
||||||
- chore: web component add notification component #825
|
- chore: web component add notification component #825
|
||||||
|
- refactor: collection behavior defaults to `MoveToActiveSpace`, and only use `CanJoinAllSpaces` when window is pinned #829
|
||||||
|
|
||||||
## 0.7.0 (2025-07-25)
|
## 0.7.0 (2025-07-25)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coco",
|
"name": "coco",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
83
src-tauri/Cargo.lock
generated
83
src-tauri/Cargo.lock
generated
@@ -840,7 +840,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coco"
|
name = "coco"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"applications",
|
"applications",
|
||||||
@@ -852,6 +852,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chinese-number",
|
"chinese-number",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"cocoa 0.24.1",
|
||||||
"derive_more 2.0.1",
|
"derive_more 2.0.1",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"enigo",
|
"enigo",
|
||||||
@@ -914,6 +915,22 @@ dependencies = [
|
|||||||
"zip 4.0.0",
|
"zip 4.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cocoa"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"block",
|
||||||
|
"cocoa-foundation 0.1.2",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-graphics 0.22.3",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
|
"libc",
|
||||||
|
"objc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa"
|
name = "cocoa"
|
||||||
version = "0.26.0"
|
version = "0.26.0"
|
||||||
@@ -922,14 +939,28 @@ checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"block",
|
"block",
|
||||||
"cocoa-foundation",
|
"cocoa-foundation 0.2.0",
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics",
|
"core-graphics 0.24.0",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cocoa-foundation"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"block",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-graphics-types 0.1.3",
|
||||||
|
"libc",
|
||||||
|
"objc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa-foundation"
|
name = "cocoa-foundation"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -939,7 +970,7 @@ dependencies = [
|
|||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"block",
|
"block",
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics-types",
|
"core-graphics-types 0.2.0",
|
||||||
"libc",
|
"libc",
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
@@ -1056,6 +1087,19 @@ version = "0.8.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-graphics"
|
||||||
|
version = "0.22.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-graphics-types 0.1.3",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-graphics"
|
name = "core-graphics"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
@@ -1064,11 +1108,22 @@ checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics-types",
|
"core-graphics-types 0.2.0",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-graphics-types"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-graphics-types"
|
name = "core-graphics-types"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1472,8 +1527,8 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67fd9ae1736d6ebb2e472740fbee86fb2178b8d56feb98a6751411d4c95b7e72"
|
checksum = "67fd9ae1736d6ebb2e472740fbee86fb2178b8d56feb98a6751411d4c95b7e72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa 0.26.0",
|
||||||
"core-graphics",
|
"core-graphics 0.24.0",
|
||||||
"dunce",
|
"dunce",
|
||||||
"gdk",
|
"gdk",
|
||||||
"gdkx11",
|
"gdkx11",
|
||||||
@@ -1562,7 +1617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0cf6f550bbbdd5fe66f39d429cb2604bcdacbf00dca0f5bbe2e9306a0009b7c6"
|
checksum = "0cf6f550bbbdd5fe66f39d429cb2604bcdacbf00dca0f5bbe2e9306a0009b7c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics",
|
"core-graphics 0.24.0",
|
||||||
"foreign-types-shared 0.3.1",
|
"foreign-types-shared 0.3.1",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@@ -2714,7 +2769,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.59.0",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5721,7 +5776,7 @@ checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"core-graphics",
|
"core-graphics 0.24.0",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
@@ -5889,7 +5944,7 @@ dependencies = [
|
|||||||
"ntapi",
|
"ntapi",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-io-kit",
|
"objc2-io-kit",
|
||||||
"windows 0.59.0",
|
"windows 0.61.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5947,7 +6002,7 @@ checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics",
|
"core-graphics 0.24.0",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dispatch",
|
"dispatch",
|
||||||
"dlopen2",
|
"dlopen2",
|
||||||
@@ -6145,9 +6200,9 @@ source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2#d4b9df797959f8fa
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"block",
|
"block",
|
||||||
"cocoa",
|
"cocoa 0.26.0",
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics",
|
"core-graphics 0.24.0",
|
||||||
"objc",
|
"objc",
|
||||||
"objc-foundation",
|
"objc-foundation",
|
||||||
"objc_id",
|
"objc_id",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "coco"
|
name = "coco"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
description = "Search, connect, collaborate – all in one place."
|
description = "Search, connect, collaborate – all in one place."
|
||||||
authors = ["INFINI Labs"]
|
authors = ["INFINI Labs"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
@@ -109,6 +109,7 @@ sysinfo = "0.35.2"
|
|||||||
|
|
||||||
[target."cfg(target_os = \"macos\")".dependencies]
|
[target."cfg(target_os = \"macos\")".dependencies]
|
||||||
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" }
|
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" }
|
||||||
|
cocoa = "0.24"
|
||||||
|
|
||||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
|
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
|
||||||
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
|
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ pub fn run() {
|
|||||||
server::synthesize::synthesize,
|
server::synthesize::synthesize,
|
||||||
util::file::get_file_icon,
|
util::file::get_file_icon,
|
||||||
util::app_lang::update_app_lang,
|
util::app_lang::update_app_lang,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
setup::toggle_move_to_active_space_attribute,
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
//credits to: https://github.com/ayangweb/ayangweb-EcoPaste/blob/169323dbe6365ffe4abb64d867439ed2ea84c6d1/src-tauri/src/core/setup/mac.rs
|
//! credits to: https://github.com/ayangweb/ayangweb-EcoPaste/blob/169323dbe6365ffe4abb64d867439ed2ea84c6d1/src-tauri/src/core/setup/mac.rs
|
||||||
use tauri::{App, Emitter, EventTarget, WebviewWindow};
|
|
||||||
|
use cocoa::appkit::NSWindow;
|
||||||
|
use tauri::Manager;
|
||||||
|
use tauri::{App, AppHandle, Emitter, EventTarget, WebviewWindow};
|
||||||
use tauri_nspanel::{WebviewWindowExt, cocoa::appkit::NSWindowCollectionBehavior, panel_delegate};
|
use tauri_nspanel::{WebviewWindowExt, cocoa::appkit::NSWindowCollectionBehavior, panel_delegate};
|
||||||
|
|
||||||
use crate::common::MAIN_WINDOW_LABEL;
|
use crate::common::MAIN_WINDOW_LABEL;
|
||||||
@@ -29,7 +32,7 @@ pub fn platform(
|
|||||||
|
|
||||||
// Share the window across all desktop spaces and full screen
|
// Share the window across all desktop spaces and full screen
|
||||||
panel.set_collection_behaviour(
|
panel.set_collection_behaviour(
|
||||||
NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces
|
NSWindowCollectionBehavior::NSWindowCollectionBehaviorMoveToActiveSpace
|
||||||
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary
|
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary
|
||||||
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary,
|
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary,
|
||||||
);
|
);
|
||||||
@@ -78,3 +81,50 @@ pub fn platform(
|
|||||||
// Set the delegate object for the window to handle window events
|
// Set the delegate object for the window to handle window events
|
||||||
panel.set_delegate(delegate);
|
panel.set_delegate(delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change NS window attribute between `NSWindowCollectionBehaviorCanJoinAllSpaces`
|
||||||
|
/// and `NSWindowCollectionBehaviorMoveToActiveSpace` accordingly.
|
||||||
|
///
|
||||||
|
/// NOTE: this tauri command is not async because we should run it in the main
|
||||||
|
/// thread, or `ns_window.setCollectionBehavior_(collection_behavior)` would lead
|
||||||
|
/// to UB.
|
||||||
|
#[tauri::command]
|
||||||
|
pub(crate) fn toggle_move_to_active_space_attribute(tauri_app_hanlde: AppHandle) {
|
||||||
|
use cocoa::appkit::NSWindowCollectionBehavior;
|
||||||
|
use cocoa::base::id;
|
||||||
|
|
||||||
|
let main_window = tauri_app_hanlde
|
||||||
|
.get_webview_window(MAIN_WINDOW_LABEL)
|
||||||
|
.unwrap();
|
||||||
|
let ns_window = main_window.ns_window().unwrap() as id;
|
||||||
|
let mut collection_behavior = unsafe { ns_window.collectionBehavior() };
|
||||||
|
let join_all_spaces = collection_behavior
|
||||||
|
.contains(NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces);
|
||||||
|
let move_to_active_space = collection_behavior
|
||||||
|
.contains(NSWindowCollectionBehavior::NSWindowCollectionBehaviorMoveToActiveSpace);
|
||||||
|
|
||||||
|
match (join_all_spaces, move_to_active_space) {
|
||||||
|
(true, false) => {
|
||||||
|
collection_behavior
|
||||||
|
.remove(NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces);
|
||||||
|
collection_behavior
|
||||||
|
.insert(NSWindowCollectionBehavior::NSWindowCollectionBehaviorMoveToActiveSpace);
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
collection_behavior
|
||||||
|
.remove(NSWindowCollectionBehavior::NSWindowCollectionBehaviorMoveToActiveSpace);
|
||||||
|
collection_behavior
|
||||||
|
.insert(NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!(
|
||||||
|
"invalid NS window attribute, NSWindowCollectionBehaviorCanJoinAllSpaces is set [{}], NSWindowCollectionBehaviorMoveToActiveSpace is set [{}]",
|
||||||
|
join_all_spaces, move_to_active_space
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ns_window.setCollectionBehavior_(collection_behavior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use tauri_plugin_updater::RemoteRelease;
|
|||||||
///
|
///
|
||||||
/// If the version string is in the `x.y.z` format and does not include a build
|
/// If the version string is in the `x.y.z` format and does not include a build
|
||||||
/// number, we assume a build number of 0.
|
/// number, we assume a build number of 0.
|
||||||
fn extract_version_number(version: &Version) -> u32 {
|
fn extract_build_number(version: &Version) -> u32 {
|
||||||
let pre = &version.pre;
|
let pre = &version.pre;
|
||||||
|
|
||||||
if pre.is_empty() {
|
if pre.is_empty() {
|
||||||
@@ -52,8 +52,8 @@ fn extract_version_number(version: &Version) -> u32 {
|
|||||||
pub(crate) fn custom_version_comparator(local: Version, remote_release: RemoteRelease) -> bool {
|
pub(crate) fn custom_version_comparator(local: Version, remote_release: RemoteRelease) -> bool {
|
||||||
let remote = remote_release.version;
|
let remote = remote_release.version;
|
||||||
|
|
||||||
let local_build_number = extract_version_number(&local);
|
let local_build_number = extract_build_number(&local);
|
||||||
let remote_build_number = extract_version_number(&remote);
|
let remote_build_number = extract_build_number(&remote);
|
||||||
|
|
||||||
let should_update = remote_build_number > local_build_number;
|
let should_update = remote_build_number > local_build_number;
|
||||||
log::debug!(
|
log::debug!(
|
||||||
@@ -65,3 +65,23 @@ pub(crate) fn custom_version_comparator(local: Version, remote_release: RemoteRe
|
|||||||
|
|
||||||
should_update
|
should_update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_build_number() {
|
||||||
|
// 0.6.0 => 0
|
||||||
|
let version = Version::parse("0.6.0").unwrap();
|
||||||
|
assert_eq!(extract_build_number(&version), 0);
|
||||||
|
|
||||||
|
// 0.6.0-2371 => 2371
|
||||||
|
let version = Version::parse("0.6.0-2371").unwrap();
|
||||||
|
assert_eq!(extract_build_number(&version), 2371);
|
||||||
|
|
||||||
|
// 0.6.0-SNAPSHOT-2371 => 2371
|
||||||
|
let version = Version::parse("0.6.0-SNAPSHOT-2371").unwrap();
|
||||||
|
assert_eq!(extract_build_number(&version), 2371);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"identifier": "rs.coco.app",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"entitlements": "./Entitlements.plist",
|
"entitlements": "./Entitlements.plist",
|
||||||
@@ -7,4 +8,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src-tauri/tauri.macos.conf.json
Normal file
8
src-tauri/tauri.macos.conf.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"identifier": "rs.coco.app",
|
||||||
|
"bundle": {
|
||||||
|
"macOS": {
|
||||||
|
"entitlements": "./Entitlements.plist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,4 +34,8 @@ export function show_check(): Promise<void> {
|
|||||||
|
|
||||||
export function hide_check(): Promise<void> {
|
export function hide_check(): Promise<void> {
|
||||||
return invoke('hide_check');
|
return invoke('hide_check');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggle_move_to_active_space_attribute(): Promise<void> {
|
||||||
|
return invoke('toggle_move_to_active_space_attribute');
|
||||||
}
|
}
|
||||||
@@ -7,12 +7,12 @@ import PinIcon from "@/icons/Pin";
|
|||||||
import WindowsFullIcon from "@/icons/WindowsFull";
|
import WindowsFullIcon from "@/icons/WindowsFull";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
import type { Chat } from "@/types/chat";
|
import type { Chat } from "@/types/chat";
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
|
||||||
import VisibleKey from "../Common/VisibleKey";
|
import VisibleKey from "../Common/VisibleKey";
|
||||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
import { HISTORY_PANEL_ID } from "@/constants";
|
import { HISTORY_PANEL_ID } from "@/constants";
|
||||||
import { AssistantList } from "./AssistantList";
|
import { AssistantList } from "./AssistantList";
|
||||||
import { ServerList } from "./ServerList";
|
import { ServerList } from "./ServerList";
|
||||||
|
import { useTogglePin } from "@/hooks/useTogglePin";
|
||||||
|
|
||||||
interface ChatHeaderProps {
|
interface ChatHeaderProps {
|
||||||
clearChat: () => void;
|
clearChat: () => void;
|
||||||
@@ -35,22 +35,12 @@ export function ChatHeader({
|
|||||||
showChatHistory = true,
|
showChatHistory = true,
|
||||||
assistantIDs,
|
assistantIDs,
|
||||||
}: ChatHeaderProps) {
|
}: ChatHeaderProps) {
|
||||||
const { isPinned, setIsPinned, isTauri } = useAppStore();
|
const { isTauri } = useAppStore();
|
||||||
|
const { isPinned, togglePin } = useTogglePin();
|
||||||
|
|
||||||
const { historicalRecords, newSession, fixedWindow, external } =
|
const { historicalRecords, newSession, fixedWindow, external } =
|
||||||
useShortcutsStore();
|
useShortcutsStore();
|
||||||
|
|
||||||
const togglePin = async () => {
|
|
||||||
try {
|
|
||||||
const newPinned = !isPinned;
|
|
||||||
await platformAdapter.setAlwaysOnTop(newPinned);
|
|
||||||
setIsPinned(newPinned);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to toggle window pin state:", err);
|
|
||||||
setIsPinned(isPinned);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className="flex items-center justify-between py-2 px-3 select-none"
|
className="flex items-center justify-between py-2 px-3 select-none"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { ArrowDown01, CornerDownLeft } from "lucide-react";
|
import { ArrowDown01, CornerDownLeft } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
@@ -19,6 +19,7 @@ import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
|||||||
import { useThemeStore } from "@/stores/themeStore";
|
import { useThemeStore } from "@/stores/themeStore";
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
import FontIcon from "../Icons/FontIcon";
|
import FontIcon from "../Icons/FontIcon";
|
||||||
|
import { useTogglePin } from "@/hooks/useTogglePin";
|
||||||
|
|
||||||
interface FooterProps {
|
interface FooterProps {
|
||||||
setIsPinnedWeb?: (value: boolean) => void;
|
setIsPinnedWeb?: (value: boolean) => void;
|
||||||
@@ -37,32 +38,24 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
|
|||||||
|
|
||||||
const isDark = useThemeStore((state) => state.isDark);
|
const isDark = useThemeStore((state) => state.isDark);
|
||||||
|
|
||||||
const { isTauri, isPinned, setIsPinned } = useAppStore();
|
const { isTauri } = useAppStore();
|
||||||
|
|
||||||
const { setVisible, updateInfo } = useUpdateStore();
|
const { isPinned, togglePin } = useTogglePin({
|
||||||
|
onPinChange: setIsPinnedWeb,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setVisible, updateInfo, skipVersions } = useUpdateStore();
|
||||||
|
|
||||||
const { fixedWindow, modifierKey } = useShortcutsStore();
|
const { fixedWindow, modifierKey } = useShortcutsStore();
|
||||||
|
|
||||||
const setWindowAlwaysOnTop = useCallback(async (isPinned: boolean) => {
|
|
||||||
setIsPinnedWeb?.(isPinned);
|
|
||||||
return platformAdapter.setAlwaysOnTop(isPinned);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const togglePin = async () => {
|
|
||||||
try {
|
|
||||||
const newPinned = !isPinned;
|
|
||||||
await setWindowAlwaysOnTop(newPinned);
|
|
||||||
setIsPinned(newPinned);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to toggle window pin state:", err);
|
|
||||||
setIsPinned(isPinned);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const openSetting = useCallback(() => {
|
const openSetting = useCallback(() => {
|
||||||
return platformAdapter.emitEvent("open_settings", "");
|
return platformAdapter.emitEvent("open_settings", "");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const hasUpdate = useMemo(() => {
|
||||||
|
return updateInfo && !skipVersions.includes(updateInfo.version);
|
||||||
|
}, [updateInfo, skipVersions]);
|
||||||
|
|
||||||
const renderLeft = () => {
|
const renderLeft = () => {
|
||||||
if (sourceData?.source?.name) {
|
if (sourceData?.source?.name) {
|
||||||
return (
|
return (
|
||||||
@@ -108,7 +101,7 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative text-xs text-gray-500 dark:text-gray-400">
|
<div className="relative text-xs text-gray-500 dark:text-gray-400">
|
||||||
{updateInfo?.available ? (
|
{hasUpdate ? (
|
||||||
<div className="cursor-pointer" onClick={() => setVisible(true)}>
|
<div className="cursor-pointer" onClick={() => setVisible(true)}>
|
||||||
<span>{t("search.footer.updateAvailable")}</span>
|
<span>{t("search.footer.updateAvailable")}</span>
|
||||||
<span className="absolute top-0 -right-2 size-1.5 bg-[#FF3434] rounded-full"></span>
|
<span className="absolute top-0 -right-2 size-1.5 bg-[#FF3434] rounded-full"></span>
|
||||||
@@ -138,7 +131,7 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
|
|||||||
onClick={togglePin}
|
onClick={togglePin}
|
||||||
className={clsx({
|
className={clsx({
|
||||||
"text-blue-500": isPinned,
|
"text-blue-500": isPinned,
|
||||||
"pl-2": updateInfo?.available,
|
"pl-2": hasUpdate,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<VisibleKey shortcut={fixedWindow} onKeyPress={togglePin}>
|
<VisibleKey shortcut={fixedWindow} onKeyPress={togglePin}>
|
||||||
|
|||||||
@@ -135,12 +135,13 @@ export function useAssistantManager({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key === "Enter" && !shiftKey) {
|
if (key === "Enter" && !shiftKey) {
|
||||||
if (!isEmpty(value)) {
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (isTauri && !isChatMode && goAskAi) {
|
if (isTauri && !isChatMode && goAskAi) {
|
||||||
|
if (!isEmpty(value)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
return handleAskAi();
|
return handleAskAi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
|||||||
const {
|
const {
|
||||||
visible,
|
visible,
|
||||||
setVisible,
|
setVisible,
|
||||||
skipVersion,
|
skipVersions,
|
||||||
setSkipVersion,
|
setSkipVersions,
|
||||||
isOptional,
|
isOptional,
|
||||||
updateInfo,
|
updateInfo,
|
||||||
setUpdateInfo,
|
setUpdateInfo,
|
||||||
@@ -50,11 +50,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!snapshotUpdate) return;
|
checkUpdateStatus();
|
||||||
|
|
||||||
checkUpdate().catch((error) => {
|
|
||||||
addError("Update failed:" + error, "error");
|
|
||||||
});
|
|
||||||
}, [snapshotUpdate]);
|
}, [snapshotUpdate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -79,13 +75,13 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
|||||||
const update = await checkUpdate();
|
const update = await checkUpdate();
|
||||||
|
|
||||||
if (update) {
|
if (update) {
|
||||||
|
const { skipVersions } = useUpdateStore.getState();
|
||||||
|
|
||||||
|
setVisible(!skipVersions.includes(update.version));
|
||||||
|
|
||||||
setUpdateInfo(update);
|
setUpdateInfo(update);
|
||||||
|
|
||||||
if (skipVersion === update.version) return;
|
|
||||||
|
|
||||||
setVisible(true);
|
|
||||||
}
|
}
|
||||||
}, [skipVersion]);
|
}, [skipVersions]);
|
||||||
|
|
||||||
const cursorClassName = useMemo(() => {
|
const cursorClassName = useMemo(() => {
|
||||||
return state.loading ? "cursor-not-allowed" : "cursor-pointer";
|
return state.loading ? "cursor-not-allowed" : "cursor-pointer";
|
||||||
@@ -133,7 +129,9 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
|||||||
const handleSkip = () => {
|
const handleSkip = () => {
|
||||||
if (state.loading) return;
|
if (state.loading) return;
|
||||||
|
|
||||||
setSkipVersion(updateInfo?.version);
|
const { skipVersions, updateInfo } = useUpdateStore.getState();
|
||||||
|
|
||||||
|
setSkipVersions([...skipVersions, updateInfo.version]);
|
||||||
|
|
||||||
isCheckPage ? hide_check() : setVisible(false);
|
isCheckPage ? hide_check() : setVisible(false);
|
||||||
};
|
};
|
||||||
@@ -182,7 +180,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
|||||||
<img src={isDark ? darkIcon : lightIcon} className="h-6" />
|
<img src={isDark ? darkIcon : lightIcon} className="h-6" />
|
||||||
|
|
||||||
<div className="text-[#333] text-sm leading-5 py-2 dark:text-[#D8D8D8] text-center">
|
<div className="text-[#333] text-sm leading-5 py-2 dark:text-[#D8D8D8] text-center">
|
||||||
{updateInfo?.available ? (
|
{updateInfo ? (
|
||||||
isOptional ? (
|
isOptional ? (
|
||||||
t("update.optional_description")
|
t("update.optional_description")
|
||||||
) : (
|
) : (
|
||||||
@@ -196,7 +194,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{updateInfo?.available ? (
|
{updateInfo ? (
|
||||||
<div
|
<div
|
||||||
className="text-xs text-[#0072FF] cursor-pointer"
|
className="text-xs text-[#0072FF] cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@@ -223,21 +221,21 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
|||||||
cursorClassName,
|
cursorClassName,
|
||||||
state.loading && "opacity-50"
|
state.loading && "opacity-50"
|
||||||
)}
|
)}
|
||||||
onClick={updateInfo?.available ? handleDownload : handleSkip}
|
onClick={updateInfo ? handleDownload : handleSkip}
|
||||||
>
|
>
|
||||||
{state.loading ? (
|
{state.loading ? (
|
||||||
<div className="flex justify-center items-center gap-2">
|
<div className="flex justify-center items-center gap-2">
|
||||||
<LoaderCircle className="animate-spin size-5" />
|
<LoaderCircle className="animate-spin size-5" />
|
||||||
{percent}%
|
{percent}%
|
||||||
</div>
|
</div>
|
||||||
) : updateInfo?.available ? (
|
) : updateInfo ? (
|
||||||
t("update.button.install")
|
t("update.button.install")
|
||||||
) : (
|
) : (
|
||||||
t("update.button.ok")
|
t("update.button.ok")
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{updateInfo?.available && isOptional && (
|
{!isCheckPage && updateInfo && isOptional && (
|
||||||
<div
|
<div
|
||||||
className={clsx("text-xs text-[#999]", cursorClassName)}
|
className={clsx("text-xs text-[#999]", cursorClassName)}
|
||||||
onClick={handleSkip}
|
onClick={handleSkip}
|
||||||
|
|||||||
35
src/hooks/useTogglePin.ts
Normal file
35
src/hooks/useTogglePin.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
|
import { toggle_move_to_active_space_attribute } from "@/commands/system";
|
||||||
|
import { isMac } from "@/utils/platform";
|
||||||
|
|
||||||
|
interface UseTogglePinOptions {
|
||||||
|
onPinChange?: (isPinned: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTogglePin = (options?: UseTogglePinOptions) => {
|
||||||
|
const { isPinned, setIsPinned } = useAppStore();
|
||||||
|
|
||||||
|
const togglePin = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const newPinned = !isPinned;
|
||||||
|
|
||||||
|
if (options?.onPinChange) {
|
||||||
|
options.onPinChange(newPinned);
|
||||||
|
}
|
||||||
|
|
||||||
|
await platformAdapter.setAlwaysOnTop(newPinned);
|
||||||
|
setIsPinned(newPinned);
|
||||||
|
isMac && toggle_move_to_active_space_attribute();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to toggle window pin state:", err);
|
||||||
|
}
|
||||||
|
}, [isPinned, setIsPinned, options?.onPinChange]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isPinned,
|
||||||
|
togglePin,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -4,8 +4,8 @@ import { persist } from "zustand/middleware";
|
|||||||
export type IUpdateStore = {
|
export type IUpdateStore = {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
setVisible: (visible: boolean) => void;
|
setVisible: (visible: boolean) => void;
|
||||||
skipVersion?: string;
|
skipVersions: string[];
|
||||||
setSkipVersion: (skipVersion?: string) => void;
|
setSkipVersions: (skipVersions: string[]) => void;
|
||||||
isOptional: boolean;
|
isOptional: boolean;
|
||||||
setIsOptional: (isOptional: boolean) => void;
|
setIsOptional: (isOptional: boolean) => void;
|
||||||
updateInfo?: any;
|
updateInfo?: any;
|
||||||
@@ -19,8 +19,9 @@ export const useUpdateStore = create<IUpdateStore>()(
|
|||||||
setVisible: (visible: boolean) => {
|
setVisible: (visible: boolean) => {
|
||||||
return set({ visible });
|
return set({ visible });
|
||||||
},
|
},
|
||||||
setSkipVersion: (skipVersion?: string) => {
|
skipVersions: [],
|
||||||
return set({ skipVersion });
|
setSkipVersions: (skipVersions: string[]) => {
|
||||||
|
return set({ skipVersions });
|
||||||
},
|
},
|
||||||
isOptional: true,
|
isOptional: true,
|
||||||
setIsOptional: (isOptional: boolean) => {
|
setIsOptional: (isOptional: boolean) => {
|
||||||
@@ -33,7 +34,7 @@ export const useUpdateStore = create<IUpdateStore>()(
|
|||||||
{
|
{
|
||||||
name: "update-store",
|
name: "update-store",
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
skipVersion: state.skipVersion,
|
skipVersions: state.skipVersions,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user