diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d8b8c6c..d009cc8 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -10,6 +10,8 @@ dependencies = [ "base64 0.22.1", "chrono", "notify", + "objc2", + "objc2-foundation", "open", "regex", "serde", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1afac9d..b301a70 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,3 +33,7 @@ regex = "1" walkdir = "2" tauri-plugin-single-instance = "2" chrono = "0.4" + +[target.'cfg(target_os = "macos")'.dependencies] +objc2 = "0.6" +objc2-foundation = { version = "0.3", features = ["NSUserDefaults", "NSString", "NSDictionary", "NSValue"] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f5c0154..fbb2993 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3632,8 +3632,54 @@ fn handle_cli_args(app: &AppHandle, args: &[String], cwd: &str) -> bool { opened_preview } +// On macOS, WKWebView reads per-app preferences from NSUserDefaults to decide +// whether to show the spelling underline and apply auto-correct / smart-substitution +// in contenteditable regions. These keys default to off for new bundle IDs, which +// is why a fresh Tauri app gets neither the red underline nor auto-replace even +// when the HTML `spellcheck`/`autocorrect` attributes are set. Register the +// WebKit defaults so users get Apple-Notes-like behavior on first launch; this +// must happen before the webview is constructed (setting them inside .setup() is +// too late). registerDefaults is used so user-toggled values via the WebKit +// right-click menu still take precedence. +#[cfg(target_os = "macos")] +fn register_webview_defaults() { + use objc2::runtime::AnyObject; + use objc2_foundation::{NSDictionary, NSNumber, NSString, NSUserDefaults}; + + let keys = [ + "WebContinuousSpellCheckingEnabled", + "WebGrammarCheckingEnabled", + "WebAutomaticSpellingCorrectionEnabled", + "WebAutomaticQuoteSubstitutionEnabled", + "WebAutomaticDashSubstitutionEnabled", + "WebAutomaticTextReplacementEnabled", + "WebAutomaticLinkDetectionEnabled", + ]; + + let ns_keys: Vec<_> = keys.iter().map(|k| NSString::from_str(k)).collect(); + let key_refs: Vec<&NSString> = ns_keys.iter().map(|k| &**k).collect(); + + let yes_values: Vec<_> = (0..keys.len()).map(|_| NSNumber::new_bool(true)).collect(); + let value_refs: Vec<&AnyObject> = yes_values + .iter() + .map(|v| { + let any: &AnyObject = v; + any + }) + .collect(); + + let dict: objc2::rc::Retained> = + NSDictionary::from_slices(&key_refs, &value_refs); + unsafe { + NSUserDefaults::standardUserDefaults().registerDefaults(&dict); + } +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + #[cfg(target_os = "macos")] + register_webview_defaults(); + let app = tauri::Builder::default() // Single-instance: forward CLI args from subsequent launches to the running instance .plugin(tauri_plugin_single_instance::init(|app, args, cwd| { diff --git a/src/components/editor/Editor.tsx b/src/components/editor/Editor.tsx index dcf0716..0c5ff65 100644 --- a/src/components/editor/Editor.tsx +++ b/src/components/editor/Editor.tsx @@ -1132,6 +1132,9 @@ export function Editor({ attributes: { class: "prose prose-lg dark:prose-invert max-w-3xl mx-auto focus:outline-none min-h-full px-6 pt-8 pb-24", + spellcheck: "true", + autocorrect: "on", + autocapitalize: "sentences", }, // Serialize copied text as markdown instead of plain text clipboardTextSerializer: (slice) => {