├── .deepsource.toml ├── .gitignore ├── .prettierrc ├── LICENSE ├── builds ├── debian │ └── config.json └── windows │ └── createinstaller.js ├── package-lock.json ├── package.json ├── readme.md ├── src ├── assets │ ├── Upload icon.svg │ ├── build │ │ ├── icon.icns │ │ ├── icon.ico │ │ └── icon.png │ ├── logo.png │ ├── logo.svg │ ├── tray-icon.png │ ├── tray-icon.svg │ └── ukraine-banner.svg ├── domains │ ├── air-alert │ │ ├── air-alert.domain.ts │ │ └── air-alert.types.ts │ ├── files │ │ ├── files.domain.ts │ │ └── files.types.ts │ ├── index.ts │ └── inline │ │ ├── inline.domain.ts │ │ └── inline.types.ts ├── errors │ ├── errors.module.ts │ └── errors.types.ts ├── main.ts ├── services │ ├── air-alert │ │ ├── air-alert.service.ts │ │ └── air-alert.types.ts │ ├── currencies-convertor │ │ ├── currencies-convertor.service.ts │ │ └── currencies-convertor.types.ts │ ├── index.ts │ ├── spell-checker │ │ ├── spell-checker.service.ts │ │ └── spell-checker.types.ts │ ├── translator │ │ ├── translator.service.ts │ │ └── translator.types.ts │ ├── transliteration │ │ ├── config │ │ │ └── index.ts │ │ └── transliteration.service.ts │ └── url-shortener │ │ └── url-shortener.service.ts ├── settings │ ├── index.ts │ └── settings.types.ts ├── shared │ ├── axios │ │ └── index.ts │ ├── index.ts │ └── utils │ │ ├── humanize-string.ts │ │ ├── index.ts │ │ └── open-website.ts └── views │ ├── air-alerts-settings.html │ ├── convert-currencies-settings.html │ ├── convert-file.html │ ├── decrypt-file.html │ ├── encrypt-file.html │ ├── errors-list.html │ ├── set-default-settings-dialogue.html │ ├── shortcut-settings.html │ ├── support-ukraine.html │ ├── translate-settings.html │ ├── transliterate-settings.html │ ├── upload-file-convert.html │ ├── upload-file-decrypt.html │ └── upload-file-encrypt.html └── tsconfig.json /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "javascript" 5 | enabled = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | *.DS_Store 107 | 108 | release-builds/ 109 | app/ 110 | 111 | .idea 112 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "useTabs": true 7 | } -------------------------------------------------------------------------------- /builds/debian/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "dest": "builds/installers/debian/", 3 | "icon": "src/assets/build/icon.png", 4 | "categories": ["Utility"], 5 | "lintianOverrides": ["changelog-file-missing-in-native-package"] 6 | } 7 | -------------------------------------------------------------------------------- /builds/windows/createinstaller.js: -------------------------------------------------------------------------------- 1 | const createWindowsInstaller = 2 | require("electron-winstaller").createWindowsInstaller; 3 | const path = require("path"); 4 | 5 | /** 6 | * It returns a promise that resolves to an object containing the configuration for the installer 7 | * @returns A promise that resolves to an object. 8 | */ 9 | function getInstallerConfig() { 10 | console.log("creating windows installer"); 11 | const rootPath = path.join("./"); 12 | const outPath = path.join(rootPath, "builds"); 13 | 14 | return Promise.resolve({ 15 | appDirectory: path.join(outPath, 'release-builds', 'windows', "Nodetools-win32-x64/"), 16 | authors: "Mike Buslenko", 17 | noMsi: false, 18 | outputDirectory: path.join(outPath, "installers", "windows"), 19 | exe: "Nodetools.exe", 20 | setupExe: "NodetoolsInstaller.exe", 21 | setupIcon: path.join(rootPath, "src", "assets", "build", "icon.ico"), 22 | }); 23 | } 24 | 25 | getInstallerConfig() 26 | .then(createWindowsInstaller) 27 | .catch((error) => { 28 | console.error(error); 29 | process.exit(1); 30 | }); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Nodetools", 3 | "version": "0.1.10", 4 | "description": "Personal all-hands assistant.", 5 | "main": "./app/main.js", 6 | "compilerOptions": { 7 | "outDir": "app" 8 | }, 9 | "scripts": { 10 | "format": "prettier --write \"src/**/*.ts\"", 11 | "build": "tsc", 12 | "watch": "tsc -w", 13 | "lint": "eslint -c .eslintrc --ext .ts ./src", 14 | "clean": "rm -rf ./app", 15 | "copy-files": "cp -r ./src/assets/ ./app/assets/ && cp -r ./src/views/ ./app/views", 16 | "start": "npm run clean && npm run build && npm run copy-files && electron ./app/main.js", 17 | "pack": "electron-builder --dir", 18 | "dist": "electron-builder", 19 | "dist-all": "electron-builder -mw", 20 | "postinstall": "electron-builder install-app-deps", 21 | "dist-linux": "electron-builder --linux deb" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/mbuslenko/nodetools.git" 26 | }, 27 | "author": "mbuslenko ", 28 | "license": "ISC", 29 | "bugs": { 30 | "url": "https://github.com/mbuslenko/nodetools/issues" 31 | }, 32 | "homepage": "https://github.com/mbuslenko/nodetools#readme", 33 | "dependencies": { 34 | "@nut-tree/nut-js": "^2.1.1", 35 | "axios": "^0.27.2", 36 | "electron-store": "^8.0.2", 37 | "jimp": "^0.16.1", 38 | "node-file-encrypt": "^0.0.6", 39 | "node-mac-permissions": "^2.2.1", 40 | "update-electron-app": "^2.0.1" 41 | }, 42 | "devDependencies": { 43 | "@types/debug": "^4.1.7", 44 | "@types/tmp": "^0.2.3", 45 | "debug": "^4.3.4", 46 | "electron": "^19.0.0", 47 | "electron-builder": "^23.1.0", 48 | "electron-installer-debian": "^3.1.0", 49 | "electron-installer-dmg": "^4.0.0", 50 | "electron-packager": "^15.5.1", 51 | "electron-rebuild": "^3.2.3", 52 | "electron-winstaller": "^5.0.0", 53 | "prettier": "^2.7.1", 54 | "typescript": "^4.7.2" 55 | }, 56 | "build": { 57 | "extraMetadata": { 58 | "main": "app/main.js" 59 | }, 60 | "files": [ 61 | "src/**/*", 62 | "app/**/*" 63 | ], 64 | "appId": "com.nodetools.app", 65 | "mac": { 66 | "target": "dmg", 67 | "icon": "src/assets/build/icon.icns", 68 | "category": "public.app-category.productivity" 69 | }, 70 | "win": { 71 | "target": "nsis", 72 | "icon": "src/assets/build/icon.ico" 73 | }, 74 | "linux": { 75 | "target": "deb", 76 | "icon": "src/assets/build/icon.png" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://i.imgur.com/omBZiP9.png) 2 | 3 | # Nodetools 4 | 5 | Personal all-hands assistant for MacOS, Windows 10+, Ubuntu, Debian and many more. 6 | 7 | ## Features 8 | 9 | - [Inline translator](#translator) 10 | - [Inline transliterator](#transliterator) 11 | - [Inline text humanizer](#humanizer) 12 | - [Inline spell fixer](#spell-fixer) 13 | - [Inline calculator](#calculator) 14 | - [Inline URL shortener](#url-shortener) 15 | - [Inline currencies convertor](#currencies-convertor) 16 | 17 | ## Installation 18 | 19 | Watch out available releases on the [releases page](https://github.com/mbuslenko/nodetools/releases) 20 | or run dev mode from command line: 21 | 22 | ```bash 23 | git clone https://github.com/mbuslenko/nodetools.git 24 | cd nodetools 25 | npm ci 26 | npm run start 27 | ``` 28 | 29 | ## Tech Stack 30 | 31 | **UI:** Vanilla JS & HTML + CSS 32 | 33 | **Logic:** Node JS 34 | 35 | **Made with [Electron](https://www.electronjs.org/) ❤️** 36 | 37 | ## Features Demo 38 | 39 | _\* All gifs were artificially slowed down_ 40 | 41 | 42 | 43 | ### Inline translator 44 | 45 | The keyboard shortcut triggers the translation of the text into the language configured in the settings. 46 | ![Demo](https://i.imgur.com/Pe34Qs3.gif) 47 | 48 | 49 | 50 | ### Inline transliterator 51 | 52 | There are times when we write text in the wrong layout. With this feature, now you just need to select the text and press the keyboard shortcut that will trigger the transliteration. 53 | ![Demo](https://i.imgur.com/LqTHu5l.gif) 54 | 55 | 56 | 57 | ### Inline text humanizer 58 | 59 | Did you accidentally write the text in upper case? It doesn't matter, text humanizer will handle this and many other problems. 60 | ![Demo](https://i.imgur.com/84FB2NC.gif) 61 | 62 | 63 | 64 | ### Inline spell fixer 65 | 66 | Easily deals with spelling, semantic and other errors in your sentences. 67 | ![Demo](https://i.imgur.com/ocngKoh.gif) 68 | 69 | 70 | 71 | ### Inline calculator 72 | 73 | Will gladly solve any arithmetic examples for you. 74 | ![Demo](https://i.imgur.com/xgdtewe.gif) 75 | 76 | 77 | 78 | ### Inline URL Shortener 79 | 80 | His main enemy is long links, and he gladly shortens them. 81 | ![Demo](https://i.imgur.com/ymstnTI.gif) 82 | 83 | 84 | 85 | ### Inline Currencies Convertor 86 | 87 | Fast converter supporting more than 180 currencies. 88 | ![Demo](https://i.imgur.com/HgSuvmS.gif) 89 | 90 | ## TODO 91 | 92 | Current roadmap for future versions of the app 93 | 94 | ### v0.0.10-alpha (current) 95 | 96 | - ~~Auto updater~~ 97 | - ~~Code refactoring~~ 98 | - ~~RU/ENG transliteration fix for MacOS~~ 99 | - ~~Better error handling~~ 100 | 101 | ### v0.1.1-alpha 102 | 103 | - About app section 104 | - New UI design 105 | - Minor fixes & improvements 106 | 107 | ### v0.1.10-alpha 108 | 109 | - File convertor basics architecture 110 | - Audio convertor 111 | - Video convertor 112 | - UA/ENG transliteration 113 | 114 | ### v0.1.20-alpha 115 | 116 | - Architecture and basic functionality of the extended clipboard 117 | - Support for pictures and other basic data types in the clipboard 118 | - Ability to remove errors via HTML 119 | 120 | ### v0.1.30-alpha 121 | 122 | - Mathematical, physics and other convertors 123 | - Final UI/UX 124 | - Text formatter 125 | 126 | ## Feedback 127 | 128 | If you have any feedback, please reach out to me through [Telergam](https://t.me/mbuslenko) or [email](mailto:m.buslenko@gmail.com) 129 | 130 | --- 131 | 132 | While making this app, I am in a European country that is currently at war. Let's help stop this together. 133 | 134 | | ![Demo](https://i.imgur.com/PYEykm8.png) | Stand for Ukraine | 135 | | :--------------------------------------: | :---------------: | 136 | 137 | President of Ukraine, launched a global initiative United24 that will start with a fundraising platform http://u24.gov.ua. It is the main venue for making one-click donations from any country in support of Ukraine 138 | -------------------------------------------------------------------------------- /src/assets/Upload icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbuslenko/nodetools/65d59ba57e5e0d621f0d339923cee5f6d8f3dc3c/src/assets/build/icon.icns -------------------------------------------------------------------------------- /src/assets/build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbuslenko/nodetools/65d59ba57e5e0d621f0d339923cee5f6d8f3dc3c/src/assets/build/icon.ico -------------------------------------------------------------------------------- /src/assets/build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbuslenko/nodetools/65d59ba57e5e0d621f0d339923cee5f6d8f3dc3c/src/assets/build/icon.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbuslenko/nodetools/65d59ba57e5e0d621f0d339923cee5f6d8f3dc3c/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/tray-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbuslenko/nodetools/65d59ba57e5e0d621f0d339923cee5f6d8f3dc3c/src/assets/tray-icon.png -------------------------------------------------------------------------------- /src/assets/tray-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/ukraine-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/domains/air-alert/air-alert.domain.ts: -------------------------------------------------------------------------------- 1 | import { Notification } from 'electron'; 2 | import settings from '../../settings'; 3 | import { AirAlertApiService } from '../../services/air-alert/air-alert.service'; 4 | import { states } from './air-alert.types'; 5 | 6 | export class AirAlertDomain { 7 | protected readonly airAlertApi: AirAlertApiService; 8 | 9 | constructor() { 10 | this.airAlertApi = new AirAlertApiService(); 11 | } 12 | 13 | private showNotification(props: { title: string; body?: string }) { 14 | const notification = new Notification({ 15 | title: props.title, 16 | ...(props.body && { body: props.body }), 17 | }); 18 | notification.show(); 19 | } 20 | 21 | listenToAirAlerts() { 22 | const alertsSettings = settings.get('airAlerts'); 23 | const state = alertsSettings.state; 24 | let previousAlertStatus = false; 25 | 26 | const stateInUkrainian = states[state as 'Kyiv']; 27 | 28 | const airAlertIsActiveMessage = { 29 | title: '🚨 Повітряна тривога', 30 | body: `Повітряна тривога в ${stateInUkrainian}. Якнайшвидше перейдіть у безпечне укриття!`, 31 | }; 32 | const airAlertIsInactiveMessage = { 33 | title: `Кінець повітряної тривоги в ${stateInUkrainian}`, 34 | }; 35 | 36 | if (state) { 37 | setInterval(() => { 38 | this.airAlertApi 39 | .getInfoAboutAirAlerts() 40 | .then((data) => { 41 | if (data) { 42 | const { states } = data; 43 | const [desiredState] = states.filter( 44 | (el) => el.name_en === state, 45 | ); 46 | 47 | if (desiredState) { 48 | const actualAlertStatus = desiredState.alert; 49 | 50 | if (actualAlertStatus !== previousAlertStatus) { 51 | previousAlertStatus = actualAlertStatus; 52 | this.showNotification( 53 | actualAlertStatus 54 | ? airAlertIsActiveMessage 55 | : airAlertIsInactiveMessage, 56 | ); 57 | } 58 | } 59 | } 60 | }) 61 | .catch((e) => { 62 | console.error(e); 63 | }); 64 | }, 30000); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/domains/air-alert/air-alert.types.ts: -------------------------------------------------------------------------------- 1 | export const states = { 2 | 'Vinnytsia oblast': 'Вінницька область', 3 | 'Volyn oblast': 'Волинська область', 4 | 'Dnipropetrovsk oblast': 'Дніпропетровська область', 5 | 'Donetsk oblast': 'Донецька область', 6 | 'Zhytomyr oblast': 'Житомирська область', 7 | 'Zakarpattia oblast': 'Закарпатська область', 8 | 'Zaporizhzhia oblast': 'Запорізька область', 9 | 'Ivano-Frankivsk oblast': 'Івано-Франківська область', 10 | 'Kyiv oblast': 'Київська область', 11 | 'Kirovohrad oblast': 'Кіровоградська область', 12 | 'Luhansk oblast': 'Луганська область', 13 | 'Lviv oblast': 'Львівська область', 14 | 'Mykolaiv oblast': 'Миколаївська область', 15 | 'Odesa oblast': 'Одеська область', 16 | 'Poltava oblast': 'Полтавська область', 17 | 'Rivne oblast': 'Рівненська область', 18 | 'Sumy oblast': 'Сумська область', 19 | 'Ternopil oblast': 'Тернопільська область', 20 | 'Kharkiv oblast': 'Харківська область', 21 | 'Kherson oblast': 'Херсонська область', 22 | 'Khmelnytskyi oblast': 'Хмельницька область', 23 | 'Cherkasy oblast': 'Черкаська область', 24 | 'Chernivtsi oblast': 'Чернівецька область', 25 | 'Chernihiv oblast': 'Чернігівська область', 26 | Kyiv: 'м. Київ', 27 | }; 28 | -------------------------------------------------------------------------------- /src/domains/files/files.domain.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import { FileType, ImagesExtensions } from './files.types'; 4 | //@ts-ignore 5 | import * as encrypt from 'node-file-encrypt'; 6 | import Jimp = require('jimp'); 7 | import ErrorsHandler from '../../errors/errors.module'; 8 | 9 | export class FilesDomain { 10 | protected errorsHandler = new ErrorsHandler(); 11 | 12 | getFileType(extension: string): FileType { 13 | if ( 14 | Object.values(ImagesExtensions).includes(extension as ImagesExtensions) 15 | ) { 16 | return 'image'; 17 | } 18 | } 19 | 20 | /** 21 | * It reads an image from a file path, converts it to the desired extension and saves it to the same 22 | * file path 23 | * @param {string} filePath - The path to the file you want to convert. 24 | * @param {ImagesExtensions} to - ImagesExtensions - the extension to which the image will be 25 | * converted 26 | */ 27 | async convertImage(filePath: string, to: ImagesExtensions): Promise { 28 | Jimp.read(filePath) 29 | .then((image) => { 30 | return image.write(`${filePath.split('.')[0]}.${to}`); 31 | }) 32 | .catch((e) => { 33 | this.errorsHandler.handleError({ 34 | message: 'Unexpected error during the image conversion', 35 | trace: e, 36 | environment: 'File converter', 37 | }); 38 | }); 39 | } 40 | 41 | /** 42 | * "Encrypt a file using a password." 43 | * 44 | * The first line of the function is a comment. It's a good idea to add comments to your code. 45 | * Comments are ignored by the compiler 46 | * @param {string} filePath - The path to the file you want to encrypt. 47 | * @param {string} password - The password to use for encryption. 48 | */ 49 | async encryptFile(filePath: string, password: string): Promise { 50 | const file = new encrypt.FileEncrypt(filePath, undefined, undefined, false); 51 | 52 | file.openSourceFile(); 53 | file.encrypt(password); 54 | 55 | // remove unencrypted file 56 | fs.unlink(filePath, () => {}); 57 | } 58 | 59 | /** 60 | * It decrypts a file 61 | * @param {string} filePath - The path to the file you want to encrypt. 62 | * @param {string} password - The password used to encrypt the file 63 | */ 64 | async decryptFile(filePath: string, password: string): Promise { 65 | try { 66 | const file = new encrypt.FileEncrypt( 67 | filePath, 68 | undefined, 69 | undefined, 70 | false, 71 | ); 72 | 73 | file.openSourceFile(); 74 | file.decrypt(password); 75 | 76 | // remove encrypted file 77 | fs.unlink(filePath, () => {}); 78 | } catch { 79 | throw new Error('Password is incorrect'); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/domains/files/files.types.ts: -------------------------------------------------------------------------------- 1 | export type FileType = 'text' | 'image' | 'video' | 'audio' | 'pdf' | 'code' | 'archive' | 'other'; 2 | 3 | export enum ImagesExtensions { 4 | PNG = 'png', 5 | JPG = 'jpg', 6 | JPEG = 'jpeg', 7 | GIF = 'gif', 8 | BMP = 'bmp', 9 | TIFF = 'tiff', 10 | } 11 | -------------------------------------------------------------------------------- /src/domains/index.ts: -------------------------------------------------------------------------------- 1 | export * as inline from './inline/inline.domain'; 2 | export * as airAlert from './air-alert/air-alert.domain'; 3 | -------------------------------------------------------------------------------- /src/domains/inline/inline.domain.ts: -------------------------------------------------------------------------------- 1 | import { clipboard } from 'electron'; 2 | import { 3 | translator, 4 | trasnliterator, 5 | currencyConvertor, 6 | spellChecker, 7 | urlShortener, 8 | } from '../../services'; 9 | import { ConvertOptions } from '../../services/currencies-convertor/currencies-convertor.types'; 10 | import { utils } from '../../shared'; 11 | import * as types from './inline.types'; 12 | import settings from '../../settings'; 13 | import ErrorsHandler from '../../errors/errors.module'; 14 | import { Key, keyboard } from '@nut-tree/nut-js'; 15 | 16 | export class InlineDomain { 17 | protected readonly translatorService: translator.TranslatorService; 18 | protected readonly transliteratorService: trasnliterator.TransliterationService; 19 | protected readonly currencyConvertorService: currencyConvertor.CurrencyConvertorService; 20 | protected readonly spellCheckerService: spellChecker.SpellCheckerService; 21 | protected readonly urlShortenerService: urlShortener.UrlShortenerService; 22 | 23 | protected errorsHandler = new ErrorsHandler(); 24 | 25 | constructor() { 26 | this.translatorService = new translator.TranslatorService(); 27 | this.transliteratorService = new trasnliterator.TransliterationService(); 28 | this.currencyConvertorService = 29 | new currencyConvertor.CurrencyConvertorService(); 30 | this.spellCheckerService = new spellChecker.SpellCheckerService(); 31 | this.urlShortenerService = new urlShortener.UrlShortenerService(); 32 | } 33 | 34 | /** 35 | * It CLEARS THE CLIPBOARD, presses the ctrl+c keys, waits 200ms, and then returns the clipboard 36 | * contents 37 | * @returns The selected text from the clipboard. 38 | */ 39 | private async getSelectedText(): Promise { 40 | clipboard.clear(); 41 | 42 | await keyboard.pressKey( 43 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 44 | Key.C, 45 | ); 46 | await keyboard.releaseKey( 47 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 48 | Key.C, 49 | ); 50 | 51 | await new Promise((resolve) => setTimeout(resolve, 200)); // add a delay before checking clipboard 52 | const selectedText = clipboard.readText(); 53 | 54 | return selectedText; 55 | } 56 | 57 | /** 58 | * It saves the current clipboard contents, translates the selected text, pastes the translated text, 59 | * waits for the clipboard to be updated, and then restores the previous clipboard contents 60 | */ 61 | async translateText(): Promise { 62 | const options: types.TranslateOptions = settings.get( 63 | 'translate', 64 | ) as types.TranslateOptions; 65 | 66 | // save current clipboard contents 67 | const previousClipboardText = clipboard.readText(); 68 | 69 | const selectedText = await this.getSelectedText(); 70 | const translatedText = await this.translatorService.translate({ 71 | ...options, 72 | text: selectedText, 73 | }); 74 | 75 | if (translatedText) { 76 | clipboard.writeText(translatedText); 77 | 78 | // paste translated text 79 | await keyboard.pressKey( 80 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 81 | Key.V, 82 | ); 83 | await keyboard.releaseKey( 84 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 85 | Key.V, 86 | ); 87 | 88 | // wait for the clipboard to be updated 89 | await new Promise((resolve) => setTimeout(resolve, 200)); 90 | } 91 | 92 | // restore previous clipboard contents 93 | clipboard.writeText(previousClipboardText); 94 | } 95 | 96 | /** 97 | * It saves the current clipboard contents, gets the selected text, transliterates it, pastes the 98 | * transliterated text, and then restores the previous clipboard contents 99 | */ 100 | async transliterateText(): Promise { 101 | // save current clipboard contents 102 | const previousClipboardText = clipboard.readText(); 103 | 104 | const selectedText = await this.getSelectedText(); 105 | 106 | const { to: selectedLanguage } = settings.get('transliterate'); 107 | const transliteratedText = 108 | this.transliteratorService.transliterate(selectedText, selectedLanguage); 109 | 110 | if (transliteratedText) { 111 | clipboard.writeText(transliteratedText); 112 | 113 | // paste translated text 114 | await keyboard.pressKey( 115 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 116 | Key.V, 117 | ); 118 | await keyboard.releaseKey( 119 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 120 | Key.V, 121 | ); 122 | 123 | // wait for the clipboard to be updated 124 | await new Promise((resolve) => setTimeout(resolve, 200)); 125 | } 126 | 127 | // restore previous clipboard contents 128 | clipboard.writeText(previousClipboardText); 129 | } 130 | 131 | /** 132 | * It gets the selected text, converts it to a number, converts it to the target currency, and pastes 133 | * the converted text 134 | */ 135 | async convertCurrency(): Promise { 136 | const options: Omit = settings.get( 137 | 'convertCurrencies', 138 | ) as Omit; 139 | 140 | // save current clipboard contents 141 | const previousClipboardText = clipboard.readText(); 142 | 143 | const selectedText = await this.getSelectedText(); 144 | 145 | if (isNaN(+selectedText)) { 146 | this.errorsHandler.handleError({ 147 | message: 148 | 'Please specify only numeric characters and ".", convertion was failed', 149 | environment: 'Currencies convertor', 150 | trace: null, 151 | date: new Date(), 152 | }); 153 | } 154 | 155 | const convertedText = await this.currencyConvertorService.convert({ 156 | ...options, 157 | amount: +selectedText, 158 | }); 159 | 160 | if (convertedText) { 161 | clipboard.writeText(convertedText); 162 | 163 | // paste converted text 164 | await keyboard.pressKey( 165 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 166 | Key.V, 167 | ); 168 | await keyboard.releaseKey( 169 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 170 | Key.V, 171 | ); 172 | 173 | // wait for the clipboard to be updated 174 | await new Promise((resolve) => setTimeout(resolve, 200)); 175 | } 176 | 177 | // restore previous clipboard contents 178 | clipboard.writeText(previousClipboardText); 179 | } 180 | 181 | /** 182 | * It gets the selected text, converts it to humanized text, pastes the converted text, and then 183 | * restores the previous clipboard contents 184 | */ 185 | async humanizeText(): Promise { 186 | // save current clipboard contents 187 | const previousClipboardText = clipboard.readText(); 188 | 189 | const selectedText = await this.getSelectedText(); 190 | const humanizedText = utils.humanizeString(selectedText); 191 | 192 | clipboard.writeText(humanizedText); 193 | 194 | // paste converted text 195 | await keyboard.pressKey( 196 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 197 | Key.V, 198 | ); 199 | await keyboard.releaseKey( 200 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 201 | Key.V, 202 | ); 203 | 204 | // wait for the clipboard to be updated 205 | await new Promise((resolve) => setTimeout(resolve, 200)); 206 | 207 | // restore previous clipboard contents 208 | clipboard.writeText(previousClipboardText); 209 | } 210 | 211 | /** 212 | * It saves the current clipboard contents, gets the selected text, spell checks it, and then pastes 213 | * the fixed text 214 | */ 215 | async spellCheck(): Promise { 216 | // save current clipboard contents 217 | const previousClipboardText = clipboard.readText(); 218 | 219 | const selectedText = await this.getSelectedText(); 220 | const fixedText = await this.spellCheckerService.check(selectedText); 221 | 222 | if (!fixedText) { 223 | // restore previous clipboard contents 224 | return clipboard.writeText(previousClipboardText); 225 | } 226 | 227 | clipboard.writeText(fixedText); 228 | 229 | // paste converted text 230 | await keyboard.pressKey( 231 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 232 | Key.V, 233 | ); 234 | await keyboard.releaseKey( 235 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 236 | Key.V, 237 | ); 238 | 239 | // wait for the clipboard to be updated 240 | await new Promise((resolve) => setTimeout(resolve, 200)); 241 | 242 | // restore previous clipboard contents 243 | clipboard.writeText(previousClipboardText); 244 | } 245 | 246 | /** 247 | * It saves the current clipboard contents, gets the selected text, shortens the URL, writes the 248 | * shortened URL to the clipboard, pastes the shortened URL, waits for the clipboard to be updated, and 249 | * then restores the previous clipboard contents 250 | */ 251 | async shortenUrl(): Promise { 252 | // save current clipboard contents 253 | const previousClipboardText = clipboard.readText(); 254 | 255 | const selectedText = await this.getSelectedText(); 256 | const shortenedUrl = await this.urlShortenerService.shortenUrl( 257 | selectedText, 258 | ); 259 | 260 | if (!shortenedUrl) { 261 | // restore previous clipboard contents 262 | return clipboard.writeText(previousClipboardText); 263 | } 264 | 265 | clipboard.writeText(shortenedUrl); 266 | 267 | // paste converted text 268 | await keyboard.pressKey( 269 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 270 | Key.V, 271 | ); 272 | await keyboard.releaseKey( 273 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 274 | Key.V, 275 | ); 276 | 277 | // wait for the clipboard to be updated 278 | await new Promise((resolve) => setTimeout(resolve, 200)); 279 | 280 | // restore previous clipboard contents 281 | clipboard.writeText(previousClipboardText); 282 | } 283 | 284 | /** 285 | * It gets the selected text, removes all non-numeric characters except *,/,+,-,. and then evaluates 286 | * the result. If the result is a number, it replaces the clipboard contents with the result and then 287 | * pastes it 288 | */ 289 | async calculate(): Promise { 290 | // save current clipboard contents 291 | const previousClipboardText = clipboard.readText(); 292 | 293 | const selectedText = await this.getSelectedText(); 294 | 295 | // remove from text all non-numeric characters except *,/,+,-,. 296 | const cleanedText = selectedText.replace(/[^-()\d/*+.]/g, ''); 297 | if (cleanedText.length === 0) { 298 | return this.errorsHandler.handleError({ 299 | message: 'No numeric characters found', 300 | environment: 'Calculator', 301 | date: new Date(), 302 | trace: null, 303 | }); 304 | } 305 | 306 | let result: number; 307 | 308 | try { 309 | result = eval(cleanedText); 310 | } catch (e) { 311 | return clipboard.writeText(previousClipboardText); 312 | } 313 | 314 | if (!isNaN(result)) { 315 | clipboard.writeText(result.toString()); 316 | 317 | // paste converted text 318 | await keyboard.pressKey( 319 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 320 | Key.V, 321 | ); 322 | await keyboard.releaseKey( 323 | process.platform === 'darwin' ? Key.LeftSuper : Key.LeftControl, 324 | Key.V, 325 | ); 326 | 327 | // wait for the clipboard to be updated 328 | await new Promise((resolve) => setTimeout(resolve, 200)); 329 | } 330 | 331 | clipboard.writeText(previousClipboardText); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/domains/inline/inline.types.ts: -------------------------------------------------------------------------------- 1 | import { Language } from '../../services/translator/translator.types'; 2 | 3 | export type TranslateOptions = { 4 | to: Language; 5 | }; 6 | -------------------------------------------------------------------------------- /src/errors/errors.module.ts: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron'; 2 | import { addErrorToStorage } from '../settings'; 3 | import * as errorTypes from './errors.types'; 4 | import * as crypto from 'crypto'; 5 | 6 | export default class ErrorsHandler { 7 | handleError(error: Partial) { 8 | const errorStructure = { 9 | id: crypto.randomUUID(), 10 | date: error.date ?? new Date(), 11 | environment: error.environment ?? 'nodetools', 12 | message: error.message, 13 | trace: error.trace ?? null, 14 | }; 15 | 16 | this.sendErrorToStorage(errorStructure); 17 | } 18 | 19 | protected sendErrorToStorage(error: errorTypes.ErrorStructure) { 20 | addErrorToStorage(error); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/errors/errors.types.ts: -------------------------------------------------------------------------------- 1 | export type ErrorStructure = { 2 | id: string; 3 | date: Date; 4 | environment: string; 5 | message: string; 6 | trace: unknown; 7 | }; 8 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | app, 3 | BrowserWindow, 4 | globalShortcut, 5 | ipcMain, 6 | ipcRenderer, 7 | Menu, 8 | nativeImage, 9 | Tray, 10 | } from 'electron'; 11 | import * as path from 'path'; 12 | import * as domains from './domains'; 13 | import settings, { 14 | changeSetting, 15 | changeSettings, 16 | initSettings, 17 | setDefaultSettings, 18 | } from './settings'; 19 | import { ShortcutsSettings } from './settings/settings.types'; 20 | import { openWebURL } from './shared/utils/open-website'; 21 | import { Settings } from './settings/settings.types'; 22 | import { AirAlertDomain } from './domains/air-alert/air-alert.domain'; 23 | import { FilesDomain } from './domains/files/files.domain'; 24 | 25 | // import { askForAccessibilityAccess, getAuthStatus } from 'node-mac-permissions'; 26 | 27 | require('update-electron-app')({ 28 | repo: 'mbuslenko/nodetools', 29 | }); 30 | 31 | /** 32 | * This prevents the app to be opened twice 33 | */ 34 | const gotTheLock = app.requestSingleInstanceLock(); 35 | if (!gotTheLock) app.quit(); 36 | 37 | /** 38 | * Create window function starts the browser window 39 | * in native mode 40 | */ 41 | function createWindow( 42 | pathToHtmlFile: string, 43 | size?: { height: number; width: number }, 44 | ) { 45 | // Create the browser window. 46 | const mainWindow = new BrowserWindow({ 47 | height: size?.height ?? 600, 48 | width: size?.width ?? 800, 49 | frame: false, 50 | webPreferences: { 51 | nodeIntegration: true, 52 | contextIsolation: false, 53 | preload: path.join(__dirname, 'preload.js'), 54 | }, 55 | icon: '../src/assets/app-icon.png', 56 | }); 57 | 58 | // and load the index.html of the app 59 | mainWindow.loadFile(path.join(__dirname, pathToHtmlFile)); 60 | mainWindow.setResizable(false); 61 | 62 | if (process.platform === 'darwin') { 63 | app.dock.show(); 64 | } 65 | } 66 | 67 | /** 68 | * Useful stuff that has to be called 69 | * from the main process 70 | */ 71 | export const relaunchApp = () => { 72 | app.relaunch(); 73 | app.exit(); 74 | }; 75 | 76 | /** 77 | * Events between the main and browser processes 78 | * configuration 79 | */ 80 | ipcMain.on('change-settings', (_event, arg) => { 81 | changeSettings(arg.data); 82 | }); 83 | 84 | ipcMain.on('change-language', (_event, arg) => { 85 | if (process.platform === 'darwin') { 86 | app.dock.hide(); 87 | } 88 | 89 | if (!arg.to) { 90 | return; 91 | } 92 | 93 | changeSetting('translate', arg); 94 | }); 95 | 96 | ipcMain.on('change-transliteration-language', (_event, arg) => { 97 | if (process.platform === 'darwin') { 98 | app.dock.hide(); 99 | } 100 | 101 | if (!arg.to) { 102 | return; 103 | } 104 | 105 | changeSetting('transliterate', arg); 106 | }); 107 | 108 | ipcMain.on('change-convert-currencies-settings', (_event, arg) => { 109 | if (process.platform === 'darwin') { 110 | app.dock.hide(); 111 | } 112 | 113 | if (!arg.from || !arg.to) { 114 | return; 115 | } 116 | 117 | changeSetting('convertCurrencies', arg); 118 | }); 119 | 120 | ipcMain.on('change-shortcuts', (_event, arg) => { 121 | if ( 122 | !arg.translate || 123 | !arg.transliterate || 124 | !arg.convertCurrency || 125 | !arg.humanizeText || 126 | !arg.spellCheck || 127 | !arg.shortenUrl || 128 | !arg.calculate 129 | ) { 130 | return; 131 | } 132 | 133 | changeSetting('shortcuts', arg); 134 | 135 | relaunchApp(); 136 | }); 137 | 138 | ipcMain.handle('get-errors', (_event, _arg) => { 139 | return settings.get('errorsStorage'); 140 | }); 141 | 142 | ipcMain.handle('get-settings', (_event, _arg) => { 143 | return settings.store; 144 | }); 145 | 146 | ipcMain.on('change-air-alerts-state', (_event, arg) => { 147 | if (process.platform === 'darwin') { 148 | app.dock.hide(); 149 | } 150 | 151 | if (!arg.state) { 152 | return; 153 | } 154 | 155 | changeSetting('airAlerts', { enabled: true, state: arg.state }); 156 | }); 157 | 158 | ipcMain.on('set-default-settings', (_event, _arg) => { 159 | setDefaultSettings() 160 | }) 161 | 162 | /** 163 | * Generate and configure shortcuts 164 | * and check for permissions on the macOS 165 | */ 166 | app.whenReady().then(async () => { 167 | await initSettings(); 168 | 169 | const shortcuts = settings.get('shortcuts') as ShortcutsSettings; 170 | const InlineDomain = new domains.inline.InlineDomain(); 171 | 172 | // * Disabled for Windows and Linux 173 | // if (getAuthStatus('accessibility') === 'denied') { 174 | // const errorsHandler = new ErrorsHandler(); 175 | 176 | // //@ts-ignore 177 | // askForAccessibilityAccess().then((status: string) => { 178 | // if (status === 'denied') { 179 | // errorsHandler.handleError({ 180 | // message: 'Accessibility access is denied', 181 | // environment: 'Nodetools', 182 | // trace: null, 183 | // }); 184 | // } 185 | // }); 186 | 187 | // if (getAuthStatus('accessibility') === 'denied') { 188 | // errorsHandler.handleError({ 189 | // message: 190 | // 'You have to grant Accessibility permission for Nodetools to work it correctly, open Settings -> Security & Privacy -> Privacy tab -> Accessibility -> Add Nodetools', 191 | // environment: 'Nodetools', 192 | // trace: null, 193 | // }); 194 | // } 195 | // } 196 | 197 | // * translate shortcut 198 | globalShortcut.register(shortcuts.translate.join('+'), async () => { 199 | await InlineDomain.translateText(); 200 | }); 201 | 202 | // * transliterator shortcut 203 | globalShortcut.register(shortcuts.transliterate.join('+'), async () => { 204 | const selectedLanguage = settings.get('transliterate').to; 205 | await InlineDomain.transliterateText(); 206 | 207 | if (!selectedLanguage) { 208 | createWindow('../src/views/transliterate-settings.html'); 209 | } 210 | }); 211 | 212 | // * currency convertor shortcut 213 | globalShortcut.register(shortcuts.convertCurrency.join('+'), async () => { 214 | await InlineDomain.convertCurrency(); 215 | }); 216 | 217 | // * humanize shortcut 218 | globalShortcut.register(shortcuts.humanizeText.join('+'), async () => { 219 | await InlineDomain.humanizeText(); 220 | }); 221 | 222 | // * spell checker shortcut 223 | globalShortcut.register(shortcuts.spellCheck.join('+'), async () => { 224 | await InlineDomain.spellCheck(); 225 | }); 226 | 227 | // * url shortener shortcut 228 | globalShortcut.register(shortcuts.shortenUrl.join('+'), async () => { 229 | await InlineDomain.shortenUrl(); 230 | }); 231 | 232 | // * calculate shortcut 233 | globalShortcut.register(shortcuts.calculate.join('+'), async () => { 234 | await InlineDomain.calculate(); 235 | }); 236 | }); 237 | 238 | /** 239 | * Generate and configure context menu 240 | */ 241 | let tray: Tray; 242 | app.whenReady().then(() => { 243 | if (process.platform === 'darwin') { 244 | app.dock.hide(); 245 | } 246 | 247 | const icon = nativeImage.createFromPath( 248 | path.join(__dirname, '../src/assets/tray-icon.png'), 249 | ); 250 | tray = new Tray(icon); 251 | 252 | const airAlertsSettings = settings.get('airAlerts'); 253 | 254 | const shortcuts = settings.get('shortcuts') as ShortcutsSettings; 255 | const InlineDomain = new domains.inline.InlineDomain(); 256 | 257 | const contextMenu = Menu.buildFromTemplate([ 258 | { 259 | label: 'About', 260 | role: 'window', 261 | click: () => openWebURL('https://nodetools.app/about'), 262 | }, 263 | { 264 | label: 'Preferences', 265 | submenu: [ 266 | { 267 | label: 'Shortcuts', 268 | role: 'window', 269 | click: () => createWindow('../src/views/shortcut-settings.html'), 270 | }, 271 | { 272 | label: 'Translate options', 273 | role: 'window', 274 | click: () => createWindow('../src/views/translate-settings.html'), 275 | }, 276 | { 277 | label: 'Transliterate options', 278 | role: 'window', 279 | click: () => createWindow('../src/views/transliterate-settings.html'), 280 | }, 281 | { 282 | label: 'Currency converter options', 283 | role: 'window', 284 | click: () => 285 | createWindow('../src/views/convert-currencies-settings.html'), 286 | }, 287 | ...(airAlertsSettings.enabled === true 288 | ? [ 289 | { 290 | label: 'Air alerts settings', 291 | click: () => 292 | createWindow('../src/views/air-alerts-settings.html'), 293 | }, 294 | ] 295 | : []), 296 | { label: 'Separator', type: 'separator' }, 297 | { 298 | label: 'Errors', 299 | role: 'window', 300 | click: () => createWindow('../src/views/errors-list.html'), 301 | }, 302 | { label: 'Separator', type: 'separator' }, 303 | { 304 | label: 'Set default settings', 305 | role: 'window', 306 | click: () => 307 | createWindow('../src/views/set-default-settings-dialogue.html', { 308 | height: 420, 309 | width: 600, 310 | }), 311 | }, 312 | ], 313 | }, 314 | { label: 'Separator', type: 'separator' }, 315 | { 316 | label: 'Translate', 317 | accelerator: shortcuts.translate.join('+'), 318 | role: 'help', 319 | click: async () => { 320 | await InlineDomain.translateText(); 321 | }, 322 | }, 323 | { 324 | label: 'Transliterate', 325 | accelerator: shortcuts.transliterate.join('+'), 326 | role: 'help', 327 | click: async () => { 328 | await InlineDomain.transliterateText(); 329 | }, 330 | }, 331 | { 332 | label: 'Humanize', 333 | accelerator: shortcuts.humanizeText.join('+'), 334 | role: 'help', 335 | click: async () => { 336 | await InlineDomain.humanizeText(); 337 | }, 338 | }, 339 | { 340 | label: 'Fix spelling', 341 | accelerator: shortcuts.spellCheck.join('+'), 342 | role: 'help', 343 | click: async () => { 344 | await InlineDomain.spellCheck(); 345 | }, 346 | }, 347 | { 348 | label: 'Calculate', 349 | accelerator: shortcuts.calculate.join('+'), 350 | role: 'help', 351 | click: async () => { 352 | await InlineDomain.calculate(); 353 | }, 354 | }, 355 | { 356 | label: 'Convert currencies', 357 | accelerator: shortcuts.convertCurrency.join('+'), 358 | role: 'help', 359 | click: async () => { 360 | await InlineDomain.convertCurrency(); 361 | }, 362 | }, 363 | { 364 | label: 'Shorten URL', 365 | accelerator: shortcuts.shortenUrl.join('+'), 366 | role: 'help', 367 | click: async () => { 368 | await InlineDomain.shortenUrl(); 369 | }, 370 | }, 371 | { label: 'Separator', type: 'separator' }, 372 | { 373 | label: 'Convert file', 374 | role: 'window', 375 | click: () => createWindow('../src/views/upload-file-convert.html'), 376 | }, 377 | { 378 | label: 'Encrypt file', 379 | role: 'window', 380 | click: () => createWindow('../src/views/upload-file-encrypt.html'), 381 | }, 382 | { 383 | label: 'Decrypt file', 384 | role: 'window', 385 | click: () => createWindow('../src/views/upload-file-decrypt.html'), 386 | }, 387 | { label: 'Separator', type: 'separator' }, 388 | { label: 'Quit', role: 'quit', click: () => app.quit() }, 389 | ]); 390 | tray.setContextMenu(contextMenu); 391 | 392 | tray.setToolTip('Nodetools'); 393 | }); 394 | 395 | /** 396 | * Air alerts configuration 397 | */ 398 | app.whenReady().then(() => { 399 | const airAlertsSettings = settings.get('airAlerts') as Settings['airAlerts']; 400 | 401 | if (airAlertsSettings.enabled === null) { 402 | createWindow('../src/views/support-ukraine.html', { 403 | height: 420, 404 | width: 600, 405 | }); 406 | 407 | ipcMain.on('init-alerts', (_event, arg) => { 408 | if (arg.enableAlerts === true) { 409 | const enabledAirAlertsSettings = { 410 | ...airAlertsSettings, 411 | enabled: true, 412 | }; 413 | 414 | changeSetting('airAlerts', enabledAirAlertsSettings); 415 | } else { 416 | const disabledAirAlertsSettings = { 417 | ...airAlertsSettings, 418 | enabled: false, 419 | }; 420 | 421 | changeSetting('airAlerts', disabledAirAlertsSettings); 422 | } 423 | }); 424 | } else if (airAlertsSettings.enabled === true && airAlertsSettings.state) { 425 | new AirAlertDomain().listenToAirAlerts(); 426 | } 427 | }); 428 | 429 | /** 430 | * Convert file 431 | */ 432 | let filePath: string = null; 433 | 434 | ipcMain.on('upload-file', (_event, arg) => { 435 | filePath = arg.filePath; 436 | 437 | switch (arg.task) { 438 | case 'convert': 439 | createWindow('../src/views/convert-file.html'); 440 | break; 441 | case 'encrypt': 442 | createWindow('../src/views/encrypt-file.html'); 443 | break; 444 | case 'decrypt': 445 | createWindow('../src/views/decrypt-file.html'); 446 | } 447 | }); 448 | 449 | ipcMain.handle('get-file-path', (_event, _arg) => { 450 | return filePath; 451 | }); 452 | 453 | ipcMain.on('convert-file', async (_event, arg) => { 454 | const filesDomain = new FilesDomain(); 455 | 456 | await filesDomain.convertImage(filePath, arg.to); 457 | }); 458 | 459 | ipcMain.on('encrypt-file', async (_event, arg) => { 460 | const filesDomain = new FilesDomain(); 461 | 462 | await filesDomain.encryptFile(filePath, arg.password); 463 | }); 464 | 465 | ipcMain.handle('decrypt-file', async (_event, arg) => { 466 | const filesDomain = new FilesDomain(); 467 | 468 | try { 469 | await filesDomain.decryptFile(filePath, arg.password); 470 | return true; 471 | } catch { 472 | return false; 473 | } 474 | }); 475 | 476 | /** 477 | * Close window handler for macOS 478 | */ 479 | app.on('window-all-closed', (e: any) => { 480 | e.preventDefault(); 481 | 482 | if (process.platform === 'darwin') { 483 | app.dock.hide(); 484 | } 485 | }); 486 | -------------------------------------------------------------------------------- /src/services/air-alert/air-alert.service.ts: -------------------------------------------------------------------------------- 1 | import ErrorsHandler from '../../errors/errors.module'; 2 | import { axiosInstance } from '../../shared/axios'; 3 | import { AirAlertsApiResponse } from './air-alert.types'; 4 | import settings from '../../settings'; 5 | import { ErrorStructure } from '../../errors/errors.types'; 6 | 7 | export class AirAlertApiService { 8 | protected errorsHandler: ErrorsHandler; 9 | 10 | constructor() { 11 | this.errorsHandler = new ErrorsHandler(); 12 | } 13 | 14 | async getInfoAboutAirAlerts(): Promise { 15 | const { data } = await axiosInstance 16 | .get('/air-alerts') 17 | .catch(() => { 18 | return null; 19 | }); 20 | 21 | if (!data) { 22 | const errors = settings.get('errorsStorage') as ErrorStructure[]; 23 | 24 | const airAlertError = errors.filter( 25 | (el) => el.environment === 'Air Alerts', 26 | ); 27 | 28 | if (airAlertError.length === 0) { 29 | this.errorsHandler.handleError({ 30 | environment: 'Air alerts', 31 | date: new Date(), 32 | trace: null, 33 | message: `The service that provides us with 34 | information about air alerts in Ukraine 35 | has stopped responding, we are aware of 36 | this and are doing our best to fix it.`, 37 | }); 38 | } 39 | 40 | return null; 41 | } 42 | 43 | return data; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/air-alert/air-alert.types.ts: -------------------------------------------------------------------------------- 1 | export type AirAlertsApiResponse = { 2 | states: AirAlertState[]; 3 | last_update: Date; 4 | }; 5 | 6 | type AirAlertState = { 7 | id: number; 8 | name: string; 9 | name_en: string; 10 | alert: boolean; 11 | changed: Date; 12 | }; 13 | -------------------------------------------------------------------------------- /src/services/currencies-convertor/currencies-convertor.service.ts: -------------------------------------------------------------------------------- 1 | import ErrorsHandler from '../../errors/errors.module'; 2 | import { utils } from '../../shared'; 3 | import { axiosInstance } from '../../shared/axios'; 4 | import * as types from './currencies-convertor.types'; 5 | 6 | export class CurrencyConvertorService { 7 | protected errorsHandler = new ErrorsHandler(); 8 | 9 | async convert(options: types.ConvertOptions) { 10 | try { 11 | const { data: response } = await axiosInstance.request< 12 | any, 13 | { data: types.ConvertResponse } 14 | >({ 15 | method: 'GET', 16 | url: `/convert-currency`, 17 | params: { 18 | from: options.from, 19 | to: options.to, 20 | amount: options.amount, 21 | }, 22 | }); 23 | 24 | return response.rateCurrency.amount; 25 | } catch (e) { 26 | this.errorsHandler.handleError({ 27 | environment: 'Currency converter', 28 | message: `An error occured while converting ${options.amount} ${options.from} to ${options.to}`, 29 | trace: e, 30 | }); 31 | 32 | return undefined; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/services/currencies-convertor/currencies-convertor.types.ts: -------------------------------------------------------------------------------- 1 | export type ConvertOptions = { 2 | from: Currency; 3 | to: Currency; 4 | amount: number; 5 | }; 6 | 7 | export type ConvertResponse = { 8 | baseCurrency: { 9 | code: Currency; 10 | name: string; 11 | amount: string; 12 | }; 13 | rateCurrency: { 14 | code: Currency; 15 | name: string; 16 | amount: string; 17 | }; 18 | updatedDate: Date; 19 | }; 20 | 21 | export enum Currency { 22 | 'US Dollar' = 'USD', 23 | 'Euro' = 'EUR', 24 | 'British Pound' = 'GBP', 25 | 'Canadian Dollar' = 'CAD', 26 | 'Australian Dollar' = 'AUD', 27 | 'Japanese Yen' = 'JPY', 28 | 'Cardano' = 'ADA', 29 | 'Emirati Dirham' = 'AED', 30 | 'Afghan Afghani' = 'AFN', 31 | 'Albanian Lek' = 'ALL', 32 | 'Armenian Dram' = 'AMD', 33 | 'Dutch Guilder' = 'ANG', 34 | 'Angolan Kwanza' = 'AOA', 35 | 'Argentine Peso' = 'ARS', 36 | 'Aruban or Dutch Guilder' = 'AWG', 37 | 'Azerbaijan Manat' = 'AZN', 38 | 'Bosnian Convertible Mark' = 'BAM', 39 | 'Barbadian or Bajan Dollar' = 'BBD', 40 | 'Bitcoin Cash' = 'BCH', 41 | 'Bangladeshi Taka' = 'BDT', 42 | 'Bulgarian Lev' = 'BGN', 43 | 'Bahraini Dinar' = 'BHD', 44 | 'Burundian Franc' = 'BIF', 45 | 'Bermudian Dollar' = 'BMD', 46 | 'Bruneian Dollar' = 'BND', 47 | 'Bolivian Bolíviano' = 'BOB', 48 | 'Brazilian Real' = 'BRL', 49 | 'Bahamian Dollar' = 'BSD', 50 | 'Bitcoin' = 'BTC', 51 | 'Bhutanese Ngultrum' = 'BTN', 52 | 'Botswana Pula' = 'BWP', 53 | 'Belarusian Ruble' = 'BYR', 54 | 'Belizean Dollar' = 'BZD', 55 | 'Congolese Franc' = 'CDF', 56 | 'Swiss Franc' = 'CHF', 57 | 'Chilean Peso' = 'CLP', 58 | 'Chinese Yuan Renminbi' = 'CNY', 59 | 'Colombian Peso' = 'COP', 60 | 'Costa Rican Colon' = 'CRC', 61 | 'Cuban Convertible Peso' = 'CUC', 62 | 'Cuban Peso' = 'CUP', 63 | 'Cape Verdean Escudo' = 'CVE', 64 | 'Czech Koruna' = 'CZK', 65 | 'Djiboutian Franc' = 'DJF', 66 | 'Danish Krone' = 'DKK', 67 | 'Dogecoin' = 'DOGE', 68 | 'Dominican Peso' = 'DOP', 69 | 'Polkadot' = 'DOT', 70 | 'Algerian Dinar' = 'DZD', 71 | 'Estonian Kroon' = 'EEK', 72 | 'Egyptian Pound' = 'EGP', 73 | 'Eritrean Nakfa' = 'ERN', 74 | 'Ethiopian Birr' = 'ETB', 75 | 'Ethereum' = 'ETH', 76 | 'Fijian Dollar' = 'FJD', 77 | 'Falkland Island Pound' = 'FKP', 78 | 'Georgian Lari' = 'GEL', 79 | 'Guernsey Pound' = 'GGP', 80 | 'Ghanaian Cedi' = 'GHS', 81 | 'Gibraltar Pound' = 'GIP', 82 | 'Gambian Dalasi' = 'GMD', 83 | 'Guinean Franc' = 'GNF', 84 | 'Guatemalan Quetzal' = 'GTQ', 85 | 'Guyanese Dollar' = 'GYD', 86 | 'Hong Kong Dollar' = 'HKD', 87 | 'Honduran Lempira' = 'HNL', 88 | 'Croatian Kuna' = 'HRK', 89 | 'Haitian Gourde' = 'HTG', 90 | 'Hungarian Forint' = 'HUF', 91 | 'Indonesian Rupiah' = 'IDR', 92 | 'Israeli Shekel' = 'ILS', 93 | 'Isle of Man Pound' = 'IMP', 94 | 'Indian Rupee' = 'INR', 95 | 'Iraqi Dinar' = 'IQD', 96 | 'Iranian Rial' = 'IRR', 97 | 'Icelandic Krona' = 'ISK', 98 | 'Jersey Pound' = 'JEP', 99 | 'Jamaican Dollar' = 'JMD', 100 | 'Jordanian Dinar' = 'JOD', 101 | 'Kenyan Shilling' = 'KES', 102 | 'Kyrgyzstani Som' = 'KGS', 103 | 'Cambodian Riel' = 'KHR', 104 | 'Comorian Franc' = 'KMF', 105 | 'North Korean Won' = 'KPW', 106 | 'South Korean Won' = 'KRW', 107 | 'Kuwaiti Dinar' = 'KWD', 108 | 'Caymanian Dollar' = 'KYD', 109 | 'Kazakhstani Tenge' = 'KZT', 110 | 'Lao Kip' = 'LAK', 111 | 'Lebanese Pound' = 'LBP', 112 | 'Chainlink' = 'LINK', 113 | 'Sri Lankan Rupee' = 'LKR', 114 | 'Liberian Dollar' = 'LRD', 115 | 'Basotho Loti' = 'LSL', 116 | 'Litecoin' = 'LTC', 117 | 'Lithuanian Litas' = 'LTL', 118 | 'Terra' = 'LUNA', 119 | 'Latvian Lat' = 'LVL', 120 | 'Libyan Dinar' = 'LYD', 121 | 'Moroccan Dirham' = 'MAD', 122 | 'Moldovan Leu' = 'MDL', 123 | 'Malagasy Ariary' = 'MGA', 124 | 'Macedonian Denar' = 'MKD', 125 | 'Burmese Kyat' = 'MMK', 126 | 'Mongolian Tughrik' = 'MNT', 127 | 'Macau Pataca' = 'MOP', 128 | 'Mauritanian Ouguiya' = 'MRU', 129 | 'Mauritian Rupee' = 'MUR', 130 | 'Maldivian Rufiyaa' = 'MVR', 131 | 'Malawian Kwacha' = 'MWK', 132 | 'Mexican Peso' = 'MXN', 133 | 'Malaysian Ringgit' = 'MYR', 134 | 'Mozambican Metical' = 'MZN', 135 | 'Namibian Dollar' = 'NAD', 136 | 'Nigerian Naira' = 'NGN', 137 | 'Nicaraguan Cordoba' = 'NIO', 138 | 'Norwegian Krone' = 'NOK', 139 | 'Nepalese Rupee' = 'NPR', 140 | 'New Zealand Dollar' = 'NZD', 141 | 'Omani Rial' = 'OMR', 142 | 'Panamanian Balboa' = 'PAB', 143 | 'Peruvian Sol' = 'PEN', 144 | 'Papua New Guinean Kina' = 'PGK', 145 | 'Philippine Peso' = 'PHP', 146 | 'Pakistani Rupee' = 'PKR', 147 | 'Polish Zloty' = 'PLN', 148 | 'Paraguayan Guarani' = 'PYG', 149 | 'Qatari Riyal' = 'QAR', 150 | 'Romanian Leu' = 'RON', 151 | 'Serbian Dinar' = 'RSD', 152 | 'Russian Ruble' = 'RUB', 153 | 'Rwandan Franc' = 'RWF', 154 | 'Saudi Arabian Riyal' = 'SAR', 155 | 'Solomon Islander Dollar' = 'SBD', 156 | 'Seychellois Rupee' = 'SCR', 157 | 'Sudanese Pound' = 'SDG', 158 | 'Swedish Krona' = 'SEK', 159 | 'Singapore Dollar' = 'SGD', 160 | 'Saint Helenian Pound' = 'SHP', 161 | 'Sierra Leonean Leone' = 'SLL', 162 | 'Somali Shilling' = 'SOS', 163 | 'Seborgan Luigino' = 'SPL', 164 | 'Surinamese Dollar' = 'SRD', 165 | 'Sao Tomean Dobra' = 'STN', 166 | 'Salvadoran Colon' = 'SVC', 167 | 'Syrian Pound' = 'SYP', 168 | 'Swazi Lilangeni' = 'SZL', 169 | 'Thai Baht' = 'THB', 170 | 'Tajikistani Somoni' = 'TJS', 171 | 'Turkmenistani Manat' = 'TMT', 172 | 'Tunisian Dinar' = 'TND', 173 | "Tongan Pa'anga" = 'TOP', 174 | 'Turkish Lira' = 'TRY', 175 | 'Trinidadian Dollar' = 'TTD', 176 | 'Tuvaluan Dollar' = 'TVD', 177 | 'Taiwan New Dollar' = 'TWD', 178 | 'Tanzanian Shilling' = 'TZS', 179 | 'Ukrainian Hryvnia' = 'UAH', 180 | 'Ugandan Shilling' = 'UGX', 181 | 'Uniswap' = 'UNI', 182 | 'Uruguayan Peso' = 'UYU', 183 | 'Uzbekistani Som' = 'UZS', 184 | 'Venezuelan Bolívar' = 'VES', 185 | 'Vietnamese Dong' = 'VND', 186 | 'Ni' = 'VUV', 187 | 'Samoan Tala' = 'WST', 188 | 'Central African CFA Franc BEAC' = 'XAF', 189 | 'Silver Ounce' = 'XAG', 190 | 'Gold Ounce' = 'XAU', 191 | 'East Caribbean Dollar' = 'XCD', 192 | 'IMF Special Drawing Rights' = 'XDR', 193 | 'Stellar Lumen' = 'XLM', 194 | 'CFA Franc' = 'XOF', 195 | 'Palladium Ounce' = 'XPD', 196 | 'CFP Franc' = 'XPF', 197 | 'Platinum Ounce' = 'XPT', 198 | 'Ripple' = 'XRP', 199 | 'Yemeni Rial' = 'YER', 200 | 'South African Rand' = 'ZAR', 201 | 'Zambian Kwacha' = 'ZMW', 202 | 'Zimbabwean Dollar' = 'ZWD', 203 | } 204 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * as translator from './translator/translator.service'; 2 | export * as trasnliterator from './transliteration/transliteration.service'; 3 | export * as currencyConvertor from './currencies-convertor/currencies-convertor.service'; 4 | export * as spellChecker from './spell-checker/spell-checker.service'; 5 | export * as urlShortener from './url-shortener/url-shortener.service'; 6 | -------------------------------------------------------------------------------- /src/services/spell-checker/spell-checker.service.ts: -------------------------------------------------------------------------------- 1 | import ErrorsHandler from '../../errors/errors.module'; 2 | import { axiosInstance } from '../../shared/axios'; 3 | import * as spellCheckerTypes from './spell-checker.types'; 4 | 5 | export class SpellCheckerService { 6 | protected errorHandler = new ErrorsHandler(); 7 | 8 | async check(text: string): Promise { 9 | try { 10 | const { data: response } = await axiosInstance.request< 11 | unknown, 12 | { data: spellCheckerTypes.SpellCheckerResponse } 13 | >({ 14 | method: 'POST', 15 | url: '/spell-check', 16 | data: { 17 | text, 18 | }, 19 | }); 20 | 21 | if (!response.elements) { 22 | const errorResponse = 23 | response as unknown as spellCheckerTypes.SpellCheckerErrorResponse; 24 | 25 | if (errorResponse.errorMessage) { 26 | return this.handleError(response); 27 | } 28 | } 29 | 30 | const typeErrors = response.elements[0].errors; 31 | type errorType = 32 | spellCheckerTypes.SpellCheckerResponse['elements'][0]['errors'][0]; 33 | 34 | typeErrors.forEach((el: errorType) => { 35 | text = text.replace(el.word, el.suggestions[0]); 36 | }); 37 | 38 | if (!text || text == 'undefined') { 39 | return this.handleError(new Error('No text was returned')); 40 | } 41 | 42 | return text; 43 | } catch (e) { 44 | return this.handleError(e); 45 | } 46 | } 47 | 48 | private handleError(e: any): undefined { 49 | this.errorHandler.handleError({ 50 | environment: 'Spell checker', 51 | date: new Date(), 52 | message: 53 | 'An error occurred while checking the spelling of the text. Note that this feature only works with English.', 54 | trace: e, 55 | }); 56 | 57 | return undefined; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/services/spell-checker/spell-checker.types.ts: -------------------------------------------------------------------------------- 1 | export type SpellCheckerResponse = { 2 | elements: { 3 | id: number; 4 | errors: { 5 | word: string; 6 | position: number; 7 | suggestions: string[]; 8 | }[]; 9 | }[]; 10 | }; 11 | 12 | export type SpellCheckerErrorResponse = { 13 | errorMessage: string; 14 | errorType: string; 15 | stackTrace: string[]; 16 | }; 17 | -------------------------------------------------------------------------------- /src/services/translator/translator.service.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { URLSearchParams } from 'url'; 3 | import * as types from './translator.types'; 4 | import ErrorsHandler from '../../errors/errors.module'; 5 | import { axiosInstance } from '../../shared/axios'; 6 | 7 | export class TranslatorService { 8 | protected errorsHandler = new ErrorsHandler(); 9 | 10 | async translate(options: types.TranslateOptions): Promise { 11 | try { 12 | const { data: response } = await axiosInstance.request< 13 | any, 14 | { data: types.TranslateResponse } 15 | >({ 16 | method: 'POST', 17 | url: '/translate', 18 | data: { 19 | to: options.to, 20 | text: options.text, 21 | }, 22 | }); 23 | 24 | return response.translated_text; 25 | } catch (e) { 26 | this.errorsHandler.handleError({ 27 | environment: 'Translator', 28 | message: `An error occurred while translating the text. Please check that provided text is valid, got ${options.text}`, 29 | trace: e, 30 | }); 31 | 32 | return undefined; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/services/translator/translator.types.ts: -------------------------------------------------------------------------------- 1 | export type TranslateOptions = { 2 | to: Language; 3 | text: string; 4 | }; 5 | 6 | export type TranslateResponse = { 7 | translated_text: string; 8 | text_lang: string; 9 | ok: boolean; 10 | }; 11 | 12 | export enum Language { 13 | 'Afrikaans' = 'af', 14 | 'Amharic' = 'am', 15 | 'Arabic' = 'ar', 16 | 'Aragonese' = 'an', 17 | 'Armenian' = 'hy', 18 | 'Avaric' = 'av', 19 | 'Avestan' = 'ae', 20 | 'Azerbaijani' = 'az', 21 | 'Basque' = 'eu', 22 | 'Belarusian' = 'be', 23 | 'Bengali' = 'bn', 24 | 'Bosnian' = 'bs', 25 | 'Bulgarian' = 'bg', 26 | 'Burmese' = 'my', 27 | 'Catalan' = 'ca', 28 | 'Chamorro' = 'ch', 29 | 'Chechen' = 'ce', 30 | 'Chichewa' = 'ny', 31 | 'Chinese' = 'zh', 32 | 'Chuvash' = 'cv', 33 | 'Cornish' = 'kw', 34 | 'Corsican' = 'co', 35 | 'Cree' = 'cr', 36 | 'Croatian' = 'hr', 37 | 'Czech' = 'cs', 38 | 'Danish' = 'da', 39 | 'Dutch' = 'nl', 40 | 'English' = 'en', 41 | 'Esperanto' = 'eo', 42 | 'Estonian' = 'et', 43 | 'Finnish' = 'fi', 44 | 'French' = 'fr', 45 | 'Fula' = 'ff', 46 | 'Galician' = 'gl', 47 | 'Georgian' = 'ka', 48 | 'German' = 'de', 49 | 'Greek' = 'el', 50 | 'Gujarati' = 'gu', 51 | 'Haitian' = 'ht', 52 | 'Hausa' = 'ha', 53 | 'Hebrew' = 'he', 54 | 'Herero' = 'hz', 55 | 'Hindi' = 'hi', 56 | 'Hiri Motu' = 'ho', 57 | 'Hungarian' = 'hu', 58 | 'Icelandic' = 'is', 59 | 'Ido' = 'io', 60 | 'Igbo' = 'ig', 61 | 'Indonesian' = 'id', 62 | 'Irish' = 'ga', 63 | 'Italian' = 'it', 64 | 'Japanese' = 'ja', 65 | 'Javanese' = 'jv', 66 | 'Kannada' = 'kn', 67 | 'Kanuri' = 'kr', 68 | 'Kazakh' = 'kk', 69 | 'Khmer' = 'km', 70 | 'Kikuyu' = 'ki', 71 | 'Kinyarwanda' = 'rw', 72 | 'Komi' = 'kv', 73 | 'Kongo' = 'kg', 74 | 'Korean' = 'ko', 75 | 'Kurdish' = 'ku', 76 | 'Kwanyama' = 'kj', 77 | 'Kyrgyz' = 'ky', 78 | 'Lao' = 'lo', 79 | 'Latin' = 'la', 80 | 'Latvian' = 'lv', 81 | 'Limburgish' = 'li', 82 | 'Lithuanian' = 'lt', 83 | 'Luba-Katanga' = 'lu', 84 | 'Luxembourgish' = 'lb', 85 | 'Macedonian' = 'mk', 86 | 'Malagasy' = 'mg', 87 | 'Malay' = 'ms', 88 | 'Malayalam' = 'ml', 89 | 'Maltese' = 'mt', 90 | 'Marathi' = 'mr', 91 | 'Marshallese' = 'mh', 92 | 'Mongolian' = 'mn', 93 | 'Māori' = 'mi', 94 | 'Navajo' = 'nv', 95 | 'Ndonga' = 'ng', 96 | 'Nepali' = 'ne', 97 | 'Northern Ndebele' = 'nd', 98 | 'Northern Sami' = 'se', 99 | 'Norwegian' = 'no', 100 | 'Norwegian Bokmål' = 'nb', 101 | 'Norwegian Nynorsk' = 'nn', 102 | 'Nuosu' = 'ii', 103 | 'Ojibwe' = 'oj', 104 | 'Old Church Slavonic' = 'cu', 105 | 'Oriya' = 'or', 106 | 'Panjabi' = 'pa', 107 | 'Pashto' = 'ps', 108 | 'Persian' = 'fa', 109 | 'Polish' = 'pl', 110 | 'Portuguese' = 'pt', 111 | 'Pāli' = 'pi', 112 | 'Romanian' = 'ro', 113 | 'Russian' = 'ru', 114 | 'Samoan' = 'sm', 115 | 'Sardinian' = 'sc', 116 | 'Scottish Gaelic' = 'gd', 117 | 'Serbian' = 'sr', 118 | 'Shona' = 'sn', 119 | 'Sindhi' = 'sd', 120 | 'Sinhala' = 'si', 121 | 'Slovak' = 'sk', 122 | 'Slovene' = 'sl', 123 | 'Somali' = 'so', 124 | 'Southern Ndebele' = 'nr', 125 | 'Southern Sotho' = 'st', 126 | 'Spanish' = 'es', 127 | 'Sundanese' = 'su', 128 | 'Swahili' = 'sw', 129 | 'Swedish' = 'sv', 130 | 'Tagalog' = 'tl', 131 | 'Tahitian' = 'ty', 132 | 'Tajik' = 'tg', 133 | 'Tamil' = 'ta', 134 | 'Tatar' = 'tt', 135 | 'Telugu' = 'te', 136 | 'Thai' = 'th', 137 | 'Turkish' = 'tr', 138 | 'Turkmen' = 'tk', 139 | 'Ukrainian' = 'uk', 140 | 'Urdu' = 'ur', 141 | 'Uyghur' = 'ug', 142 | 'Uzbek' = 'uz', 143 | 'Vietnamese' = 'vi', 144 | 'Walloon' = 'wa', 145 | 'Welsh' = 'cy', 146 | 'Western Frisian' = 'fy', 147 | 'Xhosa' = 'xh', 148 | 'Yiddish' = 'yi', 149 | 'Yoruba' = 'yo', 150 | } 151 | -------------------------------------------------------------------------------- /src/services/transliteration/config/index.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | dictionary: { 3 | winRuEn: { 4 | й: 'q', 5 | ц: 'w', 6 | у: 'e', 7 | к: 'r', 8 | е: 't', 9 | н: 'y', 10 | г: 'u', 11 | ш: 'i', 12 | щ: 'o', 13 | з: 'p', 14 | х: '[', 15 | ъ: ']', 16 | ф: 'a', 17 | ы: 's', 18 | в: 'd', 19 | а: 'f', 20 | п: 'g', 21 | р: 'h', 22 | о: 'j', 23 | л: 'k', 24 | д: 'l', 25 | ж: ';', 26 | э: "'", 27 | я: 'z', 28 | Ч: 'X', 29 | с: 'c', 30 | м: 'v', 31 | и: 'b', 32 | т: 'n', 33 | ь: 'm', 34 | б: ',', 35 | ю: '.', 36 | Й: 'Q', 37 | Ц: 'W', 38 | У: 'E', 39 | К: 'R', 40 | Е: 'T', 41 | Н: 'Y', 42 | Г: 'U', 43 | Ш: 'I', 44 | Щ: 'O', 45 | З: 'P', 46 | Х: '[', 47 | Ъ: ']', 48 | Ф: 'A', 49 | Ы: 'S', 50 | В: 'D', 51 | А: 'F', 52 | П: 'G', 53 | Р: 'H', 54 | О: 'J', 55 | Л: 'K', 56 | Д: 'L', 57 | Ж: ';', 58 | Э: "'", 59 | '?': 'Z', 60 | ч: 'x', 61 | С: 'C', 62 | М: 'V', 63 | И: 'B', 64 | Т: 'N', 65 | Ь: 'M', 66 | Б: ',', 67 | Ю: '.', 68 | ё: '`', 69 | }, 70 | macRuEng: { 71 | й: 'q', 72 | Й: 'Q', 73 | ц: 'w', 74 | Ц: 'W', 75 | у: 'e', 76 | У: 'E', 77 | к: 'r', 78 | К: 'R', 79 | е: 't', 80 | Е: 'T', 81 | н: 'y', 82 | Н: 'Y', 83 | г: 'u', 84 | Г: 'U', 85 | ш: 'i', 86 | Ш: 'I', 87 | щ: 'o', 88 | Щ: 'O', 89 | з: 'p', 90 | З: 'P', 91 | х: '[', 92 | Х: '{', 93 | ъ: ']', 94 | Ъ: '}', 95 | ё: '\\', 96 | Ё: '|', 97 | ф: 'a', 98 | Ф: 'A', 99 | ы: 's', 100 | Ы: 'S', 101 | в: 'd', 102 | В: 'D', 103 | а: 'f', 104 | А: 'F', 105 | п: 'g', 106 | П: 'G', 107 | р: 'h', 108 | Р: 'H', 109 | о: 'j', 110 | O: 'J', 111 | л: 'k', 112 | Л: 'K', 113 | д: 'l', 114 | Д: 'L', 115 | ж: ';', 116 | Ж: ':', 117 | э: "'", 118 | Э: '"', 119 | я: 'z', 120 | Я: 'Z', 121 | ч: 'x', 122 | Ч: 'X', 123 | с: 'c', 124 | С: 'C', 125 | м: 'v', 126 | М: 'V', 127 | и: 'b', 128 | И: 'B', 129 | т: 'n', 130 | Т: 'N', 131 | ь: 'm', 132 | Ь: 'M', 133 | б: ',', 134 | Б: '<', 135 | ю: '.', 136 | Ю: '>', 137 | "'": '"', 138 | ',': '^', 139 | '.': '&', 140 | }, 141 | winUaEn: { 142 | "й": "q", 143 | 'ц': 'w', 144 | 'у': 'e', 145 | 'к': 'r', 146 | 'е': 't', 147 | 'н': 'y', 148 | 'г': 'u', 149 | 'ш': 'i', 150 | 'щ': 'o', 151 | 'з': 'p', 152 | 'х': '[', 153 | 'ї': ']', 154 | 'ʼ': '\\', 155 | "ф": "a", 156 | 'і': 's', 157 | 'в': 'd', 158 | 'а': 'f', 159 | 'п': 'g', 160 | 'р': 'h', 161 | 'о': 'j', 162 | 'л': 'k', 163 | 'д': 'l', 164 | 'ж': ';', 165 | 'є': '\'', 166 | 'я': 'z', 167 | 'ч': 'x', 168 | 'с': 'c', 169 | 'м': 'v', 170 | 'и': 'b', 171 | 'т': 'n', 172 | 'ь': 'm', 173 | 'б': ',', 174 | 'ю': '.', 175 | 'Ж': ':', 176 | '\'': '"', 177 | 'Ї': '}', 178 | '₴': '|', 179 | 'Й': 'Q', 180 | 'Ц': 'W', 181 | 'У': 'E', 182 | 'К': 'R', 183 | 'Е': 'T', 184 | 'Н': 'Y', 185 | 'Г': 'U', 186 | 'Ш': 'I', 187 | 'Щ': 'O', 188 | 'З': 'P', 189 | 'Х': '{', 190 | 'Ф': 'A', 191 | 'І': 'S', 192 | 'В': 'D', 193 | 'А': 'F', 194 | 'П': 'G', 195 | 'Р': 'H', 196 | 'О': 'J', 197 | 'Л': 'K', 198 | 'Д': 'L', 199 | 'Є': '"', 200 | '/': '~', 201 | 'Я': 'Z', 202 | 'Ч': 'X', 203 | 'С': 'C', 204 | 'М': 'V', 205 | 'И': 'B', 206 | 'Т': 'N', 207 | 'Ь': 'M', 208 | 'Б': '<', 209 | 'Ю': '>', 210 | ',': '?' 211 | }, 212 | macUaEn: { 213 | "й": "q", 214 | "ц": 'w', 215 | "у": 'e', 216 | "к": 'r', 217 | "е": 't', 218 | "н": 'y', 219 | "г": 'u', 220 | "ш": 'i', 221 | "щ": 'o', 222 | "з": 'p', 223 | "х": '[', 224 | "ї": ']', 225 | "ʼ": '\\', 226 | "ф": "a", 227 | "і": 's', 228 | "в": 'd', 229 | "а": 'f', 230 | "п": 'g', 231 | "р": 'h', 232 | "о": 'j', 233 | "л": 'k', 234 | "д": 'l', 235 | "ж": ';', 236 | "є": '\'', 237 | "я": 'z', 238 | "ч": 'x', 239 | "с": 'c', 240 | "м": 'v', 241 | "и": 'b', 242 | "т": 'n', 243 | "ь": 'm', 244 | "б": ',', 245 | "ю": '.', 246 | "Ж": ':', 247 | "'": '"', 248 | "Ї": '}', 249 | "₴": '|', 250 | "Й": 'Q', 251 | "Ц": 'W', 252 | "У": 'E', 253 | "К": 'R', 254 | "Е": 'T', 255 | "Н": 'Y', 256 | "Г": 'U', 257 | "Ш": 'I', 258 | "Щ": 'O', 259 | "З": 'P', 260 | "Х": '{', 261 | "Ф": 'A', 262 | "І": 'S', 263 | "В": 'D', 264 | "А": 'F', 265 | "П": 'G', 266 | "Р": 'H', 267 | "О": 'J', 268 | "Л": 'K', 269 | "Д": 'L', 270 | "Є": '"', 271 | "/": '~', 272 | "Я": 'Z', 273 | "Ч": 'X', 274 | "С": 'C', 275 | "М": 'V', 276 | "И": 'B', 277 | "Т": 'N', 278 | "Ь": 'M', 279 | "Б": '<', 280 | "Ю": '>', 281 | ",": '?' 282 | } 283 | }, 284 | words: { 285 | 'Ю ': '. ', 286 | 'Б ': ', ', 287 | 'Ь ': 'ь ', 288 | }, 289 | default: {}, 290 | }; 291 | -------------------------------------------------------------------------------- /src/services/transliteration/transliteration.service.ts: -------------------------------------------------------------------------------- 1 | import ErrorsHandler from '../../errors/errors.module'; 2 | import { config } from './config'; 3 | 4 | export class TransliterationService { 5 | protected errorsHandler = new ErrorsHandler(); 6 | 7 | protected normalize(str: string) { 8 | str = str 9 | .replace(/(Ю\s|Б\s|Ь\s)/g, (s) => { 10 | return config.words[s as keyof typeof config.words]; 11 | }) 12 | .replace(/\s{2,}/g, ' ') 13 | .trim(); 14 | 15 | return str; 16 | } 17 | 18 | protected flip(trans: { [key: string]: any }) { 19 | let key; 20 | const tmp: { [key: string]: any } = {}; 21 | 22 | for (key in trans) { 23 | tmp[trans[key]] = key; 24 | } 25 | 26 | return tmp; 27 | } 28 | 29 | transliterate( 30 | text: string, 31 | language?: 'ua' | 'ru', 32 | normalize?: boolean, 33 | ): string { 34 | try { 35 | const cyrillicPattern = /^\p{Script=Cyrillic}+$/u; 36 | const ukrainianPattern = /[А-ЩЬЮЯҐЄІЇа-щьюяґєії]/gi; 37 | 38 | let type: string; 39 | if (language) { 40 | if (language === 'ua') { 41 | if (ukrainianPattern.test(text)) { 42 | type = 'uaeng'; 43 | } else if (cyrillicPattern.test(text)) { 44 | type = 'uaeng'; 45 | } else { 46 | type = 'engua'; 47 | } 48 | } else if (language === 'ru') { 49 | if (cyrillicPattern.test(text)) { 50 | type = 'rueng'; 51 | } else { 52 | type = 'engru'; 53 | } 54 | } 55 | } else { 56 | if (cyrillicPattern.test(text)) { 57 | type = 'rueng'; 58 | } else if (ukrainianPattern.test(text)) { 59 | console.log('+++++++'); 60 | type = 'uaeng'; 61 | } else { 62 | type = 'engru'; 63 | } 64 | } 65 | 66 | let obj = {}; 67 | switch (type) { 68 | case 'rueng': { 69 | if (process.platform == 'darwin') { 70 | obj = config.dictionary.macRuEng; 71 | } else { 72 | obj = config.dictionary.winRuEn; 73 | } 74 | 75 | break; 76 | } 77 | case 'engru': { 78 | if (process.platform == 'darwin') { 79 | obj = this.flip(config.dictionary.macRuEng); 80 | } else { 81 | obj = this.flip(config.dictionary.winRuEn); 82 | } 83 | 84 | break; 85 | } 86 | case 'uaeng': 87 | if (process.platform == 'darwin') { 88 | obj = config.dictionary.winUaEn; 89 | } else { 90 | obj = config.dictionary.winUaEn; 91 | } 92 | 93 | break; 94 | case 'engua': 95 | if (process.platform == 'darwin') { 96 | obj = this.flip(config.dictionary.macUaEn); 97 | } else { 98 | obj = this.flip(config.dictionary.winUaEn); 99 | } 100 | 101 | break; 102 | default: { 103 | config.default = this.flip(config.dictionary.winRuEn); 104 | break; 105 | } 106 | } 107 | const textToArray = text.split(''); 108 | const result: any[] = []; 109 | 110 | textToArray.forEach(function (sym, i) { 111 | if (obj.hasOwnProperty(textToArray[i])) { 112 | //@ts-ignore 113 | result.push(obj[textToArray[i]]); 114 | } else { 115 | result.push(sym); 116 | } 117 | }); 118 | 119 | if (normalize) { 120 | return this.normalize(result.join('')); 121 | } else { 122 | return result.join(''); 123 | } 124 | } catch (e) { 125 | this.errorsHandler.handleError({ 126 | environment: 'Transliteration', 127 | message: `An error occurred while transliterating the text, got ${text}`, 128 | trace: e, 129 | }); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/services/url-shortener/url-shortener.service.ts: -------------------------------------------------------------------------------- 1 | import ErrorsHandler from '../../errors/errors.module'; 2 | import { axiosInstance } from '../../shared/axios'; 3 | 4 | export class UrlShortenerService { 5 | protected errorsHandler = new ErrorsHandler(); 6 | 7 | async shortenUrl(url: string): Promise { 8 | try { 9 | const { data: response } = await axiosInstance.request({ 10 | method: 'POST', 11 | url: '/shorten-url', 12 | data: { 13 | url, 14 | }, 15 | }); 16 | 17 | return response.short_link; 18 | } catch (e) { 19 | this.errorsHandler.handleError({ 20 | environment: 'Url shortener', 21 | message: `An error occurred while shortening the url. Please check that provided URL is valid, got ${url}`, 22 | trace: e, 23 | }); 24 | 25 | return undefined; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/settings/index.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from '../services/currencies-convertor/currencies-convertor.types'; 2 | import { Language } from '../services/translator/translator.types'; 3 | import * as Store from 'electron-store'; 4 | import { Settings } from './settings.types'; 5 | import { relaunchApp } from '../main'; 6 | import { ErrorStructure } from '../errors/errors.types'; 7 | import { ipcMain } from 'electron'; 8 | import axios from 'axios'; 9 | 10 | const defaultSettings: Settings = { 11 | shortcuts: { 12 | translate: ['Control', 'I'], 13 | transliterate: ['Control', 'T'], 14 | convertCurrency: ['Control', 'G'], 15 | humanizeText: ['Control', 'H'], 16 | spellCheck: ['Control', 'S'], 17 | shortenUrl: ['Control', 'U'], 18 | calculate: ['Control', 'K'], 19 | }, 20 | convertCurrencies: { 21 | from: Currency['US Dollar'], 22 | to: Currency['Ukrainian Hryvnia'], 23 | }, 24 | translate: { 25 | to: Language.English, 26 | }, 27 | transliterate: { 28 | to: null, 29 | }, 30 | restartToApplyChanges: false, 31 | errorsStorage: [], 32 | airAlerts: { 33 | state: null, 34 | enabled: null, 35 | }, 36 | }; 37 | 38 | const settings = new Store(); 39 | 40 | export const setDefaultSettings = () => { 41 | settings.store = defaultSettings; 42 | } 43 | 44 | export const initSettings = async () => { 45 | const currentSettings = settings.store as Settings; 46 | 47 | if (!currentSettings.shortcuts) { 48 | setDefaultSettings(); 49 | 50 | // if settings are not set, so it's a new user 51 | await axios.request({ 52 | method: 'POST', 53 | url: 'https://nodetools-back.herokuapp.com/api/v1/users/new', 54 | data: { 55 | platform: process.platform, 56 | }, 57 | }); 58 | } 59 | 60 | settings.set('restartToApplyChanges', false); 61 | }; 62 | 63 | export const changeSettings = (newSettings: Settings) => { 64 | settings.set('convertCurrencies', newSettings.convertCurrencies); 65 | settings.set('translate', newSettings.translate); 66 | 67 | const shortcutsSettings = settings.get('shortcuts') as Settings['shortcuts']; 68 | if ( 69 | JSON.stringify(shortcutsSettings) != JSON.stringify(newSettings.shortcuts) 70 | ) { 71 | settings.set('shortcuts', newSettings.shortcuts); 72 | settings.set('restartToApplyChanges', true); 73 | 74 | relaunchApp(); 75 | } 76 | }; 77 | 78 | /** 79 | * Change one property of settings 80 | * @param {string} key - The key of the setting to change. 81 | * @param {unknown} value - The value to set the setting to. 82 | */ 83 | export const changeSetting = (key: string, value: unknown) => { 84 | settings.set(key, value); 85 | }; 86 | 87 | export const addErrorToStorage = (error: ErrorStructure) => { 88 | const errors = settings.get('errorsStorage') as ErrorStructure[]; 89 | errors.push(error); 90 | 91 | settings.set('errorsStorage', errors); 92 | 93 | ipcMain.emit('handle-error', errors); 94 | }; 95 | 96 | export const removeErrorFromStorage = (id: string) => { 97 | const errorsStorage = settings.get('errorsStorage') as ErrorStructure[]; 98 | 99 | const newErrorsStorage = errorsStorage.filter((error) => error.id !== id); 100 | 101 | settings.set('errorsStorage', newErrorsStorage); 102 | }; 103 | 104 | export default settings; 105 | -------------------------------------------------------------------------------- /src/settings/settings.types.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from '../services/currencies-convertor/currencies-convertor.types'; 2 | import { Language } from '../services/translator/translator.types'; 3 | import { ErrorStructure } from '../errors/errors.types'; 4 | 5 | export type Settings = { 6 | shortcuts: ShortcutsSettings; 7 | convertCurrencies: { 8 | from: Currency; 9 | to: Currency; 10 | }; 11 | translate: { 12 | to: Language; 13 | }; 14 | transliterate: { 15 | to: 'ru' | 'ua'; 16 | }, 17 | restartToApplyChanges?: boolean; 18 | errorsStorage: ErrorStructure[]; 19 | airAlerts: { 20 | enabled: boolean; 21 | state: string; 22 | }; 23 | }; 24 | 25 | export type ShortcutsSettings = { 26 | translate: string[]; 27 | transliterate: string[]; 28 | convertCurrency: string[]; 29 | humanizeText: string[]; 30 | spellCheck: string[]; 31 | shortenUrl: string[]; 32 | calculate: string[]; 33 | }; 34 | -------------------------------------------------------------------------------- /src/shared/axios/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const axiosInstance = axios.create({ 4 | baseURL: 'https://nodetools-back.herokuapp.com/api/v1/third-party', 5 | }); 6 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * as utils from './utils'; 2 | -------------------------------------------------------------------------------- /src/shared/utils/humanize-string.ts: -------------------------------------------------------------------------------- 1 | export const humanizeString = (str: string) => { 2 | str = str 3 | .replace(/^[\s_]+|[\s_]+$/g, '') 4 | .replace(/[_\s]+/g, ' ') 5 | .replace(/, /g, ',') 6 | .replace(/,/g, ', ') 7 | .replace(/\. /g, '.') 8 | .replace(/\./g, '. ') 9 | .toLowerCase(); 10 | 11 | return str.charAt(0).toUpperCase() + str.slice(1); 12 | }; 13 | -------------------------------------------------------------------------------- /src/shared/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './humanize-string'; 2 | -------------------------------------------------------------------------------- /src/shared/utils/open-website.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process' 2 | 3 | export const openWebURL = (url: string) => { 4 | const start = 5 | process.platform == 'darwin' 6 | ? 'open' 7 | : process.platform == 'win32' 8 | ? 'start' 9 | : 'xdg-open'; 10 | 11 | exec(start + ' ' + url); 12 | }; 13 | -------------------------------------------------------------------------------- /src/views/air-alerts-settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodetools 6 | 75 | 81 | 82 | 83 |
84 | 101 | 102 |

