├── README.md ├── public ├── icon.icns ├── fonts │ ├── Montserrat-Bold.ttf │ ├── Montserrat-Medium.ttf │ ├── JetBrainsMono-Medium.ttf │ ├── Montserrat-BoldItalic.ttf │ └── Montserrat-MediumItalic.ttf ├── svg_icons │ ├── habits.svg │ ├── plus.svg │ ├── settings.svg │ ├── trash.svg │ ├── hashtag.svg │ ├── icon.svg │ ├── codes.svg │ └── notes.svg └── splash.html ├── src ├── main.jsx ├── utils │ ├── dates.js │ ├── readDataFromFile.js │ └── handlers.js ├── context │ ├── GithubProvider.jsx │ ├── context.js │ ├── FunctionsProvider.jsx │ └── FoldersProvider.jsx ├── components │ ├── Topbar.jsx │ ├── Bottombar.jsx │ ├── ShowCase.jsx │ ├── SettingsMenu.jsx │ ├── MarkdownEditor.jsx │ └── GitHubActivity.jsx ├── App.jsx └── index.css ├── vite.config.js ├── .gitignore ├── index.html ├── eslint.config.js ├── package.json └── electron └── main.js /README.md: -------------------------------------------------------------------------------- 1 | # Hashnote 2 | 3 | ![image](https://hashnoteapp.vercel.app/preview.png) 4 | -------------------------------------------------------------------------------- /public/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developerbola/hashnote-electron/HEAD/public/icon.icns -------------------------------------------------------------------------------- /public/fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developerbola/hashnote-electron/HEAD/public/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/Montserrat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developerbola/hashnote-electron/HEAD/public/fonts/Montserrat-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/JetBrainsMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developerbola/hashnote-electron/HEAD/public/fonts/JetBrainsMono-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/Montserrat-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developerbola/hashnote-electron/HEAD/public/fonts/Montserrat-BoldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/Montserrat-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developerbola/hashnote-electron/HEAD/public/fonts/Montserrat-MediumItalic.ttf -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.jsx' 5 | 6 | createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | export default defineConfig({ 4 | plugins: [react()], 5 | server: { 6 | port: 3005, 7 | }, 8 | base: "./", 9 | build: { 10 | outDir: "dist", 11 | emptyOutDir: true, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /public/svg_icons/habits.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/svg_icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/svg_icons/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.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 | release 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /public/svg_icons/trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/svg_icons/hashtag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/svg_icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hashnote 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/utils/dates.js: -------------------------------------------------------------------------------- 1 | export const months = [ 2 | "January", 3 | "February", 4 | "March", 5 | "April", 6 | "May", 7 | "June", 8 | "July", 9 | "August", 10 | "September", 11 | "October", 12 | "November", 13 | "December", 14 | ]; 15 | 16 | const weekdays = [ 17 | "Sun", 18 | "Mon", 19 | "Tue", 20 | "Wed", 21 | "Thu", 22 | "Fri", 23 | "Sat", 24 | ]; 25 | 26 | export const date = [ 27 | new Date().getDate(), // day 28 | months[new Date().getMonth()], // month 29 | weekdays[new Date().getDay()], // week day 30 | ]; 31 | -------------------------------------------------------------------------------- /src/context/GithubProvider.jsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import { GithubContext } from "./context"; 3 | import { readToken, readUsername } from "../utils/readDataFromFile"; 4 | 5 | export const GithubProvider = ({ children }) => { 6 | const [username, setUsername] = useState(readUsername()); 7 | const [token, setToken] = useState(readToken()); 8 | return ( 9 | 10 | {children} 11 | 12 | ); 13 | }; 14 | 15 | export const useGithub = () => { 16 | return useContext(GithubContext); 17 | }; 18 | -------------------------------------------------------------------------------- /src/context/context.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const initialFunctions = { 4 | editorValue: "", 5 | setEditorValue: null, 6 | filePath: "", 7 | setFilePath: null, 8 | }; 9 | 10 | const initialFolders = { 11 | folders: {}, 12 | setFolders: null, 13 | loadFilesFromDisk: null, 14 | }; 15 | 16 | const initialGithub = { 17 | username: "", 18 | token: "", 19 | setUsername: null, 20 | setToken: null, 21 | }; 22 | 23 | export const FunctionsContext = createContext(initialFunctions); 24 | export const FoldersContext = createContext(initialFolders); 25 | export const GithubContext = createContext(initialGithub); 26 | -------------------------------------------------------------------------------- /src/components/Topbar.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { date } from "../utils/dates"; 3 | import SettingsMenu from "./SettingsMenu"; 4 | const Topbar = () => { 5 | const [active, setActive] = useState(false); 6 | const addZero = (number) => { 7 | return number < 10 ? "0" + number : number; 8 | }; 9 | return ( 10 |
11 |
12 |

