├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ └── deploy-gh-pages.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── electron-preload.cjs ├── electron.cjs ├── forge.config.cjs ├── index.html ├── package-lock.json ├── package.json ├── preview_dark.png ├── preview_light.png ├── public ├── favicon.icns ├── favicon.ico ├── favicon.png ├── favicon.svg ├── favicon@1x.png ├── favicon@2x.png └── service-worker.js ├── src ├── App.svelte ├── global.css ├── global.d.ts ├── lib │ ├── Close.svelte │ ├── Editor.svelte │ ├── Fullscreen.svelte │ ├── Hide.svelte │ ├── IconButton.svelte │ ├── Statistics.svelte │ ├── Stopwatch.svelte │ ├── Theme.svelte │ ├── Time.svelte │ ├── Titlebar.svelte │ ├── stores │ │ ├── app.ts │ │ └── editor.ts │ └── utils │ │ ├── calculatePosition │ │ └── index.ts │ │ ├── classnames │ │ └── index.ts │ │ ├── countCharacters │ │ └── index.ts │ │ ├── countParagraphs │ │ └── index.ts │ │ ├── countSentences │ │ └── index.ts │ │ ├── countWords │ │ └── index.ts │ │ ├── fullscreen │ │ └── index.ts │ │ └── persistenceStorage │ │ └── index.ts ├── main.ts ├── registerServiceWorker.ts └── vite-env.d.ts ├── svelte.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /dist 5 | /out 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | 11 | # Ignore files for PNPM, NPM and YARN 12 | pnpm-lock.yaml 13 | package-lock.json 14 | yarn.lock 15 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier", 8 | ], 9 | plugins: ["svelte3", "@typescript-eslint"], 10 | ignorePatterns: ["*.cjs"], 11 | overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }], 12 | settings: { 13 | "svelte3/typescript": () => require("typescript"), 14 | }, 15 | parserOptions: { 16 | sourceType: "module", 17 | ecmaVersion: 2020, 18 | }, 19 | env: { 20 | browser: true, 21 | es2017: true, 22 | node: true, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.github/workflows/deploy-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ['main'] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: 'pages' 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Set up Node 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: 18 37 | cache: 'npm' 38 | - name: Install dependencies 39 | run: npm install 40 | - name: Build 41 | run: npm run frontend:build:web 42 | - name: Setup Pages 43 | uses: actions/configure-pages@v3 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v1 46 | with: 47 | # Upload dist repository 48 | path: './dist' 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v1 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | out/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /dist 5 | /out 6 | /.svelte-kit 7 | /package 8 | .env 9 | .env.* 10 | !.env.example 11 | 12 | # Ignore files for PNPM, NPM and YARN 13 | pnpm-lock.yaml 14 | package-lock.json 15 | yarn.lock 16 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-svelte"], 3 | "pluginSearchDirs": ["."], 4 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Stepan Kurennykh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Writer 2 | 3 | Writer is a distraction-free text editor to improve your writing skills. It includes statistics, stopwatch, dark/light theme and many other little tricks to make your writing experience more pleasant. 4 | 5 | I started this project to help myself to improve my writing skills, especially IELTS writing part. While I was experimenting with many tools and approaches, I discovered that the best way to level-up is to eliminate any grammar and spellcheckers and focus solely on the process of writing. Unfortunately, I couldn't find the right app for me because all of them either they had bad design/interface, IMHO, or they were bloated with unnecessary futures or spellcheckers. 6 | 7 | The app has statistics that shows how many characters, words, sentences, or paragraphs you have written. It's helpful when you're trying to meet some goals, for example, at least 250 words for IELTS essay part. Moreover, the app has a stopwatch that you can use to track your writing speed or create the pressure to emulate exam conditions. 8 | 9 | The app available [online](https://stoope.github.io/writer/). For the better experience, I recommend building your own desktop version 10 | 11 | ![Screenshot](preview_dark.png) 12 | ![Screenshot](preview_light.png) 13 | 14 | ## Prerequisites 15 | 16 | You must have `nodejs` >= 18 and `npm` >= 9 to run and build this app. Work with early versions is not guaranteed. 17 | 18 | ## Install 19 | 20 | ```bash 21 | git clone https://github.com/stoope/writer.git 22 | npm install 23 | ``` 24 | 25 | ## Run 26 | 27 | ```bash 28 | npm run start 29 | ``` 30 | 31 | ## Build 32 | 33 | You might have to change the `electron-forge` configuration file `forge.config.cjs` to build the app for your specific platform. 34 | 35 | ```bash 36 | npm run build 37 | ``` 38 | 39 | After build, you could find the app under `out` folder. 40 | 41 | ## Your help is appreciated! 42 | 43 | If you want to discuss your ideas or create a Pull Requests you're welcome! 44 | -------------------------------------------------------------------------------- /electron-preload.cjs: -------------------------------------------------------------------------------- 1 | const { ipcRenderer, contextBridge } = require("electron"); 2 | 3 | contextBridge.exposeInMainWorld("ipcRenderer", { 4 | send: ipcRenderer.send.bind(ipcRenderer), 5 | on: ipcRenderer.on.bind(ipcRenderer), 6 | invoke: ipcRenderer.invoke.bind(ipcRenderer), 7 | }); 8 | -------------------------------------------------------------------------------- /electron.cjs: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, ipcMain, Menu, nativeTheme } = require("electron"); 2 | const path = require("path"); 3 | const Store = require("electron-store"); 4 | 5 | const store = new Store(); 6 | 7 | const WINDOW_BONDS_KEY = "editor:winBounds"; 8 | 9 | const isMac = process.platform === "darwin"; 10 | 11 | const createWindow = async () => { 12 | const windowBound = store.get(WINDOW_BONDS_KEY) ?? {}; 13 | 14 | const mainWindow = new BrowserWindow({ 15 | width: 600, 16 | height: 800, 17 | minWidth: 360, 18 | minHeight: 360, 19 | icon: path.join( 20 | __dirname, 21 | "public", 22 | { darwin: "favicon.icns", linux: "favicon.png", win32: "favicon.ico" }[ 23 | process.platform 24 | ] || "favicon.ico" 25 | ), 26 | frame: !isMac, 27 | skipTaskbar: isMac, 28 | autoHideMenuBar: isMac, 29 | webPreferences: { 30 | backgroundThrottling: false, 31 | preload: path.resolve(__dirname, "electron-preload.cjs"), 32 | contextIsolation: true, 33 | }, 34 | ...windowBound, 35 | }); 36 | 37 | ipcMain.handle("toggleFullscreen", function () { 38 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 39 | }); 40 | 41 | ipcMain.handle("close", function () { 42 | app.quit(); 43 | }); 44 | 45 | ipcMain.handle("minimize", function () { 46 | mainWindow.minimize(); 47 | }); 48 | 49 | ipcMain.handle("setSetting", function (_event, { key, value }) { 50 | if (key === "app:theme") { 51 | nativeTheme.themeSource = value; 52 | } 53 | return store.set(key, value); 54 | }); 55 | 56 | ipcMain.handle("getSetting", function (_event, { key }) { 57 | return store.get(key); 58 | }); 59 | 60 | mainWindow.on("close", function () { 61 | store.set(WINDOW_BONDS_KEY, mainWindow.getBounds()); 62 | }); 63 | 64 | mainWindow.on("closed", function () { 65 | app.quit(); 66 | }); 67 | 68 | if (process.env.NODE_ENV !== "development") { 69 | await mainWindow.loadFile(path.join(__dirname, "dist", "index.html")); 70 | } else { 71 | await mainWindow.loadURL("http://localhost:3000/"); 72 | } 73 | 74 | if (process.env.NODE_ENV === "development") { 75 | mainWindow.webContents.openDevTools(); 76 | } 77 | }; 78 | 79 | app.whenReady().then(() => { 80 | createWindow(); 81 | 82 | app.on("activate", () => { 83 | if (BrowserWindow.getAllWindows().length === 0) createWindow(); 84 | }); 85 | }); 86 | 87 | app.on("window-all-closed", () => { 88 | if (!isMac) app.quit(); 89 | }); 90 | -------------------------------------------------------------------------------- /forge.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | 4 | module.exports = { 5 | packagerConfig: { 6 | icon: "./public/favicon", 7 | }, 8 | rebuildConfig: {}, 9 | makers: [ 10 | { 11 | name: "@electron-forge/maker-squirrel", 12 | config: {}, 13 | }, 14 | { 15 | name: "@electron-forge/maker-zip", 16 | platforms: ["darwin"], 17 | }, 18 | { 19 | name: "@electron-forge/maker-deb", 20 | config: {}, 21 | }, 22 | ], 23 | publishers: [ 24 | { 25 | name: "@electron-forge/publisher-github", 26 | config: { 27 | repository: { 28 | owner: "stoope", 29 | name: "writer", 30 | }, 31 | prerelease: false, 32 | draft: false, 33 | }, 34 | }, 35 | ], 36 | }; 37 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | Writer 13 | 14 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "writer", 3 | "description": "Writer without distractions", 4 | "private": true, 5 | "version": "1.0.0", 6 | "type": "module", 7 | "main": "electron.cjs", 8 | "scripts": { 9 | "start": "NODE_ENV=development concurrently \"npm run frontend:start\" \"npm run electron:start\"", 10 | "frontend:start": "vite --port 3000 --host localhost", 11 | "frontend:start:web": "VITE_WEB=true vite --port 3000 --host localhost", 12 | "frontend:build": "vite build", 13 | "frontend:build:web": "VITE_WEB=true vite build", 14 | "frontend:preview": "vite preview", 15 | "frontend:check": "svelte-check --tsconfig ./tsconfig.json", 16 | "electron:start": "electron .", 17 | "electron:build": "electron-forge make", 18 | "build": "npm run frontend:build && npm run electron:build", 19 | "package": "electron-forge package", 20 | "publish": "electron-forge publish", 21 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 22 | "format": "prettier --plugin-search-dir . --write ." 23 | }, 24 | "devDependencies": { 25 | "@electron-forge/cli": "^6.0.5", 26 | "@electron-forge/maker-deb": "^6.0.5", 27 | "@electron-forge/maker-rpm": "^6.0.5", 28 | "@electron-forge/maker-squirrel": "^6.0.5", 29 | "@electron-forge/maker-zip": "^6.0.5", 30 | "@electron-forge/publisher-github": "^6.0.5", 31 | "@sveltejs/vite-plugin-svelte": "^2.0.3", 32 | "@tsconfig/svelte": "^3.0.0", 33 | "@types/debounce": "^1.2.1", 34 | "@typescript-eslint/eslint-plugin": "^5.54.0", 35 | "@typescript-eslint/parser": "^5.54.0", 36 | "concurrently": "^7.6.0", 37 | "electron": "^23.1.1", 38 | "eslint": "^8.35.0", 39 | "eslint-config-prettier": "^8.6.0", 40 | "eslint-plugin-svelte3": "^4.0.0", 41 | "prettier": "^2.8.4", 42 | "prettier-plugin-svelte": "^2.9.0", 43 | "svelte": "^3.55.1", 44 | "svelte-check": "^3.0.4", 45 | "tslib": "^2.5.0", 46 | "typescript": "^4.9.5", 47 | "vite": "^4.1.4" 48 | }, 49 | "dependencies": { 50 | "@fontsource/courier-prime": "^4.5.9", 51 | "@fontsource/fira-code": "^4.5.13", 52 | "debounce": "^1.2.1", 53 | "electron-squirrel-startup": "^1.0.0", 54 | "electron-store": "^8.1.0" 55 | }, 56 | "volta": { 57 | "node": "18.14.1" 58 | }, 59 | "engines": { 60 | "node": ">=18", 61 | "npm": ">=9" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /preview_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoope/writer/8c7a01a8b1251e6df3d60297026ec6ffbe2a3ecf/preview_dark.png -------------------------------------------------------------------------------- /preview_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoope/writer/8c7a01a8b1251e6df3d60297026ec6ffbe2a3ecf/preview_light.png -------------------------------------------------------------------------------- /public/favicon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoope/writer/8c7a01a8b1251e6df3d60297026ec6ffbe2a3ecf/public/favicon.icns -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoope/writer/8c7a01a8b1251e6df3d60297026ec6ffbe2a3ecf/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoope/writer/8c7a01a8b1251e6df3d60297026ec6ffbe2a3ecf/public/favicon.png -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 12 | 13 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 76 | 78 | 80 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /public/favicon@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoope/writer/8c7a01a8b1251e6df3d60297026ec6ffbe2a3ecf/public/favicon@1x.png -------------------------------------------------------------------------------- /public/favicon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoope/writer/8c7a01a8b1251e6df3d60297026ec6ffbe2a3ecf/public/favicon@2x.png -------------------------------------------------------------------------------- /public/service-worker.js: -------------------------------------------------------------------------------- 1 | const cacheName = "v1"; 2 | 3 | self.addEventListener("fetch", async function (event) { 4 | if (event.request.destination === "font") { 5 | event.respondWith( 6 | caches.open(cacheName).then((cache) => { 7 | return cache.match(event.request).then((cachedResponse) => { 8 | const fetchedResponse = fetch(event.request).then( 9 | (networkResponse) => { 10 | cache.put(event.request, networkResponse.clone()); 11 | 12 | return networkResponse; 13 | } 14 | ); 15 | 16 | return cachedResponse || fetchedResponse; 17 | }); 18 | }) 19 | ); 20 | } else { 21 | return; 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 | 78 | -------------------------------------------------------------------------------- /src/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | --background: rgb(var(--background-rgb)); 3 | --foreground: rgb(var(--foreground-rgb)); 4 | --accent: var(--cyan); 5 | --black: rgb(var(--black-rgb)); 6 | --white: rgb(var(--white-rgb)); 7 | --comment: rgb(var(--comment-rgb)); 8 | --comment-rgb: 121, 112, 169; 9 | --selection-rgb: 69, 65, 88; 10 | --cyan-rgb: 128, 255, 234; 11 | --green-rgb: 138, 255, 128; 12 | --orange-rgb: 255, 202, 128; 13 | --pink-rgb: 255, 128, 191; 14 | --purple-rgb: 149, 128, 255; 15 | --red-rgb: 255, 149, 128; 16 | --yellow-rgb: 255, 255, 128; 17 | --black-rgb: 34, 33, 44; 18 | --white-rgb: 248, 248, 242; 19 | --selection-rgb-light: 176, 172, 194; 20 | --cyan-rgb-light: 0, 229, 191; 21 | --green-rgb-light: 18, 229, 0; 22 | --orange-rgb-light: 229, 133, 0; 23 | --pink-rgb-light: 229, 0, 113; 24 | --purple-rgb-light: 37, 0, 229; 25 | --red-rgb-light: 229, 37, 0; 26 | --yellow-rgb-light: 229, 229, 0; 27 | -webkit-app-region: drag; 28 | } 29 | 30 | body { 31 | background-color: var(--background); 32 | color: var(--foreground); 33 | font-size: 16px; 34 | font-family: "Fira Code", monospace; 35 | } 36 | 37 | @media (prefers-color-scheme: dark) { 38 | body:not(.light-theme) { 39 | --background-rgb: var(--black-rgb); 40 | --foreground-rgb: var(--white-rgb); 41 | --selection: rgb(var(--selection-rgb)); 42 | --cyan: rgb(var(--cyan-rgb)); 43 | --green: rgb(var(--green-rgb)); 44 | --orange: rgb(var(--orange-rgb)); 45 | --pink: rgb(var(--pink-rgb)); 46 | --purple: rgb(var(--purple-rgb)); 47 | --red: rgb(var(--red-rgb)); 48 | --yellow: rgb(var(--yellow-rgb)); 49 | } 50 | } 51 | body.dark-theme:not(.light-theme) { 52 | --background-rgb: var(--black-rgb); 53 | --foreground-rgb: var(--white-rgb); 54 | --selection: rgb(var(--selection-rgb)); 55 | --cyan: rgb(var(--cyan-rgb)); 56 | --green: rgb(var(--green-rgb)); 57 | --orange: rgb(var(--orange-rgb)); 58 | --pink: rgb(var(--pink-rgb)); 59 | --purple: rgb(var(--purple-rgb)); 60 | --red: rgb(var(--red-rgb)); 61 | --yellow: rgb(var(--yellow-rgb)); 62 | } 63 | 64 | @media (prefers-color-scheme: light) { 65 | body:not(.dark-theme) { 66 | --background-rgb: var(--white-rgb); 67 | --foreground-rgb: var(--black-rgb); 68 | --accent: var(--orange); 69 | --selection: rgb(var(--selection-rgb-light)); 70 | --cyan: rgb(var(--cyan-rgb-light)); 71 | --green: rgb(var(--green-rgb-light)); 72 | --orange: rgb(var(--orange-rgb-light)); 73 | --pink: rgb(var(--pink-rgb-light)); 74 | --purple: rgb(var(--purple-rgb-light)); 75 | --red: rgb(var(--red-rgb-light)); 76 | --yellow: rgb(var(--yellow-rgb-light)); 77 | } 78 | } 79 | body.light-theme:not(.dark-theme) { 80 | --background-rgb: var(--white-rgb); 81 | --foreground-rgb: var(--black-rgb); 82 | --accent: var(--orange); 83 | --selection: rgb(var(--selection-rgb-light)); 84 | --cyan: rgb(var(--cyan-rgb-light)); 85 | --green: rgb(var(--green-rgb-light)); 86 | --orange: rgb(var(--orange-rgb-light)); 87 | --pink: rgb(var(--pink-rgb-light)); 88 | --purple: rgb(var(--purple-rgb-light)); 89 | --red: rgb(var(--red-rgb-light)); 90 | --yellow: rgb(var(--yellow-rgb-light)); 91 | } 92 | 93 | * { 94 | box-sizing: border-box; 95 | padding: 0; 96 | margin: 0; 97 | } 98 | 99 | ::selection { 100 | color: var(--foreground); 101 | background-color: var(--selection); 102 | opacity: 1; 103 | } 104 | 105 | html, 106 | body, 107 | #app { 108 | height: 100%; 109 | } 110 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare interface Window { 3 | ipcRenderer: { 4 | invoke(channel: string, ...args: any[]): Promise; 5 | send(channel: string, ...args: any[]): void; 6 | on(channel: string, listener: (event: any, ...args: any[]) => void): this; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/Close.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 | 25 |
26 | 27 | 36 | -------------------------------------------------------------------------------- /src/lib/Editor.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 |