Alpha version

103 |
104 |
105 | 106 |
107 |

Налаштування повітряної тривоги

108 | 109 |

🇺🇦 Виберіть область, в якій ви знаходитесь,
ми будемо повідомляти вас про повітряні тривоги в ній

110 | 115 | 116 |
117 | 120 |
121 |
122 | 123 | 129 | 130 | 214 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /src/views/convert-file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodetools 6 | 87 | 93 | 94 | 95 |
96 | 113 | 117 |

Alpha version

118 |
119 |
120 | 121 |
122 |
123 | 130 | 139 | 145 | 146 |
147 |

Select the type you want to convert file to

148 | 153 |
154 | 157 |
158 |
159 | 160 | 166 | 167 | 236 | 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /src/views/decrypt-file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodetools 6 | 104 | 110 | 111 | 112 |
113 | 130 | 134 |

Alpha version

135 |
136 |
137 | 138 |
139 |
140 | 147 | 156 | 162 | 163 |
164 |

Enter the password we use to decrypt the file

165 | 166 | 173 |
174 | 177 |
178 |
179 | 180 | 186 | 187 | 246 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /src/views/encrypt-file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodetools 6 | 95 | 101 | 102 | 103 |
104 | 121 | 125 |

Alpha version

126 |
127 |
128 | 129 |
130 |
131 | 138 | 147 | 153 | 154 |
155 |