{addZero(date[0])}

13 |
14 |

{date[2]}

15 |

{date[1]}

16 |
17 |
18 |
19 | 22 | 23 |
24 |
25 | ); 26 | }; 27 | 28 | export default Topbar; 29 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | 6 | export default [ 7 | { ignores: ['dist'] }, 8 | { 9 | files: ['**/*.{js,jsx}'], 10 | languageOptions: { 11 | ecmaVersion: 2020, 12 | globals: globals.browser, 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | ecmaFeatures: { jsx: true }, 16 | sourceType: 'module', 17 | }, 18 | }, 19 | plugins: { 20 | 'react-hooks': reactHooks, 21 | 'react-refresh': reactRefresh, 22 | }, 23 | rules: { 24 | ...js.configs.recommended.rules, 25 | ...reactHooks.configs.recommended.rules, 26 | 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], 27 | 'react-refresh/only-export-components': [ 28 | 'warn', 29 | { allowConstantExport: true }, 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /public/splash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loading... 7 | 33 | 34 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /public/svg_icons/codes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/context/FunctionsProvider.jsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import { FunctionsContext } from "./context"; 3 | import { readFile } from "../utils/readDataFromFile"; 4 | 5 | export const FunctionsProvider = ({ children }) => { 6 | const [editorValue, setEditorValue] = useState(""); 7 | const [filePath, setFilePath] = useState(""); 8 | 9 | useEffect(() => { 10 | if (!filePath) return; 11 | 12 | const readValue = async () => { 13 | try { 14 | const data = await readFile(filePath); 15 | setEditorValue(data); 16 | } catch (error) { 17 | console.error("Failed to read file:", error); 18 | } 19 | }; 20 | 21 | readValue(); 22 | }, [filePath]); 23 | 24 | return ( 25 | 28 | {children} 29 | 30 | ); 31 | }; 32 | 33 | export const useFunctions = () => { 34 | return useContext(FunctionsContext); 35 | }; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hashnote", 3 | "private": true, 4 | "type": "module", 5 | "version": "1.0.0-beta", 6 | "description": "A simple markdown and note-taking app", 7 | "author": "Mutawirr", 8 | "main": "electron/main.js", 9 | "build": { 10 | "appId": "com.hashnote.mutawirr", 11 | "productName": "Hashnote", 12 | "icon": "public/icon.icns", 13 | "mac": { 14 | "target": "dmg" 15 | }, 16 | "files": [ 17 | "dist/", 18 | "electron/**/*", 19 | "public/**/**" 20 | ], 21 | "directories": { 22 | "buildResources": "public", 23 | "output": "release" 24 | } 25 | }, 26 | "mac": { 27 | "identity": null 28 | }, 29 | "scripts": { 30 | "dev": "vite", 31 | "build": "vite build", 32 | "lint": "eslint .", 33 | "start": "npx electron .", 34 | "build-renderer": "vite build", 35 | "build-electron": "electron-builder", 36 | "package": "npm run build-renderer && npm run build-electron" 37 | }, 38 | "devDependencies": { 39 | "@eslint/js": "^9.21.0", 40 | "@mdxeditor/editor": "^3.29.1", 41 | "@types/react": "^19.0.10", 42 | "@types/react-dom": "^19.0.4", 43 | "@vitejs/plugin-react": "^4.3.4", 44 | "electron": "^35.1.2", 45 | "electron-builder": "^26.0.12", 46 | "eslint": "^9.21.0", 47 | "eslint-plugin-react-hooks": "^5.1.0", 48 | "eslint-plugin-react-refresh": "^0.4.19", 49 | "globals": "^15.15.0", 50 | "marked": "^15.0.7", 51 | "react": "^19.0.0", 52 | "react-dom": "^19.0.0", 53 | "vite": "^6.2.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Bottombar.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import GitHubActivity from "./GitHubActivity"; 3 | import ShowCase from "./ShowCase"; 4 | import { readToken } from "../utils/readDataFromFile"; 5 | import { useFolders } from "../context/FoldersProvider"; 6 | 7 | const Bottombar = ({ setActiveFile }) => { 8 | const [activeFolder, setActiveFolder] = useState("Notes"); 9 | const { folders, loadFilesFromDisk } = useFolders(); 10 | useEffect(() => { 11 | loadFilesFromDisk(); 12 | readToken(); 13 | }, []); 14 | 15 | useEffect(() => { 16 | loadFilesFromDisk(); 17 | }, [activeFolder]); 18 | 19 | return ( 20 |
21 | 22 | {/* Show active folder data */} 23 | 24 | 25 |
26 |
27 | {Object.keys(folders).map((folderKey) => ( 28 | 45 | ))} 46 |
47 |
48 |
49 | ); 50 | }; 51 | 52 | export default Bottombar; 53 | -------------------------------------------------------------------------------- /public/svg_icons/notes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /electron/main.js: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from "electron"; 2 | import path from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = path.dirname(__filename); 7 | 8 | let mainWindow; 9 | let splashWindow; 10 | 11 | function createWindow() { 12 | // Splash screen window 13 | splashWindow = new BrowserWindow({ 14 | width: 600, 15 | height: 600, 16 | frame: false, 17 | alwaysOnTop: true, 18 | transparent: true, 19 | resizable: false, 20 | }); 21 | 22 | splashWindow.loadURL(`file://${path.join(__dirname, "../public/splash.html")}`); 23 | 24 | // Main window 25 | mainWindow = new BrowserWindow({ 26 | width: 600, 27 | height: 600, 28 | icon: path.join(__dirname, "../public/icon.icns"), 29 | frame: false, 30 | resizable: false, 31 | transparent: true, 32 | backgroundColor: "#151515", 33 | webPreferences: { 34 | nodeIntegration: true, 35 | contextIsolation: false, 36 | }, 37 | }); 38 | 39 | const startURL = app.isPackaged 40 | ? `file://${path.join(__dirname, "../dist/index.html")}` 41 | : "http://localhost:3005"; 42 | 43 | mainWindow.loadURL(startURL); 44 | 45 | mainWindow.once("ready-to-show", () => { 46 | // Close splash and show main window 47 | if (splashWindow) { 48 | splashWindow.close(); 49 | } 50 | mainWindow.show(); 51 | }); 52 | } 53 | 54 | app.whenReady().then(() => { 55 | createWindow(); 56 | 57 | if (process.platform === "darwin") { 58 | app.dock.setIcon(path.join(__dirname, "public", "icon.icns")); 59 | } 60 | 61 | app.on("activate", () => { 62 | if (BrowserWindow.getAllWindows().length === 0) createWindow(); 63 | }); 64 | }); 65 | 66 | app.on("window-all-closed", () => { 67 | if (process.platform !== "darwin") app.quit(); 68 | }); 69 | -------------------------------------------------------------------------------- /src/utils/readDataFromFile.js: -------------------------------------------------------------------------------- 1 | const fs = window.require("fs"); 2 | const path = window.require("path"); 3 | const os = window.require("os"); 4 | const homeDir = os.homedir(); 5 | const appBaseDir = path.join(homeDir, ".hashnote"); 6 | 7 | export const readToken = () => { 8 | try { 9 | // Get directory 10 | const filePath = path.join(homeDir, ".hashnote", "token.txt"); 11 | 12 | if (!fs.existsSync(appBaseDir)) { 13 | fs.mkdirSync(appBaseDir, { recursive: true }); 14 | } 15 | 16 | // Check if token file exists 17 | if (fs.existsSync(filePath)) { 18 | const token = fs.readFileSync(filePath, "utf-8"); 19 | return token; 20 | } else { 21 | console.warn("Token file does not exist. So created one."); 22 | fs.writeFileSync(filePath, ""); 23 | return ""; 24 | } 25 | } catch (e) { 26 | console.error("Error reading token:", e); 27 | return ""; 28 | } 29 | }; 30 | 31 | export const readUsername = () => { 32 | try { 33 | // Get directory 34 | const filePath = path.join(homeDir, ".hashnote", "username.txt"); 35 | // Check if username file exists 36 | if (fs.existsSync(filePath)) { 37 | const username = fs.readFileSync(filePath, "utf-8"); 38 | return username; 39 | } else { 40 | console.warn("Username file does not exist. So created one."); 41 | fs.writeFileSync(filePath, ""); 42 | return ""; 43 | } 44 | } catch (e) { 45 | console.error("Error reading username:", e); 46 | return ""; 47 | } 48 | }; 49 | 50 | export const readFile = async (filePath) => { 51 | return new Promise((resolve, reject) => { 52 | try { 53 | if (fs.existsSync(filePath)) { 54 | const fileValue = fs.readFileSync(filePath, "utf-8"); 55 | resolve(fileValue); 56 | } else { 57 | console.warn("File does not exist."); 58 | resolve(""); 59 | } 60 | } catch (e) { 61 | console.error("Error reading file:", e); 62 | reject(e); 63 | } 64 | }); 65 | }; 66 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import Topbar from "./components/Topbar"; 3 | import Bottombar from "./components/Bottombar"; 4 | const MarkdownEditor = React.lazy(() => import("./components/MarkdownEditor")); 5 | import { FunctionsProvider } from "./context/FunctionsProvider"; 6 | import { FoldersProvider } from "./context/FoldersProvider"; 7 | import { GithubProvider } from "./context/GithubProvider"; 8 | 9 | function App() { 10 | const [activeFile, setActiveFile] = useState(false); 11 | const bottomRef = useRef(null); 12 | const editorRef = useRef(null); 13 | 14 | useEffect(() => { 15 | if (!bottomRef.current || !editorRef.current) return; 16 | 17 | if (activeFile) { 18 | bottomRef.current.style.opacity = 0; 19 | editorRef.current.style.display = "block"; 20 | 21 | setTimeout(() => { 22 | bottomRef.current.style.display = "none"; 23 | editorRef.current.style.opacity = 1; 24 | }, 200); 25 | } else { 26 | editorRef.current.style.opacity = 0; 27 | setTimeout(() => { 28 | editorRef.current.style.display = "none"; 29 | bottomRef.current.style.display = "block"; 30 | setTimeout(() => { 31 | bottomRef.current.style.opacity = 1; 32 | }, 10); 33 | }, 190); 34 | } 35 | }, [activeFile]); 36 | 37 | return ( 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 | 46 |
47 | 52 |
53 |
54 |
55 |
56 | ); 57 | } 58 | 59 | export default App; 60 | -------------------------------------------------------------------------------- /src/context/FoldersProvider.jsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import { FoldersContext } from "./context"; 3 | 4 | export const FoldersProvider = ({ children }) => { 5 | const [folders, setFolders] = useState({ 6 | Notes: { 7 | title: "Notes", 8 | icon: "./svg_icons/notes.svg", 9 | data: [], 10 | }, 11 | Habits: { 12 | title: "Habits", 13 | icon: "./svg_icons/habits.svg", 14 | data: [], 15 | }, 16 | Codes: { 17 | title: "Codes", 18 | icon: "./svg_icons/codes.svg", 19 | data: [], 20 | }, 21 | }); 22 | 23 | const loadFilesFromDisk = () => { 24 | setFolders((prev) => { 25 | const fs = window.require("fs"); 26 | const path = window.require("path"); 27 | const os = window.require("os"); 28 | 29 | const homeDir = os.homedir(); 30 | const appBaseDir = path.join(homeDir, ".hashnote"); 31 | const updated = { ...prev }; 32 | 33 | Object.keys(updated).forEach((folderKey) => { 34 | const directory = path.join(appBaseDir, folderKey.toLowerCase()); 35 | 36 | if (!fs.existsSync(directory)) { 37 | fs.mkdirSync(directory, { recursive: true }); 38 | } 39 | 40 | const files = fs.readdirSync(directory); 41 | 42 | const filesWithStats = files.map((fileName) => { 43 | const fullPath = path.join(directory, fileName); 44 | const stats = fs.statSync(fullPath); // get file metadata 45 | 46 | return { 47 | title: fileName.split(".")[0].split("-").join(" "), 48 | path: fullPath, 49 | createdAt: stats.birthtime, // or stats.ctime 50 | }; 51 | }); 52 | 53 | // Sort newest first 54 | filesWithStats.sort((a, b) => b.createdAt - a.createdAt); 55 | 56 | updated[folderKey].data = filesWithStats; 57 | }); 58 | 59 | return updated; 60 | }); 61 | }; 62 | 63 | return ( 64 | 65 | {children} 66 | 67 | ); 68 | }; 69 | 70 | export const useFolders = () => { 71 | return useContext(FoldersContext); 72 | }; 73 | -------------------------------------------------------------------------------- /src/components/ShowCase.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { handleCreateNewFile, handleDeleteFile } from "../utils/handlers"; 3 | import { useFolders } from "../context/FoldersProvider"; 4 | import { useFunctions } from "../context/FunctionsProvider"; 5 | 6 | const ShowCase = ({ data, setActiveFile }) => { 7 | const { loadFilesFromDisk } = useFolders(); 8 | const [datas, setDatas] = useState([]); 9 | const [opacity, setOpacity] = useState(1); 10 | const { setFilePath } = useFunctions(); 11 | useEffect(() => { 12 | setOpacity(0); 13 | const timeout = setTimeout(() => { 14 | setDatas(data); 15 | setOpacity(1); 16 | }, 150); 17 | return () => clearTimeout(timeout); 18 | }, [data]); 19 | 20 | return ( 21 |
27 |
28 |
29 |

{datas.title}

30 | {datas.title && } 31 |
32 |
33 | 39 |
40 |
41 |
42 | {datas.data?.map((note, idx) => ( 43 |
{ 47 | setActiveFile(true); 48 | setFilePath(""); 49 | setTimeout(() => { 50 | setFilePath(note.path); 51 | }, 0); 52 | }} 53 | > 54 | 55 |
56 |

{note.title}

57 | { 63 | e.stopPropagation(); 64 | handleDeleteFile(note, idx, e, datas, loadFilesFromDisk); 65 | }} 66 | /> 67 |
68 |
69 | ))} 70 |
71 |
72 | ); 73 | }; 74 | 75 | export default ShowCase; 76 | -------------------------------------------------------------------------------- /src/components/SettingsMenu.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { handleSaveToFile } from "../utils/handlers"; 3 | import { useGithub } from "../context/GithubProvider"; 4 | const shell = window.require("electron").shell; 5 | 6 | const SettingsMenu = ({ active, setActive }) => { 7 | const { username, setUsername, token, setToken } = useGithub(); 8 | const [fieldValues, setFieldValues] = useState({ username, token }); 9 | const handleOnChange = (name, value) => { 10 | setFieldValues((prev) => ({ ...prev, [name]: value })); 11 | }; 12 | 13 | const path = window.require("path"); 14 | const os = window.require("os"); 15 | 16 | const homeDir = os.homedir(); // ~/ 17 | const appBaseDir = path.join(homeDir, ".hashnote"); // ~/.hashnote 18 | 19 | const handleClose = () => { 20 | setActive(!active); 21 | // Username saving 22 | const usernamePath = path.join(appBaseDir, "username.txt"); 23 | setUsername(fieldValues.username); 24 | handleSaveToFile(usernamePath, fieldValues.username); 25 | // Token saving 26 | const tokenPath = path.join(appBaseDir, "token.txt"); 27 | setToken(fieldValues.token); 28 | handleSaveToFile(tokenPath, fieldValues.token); 29 | }; 30 | 31 | return ( 32 |
36 |
37 |

Settings

38 | 41 |
42 |
43 |
44 |

Github Username

45 | handleOnChange("username", e.target.value)} 49 | value={fieldValues.username} 50 | /> 51 |
52 |
53 |

Github Token

54 |