diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ec9fb3c..7cc3801 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,8 +31,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tagName: nightly - releaseName: 'Typability Nightly' - releaseBody: 'Here is a Typability Night Build.' - releaseDraft: false - prerelease: true + tagName: __VERSION__ + releaseName: 'Typability __VERSION__' + releaseBody: 'Write here' + releaseDraft: true + prerelease: false diff --git a/package.json b/package.json index 1e62973..e07281a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "typability", "private": true, - "version": "0.0.0", + "version": "0.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 95148f8..cbbc699 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -245,6 +245,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cocoa" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667fdc068627a2816b9ff831201dd9864249d6ee8d190b9532357f1fc0f61ea7" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.9.3", + "core-graphics 0.21.0", + "foreign-types", + "libc", + "objc", +] + [[package]] name = "cocoa" version = "0.24.1" @@ -254,8 +269,8 @@ dependencies = [ "bitflags", "block", "cocoa-foundation", - "core-foundation", - "core-graphics", + "core-foundation 0.9.3", + "core-graphics 0.22.3", "foreign-types", "libc", "objc", @@ -269,7 +284,7 @@ checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" dependencies = [ "bitflags", "block", - "core-foundation", + "core-foundation 0.9.3", "core-graphics-types", "foreign-types", "libc", @@ -298,22 +313,62 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + [[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-foundation-sys" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a67c4378cf203eace8fb6567847eb641fd6ff933c1145a115c6ee820ebb978" +dependencies = [ + "bitflags", + "core-foundation 0.9.3", + "foreign-types", + "libc", +] + [[package]] name = "core-graphics" version = "0.22.3" @@ -321,7 +376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.3", "core-graphics-types", "foreign-types", "libc", @@ -334,7 +389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.3", "foreign-types", "libc", ] @@ -2033,6 +2088,22 @@ dependencies = [ "cty", ] +[[package]] +name = "rdev" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f183f2103ea19c0e2ab5c41c930f0fb3270b6ba7695fd0f5d12b73b86e64b10" +dependencies = [ + "cocoa 0.22.0", + "core-foundation 0.7.0", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "lazy_static", + "libc", + "winapi", + "x11", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2186,8 +2257,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" dependencies = [ "bitflags", - "core-foundation", - "core-foundation-sys", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", "libc", "security-framework-sys", ] @@ -2198,7 +2269,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", ] @@ -2550,9 +2621,9 @@ dependencies = [ "bitflags", "cairo-rs", "cc", - "cocoa", - "core-foundation", - "core-graphics", + "cocoa 0.24.1", + "core-foundation 0.9.3", + "core-graphics 0.22.3", "crossbeam-channel", "dispatch", "gdk", @@ -2606,7 +2677,7 @@ checksum = "5b48820ee3bb6a5031a83b2b6e11f8630bdc5a2f68cb841ab8ebc7a15a916679" dependencies = [ "anyhow", "attohttpc", - "cocoa", + "cocoa 0.24.1", "dirs-next", "embed_plist", "encoding_rs", @@ -2733,7 +2804,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30" dependencies = [ - "cocoa", + "cocoa 0.24.1", "gtk", "percent-encoding", "rand 0.8.5", @@ -2968,9 +3039,10 @@ dependencies = [ [[package]] name = "typability" -version = "0.0.0" +version = "0.1.0" dependencies = [ "os_info", + "rdev", "serde", "serde_json", "tauri", @@ -3302,7 +3374,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29d30320647cfc3dc45554c8ad825b84831def81f967a2f7589931328ff9b16d" dependencies = [ - "cocoa", + "cocoa 0.24.1", "objc", "raw-window-handle", "windows-sys 0.42.0", @@ -3314,7 +3386,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f762d9cc392fb85e6b1b5eed1ef13d73fed5149a5cbb017a7137497d14ef612" dependencies = [ - "cocoa", + "cocoa 0.24.1", "objc", "raw-window-handle", "windows-sys 0.42.0", @@ -3556,8 +3628,8 @@ checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98" dependencies = [ "base64", "block", - "cocoa", - "core-graphics", + "cocoa 0.24.1", + "core-graphics 0.22.3", "crossbeam-channel", "dunce", "gdk", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 41480a8..14ee68c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "typability" -version = "0.0.0" -description = "A Tauri App" -authors = ["you"] +version = "0.1.0" +description = "a WYSIWYG markdown editor based on Milkdown." +authors = ["SimonShiki"] license = "" repository = "" edition = "2021" @@ -20,6 +20,7 @@ tauri = { version = "1.2", features = ["api-all"] } window-vibrancy = "0.3.2" window-shadows = "0.2.1" os_info = "3.5.1" +rdev = "0.5.2" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4c2d2e4..d5c8f0e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,18 +3,34 @@ windows_subsystem = "windows" )] +use rdev::{simulate, Button, EventType, Key, SimulateError}; +use std::{thread, time}; + mod setup; +fn send(event_type: &EventType) { + let delay = time::Duration::from_millis(20); + match simulate(event_type) { + Ok(()) => (), + Err(SimulateError) => { + println!("We could not send {:?}", event_type); + } + } + // Let ths OS catchup (at least MacOS) + thread::sleep(delay); +} + // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) +fn open_emoji_panel() { + send(&EventType::KeyPress(Key::MetaLeft)); + send(&EventType::KeyPress(Key::Dot)); } fn main() { tauri::Builder::default() .setup(setup::init) - .invoke_handler(tauri::generate_handler![greet]) + .invoke_handler(tauri::generate_handler![open_emoji_panel]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f599f2a..91d7ab2 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -7,7 +7,7 @@ }, "package": { "productName": "Typability", - "version": "0.0.0" + "version": "0.1.0" }, "tauri": { "allowlist": { diff --git a/src/App.tsx b/src/App.tsx index deb29b8..f71ed91 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,7 @@ import styles from './App.module.scss'; import TitleBar from "./components/TitleBar"; import { useAtom } from "jotai"; import { contentJotai, filePathJotai } from "./jotais/file"; -import { loadingJotai, preferenceJotai } from "./jotais/ui"; +import { aboutJotai, loadingJotai, preferenceJotai } from "./jotais/ui"; import classNames from "classnames"; import { Spinner } from "@fluentui/react-components"; import { useLayoutEffect } from "react"; @@ -13,7 +13,8 @@ import useIsDarkMode from "./hooks/dark"; import Preferences from "./components/Preferences"; import { writeTextFile } from '@tauri-apps/api/fs'; import { settingsJotai } from "./jotais/settings"; -import { useKeyPress } from "ahooks"; +import { useKeyPress, useInterval, useEventListener } from "ahooks"; +import About from "./components/About"; function App() { const [content, setContent] = useAtom(contentJotai); @@ -21,8 +22,22 @@ function App() { const [loading] = useAtom(loadingJotai); const [settings] = useAtom(settingsJotai); const [preference, setPreference] = useAtom(preferenceJotai); + const [about, setAbout] = useAtom(aboutJotai); const isDarkMode = useIsDarkMode(); + // Auto save + useInterval(async () => { + if (filePath === null) return; + await writeTextFile({ path: filePath, contents: content }); + }, settings.autoSave ? settings.saveInterval * 1000 : -1); + + // Save when editor blurred + useEventListener('blur', async () => { + if (!settings.saveBlur || filePath === null) return; + await writeTextFile({ path: filePath, contents: content }); + }); + + // Shortcuts useKeyPress('ctrl.s', async () => { if (filePath === null) return; await writeTextFile({ path: filePath, contents: content }); @@ -63,6 +78,9 @@ function App() { { setPreference(false); }} /> + { + setAbout(false); + }} /> ); diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/typability-icon.svg b/src/assets/typability-icon.svg new file mode 100644 index 0000000..4ca2970 --- /dev/null +++ b/src/assets/typability-icon.svg @@ -0,0 +1 @@ +typability-icon \ No newline at end of file diff --git a/src/components/About/about.module.scss b/src/components/About/about.module.scss new file mode 100644 index 0000000..db8d081 --- /dev/null +++ b/src/components/About/about.module.scss @@ -0,0 +1,19 @@ +.body { + user-select: none; + .about { + margin: 2rem 0; + display: flex; + user-select: text; + img { + width: 50px; + user-select: none; + filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.2)); + margin-right: 2rem; + } + + .version { + display: flex; + flex-direction: column; + } + } +} \ No newline at end of file diff --git a/src/components/About/index.tsx b/src/components/About/index.tsx new file mode 100644 index 0000000..fa79b2f --- /dev/null +++ b/src/components/About/index.tsx @@ -0,0 +1,61 @@ +import React, { useState } from 'react'; +import { + Dialog, + DialogSurface, + DialogBody, + DialogTitle, + DialogContent, + DialogActions, + Button, + Text +} from '@fluentui/react-components'; +import styles from './about.module.scss'; +import icon from '../../assets/typability-icon.svg'; +import { useAsyncEffect } from 'ahooks'; +import { getVersion, getTauriVersion } from '@tauri-apps/api/app'; + +interface AboutProps { + open: boolean; + onClose?: () => void; +} + +const About: React.FC = ({ + open, + onClose +}) => { + const [version, setVersion] = useState(null); + const [tauriVersion, setTauriVersion] = useState(null); + useAsyncEffect(async () => { + setVersion(await getVersion()); + setTauriVersion(await getTauriVersion()); + }, []); + return ( + + + + About + +
+ +
+ Typability + Version: {version === null ? 'Loading...' : version} + Tauri Version: {tauriVersion === null ? 'Loading...' : tauriVersion} +
+
+
+ + + + +
+
+
+ ); +}; + +export default About; diff --git a/src/components/EditMenu/index.tsx b/src/components/EditMenu/index.tsx index 131a0dd..656534d 100644 --- a/src/components/EditMenu/index.tsx +++ b/src/components/EditMenu/index.tsx @@ -1,8 +1,19 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { invoke } from '@tauri-apps/api/tauri'; +import { version as getVersion, type as getType } from '@tauri-apps/api/os'; import { Menu, MenuButton, MenuDivider, MenuItem, MenuList, MenuPopover, MenuTrigger } from '@fluentui/react-components'; import { DocumentEdit16Regular } from '@fluentui/react-icons'; +import { useAsyncEffect } from 'ahooks'; const EditMenu: React.FC = () => { + const [emojiAvailable, setEmojiAvailable] = useState(false); + useAsyncEffect(async () => { + const type = await getType(); + if (type !== 'Windows_NT') return; + const version = await getVersion(); + const buildNumber = parseInt(version.substring(version.lastIndexOf('.') + 1)); + if (buildNumber >= 17134) setEmojiAvailable(true); + }, []); return ( @@ -19,7 +30,12 @@ const EditMenu: React.FC = () => { Find Replace - Emoji & Symbols + { + if (emojiAvailable) invoke('open_emoji_panel'); + }} + >Emoji & Symbols Add table Add diagram diff --git a/src/components/FileMenu/index.tsx b/src/components/FileMenu/index.tsx index 85f79aa..2297590 100644 --- a/src/components/FileMenu/index.tsx +++ b/src/components/FileMenu/index.tsx @@ -6,7 +6,7 @@ import { open as openFilePicker, save as saveFilePicker } from '@tauri-apps/api import { documentDir } from '@tauri-apps/api/path'; import { contentJotai, filePathJotai } from '../../jotais/file'; import { writeTextFile } from '@tauri-apps/api/fs'; -import { preferenceJotai } from '../../jotais/ui'; +import { aboutJotai, preferenceJotai } from '../../jotais/ui'; import * as showdown from 'showdown'; const availbleExts = [{ @@ -26,6 +26,7 @@ const FileMenu: React.FC = () => { const [filePath, setFilePath] = useAtom(filePathJotai); const [,setContent] = useAtom(contentJotai); const [, setPreference] = useAtom(preferenceJotai); + const [, setAbout] = useAtom(aboutJotai); const [content] = useAtom(contentJotai); return ( @@ -91,6 +92,9 @@ const FileMenu: React.FC = () => { { setPreference(true); }}>Preferences + { + setAbout(true); + }}>About diff --git a/src/components/Preferences/index.tsx b/src/components/Preferences/index.tsx index 17eaa19..34c31c0 100644 --- a/src/components/Preferences/index.tsx +++ b/src/components/Preferences/index.tsx @@ -105,14 +105,14 @@ const Preferences: React.FC = ({

- Save interval + Save interval (sec)

{ - set('saveInterval', data.value); + set('saveInterval', Math.max(parseInt(data.value) || 0, 10)); }} />
diff --git a/src/components/TitleBar/index.tsx b/src/components/TitleBar/index.tsx index a2203d5..fb6022b 100644 --- a/src/components/TitleBar/index.tsx +++ b/src/components/TitleBar/index.tsx @@ -6,10 +6,11 @@ import { Add20Regular, ArrowDown20Regular, FullScreenMaximize20Regular } from '@ import FileMenu from '../FileMenu'; import EditMenu from '../EditMenu'; import { useAtom } from 'jotai'; -import { preferenceJotai } from '../../jotais/ui'; +import { aboutJotai, preferenceJotai } from '../../jotais/ui'; const TitleBar : React.FC = () => { const [preference] = useAtom(preferenceJotai); + const [about] = useAtom(aboutJotai); return ( <>
@@ -43,7 +44,7 @@ const TitleBar : React.FC = () => { />
- {preference &&
} + {(preference || about) &&
} ); }; diff --git a/src/jotais/ui.ts b/src/jotais/ui.ts index 8158c2d..9a1dae9 100644 --- a/src/jotais/ui.ts +++ b/src/jotais/ui.ts @@ -1,3 +1,4 @@ import { atom } from "jotai"; export const loadingJotai = atom(false); export const preferenceJotai = atom(false); +export const aboutJotai = atom(false);