Enter the password we use to encrypt the file

156 |
157 |

158 | Watch out! If you forget 159 | this password, the file cannot be recovered! 160 |

161 |
162 | 167 |
168 | 171 |
172 |
173 | 174 | 180 | 181 | 216 | 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /src/views/errors-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodetools 6 | 69 | 75 | 76 | 77 |
78 | 95 | 96 |

Alpha version

97 |
98 |
99 | 100 |
101 |

Errors that have occured recently

102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
DatetimeEnvironmentMessage
115 |

Yay! So far we haven't spotted any errors!

116 |
117 | 118 | 124 | 125 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /src/views/set-default-settings-dialogue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodetools 6 | 79 | 85 | 86 | 87 |
88 | 105 |
106 | 107 |
108 |

Are you sure?

109 |

110 | You are about to reset your settings to the factory defaults. This 111 | action cannot be undone. 112 |

113 |
114 | 121 | 124 |
125 |
126 | 127 | 133 | 134 | 154 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /src/views/transliterate-settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodetools 6 | 75 | 81 | 82 | 83 |
84 | 101 | 102 |

Alpha version

103 |
104 |
105 | 106 |
107 |

Transliterate options

108 | 109 |

Select the language to/from which we will transliterate the text at the following times

110 | 115 | 116 |
117 | 120 |
121 |
122 | 123 | 129 | 130 | 189 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /src/views/upload-file-encrypt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodetools 6 | 104 | 110 | 111 | 112 |
113 | 130 | 134 |

Alpha version

135 |
136 |
137 | 138 |
139 |

Encrypt file

140 | 141 |
142 |
143 |
144 | 145 | 146 | 147 | 148 | 149 |
150 |

Drag and drop files here

151 |
152 | 153 |
154 |
155 | 156 | 162 | 163 | 207 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "sourceMap": true, 6 | "outDir": "app", 7 | "baseUrl": ".", 8 | "paths": { 9 | "*": ["node_modules/*"] 10 | } 11 | }, 12 | "include": ["src/**/*"] 13 | } 14 | --------------------------------------------------------------------------------