Compare commits
8 Commits
v0.12.3-de
...
ready/ui-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f59e66065 | ||
|
|
51ac7f152d | ||
|
|
df74c06ba2 | ||
|
|
5f144ca24d | ||
|
|
de66b1349a | ||
|
|
3d888fee64 | ||
|
|
1abcc8ee3c | ||
|
|
d0d5c309e6 |
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"minServerVersion": "0.12.3",
|
||||
"latestUIVersion": "0.12.3-rtl",
|
||||
"uiPackageURL": "https://github.com/MusiCode1/CodeNomad/releases/download/v0.12.3-rtl/codenomad-ui-rtl.zip",
|
||||
"sha256": "a2ce1aaa04345a2f9ca9d3c3149567867f3a5e477cf6eb269381e6dc1bec7ca2"
|
||||
}
|
||||
67
packages/tauri-app/Cargo.lock
generated
67
packages/tauri-app/Cargo.lock
generated
@@ -473,7 +473,6 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-global-shortcut",
|
||||
"tauri-plugin-notification",
|
||||
"tauri-plugin-opener",
|
||||
"thiserror 1.0.69",
|
||||
@@ -1351,16 +1350,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||
dependencies = [
|
||||
"rustix 1.1.4",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
@@ -1493,24 +1482,6 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "global-hotkey"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"keyboard-types",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
"windows-sys 0.59.0",
|
||||
"x11rb",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.18.0"
|
||||
@@ -4084,21 +4055,6 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-global-shortcut"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405"
|
||||
dependencies = [
|
||||
"global-hotkey",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-notification"
|
||||
version = "2.3.3"
|
||||
@@ -5779,29 +5735,6 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix 1.1.4",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "xkeysym"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
|
||||
@@ -23,7 +23,6 @@ keepawake = "0.6"
|
||||
tauri-plugin-dialog = "2"
|
||||
dirs = "5"
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-global-shortcut = "2"
|
||||
url = "2"
|
||||
tauri-plugin-notification = "2"
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2378,72 +2378,6 @@
|
||||
"const": "dialog:deny-save",
|
||||
"markdownDescription": "Denies the save command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:default",
|
||||
"markdownDescription": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n"
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_registered command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:allow-is-registered",
|
||||
"markdownDescription": "Enables the is_registered command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the register command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:allow-register",
|
||||
"markdownDescription": "Enables the register command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the register_all command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:allow-register-all",
|
||||
"markdownDescription": "Enables the register_all command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the unregister command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:allow-unregister",
|
||||
"markdownDescription": "Enables the unregister command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the unregister_all command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:allow-unregister-all",
|
||||
"markdownDescription": "Enables the unregister_all command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_registered command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:deny-is-registered",
|
||||
"markdownDescription": "Denies the is_registered command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the register command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:deny-register",
|
||||
"markdownDescription": "Denies the register command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the register_all command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:deny-register-all",
|
||||
"markdownDescription": "Denies the register_all command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the unregister command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:deny-unregister",
|
||||
"markdownDescription": "Denies the unregister command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the unregister_all command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "global-shortcut:deny-unregister-all",
|
||||
"markdownDescription": "Denies the unregister_all command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||
"type": "string",
|
||||
|
||||
@@ -8,14 +8,10 @@ use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Mutex;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder};
|
||||
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
|
||||
use tauri::webview::Webview;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WindowEvent, Wry};
|
||||
use tauri_plugin_global_shortcut::{
|
||||
Code as ShortcutCode, GlobalShortcutExt, Shortcut, ShortcutState,
|
||||
};
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, Wry};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use url::Url;
|
||||
|
||||
@@ -29,10 +25,6 @@ use std::os::windows::ffi::OsStrExt;
|
||||
use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
||||
|
||||
static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
||||
const DEFAULT_ZOOM_LEVEL: f64 = 1.0;
|
||||
const ZOOM_STEP: f64 = 0.2;
|
||||
const MIN_ZOOM_LEVEL: f64 = 0.2;
|
||||
const MAX_ZOOM_LEVEL: f64 = 5.0;
|
||||
|
||||
#[cfg(windows)]
|
||||
const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
||||
@@ -40,7 +32,6 @@ const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
||||
pub struct AppState {
|
||||
pub manager: CliProcessManager,
|
||||
pub wake_lock: Mutex<Option<KeepAwake>>,
|
||||
pub zoom_level: Mutex<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
@@ -166,83 +157,6 @@ fn emit_folder_drop_event(
|
||||
}
|
||||
}
|
||||
|
||||
fn clamp_zoom_level(value: f64) -> f64 {
|
||||
value.clamp(MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)
|
||||
}
|
||||
|
||||
fn set_main_window_zoom(app_handle: &AppHandle, next_zoom: f64) {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let normalized = clamp_zoom_level(next_zoom);
|
||||
if window.set_zoom(normalized).is_ok() {
|
||||
if let Ok(mut zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
||||
*zoom_level = normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reload_main_window(app_handle: &AppHandle) {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.reload();
|
||||
}
|
||||
}
|
||||
|
||||
fn force_reload_main_window(app_handle: &AppHandle) {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
if let Ok(mut url) = window.url() {
|
||||
if should_allow_internal(&url) {
|
||||
let reload_token = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis()
|
||||
.to_string();
|
||||
|
||||
let existing_pairs: Vec<(String, String)> = url
|
||||
.query_pairs()
|
||||
.into_owned()
|
||||
.filter(|(key, _)| key != "__codenomad_force_reload")
|
||||
.collect();
|
||||
|
||||
{
|
||||
let mut pairs = url.query_pairs_mut();
|
||||
pairs.clear();
|
||||
for (key, value) in existing_pairs {
|
||||
pairs.append_pair(&key, &value);
|
||||
}
|
||||
pairs.append_pair("__codenomad_force_reload", &reload_token);
|
||||
}
|
||||
|
||||
let _ = window.navigate(url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = window.reload();
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_fullscreen_window(app_handle: &AppHandle) {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let next_fullscreen = !window.is_fullscreen().unwrap_or(false);
|
||||
let _ = window.set_fullscreen(next_fullscreen);
|
||||
if cfg!(not(target_os = "macos")) {
|
||||
if next_fullscreen {
|
||||
let _ = window.hide_menu();
|
||||
} else {
|
||||
let _ = window.show_menu();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fullscreen_shortcut() -> Option<Shortcut> {
|
||||
if cfg!(target_os = "macos") {
|
||||
None
|
||||
} else {
|
||||
Some(Shortcut::new(None, ShortcutCode::F11))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn set_windows_app_user_model_id() {
|
||||
let app_id: Vec<u16> = OsStr::new(WINDOWS_APP_USER_MODEL_ID)
|
||||
@@ -267,48 +181,15 @@ fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(
|
||||
tauri_plugin_global_shortcut::Builder::new()
|
||||
.with_handler(|app, shortcut, event| {
|
||||
if event.state() != ShortcutState::Pressed {
|
||||
return;
|
||||
}
|
||||
|
||||
if fullscreen_shortcut().as_ref() == Some(shortcut) {
|
||||
toggle_fullscreen_window(app);
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(navigation_guard)
|
||||
.manage(AppState {
|
||||
manager: CliProcessManager::new(),
|
||||
wake_lock: Mutex::new(None),
|
||||
zoom_level: Mutex::new(DEFAULT_ZOOM_LEVEL),
|
||||
})
|
||||
.setup(|app| {
|
||||
set_windows_app_user_model_id();
|
||||
build_menu(&app.handle())?;
|
||||
if let Some(shortcut) = fullscreen_shortcut() {
|
||||
let shortcut_manager = app.handle().global_shortcut();
|
||||
let _ = shortcut_manager.register(shortcut.clone());
|
||||
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let app_handle = app.handle().clone();
|
||||
window.on_window_event(move |event| {
|
||||
if let WindowEvent::Focused(focused) = event {
|
||||
let shortcut_manager = app_handle.global_shortcut();
|
||||
if *focused {
|
||||
let _ = shortcut_manager.register(shortcut.clone());
|
||||
} else {
|
||||
let _ = shortcut_manager.unregister(shortcut.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let dev_mode = is_dev_mode();
|
||||
let app_handle = app.handle().clone();
|
||||
let manager = app.state::<AppState>().manager.clone();
|
||||
@@ -333,42 +214,36 @@ fn main() {
|
||||
let _ = window.emit("menu:newInstance", ());
|
||||
}
|
||||
}
|
||||
"close" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.close();
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
app_handle.exit(0);
|
||||
}
|
||||
|
||||
// View menu
|
||||
"reload" => {
|
||||
reload_main_window(app_handle);
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.eval("window.location.reload()");
|
||||
}
|
||||
}
|
||||
"force_reload" => {
|
||||
force_reload_main_window(app_handle);
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.eval("window.location.reload(true)");
|
||||
}
|
||||
}
|
||||
"toggle_devtools" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
if window.is_devtools_open() {
|
||||
window.close_devtools();
|
||||
} else {
|
||||
window.open_devtools();
|
||||
}
|
||||
}
|
||||
}
|
||||
"reset_zoom" => {
|
||||
set_main_window_zoom(app_handle, DEFAULT_ZOOM_LEVEL);
|
||||
}
|
||||
"zoom_in" => {
|
||||
if let Ok(zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
||||
set_main_window_zoom(app_handle, *zoom_level + ZOOM_STEP);
|
||||
}
|
||||
}
|
||||
"zoom_out" => {
|
||||
if let Ok(zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
||||
set_main_window_zoom(app_handle, *zoom_level - ZOOM_STEP);
|
||||
window.open_devtools();
|
||||
}
|
||||
}
|
||||
|
||||
"toggle_fullscreen" => {
|
||||
toggle_fullscreen_window(app_handle);
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.set_fullscreen(!window.is_fullscreen().unwrap_or(false));
|
||||
}
|
||||
}
|
||||
|
||||
// Window menu
|
||||
@@ -382,11 +257,6 @@ fn main() {
|
||||
let _ = window.maximize();
|
||||
}
|
||||
}
|
||||
"close_window" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.close();
|
||||
}
|
||||
}
|
||||
|
||||
// App menu (macOS)
|
||||
"about" => {
|
||||
@@ -474,7 +344,6 @@ fn main() {
|
||||
|
||||
fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
let is_mac = cfg!(target_os = "macos");
|
||||
let is_linux = cfg!(target_os = "linux");
|
||||
|
||||
// Create submenus
|
||||
let mut submenus = Vec::new();
|
||||
@@ -502,74 +371,16 @@ fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
Some("CmdOrCtrl+N"),
|
||||
)?;
|
||||
|
||||
let file_menu = if is_mac {
|
||||
SubmenuBuilder::new(app, "File")
|
||||
.item(&new_instance_item)
|
||||
.separator()
|
||||
.close_window()
|
||||
.build()?
|
||||
} else {
|
||||
SubmenuBuilder::new(app, "File")
|
||||
.item(&new_instance_item)
|
||||
.separator()
|
||||
.text("quit", "Quit")
|
||||
.build()?
|
||||
};
|
||||
let file_menu = SubmenuBuilder::new(app, "File")
|
||||
.item(&new_instance_item)
|
||||
.separator()
|
||||
.text(
|
||||
if is_mac { "close" } else { "quit" },
|
||||
if is_mac { "Close" } else { "Quit" },
|
||||
)
|
||||
.build()?;
|
||||
submenus.push(file_menu);
|
||||
|
||||
let reload_item = MenuItem::with_id(app, "reload", "Reload", true, Some("CmdOrCtrl+R"))?;
|
||||
let force_reload_item = MenuItem::with_id(
|
||||
app,
|
||||
"force_reload",
|
||||
"Force Reload",
|
||||
true,
|
||||
Some("CmdOrCtrl+Shift+R"),
|
||||
)?;
|
||||
let toggle_devtools_item = MenuItem::with_id(
|
||||
app,
|
||||
"toggle_devtools",
|
||||
"Toggle Developer Tools",
|
||||
true,
|
||||
Some("Alt+CmdOrCtrl+I"),
|
||||
)?;
|
||||
let reset_zoom_item =
|
||||
MenuItem::with_id(app, "reset_zoom", "Actual Size", true, Some("CmdOrCtrl+0"))?;
|
||||
let zoom_in_item = MenuItem::with_id(
|
||||
app,
|
||||
"zoom_in",
|
||||
if is_mac { "Zoom In" } else { "Zoom In\tCtrl++" },
|
||||
true,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let zoom_out_item = MenuItem::with_id(
|
||||
app,
|
||||
"zoom_out",
|
||||
if is_mac {
|
||||
"Zoom Out"
|
||||
} else {
|
||||
"Zoom Out\tCtrl+-"
|
||||
},
|
||||
true,
|
||||
None::<&str>,
|
||||
)?;
|
||||
let toggle_fullscreen_item = MenuItem::with_id(
|
||||
app,
|
||||
"toggle_fullscreen",
|
||||
if is_mac {
|
||||
"Toggle Full Screen"
|
||||
} else {
|
||||
"Toggle Full Screen\tF11"
|
||||
},
|
||||
true,
|
||||
if is_mac {
|
||||
Some("Ctrl+Cmd+F")
|
||||
} else {
|
||||
None::<&str>
|
||||
},
|
||||
)?;
|
||||
let close_window_item =
|
||||
MenuItem::with_id(app, "close_window", "Close", true, Some("CmdOrCtrl+W"))?;
|
||||
|
||||
// Edit menu with predefined items for standard functionality
|
||||
let edit_menu = SubmenuBuilder::new(app, "Edit")
|
||||
.undo()
|
||||
@@ -585,39 +396,20 @@ fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
|
||||
// View menu
|
||||
let view_menu = SubmenuBuilder::new(app, "View")
|
||||
.item(&reload_item)
|
||||
.item(&force_reload_item)
|
||||
.item(&toggle_devtools_item)
|
||||
.text("reload", "Reload")
|
||||
.text("force_reload", "Force Reload")
|
||||
.text("toggle_devtools", "Toggle Developer Tools")
|
||||
.separator()
|
||||
.item(&reset_zoom_item)
|
||||
.item(&zoom_in_item)
|
||||
.item(&zoom_out_item)
|
||||
.separator()
|
||||
.item(&toggle_fullscreen_item)
|
||||
.text("toggle_fullscreen", "Toggle Full Screen")
|
||||
.build()?;
|
||||
submenus.push(view_menu);
|
||||
|
||||
// Window menu
|
||||
let window_menu = if is_linux {
|
||||
SubmenuBuilder::new(app, "Window")
|
||||
.text("minimize", "Minimize")
|
||||
.text("zoom", "Zoom")
|
||||
.separator()
|
||||
.item(&close_window_item)
|
||||
.build()?
|
||||
} else if is_mac {
|
||||
SubmenuBuilder::new(app, "Window")
|
||||
.minimize()
|
||||
.maximize()
|
||||
.build()?
|
||||
} else {
|
||||
SubmenuBuilder::new(app, "Window")
|
||||
.minimize()
|
||||
.maximize()
|
||||
.separator()
|
||||
.close_window()
|
||||
.build()?
|
||||
};
|
||||
let window_menu = SubmenuBuilder::new(app, "Window")
|
||||
.text("minimize", "Minimize")
|
||||
.text("zoom", "Zoom")
|
||||
.build()?;
|
||||
submenus.push(window_menu);
|
||||
|
||||
// Build the main menu with all submenus
|
||||
|
||||
@@ -404,7 +404,6 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="text-sm font-medium text-primary whitespace-normal break-words transition-colors"
|
||||
dir="auto"
|
||||
classList={{
|
||||
"text-accent": isFocused(),
|
||||
}}
|
||||
|
||||
@@ -244,7 +244,6 @@ export function Markdown(props: MarkdownProps) {
|
||||
<div
|
||||
ref={containerRef}
|
||||
class="markdown-body"
|
||||
dir="auto"
|
||||
data-view="markdown"
|
||||
data-part-id={resolved().partId}
|
||||
data-markdown-theme={resolved().themeKey}
|
||||
|
||||
@@ -902,7 +902,6 @@ export default function MessageBlock(props: MessageBlockProps) {
|
||||
onDeleteMessagesUpTo={props.onDeleteMessagesUpTo}
|
||||
selectedMessageIds={props.selectedMessageIds}
|
||||
onToggleSelectedMessage={props.onToggleSelectedMessage}
|
||||
onContentRendered={props.onContentRendered}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
@@ -1281,7 +1280,6 @@ interface ReasoningCardProps {
|
||||
onDeleteMessagesUpTo?: (messageId: string) => void | Promise<void>
|
||||
selectedMessageIds?: () => Set<string>
|
||||
onToggleSelectedMessage?: (messageId: string, selected: boolean) => void
|
||||
onContentRendered?: () => void
|
||||
}
|
||||
|
||||
function ReasoningCard(props: ReasoningCardProps) {
|
||||
@@ -1290,25 +1288,6 @@ function ReasoningCard(props: ReasoningCardProps) {
|
||||
const [deletingMessage, setDeletingMessage] = createSignal(false)
|
||||
const [deletingUpTo, setDeletingUpTo] = createSignal(false)
|
||||
const isSelectedForDeletion = () => Boolean(props.selectedMessageIds?.().has(props.messageId))
|
||||
let pendingRenderNotificationFrame: number | null = null
|
||||
|
||||
const notifyContentRendered = () => {
|
||||
if (!props.onContentRendered || typeof requestAnimationFrame !== "function") return
|
||||
if (pendingRenderNotificationFrame !== null) {
|
||||
cancelAnimationFrame(pendingRenderNotificationFrame)
|
||||
}
|
||||
pendingRenderNotificationFrame = requestAnimationFrame(() => {
|
||||
pendingRenderNotificationFrame = null
|
||||
props.onContentRendered?.()
|
||||
})
|
||||
}
|
||||
|
||||
onCleanup(() => {
|
||||
if (pendingRenderNotificationFrame !== null) {
|
||||
cancelAnimationFrame(pendingRenderNotificationFrame)
|
||||
pendingRenderNotificationFrame = null
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
setExpanded(Boolean(props.defaultExpanded))
|
||||
@@ -1377,12 +1356,6 @@ function ReasoningCard(props: ReasoningCardProps) {
|
||||
const viewHideLabel = () =>
|
||||
expanded() ? t("messageBlock.reasoning.indicator.hide") : t("messageBlock.reasoning.indicator.view")
|
||||
|
||||
createEffect(() => {
|
||||
if (!expanded()) return
|
||||
reasoningText()
|
||||
notifyContentRendered()
|
||||
})
|
||||
|
||||
const canDeleteMessage = () => Boolean(props.showDeleteMessage) && !deletingMessage()
|
||||
|
||||
const handleDeleteMessage = async (event: MouseEvent) => {
|
||||
|
||||
@@ -542,7 +542,7 @@ export default function MessageItem(props: MessageItemProps) {
|
||||
|
||||
</header>
|
||||
|
||||
<div class="pt-1 whitespace-pre-wrap break-words leading-[1.1]" dir="auto">
|
||||
<div class="pt-1 whitespace-pre-wrap break-words leading-[1.1]">
|
||||
|
||||
|
||||
<Show when={props.isQueued && isUser()}>
|
||||
@@ -550,7 +550,7 @@ export default function MessageItem(props: MessageItemProps) {
|
||||
</Show>
|
||||
|
||||
<Show when={errorMessage()}>
|
||||
<div class="message-error-block" dir="auto">⚠️ {errorMessage()}</div>
|
||||
<div class="message-error-block">⚠️ {errorMessage()}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={isGenerating()}>
|
||||
|
||||
@@ -133,12 +133,11 @@ export default function MessagePart(props: MessagePartProps) {
|
||||
<Show when={!shouldHideTextPart() && partHasRenderableText(props.part)}>
|
||||
<div
|
||||
class={canRenderMarkdown() ? markdownContainerClass() : textContainerClass()}
|
||||
dir="auto"
|
||||
data-role={textContainerRole()}
|
||||
data-part-type="text"
|
||||
data-part-id={typeof (props.part as any)?.id === "string" ? (props.part as any).id : undefined}
|
||||
>
|
||||
<Show when={canRenderMarkdown()} fallback={<span class="text-primary" dir="auto">{plainTextContent()}</span>}>
|
||||
<Show when={canRenderMarkdown()} fallback={<span class="text-primary">{plainTextContent()}</span>}>
|
||||
<Markdown
|
||||
part={createTextPartForMarkdown()}
|
||||
instanceId={props.instanceId}
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { DeleteHoverState } from "../types/delete-hover"
|
||||
import { buildRecordDisplayData } from "../stores/message-v2/record-display-cache"
|
||||
import { getPartCharCount } from "../lib/token-utils"
|
||||
|
||||
const SCROLL_SENTINEL_MARGIN_PX = 8
|
||||
const SCROLL_SENTINEL_MARGIN_PX = 48
|
||||
const MESSAGE_SCROLL_CACHE_SCOPE = "message-stream"
|
||||
const QUOTE_SELECTION_MAX_LENGTH = 2000
|
||||
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
||||
|
||||
@@ -488,7 +488,6 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
class={`prompt-input ${mode() === "shell" ? "shell-mode" : ""} ${expandState() === "expanded" ? "is-expanded" : ""}`}
|
||||
dir="auto"
|
||||
placeholder={getPlaceholder()}
|
||||
value={prompt()}
|
||||
onInput={handleInput}
|
||||
|
||||
@@ -444,7 +444,7 @@ const SessionList: Component<SessionListProps> = (props) => {
|
||||
</Show>
|
||||
|
||||
{rowProps.isChild ? <Bot class="w-4 h-4 flex-shrink-0" /> : <User class="w-4 h-4 flex-shrink-0" />}
|
||||
<span class="session-item-title session-item-title--clamp" dir="auto">{title()}</span>
|
||||
<span class="session-item-title session-item-title--clamp">{title()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="session-item-row session-item-meta">
|
||||
|
||||
@@ -76,7 +76,6 @@ const SessionRenameDialog: Component<SessionRenameDialogProps> = (props) => {
|
||||
inputRef = element
|
||||
}}
|
||||
type="text"
|
||||
dir="auto"
|
||||
value={title()}
|
||||
onInput={(event) => setTitle(event.currentTarget.value)}
|
||||
placeholder={t("sessionRenameDialog.input.placeholder")}
|
||||
|
||||
@@ -88,7 +88,7 @@ export function createAnsiContentRenderer(params: {
|
||||
|
||||
return (
|
||||
<div class={messageClass} ref={params.scrollHelpers.registerContainer} onScroll={params.scrollHelpers.handleScroll}>
|
||||
<pre class="tool-call-content tool-call-ansi" dir="auto" innerHTML={nextCache.html} />
|
||||
<pre class="tool-call-content tool-call-ansi" innerHTML={nextCache.html} />
|
||||
{params.scrollHelpers.renderSentinel()}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ export function renderDiagnosticsSection(
|
||||
{entry.displayPath}
|
||||
<span class="tool-call-diagnostic-coords">:L{entry.line || "-"}:C{entry.column || "-"}</span>
|
||||
</span>
|
||||
<span class="tool-call-diagnostic-message" dir="auto">{entry.message}</span>
|
||||
<span class="tool-call-diagnostic-message">{entry.message}</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -43,7 +43,7 @@ export function createMarkdownContentRenderer(params: {
|
||||
ref={registerRef}
|
||||
onScroll={disableScrollTracking ? undefined : params.scrollHelpers.handleScroll}
|
||||
>
|
||||
<pre class="whitespace-pre-wrap break-words text-sm font-mono" dir="auto">{options.content}</pre>
|
||||
<pre class="whitespace-pre-wrap break-words text-sm font-mono">{options.content}</pre>
|
||||
{params.scrollHelpers.renderSentinel({ disableTracking: disableScrollTracking })}
|
||||
</div>
|
||||
)
|
||||
|
||||
200
packages/ui/src/lib/git-diff-lowlight.ts
Normal file
200
packages/ui/src/lib/git-diff-lowlight.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { createLowlight, common } from "lowlight"
|
||||
|
||||
type AstNode = {
|
||||
type: string
|
||||
value?: string
|
||||
children?: AstNode[]
|
||||
startIndex?: number
|
||||
endIndex?: number
|
||||
lineNumber?: number
|
||||
}
|
||||
|
||||
type SyntaxNodeEntry = {
|
||||
node: AstNode
|
||||
wrapper?: AstNode
|
||||
}
|
||||
|
||||
type SyntaxFileLine = {
|
||||
value: string
|
||||
lineNumber: number
|
||||
valueLength: number
|
||||
nodeList: SyntaxNodeEntry[]
|
||||
}
|
||||
|
||||
type LowlightApi = ReturnType<typeof createLowlight>
|
||||
|
||||
export function processAST(ast: { children: AstNode[] }) {
|
||||
let lineNumber = 1
|
||||
const syntaxObj: Record<number, SyntaxFileLine> = {}
|
||||
|
||||
const loopAST = (nodes: AstNode[], wrapper?: AstNode) => {
|
||||
nodes.forEach((node) => {
|
||||
if (node.type === "text") {
|
||||
const textValue = node.value ?? ""
|
||||
if (!textValue.includes("\n")) {
|
||||
const valueLength = textValue.length
|
||||
if (!syntaxObj[lineNumber]) {
|
||||
node.startIndex = 0
|
||||
node.endIndex = valueLength - 1
|
||||
syntaxObj[lineNumber] = {
|
||||
value: textValue,
|
||||
lineNumber,
|
||||
valueLength,
|
||||
nodeList: [{ node, wrapper }],
|
||||
}
|
||||
} else {
|
||||
node.startIndex = syntaxObj[lineNumber].valueLength
|
||||
node.endIndex = node.startIndex + valueLength - 1
|
||||
syntaxObj[lineNumber].value += textValue
|
||||
syntaxObj[lineNumber].valueLength += valueLength
|
||||
syntaxObj[lineNumber].nodeList.push({ node, wrapper })
|
||||
}
|
||||
node.lineNumber = lineNumber
|
||||
return
|
||||
}
|
||||
|
||||
const lines = textValue.split("\n")
|
||||
node.children = node.children || []
|
||||
for (let index = 0; index < lines.length; index++) {
|
||||
const value = index === lines.length - 1 ? lines[index] : `${lines[index]}\n`
|
||||
const currentLineNumber = index === 0 ? lineNumber : ++lineNumber
|
||||
const valueLength = value.length
|
||||
const childNode: AstNode = {
|
||||
type: "text",
|
||||
value,
|
||||
startIndex: Infinity,
|
||||
endIndex: Infinity,
|
||||
lineNumber: currentLineNumber,
|
||||
}
|
||||
|
||||
if (!syntaxObj[currentLineNumber]) {
|
||||
childNode.startIndex = 0
|
||||
childNode.endIndex = valueLength - 1
|
||||
syntaxObj[currentLineNumber] = {
|
||||
value,
|
||||
lineNumber: currentLineNumber,
|
||||
valueLength,
|
||||
nodeList: [{ node: childNode, wrapper }],
|
||||
}
|
||||
} else {
|
||||
childNode.startIndex = syntaxObj[currentLineNumber].valueLength
|
||||
childNode.endIndex = childNode.startIndex + valueLength - 1
|
||||
syntaxObj[currentLineNumber].value += value
|
||||
syntaxObj[currentLineNumber].valueLength += valueLength
|
||||
syntaxObj[currentLineNumber].nodeList.push({ node: childNode, wrapper })
|
||||
}
|
||||
|
||||
node.children.push(childNode)
|
||||
}
|
||||
|
||||
node.lineNumber = lineNumber
|
||||
return
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
loopAST(node.children, node)
|
||||
node.lineNumber = lineNumber
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loopAST(ast.children)
|
||||
return { syntaxFileObject: syntaxObj, syntaxFileLineNumber: lineNumber }
|
||||
}
|
||||
|
||||
export function _getAST() {
|
||||
return {}
|
||||
}
|
||||
|
||||
const lowlight = createLowlight(common)
|
||||
|
||||
lowlight.register("vue", function hljsDefineVue(hljs: any) {
|
||||
return {
|
||||
subLanguage: "xml",
|
||||
contains: [
|
||||
hljs.COMMENT("<!--", "-->", { relevance: 10 }),
|
||||
{
|
||||
begin: /^(\s*)(<script>)/gm,
|
||||
end: /^(\s*)(<\/script>)/gm,
|
||||
subLanguage: "javascript",
|
||||
excludeBegin: true,
|
||||
excludeEnd: true,
|
||||
},
|
||||
{
|
||||
begin: /^(?:\s*)(?:<script\s+lang=(["'])ts\1>)/gm,
|
||||
end: /^(\s*)(<\/script>)/gm,
|
||||
subLanguage: "typescript",
|
||||
excludeBegin: true,
|
||||
excludeEnd: true,
|
||||
},
|
||||
{
|
||||
begin: /^(\s*)(<style(\s+scoped)?>)/gm,
|
||||
end: /^(\s*)(<\/style>)/gm,
|
||||
subLanguage: "css",
|
||||
excludeBegin: true,
|
||||
excludeEnd: true,
|
||||
},
|
||||
{
|
||||
begin: /^(?:\s*)(?:<style(?:\s+scoped)?\s+lang=(["'])(?:s[ca]ss)\1(?:\s+scoped)?>)/gm,
|
||||
end: /^(\s*)(<\/style>)/gm,
|
||||
subLanguage: "scss",
|
||||
excludeBegin: true,
|
||||
excludeEnd: true,
|
||||
},
|
||||
{
|
||||
begin: /^(?:\s*)(?:<style(?:\s+scoped)?\s+lang=(["'])stylus\1(?:\s+scoped)?>)/gm,
|
||||
end: /^(\s*)(<\/style>)/gm,
|
||||
subLanguage: "stylus",
|
||||
excludeBegin: true,
|
||||
excludeEnd: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
let maxLineToIgnoreSyntax = 2000
|
||||
const ignoreSyntaxHighlightList: (string | RegExp)[] = []
|
||||
|
||||
export const highlighter = {
|
||||
name: "lowlight",
|
||||
get maxLineToIgnoreSyntax() {
|
||||
return maxLineToIgnoreSyntax
|
||||
},
|
||||
setMaxLineToIgnoreSyntax(value: number) {
|
||||
maxLineToIgnoreSyntax = value
|
||||
},
|
||||
get ignoreSyntaxHighlightList() {
|
||||
return ignoreSyntaxHighlightList
|
||||
},
|
||||
setIgnoreSyntaxHighlightList(values: (string | RegExp)[]) {
|
||||
ignoreSyntaxHighlightList.length = 0
|
||||
ignoreSyntaxHighlightList.push(...values)
|
||||
},
|
||||
getAST(raw: string, fileName?: string, lang?: string) {
|
||||
const language = typeof lang === "string" ? lang.trim() : ""
|
||||
if (
|
||||
fileName &&
|
||||
ignoreSyntaxHighlightList.some((item) => (item instanceof RegExp ? item.test(fileName) : fileName === item))
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (language && lowlight.registered(language)) {
|
||||
return lowlight.highlight(language, raw)
|
||||
}
|
||||
|
||||
return lowlight.highlightAuto(raw)
|
||||
},
|
||||
processAST(ast: { children: AstNode[] }) {
|
||||
return processAST(ast)
|
||||
},
|
||||
hasRegisteredCurrentLang(lang: string) {
|
||||
return lowlight.registered(lang)
|
||||
},
|
||||
getHighlighterEngine(): LowlightApi {
|
||||
return lowlight
|
||||
},
|
||||
type: "class" as const,
|
||||
}
|
||||
|
||||
export const versions = "local-common"
|
||||
@@ -24,21 +24,6 @@
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Auto-detect text direction per block element for RTL language support (e.g. Hebrew, Arabic) */
|
||||
.markdown-body p,
|
||||
.markdown-body li,
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6,
|
||||
.markdown-body blockquote,
|
||||
.markdown-body td,
|
||||
.markdown-body th {
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
@@ -144,19 +129,16 @@
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
border-inline-start: 3px solid var(--border-base);
|
||||
border-left: 3px solid var(--border-base);
|
||||
color: var(--text-secondary);
|
||||
background-color: var(--surface-muted);
|
||||
padding: 0.5rem 1rem;
|
||||
border-start-start-radius: 0;
|
||||
border-start-end-radius: 8px;
|
||||
border-end-end-radius: 8px;
|
||||
border-end-start-radius: 0;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
padding-inline-start: 1.5rem;
|
||||
padding-left: 1.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
@@ -184,7 +166,7 @@
|
||||
.markdown-body td {
|
||||
border: 1px solid var(--border-base);
|
||||
padding: 0.5rem 0.75rem;
|
||||
text-align: start;
|
||||
text-align: left;
|
||||
color: var(--text-primary);
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -239,7 +221,7 @@
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
|
||||
margin-inline-start: auto;
|
||||
margin-left: auto;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
|
||||
@@ -132,13 +132,6 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-stream-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.message-step-start {
|
||||
background-color: var(--message-assistant-bg);
|
||||
border-left: 4px solid var(--message-assistant-border);
|
||||
|
||||
Reference in New Issue
Block a user