commit 794d2125a595c9538ed097b726ad1141918ea82c Author: amitwh Date: Mon Sep 1 19:19:45 2025 +0530 Initial commit: Pan Converter - Cross-platform Markdown editor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bff97e --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +node_modules/ +dist/ +*.log +.DS_Store +Thumbs.db +.env +.env.local +*.swp +*.swo +*~ +.vscode/ +.idea/ +*.iml +out/ +.cache/ +.npm/ +.electron/ +package-lock.json \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6d928cc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,189 @@ +# Pan Converter + +## Overview +Pan Converter is a cross-platform desktop application for editing and converting Markdown files using Pandoc. It features a rich markdown editor with live preview, multiple themes, and support for exporting to various formats. + +## Features +- **Markdown Editor**: Full-featured editor with syntax highlighting +- **Live Preview**: Real-time preview of your markdown content +- **Multiple Themes**: Light, Dark, Solarized, Monokai, and GitHub themes +- **Format Conversion**: Export to HTML, PDF, DOCX, LaTeX, RTF, ODT, and EPUB using Pandoc +- **Cross-Platform**: Runs on Windows, macOS, and Linux +- **Auto-Save**: Automatic saving every 30 seconds + +## Requirements +- **Pandoc**: Must be installed on the system for export functionality + - Ubuntu/Debian: `sudo apt-get install pandoc` + - macOS: `brew install pandoc` + - Windows: Download from https://pandoc.org/installing.html + +## Development Setup + +### Prerequisites +- Node.js (v14 or higher) +- npm or yarn +- Pandoc (for export features) + +### Installation +```bash +# Clone the repository +git clone +cd pan-converter + +# Install dependencies +npm install + +# Generate icons +npm run generate-icons + +# Start the application +npm start +``` + +## Building + +### Build for Current Platform +```bash +npm run build +``` + +### Build for Specific Platforms +```bash +# Windows +npm run build:win + +# macOS +npm run build:mac + +# Linux (generates .deb, .AppImage, and .snap) +npm run build:linux +``` + +### Build for All Platforms +```bash +npm run dist:all +``` + +## Project Structure +``` +pan-converter/ +├── src/ +│ ├── main.js # Main process +│ ├── renderer.js # Renderer process +│ ├── index.html # Main window HTML +│ └── styles.css # Application styles +├── assets/ +│ ├── icon.svg # Source icon +│ └── icon.png # Generated icons +├── scripts/ +│ └── generate-icons.js # Icon generation script +├── package.json # Project configuration +└── CLAUDE.md # This file +``` + +## Architecture + +### Main Process (`src/main.js`) +- Manages application lifecycle +- Creates and manages windows +- Handles file operations +- Manages menu bar +- Communicates with renderer via IPC + +### Renderer Process (`src/renderer.js`) +- Handles UI interactions +- Manages editor state +- Renders markdown preview +- Handles theme switching +- Communicates with main process via IPC + +### Key Libraries +- **Electron**: Desktop application framework +- **marked**: Markdown parsing +- **highlight.js**: Syntax highlighting +- **DOMPurify**: HTML sanitization +- **electron-store**: Persistent storage +- **electron-builder**: Application packaging + +## Features Implementation + +### Markdown Editor +- Uses native textarea with custom styling +- Supports common markdown shortcuts via toolbar +- Tab key inserts 4 spaces for code indentation +- Auto-save every 30 seconds when content changes + +### Live Preview +- Updates in real-time as you type +- Sanitized HTML output for security +- Syntax highlighting for code blocks +- GitHub-flavored markdown support + +### Theme System +- Themes stored in user preferences +- Applied via CSS classes on body element +- Persists across application restarts +- Five built-in themes + +### Export System +- Uses Pandoc command-line tool +- Supports multiple output formats +- Maintains original file structure +- Shows error messages if Pandoc not installed + +## Platform-Specific Branches + +The repository maintains separate branches for platform-specific customizations: +- `master`: Main development branch +- `linux`: Linux-specific configurations +- `macos`: macOS-specific configurations +- `windows`: Windows-specific configurations + +## Debian Package + +The application can be built as a `.deb` package for Debian-based systems: +```bash +npm run build:linux +``` + +The generated `.deb` file will: +- Install to `/opt/pan-converter` +- Create desktop entry for application menu +- Set up proper file associations +- Declare Pandoc as dependency + +## Testing + +### Manual Testing +1. File Operations: New, Open, Save, Save As +2. Editor Features: Bold, Italic, Headings, Links, Code, Lists, Quotes +3. Preview Toggle: Show/hide preview pane +4. Theme Switching: Test all five themes +5. Export Functions: Test each export format +6. Auto-save: Verify 30-second auto-save works + +### Platform Testing +Test on: +- Windows 10/11 +- macOS 11+ (Big Sur and later) +- Ubuntu 20.04+, Debian 11+ + +## Known Issues +- ICO and ICNS icons need manual conversion from PNG +- Pandoc must be installed separately +- Large files may cause performance issues + +## Future Enhancements +- [ ] Add spell checking +- [ ] Implement find and replace +- [ ] Add custom CSS for preview +- [ ] Support for markdown extensions +- [ ] Plugin system for custom exporters +- [ ] Cloud sync capabilities +- [ ] Collaborative editing + +## License +MIT License + +## Support +For issues or feature requests, please open an issue on the GitHub repository. \ No newline at end of file diff --git a/PUSH_INSTRUCTIONS.md b/PUSH_INSTRUCTIONS.md new file mode 100644 index 0000000..88ef9f0 --- /dev/null +++ b/PUSH_INSTRUCTIONS.md @@ -0,0 +1,95 @@ +# Push Instructions for Pan Converter + +## Prerequisites +1. Create a new repository on GitHub: https://github.com/new + - Repository name: `pan-converter` + - Keep it empty (no README, .gitignore, or license) + - Make it public or private as desired + +2. Set up authentication: + - **Option A - SSH Key** (Recommended): + ```bash + # Check if you have SSH key + ls -la ~/.ssh/id_rsa.pub + + # If not, generate one: + ssh-keygen -t rsa -b 4096 -C "your_email@example.com" + + # Add to GitHub: Settings > SSH and GPG keys > New SSH key + cat ~/.ssh/id_rsa.pub + ``` + + - **Option B - Personal Access Token**: + - Go to GitHub Settings > Developer settings > Personal access tokens + - Generate new token with 'repo' scope + - Save the token securely + +## Push Commands + +### Using SSH (Recommended) +```bash +# Set SSH remote +git remote set-url origin git@github.com:amitwh/pan-converter.git + +# Push all branches +git push -u origin master +git push origin linux +git push origin macos +git push origin windows +``` + +### Using HTTPS with Token +```bash +# Set HTTPS remote +git remote set-url origin https://github.com/amitwh/pan-converter.git + +# Push all branches (you'll be prompted for username and token) +git push -u origin master +git push origin linux +git push origin macos +git push origin windows + +# When prompted: +# Username: amitwh +# Password: [paste your Personal Access Token] +``` + +### Using GitHub CLI (if installed) +```bash +# Login to GitHub CLI +gh auth login + +# Create repo and push +gh repo create pan-converter --public --source=. --remote=origin --push +``` + +## Automated Script +```bash +# Run the provided script +./push-to-github.sh +``` + +## Verify Success +After pushing, verify at: https://github.com/amitwh/pan-converter + +You should see: +- 4 branches (master, linux, macos, windows) +- All project files +- Complete commit history + +## Troubleshooting + +### Authentication Failed +- For SSH: Ensure your SSH key is added to GitHub +- For HTTPS: Use Personal Access Token, not password +- Check token has 'repo' scope + +### Repository Not Found +- Ensure repository exists on GitHub +- Check spelling: `pan-converter` +- Verify you're logged into the correct GitHub account + +### Permission Denied +- Check repository ownership +- Ensure you have write access +- Verify authentication method is correct \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7273e3 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Pan Converter + +A cross-platform Markdown editor and converter powered by Pandoc. + +![Pan Converter](assets/icon.png) + +## Features + +- 📝 **Rich Markdown Editor** - Full-featured editor with syntax highlighting +- 👁️ **Live Preview** - See your markdown rendered in real-time +- 🎨 **Multiple Themes** - Choose from Light, Dark, Solarized, Monokai, or GitHub themes +- 📤 **Export to Multiple Formats** - Convert to HTML, PDF, DOCX, LaTeX, RTF, ODT, and EPUB +- 💾 **Auto-Save** - Never lose your work with automatic saving +- 🖥️ **Cross-Platform** - Works on Windows, macOS, and Linux + +## Installation + +### Prerequisites +- [Pandoc](https://pandoc.org/installing.html) must be installed for export functionality + +### Download +Download the latest release for your platform from the [Releases](https://github.com/yourusername/pan-converter/releases) page. + +### Install from Source +```bash +git clone https://github.com/yourusername/pan-converter.git +cd pan-converter +npm install +npm start +``` + +## Usage + +1. **Write** - Use the editor to write your Markdown content +2. **Preview** - Toggle the preview pane to see rendered output +3. **Theme** - Choose your preferred theme from the View menu +4. **Export** - Export your document to various formats via File > Export + +## Keyboard Shortcuts + +- `Ctrl/Cmd + N` - New file +- `Ctrl/Cmd + O` - Open file +- `Ctrl/Cmd + S` - Save file +- `Ctrl/Cmd + Shift + S` - Save as +- `Ctrl/Cmd + P` - Toggle preview +- `Ctrl/Cmd + Enter` - Toggle preview (alternative) + +## Building + +```bash +# Build for current platform +npm run build + +# Build for specific platform +npm run build:win +npm run build:mac +npm run build:linux + +# Build for all platforms +npm run dist:all +``` + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +MIT License - see LICENSE file for details + +## Acknowledgments + +- Built with [Electron](https://www.electronjs.org/) +- Markdown parsing by [marked](https://marked.js.org/) +- Export functionality powered by [Pandoc](https://pandoc.org/) \ No newline at end of file diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..68324cd Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/icon.svg b/assets/icon.svg new file mode 100644 index 0000000..5790d80 --- /dev/null +++ b/assets/icon.svg @@ -0,0 +1,8 @@ + + + + + + + PAN + \ No newline at end of file diff --git a/assets/icon@2x.png b/assets/icon@2x.png new file mode 100644 index 0000000..f4cd1ac Binary files /dev/null and b/assets/icon@2x.png differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..9275336 --- /dev/null +++ b/package.json @@ -0,0 +1,69 @@ +{ + "name": "pan-converter", + "version": "1.0.0", + "description": "Cross-platform Markdown editor and converter using Pandoc", + "main": "src/main.js", + "scripts": { + "start": "electron .", + "test": "echo \"Error: no test specified\" && exit 1", + "build": "electron-builder", + "build:win": "electron-builder --win", + "build:mac": "electron-builder --mac", + "build:linux": "electron-builder --linux", + "dist": "electron-builder --publish=never", + "dist:all": "electron-builder -mwl", + "generate-icons": "node scripts/generate-icons.js" + }, + "keywords": ["markdown", "pandoc", "converter", "editor"], + "author": "Your Name", + "license": "MIT", + "devDependencies": { + "electron": "^37.4.0", + "electron-builder": "^26.0.12", + "sharp": "^0.34.3" + }, + "dependencies": { + "codemirror": "^6.0.2", + "dompurify": "^3.2.6", + "electron-store": "^10.1.0", + "highlight.js": "^11.11.1", + "marked": "^16.2.1" + }, + "build": { + "appId": "com.panconverter.app", + "productName": "Pan Converter", + "directories": { + "output": "dist" + }, + "files": [ + "src/**/*", + "assets/**/*", + "node_modules/**/*", + "package.json" + ], + "mac": { + "category": "public.app-category.productivity", + "icon": "assets/icon.icns" + }, + "win": { + "target": "nsis", + "icon": "assets/icon.ico" + }, + "linux": { + "target": [ + "deb", + "AppImage", + "snap" + ], + "category": "Utility", + "icon": "assets/icon.png" + }, + "deb": { + "depends": [ + "pandoc" + ], + "description": "Markdown editor and converter using Pandoc", + "maintainer": "Your Name " + } + } +} \ No newline at end of file diff --git a/push-to-github.sh b/push-to-github.sh new file mode 100755 index 0000000..db22e4f --- /dev/null +++ b/push-to-github.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +echo "==========================================" +echo "Pan Converter - GitHub Push Helper" +echo "==========================================" +echo "" +echo "Before running this script, please ensure:" +echo "1. You have created a repository named 'pan-converter' on GitHub" +echo "2. The repository is empty (no README, .gitignore, or license)" +echo "3. You have set up authentication (SSH key or Personal Access Token)" +echo "" +read -p "Have you completed the above steps? (y/n): " -n 1 -r +echo "" + +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Please complete the setup first:" + echo "1. Go to https://github.com/new" + echo "2. Create a new repository named 'pan-converter'" + echo "3. DO NOT initialize with README, .gitignore, or license" + echo "4. Set up SSH key or Personal Access Token for authentication" + exit 1 +fi + +echo "" +echo "Choose authentication method:" +echo "1. SSH (Recommended if you have SSH keys set up)" +echo "2. HTTPS (Requires Personal Access Token)" +read -p "Enter your choice (1 or 2): " AUTH_CHOICE + +if [ "$AUTH_CHOICE" = "1" ]; then + echo "Using SSH authentication..." + git remote set-url origin git@github.com:amitwh/pan-converter.git +elif [ "$AUTH_CHOICE" = "2" ]; then + echo "Using HTTPS authentication..." + echo "You'll need to enter your GitHub username and Personal Access Token" + git remote set-url origin https://github.com/amitwh/pan-converter.git +else + echo "Invalid choice. Exiting." + exit 1 +fi + +echo "" +echo "Pushing branches to GitHub..." +echo "==============================" + +# Push master branch with upstream tracking +echo "Pushing master branch..." +if git push -u origin master; then + echo "✓ Master branch pushed successfully" +else + echo "✗ Failed to push master branch" + echo "Please check your authentication and try again" + exit 1 +fi + +# Push other branches +echo "Pushing linux branch..." +if git push origin linux; then + echo "✓ Linux branch pushed successfully" +else + echo "✗ Failed to push linux branch" +fi + +echo "Pushing macos branch..." +if git push origin macos; then + echo "✓ macOS branch pushed successfully" +else + echo "✗ Failed to push macos branch" +fi + +echo "Pushing windows branch..." +if git push origin windows; then + echo "✓ Windows branch pushed successfully" +else + echo "✗ Failed to push windows branch" +fi + +echo "" +echo "==========================================" +echo "Push complete!" +echo "Your repository is available at:" +echo "https://github.com/amitwh/pan-converter" +echo "==========================================" \ No newline at end of file diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js new file mode 100644 index 0000000..46d1f69 --- /dev/null +++ b/scripts/generate-icons.js @@ -0,0 +1,43 @@ +const sharp = require('sharp'); +const fs = require('fs'); +const path = require('path'); + +const sizes = { + 'icon.png': 512, + 'icon@2x.png': 1024, + 'icon.ico': 256, + 'icon.icns': 512 +}; + +const svgPath = path.join(__dirname, '..', 'assets', 'icon.svg'); +const svgBuffer = fs.readFileSync(svgPath); + +async function generateIcons() { + for (const [filename, size] of Object.entries(sizes)) { + const outputPath = path.join(__dirname, '..', 'assets', filename); + + if (filename.endsWith('.png')) { + await sharp(svgBuffer) + .resize(size, size) + .png() + .toFile(outputPath); + console.log(`Generated ${filename}`); + } else if (filename.endsWith('.ico')) { + // For ICO, we'll just use the PNG version + await sharp(svgBuffer) + .resize(size, size) + .png() + .toFile(outputPath.replace('.ico', '.png')); + console.log(`Generated ${filename.replace('.ico', '.png')} (use png2ico to convert)`); + } else if (filename.endsWith('.icns')) { + // For ICNS, we'll just use the PNG version + await sharp(svgBuffer) + .resize(size, size) + .png() + .toFile(outputPath.replace('.icns', '.png')); + console.log(`Generated ${filename.replace('.icns', '.png')} (use png2icns to convert)`); + } + } +} + +generateIcons().catch(console.error); \ No newline at end of file diff --git a/setup-upstream.sh b/setup-upstream.sh new file mode 100755 index 0000000..5fe3e5b --- /dev/null +++ b/setup-upstream.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Pan Converter - Git Remote Setup Script +# +# Instructions: +# 1. Create a new repository on GitHub named "pan-converter" +# 2. Replace YOUR_GITHUB_USERNAME with your actual GitHub username +# 3. Run this script: bash setup-upstream.sh + +GITHUB_USERNAME="amitwh" +REPO_NAME="pan-converter" + +echo "Setting up remote repository..." + +# Add remote origin +git remote add origin "https://github.com/$GITHUB_USERNAME/$REPO_NAME.git" + +echo "Pushing all branches to remote..." + +# Push master branch +git push -u origin master + +# Push platform-specific branches +git push origin linux +git push origin macos +git push origin windows + +echo "Repository setup complete!" +echo "" +echo "Your repository is now available at:" +echo "https://github.com/$GITHUB_USERNAME/$REPO_NAME" +echo "" +echo "Branch structure:" +echo " - master (main development)" +echo " - linux (Linux-specific)" +echo " - macos (macOS-specific)" +echo " - windows (Windows-specific)" diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..1d6882c --- /dev/null +++ b/src/index.html @@ -0,0 +1,88 @@ + + + + + + Pan Converter - Markdown Editor + + + + + +
+
+ + + + + + + +
+ +
+ +
+
+ +
+
+
+
+
+ +
+ Ready + Words: 0 | Characters: 0 +
+
+ + + + \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..7f3a9a7 --- /dev/null +++ b/src/main.js @@ -0,0 +1,242 @@ +const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron'); +const path = require('path'); +const fs = require('fs'); +const { exec } = require('child_process'); +const Store = require('electron-store'); + +const store = new Store(); + +let mainWindow; +let currentFile = null; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + webPreferences: { + nodeIntegration: true, + contextIsolation: false + }, + icon: path.join(__dirname, '../assets/icon.png') + }); + + mainWindow.loadFile(path.join(__dirname, 'index.html')); + + createMenu(); + + mainWindow.on('closed', () => { + mainWindow = null; + }); +} + +function createMenu() { + const template = [ + { + label: 'File', + submenu: [ + { + label: 'New', + accelerator: 'CmdOrCtrl+N', + click: () => mainWindow.webContents.send('file-new') + }, + { + label: 'Open', + accelerator: 'CmdOrCtrl+O', + click: openFile + }, + { + label: 'Save', + accelerator: 'CmdOrCtrl+S', + click: () => mainWindow.webContents.send('file-save') + }, + { + label: 'Save As', + accelerator: 'CmdOrCtrl+Shift+S', + click: saveAsFile + }, + { type: 'separator' }, + { + label: 'Export', + submenu: [ + { label: 'HTML', click: () => exportFile('html') }, + { label: 'PDF', click: () => exportFile('pdf') }, + { label: 'DOCX', click: () => exportFile('docx') }, + { label: 'LaTeX', click: () => exportFile('latex') }, + { label: 'RTF', click: () => exportFile('rtf') }, + { label: 'ODT', click: () => exportFile('odt') }, + { label: 'EPUB', click: () => exportFile('epub') } + ] + }, + { type: 'separator' }, + { + label: 'Quit', + accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q', + click: () => app.quit() + } + ] + }, + { + label: 'Edit', + submenu: [ + { label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, + { label: 'Redo', accelerator: 'CmdOrCtrl+Y', role: 'redo' }, + { type: 'separator' }, + { label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' }, + { label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' }, + { label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' }, + { label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' } + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Toggle Preview', + accelerator: 'CmdOrCtrl+P', + click: () => mainWindow.webContents.send('toggle-preview') + }, + { + label: 'Theme', + submenu: [ + { label: 'Light', click: () => setTheme('light') }, + { label: 'Dark', click: () => setTheme('dark') }, + { label: 'Solarized', click: () => setTheme('solarized') }, + { label: 'Monokai', click: () => setTheme('monokai') }, + { label: 'GitHub', click: () => setTheme('github') } + ] + }, + { type: 'separator' }, + { label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' }, + { label: 'Toggle DevTools', accelerator: 'F12', role: 'toggleDevTools' }, + { type: 'separator' }, + { label: 'Zoom In', accelerator: 'CmdOrCtrl+Plus', role: 'zoomIn' }, + { label: 'Zoom Out', accelerator: 'CmdOrCtrl+-', role: 'zoomOut' }, + { label: 'Reset Zoom', accelerator: 'CmdOrCtrl+0', role: 'resetZoom' } + ] + }, + { + label: 'Help', + submenu: [ + { + label: 'About', + click: () => { + dialog.showMessageBox(mainWindow, { + type: 'info', + title: 'About Pan Converter', + message: 'Pan Converter', + detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.0.0\nLicense: MIT', + buttons: ['OK'] + }); + } + }, + { + label: 'Documentation', + click: () => shell.openExternal('https://github.com/yourusername/pan-converter') + } + ] + } + ]; + + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +} + +function openFile() { + const files = dialog.showOpenDialogSync(mainWindow, { + properties: ['openFile'], + filters: [ + { name: 'Markdown', extensions: ['md', 'markdown'] }, + { name: 'All Files', extensions: ['*'] } + ] + }); + + if (files && files[0]) { + currentFile = files[0]; + const content = fs.readFileSync(currentFile, 'utf-8'); + mainWindow.webContents.send('file-opened', { path: currentFile, content }); + } +} + +function saveAsFile() { + const file = dialog.showSaveDialogSync(mainWindow, { + defaultExt: '.md', + filters: [ + { name: 'Markdown', extensions: ['md', 'markdown'] }, + { name: 'All Files', extensions: ['*'] } + ] + }); + + if (file) { + currentFile = file; + mainWindow.webContents.send('get-content-for-save', file); + } +} + +function exportFile(format) { + if (!currentFile) { + dialog.showErrorBox('Error', 'Please save the file first'); + return; + } + + const outputFile = dialog.showSaveDialogSync(mainWindow, { + defaultPath: currentFile.replace(/\.[^/.]+$/, `.${format}`), + filters: [ + { name: format.toUpperCase(), extensions: [format] } + ] + }); + + if (outputFile) { + const pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`; + + exec(pandocCmd, (error, stdout, stderr) => { + if (error) { + dialog.showErrorBox('Export Error', `Failed to export: ${error.message}\n\nMake sure Pandoc is installed.`); + } else { + dialog.showMessageBox(mainWindow, { + type: 'info', + title: 'Export Complete', + message: `File exported successfully to ${outputFile}`, + buttons: ['OK'] + }); + } + }); + } +} + +function setTheme(theme) { + store.set('theme', theme); + mainWindow.webContents.send('theme-changed', theme); +} + +// IPC handlers +ipcMain.on('save-file', (event, { path, content }) => { + fs.writeFileSync(path, content, 'utf-8'); + currentFile = path; +}); + +ipcMain.on('save-current-file', (event, content) => { + if (currentFile) { + fs.writeFileSync(currentFile, content, 'utf-8'); + } else { + saveAsFile(); + } +}); + +ipcMain.on('get-theme', (event) => { + const theme = store.get('theme', 'light'); + event.reply('theme-changed', theme); +}); + +app.whenReady().then(createWindow); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } +}); \ No newline at end of file diff --git a/src/renderer.js b/src/renderer.js new file mode 100644 index 0000000..6fb247a --- /dev/null +++ b/src/renderer.js @@ -0,0 +1,218 @@ +const { ipcRenderer } = require('electron'); +const marked = require('marked'); +const DOMPurify = require('dompurify'); +const hljs = require('highlight.js'); + +// Configure marked +marked.setOptions({ + highlight: function(code, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(code, { language: lang }).value; + } catch (err) {} + } + return hljs.highlightAuto(code).value; + }, + breaks: true, + gfm: true +}); + +// Elements +const editor = document.getElementById('editor'); +const preview = document.getElementById('preview'); +const previewPane = document.getElementById('preview-pane'); +const editorPane = document.getElementById('editor-pane'); +const statusText = document.getElementById('status-text'); +const wordCount = document.getElementById('word-count'); + +// State +let isPreviewVisible = true; +let currentContent = ''; +let isDirty = false; + +// Initialize +document.addEventListener('DOMContentLoaded', () => { + // Request current theme + ipcRenderer.send('get-theme'); + + // Set up auto-save interval + setInterval(autoSave, 30000); // Auto-save every 30 seconds + + // Initialize with empty content + updatePreview(); + updateWordCount(); +}); + +// Editor input handler +editor.addEventListener('input', () => { + currentContent = editor.value; + isDirty = true; + updatePreview(); + updateWordCount(); + updateStatus('Modified'); +}); + +// Toolbar button handlers +document.getElementById('btn-bold').addEventListener('click', () => insertMarkdown('**', '**')); +document.getElementById('btn-italic').addEventListener('click', () => insertMarkdown('*', '*')); +document.getElementById('btn-heading').addEventListener('click', () => insertMarkdown('## ', '')); +document.getElementById('btn-link').addEventListener('click', () => insertMarkdown('[', '](url)')); +document.getElementById('btn-code').addEventListener('click', () => insertMarkdown('`', '`')); +document.getElementById('btn-list').addEventListener('click', () => insertMarkdown('- ', '')); +document.getElementById('btn-quote').addEventListener('click', () => insertMarkdown('> ', '')); +document.getElementById('btn-preview-toggle').addEventListener('click', togglePreview); + +// Markdown insertion helper +function insertMarkdown(before, after) { + const start = editor.selectionStart; + const end = editor.selectionEnd; + const selectedText = editor.value.substring(start, end); + const replacement = before + (selectedText || 'text') + after; + + editor.value = editor.value.substring(0, start) + replacement + editor.value.substring(end); + + // Set cursor position + if (selectedText) { + editor.selectionStart = start; + editor.selectionEnd = start + replacement.length; + } else { + editor.selectionStart = start + before.length; + editor.selectionEnd = start + before.length + 4; // Select "text" + } + + editor.focus(); + + // Trigger input event + editor.dispatchEvent(new Event('input')); +} + +// Update preview +function updatePreview() { + const html = marked.parse(editor.value); + const clean = DOMPurify.sanitize(html); + preview.innerHTML = clean; + + // Re-highlight code blocks + preview.querySelectorAll('pre code').forEach((block) => { + hljs.highlightElement(block); + }); +} + +// Toggle preview visibility +function togglePreview() { + isPreviewVisible = !isPreviewVisible; + + if (isPreviewVisible) { + previewPane.classList.remove('hidden'); + editorPane.classList.remove('full-width'); + } else { + previewPane.classList.add('hidden'); + editorPane.classList.add('full-width'); + } +} + +// Update word count +function updateWordCount() { + const text = editor.value; + const words = text.trim() ? text.trim().split(/\s+/).length : 0; + const chars = text.length; + wordCount.textContent = `Words: ${words} | Characters: ${chars}`; +} + +// Update status +function updateStatus(text) { + statusText.textContent = text; +} + +// Auto-save function +function autoSave() { + if (isDirty && currentContent) { + ipcRenderer.send('save-current-file', currentContent); + isDirty = false; + updateStatus('Auto-saved'); + setTimeout(() => updateStatus('Ready'), 2000); + } +} + +// IPC event handlers +ipcRenderer.on('file-new', () => { + if (isDirty) { + if (confirm('You have unsaved changes. Do you want to continue?')) { + editor.value = ''; + currentContent = ''; + isDirty = false; + updatePreview(); + updateWordCount(); + updateStatus('New file'); + } + } else { + editor.value = ''; + currentContent = ''; + updatePreview(); + updateWordCount(); + updateStatus('New file'); + } +}); + +ipcRenderer.on('file-opened', (event, { path, content }) => { + editor.value = content; + currentContent = content; + isDirty = false; + updatePreview(); + updateWordCount(); + updateStatus(`Opened: ${path}`); +}); + +ipcRenderer.on('file-save', () => { + ipcRenderer.send('save-current-file', editor.value); + isDirty = false; + updateStatus('Saved'); +}); + +ipcRenderer.on('get-content-for-save', (event, path) => { + ipcRenderer.send('save-file', { path, content: editor.value }); + isDirty = false; + updateStatus(`Saved: ${path}`); +}); + +ipcRenderer.on('toggle-preview', () => { + togglePreview(); +}); + +ipcRenderer.on('theme-changed', (event, theme) => { + // Remove all theme classes + document.body.classList.remove('theme-light', 'theme-dark', 'theme-solarized', 'theme-monokai', 'theme-github'); + + // Add new theme class + if (theme !== 'light') { + document.body.classList.add(`theme-${theme}`); + } + + updateStatus(`Theme: ${theme}`); +}); + +// Keyboard shortcuts +document.addEventListener('keydown', (e) => { + // Ctrl/Cmd + Enter to toggle preview + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + togglePreview(); + } + + // Tab key in editor + if (e.key === 'Tab' && e.target === editor) { + e.preventDefault(); + const start = editor.selectionStart; + const end = editor.selectionEnd; + + editor.value = editor.value.substring(0, start) + ' ' + editor.value.substring(end); + editor.selectionStart = editor.selectionEnd = start + 4; + } +}); + +// Prevent accidental navigation +window.addEventListener('beforeunload', (e) => { + if (isDirty) { + e.preventDefault(); + e.returnValue = ''; + } +}); \ No newline at end of file diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..04004b1 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,462 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + overflow: hidden; + height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + height: 100vh; +} + +/* Toolbar */ +.toolbar { + display: flex; + align-items: center; + padding: 8px 12px; + background: #f5f5f5; + border-bottom: 1px solid #ddd; + gap: 4px; +} + +.toolbar button { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: 1px solid transparent; + background: transparent; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; +} + +.toolbar button:hover { + background: #e0e0e0; + border-color: #ccc; +} + +.toolbar button:active { + background: #d0d0d0; +} + +.toolbar-separator { + width: 1px; + height: 24px; + background: #ccc; + margin: 0 8px; +} + +/* Editor Container */ +.editor-container { + display: flex; + flex: 1; + overflow: hidden; +} + +.pane { + flex: 1; + overflow: auto; +} + +#editor-pane { + border-right: 1px solid #ddd; +} + +#editor { + width: 100%; + height: 100%; + padding: 20px; + font-family: 'SF Mono', Monaco, 'Courier New', monospace; + font-size: 14px; + line-height: 1.6; + border: none; + outline: none; + resize: none; +} + +#preview-pane { + padding: 20px; + background: #fff; +} + +#preview { + max-width: 800px; + margin: 0 auto; +} + +/* Status Bar */ +.status-bar { + display: flex; + justify-content: space-between; + padding: 4px 12px; + background: #f5f5f5; + border-top: 1px solid #ddd; + font-size: 12px; + color: #666; +} + +/* Preview Styles */ +#preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +#preview h1 { + font-size: 2em; + border-bottom: 1px solid #eaecef; + padding-bottom: 0.3em; +} + +#preview h2 { + font-size: 1.5em; + border-bottom: 1px solid #eaecef; + padding-bottom: 0.3em; +} + +#preview p { + margin-bottom: 16px; + line-height: 1.6; +} + +#preview code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27,31,35,0.05); + border-radius: 3px; + font-family: 'SF Mono', Monaco, 'Courier New', monospace; +} + +#preview pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f6f8fa; + border-radius: 3px; + margin-bottom: 16px; +} + +#preview pre code { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +#preview blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; + margin-bottom: 16px; +} + +#preview ul, #preview ol { + padding-left: 2em; + margin-bottom: 16px; +} + +#preview li { + margin-bottom: 4px; +} + +#preview a { + color: #0366d6; + text-decoration: none; +} + +#preview a:hover { + text-decoration: underline; +} + +#preview img { + max-width: 100%; + height: auto; +} + +#preview table { + border-collapse: collapse; + margin-bottom: 16px; + width: 100%; +} + +#preview table th, +#preview table td { + padding: 6px 13px; + border: 1px solid #dfe2e5; +} + +#preview table th { + font-weight: 600; + background-color: #f6f8fa; +} + +#preview table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +/* Theme: Dark */ +body.theme-dark { + background: #1e1e1e; + color: #d4d4d4; +} + +body.theme-dark .toolbar { + background: #2d2d30; + border-bottom-color: #3e3e42; +} + +body.theme-dark .toolbar button { + color: #cccccc; +} + +body.theme-dark .toolbar button:hover { + background: #3e3e42; + border-color: #464647; +} + +body.theme-dark .toolbar-separator { + background: #3e3e42; +} + +body.theme-dark #editor-pane { + border-right-color: #3e3e42; +} + +body.theme-dark #editor { + background: #1e1e1e; + color: #d4d4d4; +} + +body.theme-dark #preview-pane { + background: #252526; +} + +body.theme-dark #preview { + color: #d4d4d4; +} + +body.theme-dark #preview h1, +body.theme-dark #preview h2 { + border-bottom-color: #3e3e42; +} + +body.theme-dark #preview code { + background-color: rgba(255,255,255,0.1); +} + +body.theme-dark #preview pre { + background-color: #2d2d30; +} + +body.theme-dark #preview blockquote { + color: #808080; + border-left-color: #3e3e42; +} + +body.theme-dark #preview a { + color: #569cd6; +} + +body.theme-dark #preview table th, +body.theme-dark #preview table td { + border-color: #3e3e42; +} + +body.theme-dark #preview table th { + background-color: #2d2d30; +} + +body.theme-dark #preview table tr:nth-child(2n) { + background-color: #2d2d30; +} + +body.theme-dark .status-bar { + background: #2d2d30; + border-top-color: #3e3e42; + color: #969696; +} + +/* Theme: Solarized */ +body.theme-solarized { + background: #fdf6e3; + color: #657b83; +} + +body.theme-solarized .toolbar { + background: #eee8d5; + border-bottom-color: #93a1a1; +} + +body.theme-solarized .toolbar button { + color: #586e75; +} + +body.theme-solarized .toolbar button:hover { + background: #fdf6e3; + border-color: #93a1a1; +} + +body.theme-solarized #editor { + background: #fdf6e3; + color: #657b83; +} + +body.theme-solarized #preview { + color: #586e75; +} + +body.theme-solarized #preview code { + background-color: #eee8d5; +} + +body.theme-solarized #preview pre { + background-color: #eee8d5; +} + +body.theme-solarized #preview a { + color: #268bd2; +} + +/* Theme: Monokai */ +body.theme-monokai { + background: #272822; + color: #f8f8f2; +} + +body.theme-monokai .toolbar { + background: #3e3d32; + border-bottom-color: #75715e; +} + +body.theme-monokai .toolbar button { + color: #f8f8f2; +} + +body.theme-monokai .toolbar button:hover { + background: #49483e; + border-color: #75715e; +} + +body.theme-monokai #editor { + background: #272822; + color: #f8f8f2; +} + +body.theme-monokai #preview-pane { + background: #272822; +} + +body.theme-monokai #preview { + color: #f8f8f2; +} + +body.theme-monokai #preview h1, +body.theme-monokai #preview h2 { + border-bottom-color: #75715e; +} + +body.theme-monokai #preview code { + background-color: #3e3d32; +} + +body.theme-monokai #preview pre { + background-color: #3e3d32; +} + +body.theme-monokai #preview blockquote { + color: #75715e; + border-left-color: #75715e; +} + +body.theme-monokai #preview a { + color: #66d9ef; +} + +body.theme-monokai .status-bar { + background: #3e3d32; + border-top-color: #75715e; + color: #75715e; +} + +/* Theme: GitHub */ +body.theme-github { + background: #fff; + color: #24292e; +} + +body.theme-github .toolbar { + background: #fafbfc; + border-bottom-color: #e1e4e8; +} + +body.theme-github .toolbar button { + color: #586069; +} + +body.theme-github .toolbar button:hover { + background: #f6f8fa; + border-color: #e1e4e8; +} + +body.theme-github #editor { + background: #fff; + color: #24292e; +} + +body.theme-github #preview { + color: #24292e; +} + +body.theme-github #preview h1, +body.theme-github #preview h2 { + border-bottom-color: #eaecef; +} + +body.theme-github #preview code { + background-color: rgba(27,31,35,0.05); +} + +body.theme-github #preview pre { + background-color: #f6f8fa; +} + +body.theme-github #preview blockquote { + color: #6a737d; + border-left-color: #dfe2e5; +} + +body.theme-github #preview a { + color: #0366d6; +} + +body.theme-github .status-bar { + background: #fafbfc; + border-top-color: #e1e4e8; + color: #586069; +} + +/* Hide preview pane by default */ +#preview-pane.hidden { + display: none; +} + +#editor-pane.full-width { + border-right: none; +} \ No newline at end of file