mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
Compare commits
24
Commits
phase-2-shell
...
v1.3.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aac496923e | ||
|
|
256f944b88 | ||
|
|
dbe57d2fad | ||
|
|
9a45849003 | ||
|
|
dfb603c2ea | ||
|
|
fda173d9a1 | ||
|
|
ddababf284 | ||
|
|
376fa00b08 | ||
|
|
5cbcfa466d | ||
|
|
328451f88d | ||
|
|
1b10a39b15 | ||
|
|
7fe53b4b9c | ||
|
|
edb163eab4 | ||
|
|
7131d1a38e | ||
|
|
6d3813e5e9 | ||
|
|
bfe362245d | ||
|
|
0d21ddbf15 | ||
|
|
7b5165bcb1 | ||
|
|
5c5cffed83 | ||
|
|
becfec5ae6 | ||
|
|
bd38c5342d | ||
|
|
f3e829ca63 | ||
|
|
b0a96f680a | ||
|
|
a6da95b057 |
@@ -1,189 +1,334 @@
|
|||||||
# Pan Converter
|
# PanConverter - Claude Development Guide
|
||||||
|
|
||||||
## Overview
|
## Project 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
|
**PanConverter** is a cross-platform Markdown editor and converter powered by Pandoc, built with Electron. It provides professional-grade editing capabilities with comprehensive export options.
|
||||||
- **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
|
**Current Version**: v1.3.3
|
||||||
- **Pandoc**: Must be installed on the system for export functionality
|
**Author**: Amit Haridas (amit.wh@gmail.com)
|
||||||
- Ubuntu/Debian: `sudo apt-get install pandoc`
|
**License**: MIT
|
||||||
- macOS: `brew install pandoc`
|
**Repository**: https://github.com/amitwh/pan-converter
|
||||||
- Windows: Download from https://pandoc.org/installing.html
|
|
||||||
|
|
||||||
## Development Setup
|
## Architecture & Technology Stack
|
||||||
|
|
||||||
|
### Core Technologies
|
||||||
|
- **Electron** - Cross-platform desktop application framework
|
||||||
|
- **Pandoc** - Universal document converter (required dependency)
|
||||||
|
- **marked** - Markdown parsing and rendering
|
||||||
|
- **highlight.js** - Syntax highlighting
|
||||||
|
- **XLSX** - Spreadsheet export functionality
|
||||||
|
- **DOMPurify** - HTML sanitization
|
||||||
|
|
||||||
|
### Application Structure
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.js # Electron main process, menu system, IPC handlers
|
||||||
|
├── renderer.js # TabManager class, multi-file editing, event handling
|
||||||
|
├── index.html # Application layout with tabbed interface
|
||||||
|
└── styles.css # Comprehensive styling with multi-theme support
|
||||||
|
|
||||||
|
assets/
|
||||||
|
└── icon.png # Application icon
|
||||||
|
|
||||||
|
package.json # Dependencies and build configuration
|
||||||
|
CLAUDE.md # Development documentation for AI assistants
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Node.js (v14 or higher)
|
|
||||||
- npm or yarn
|
|
||||||
- Pandoc (for export features)
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the repository
|
# Install Node.js dependencies
|
||||||
git clone <repository-url>
|
|
||||||
cd pan-converter
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Generate icons
|
# Install Pandoc (required for export functionality)
|
||||||
|
# Ubuntu/Debian:
|
||||||
|
sudo apt-get install pandoc
|
||||||
|
|
||||||
|
# macOS:
|
||||||
|
brew install pandoc
|
||||||
|
|
||||||
|
# Windows: Download from https://pandoc.org/installing.html
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
```bash
|
||||||
|
# Start development server
|
||||||
|
npm start
|
||||||
|
|
||||||
|
# Start with debugging
|
||||||
|
npm start --enable-logging
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building & Packaging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate application icons
|
||||||
npm run generate-icons
|
npm run generate-icons
|
||||||
|
|
||||||
# Start the application
|
# Build for current platform
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
### Build for Current Platform
|
|
||||||
```bash
|
|
||||||
npm run build
|
npm run build
|
||||||
```
|
|
||||||
|
|
||||||
### Build for Specific Platforms
|
# Platform-specific builds
|
||||||
```bash
|
npm run build:win # Windows
|
||||||
# Windows
|
npm run build:mac # macOS
|
||||||
npm run build:win
|
npm run build:linux # Linux (AppImage, .deb, .snap)
|
||||||
|
|
||||||
# macOS
|
# Build for all platforms
|
||||||
npm run build:mac
|
|
||||||
|
|
||||||
# Linux (generates .deb, .AppImage, and .snap)
|
|
||||||
npm run build:linux
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build for All Platforms
|
|
||||||
```bash
|
|
||||||
npm run dist:all
|
npm run dist:all
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project Structure
|
### Git Branch Management
|
||||||
```
|
```bash
|
||||||
pan-converter/
|
# Switch to platform-specific branches
|
||||||
├── src/
|
git checkout linux # Linux development
|
||||||
│ ├── main.js # Main process
|
git checkout macos # macOS development
|
||||||
│ ├── renderer.js # Renderer process
|
git checkout windows # Windows development
|
||||||
│ ├── index.html # Main window HTML
|
git checkout master # Main development branch
|
||||||
│ └── styles.css # Application styles
|
|
||||||
├── assets/
|
# Update all branches with latest changes
|
||||||
│ ├── icon.svg # Source icon
|
git checkout master
|
||||||
│ └── icon.png # Generated icons
|
git push origin master
|
||||||
├── scripts/
|
git checkout linux && git merge master && git push origin linux
|
||||||
│ └── generate-icons.js # Icon generation script
|
git checkout macos && git merge master && git push origin macos
|
||||||
├── package.json # Project configuration
|
git checkout windows && git merge master && git push origin windows
|
||||||
└── CLAUDE.md # This file
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture
|
### Release Management
|
||||||
|
```bash
|
||||||
|
# Create and push release tag
|
||||||
|
git tag v1.2.1 -m "Release message"
|
||||||
|
git push origin v1.2.1
|
||||||
|
|
||||||
|
# Create GitHub release with packages
|
||||||
|
gh release create v1.2.1 --title "Title" --notes "Release notes" \
|
||||||
|
"dist/PanConverter-1.2.1.AppImage" \
|
||||||
|
"dist/pan-converter_1.2.1_amd64.deb" \
|
||||||
|
"dist/pan-converter_1.2.1_amd64.snap"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Feature Implementation Guide
|
||||||
|
|
||||||
|
### v1.3.x Tabbed Interface & Enhanced Features
|
||||||
|
|
||||||
|
#### 🗂️ Tabbed Multi-File Support (v1.3.0)
|
||||||
|
**TabManager Class** (`src/renderer.js`)
|
||||||
|
- Complete tab management system for multiple files
|
||||||
|
- Tab switching, creation, and closure
|
||||||
|
- State preservation per tab (content, cursor position, scroll)
|
||||||
|
- File path tracking for each tab
|
||||||
|
- Keyboard shortcuts: `Ctrl/Cmd+T` (new tab), `Ctrl/Cmd+W` (close tab)
|
||||||
|
|
||||||
|
#### 🎯 Enhanced PDF Export (v1.3.0)
|
||||||
|
**Multi-Engine Fallback System** (`src/main.js:239-280`)
|
||||||
|
- Primary: XeLaTeX with proper margins
|
||||||
|
- Fallback 1: PDFLaTeX
|
||||||
|
- Fallback 2: wkhtmltopdf
|
||||||
|
- Automatic engine detection and switching
|
||||||
|
|
||||||
|
#### 📁 File Association Support (v1.3.1)
|
||||||
|
**OS Integration** (`src/main.js:452-498`, `package.json:50-65`)
|
||||||
|
- Double-click .md files to open in PanConverter
|
||||||
|
- Command-line argument handling
|
||||||
|
- Pending file queue for startup loading
|
||||||
|
|
||||||
|
#### 🎨 Typography & Spacing (v1.3.2-1.3.3)
|
||||||
|
**Preview Enhancement** (`src/styles.css`)
|
||||||
|
- Restored ideal text spacing from v1.0
|
||||||
|
- Font sizes increased to 15px for better readability
|
||||||
|
- Comprehensive selector coverage for legacy and new containers
|
||||||
|
- Theme-aware typography for all content types
|
||||||
|
|
||||||
|
### v1.2.1 Comprehensive Editor Enhancements
|
||||||
|
|
||||||
|
#### ✨ Advanced Editor Features
|
||||||
|
|
||||||
|
**Find & Replace System** (`src/renderer.js:200-350`)
|
||||||
|
- Dialog-based interface with match highlighting
|
||||||
|
- Forward/backward navigation through matches
|
||||||
|
- Replace single or replace all functionality
|
||||||
|
- Real-time match counting and status display
|
||||||
|
- Escape key closes dialog
|
||||||
|
|
||||||
|
**Line Numbers** (`src/renderer.js:450-500`, `src/styles.css:517-598`)
|
||||||
|
- Toggle-able line numbers with toolbar button
|
||||||
|
- Synchronized scrolling with editor content
|
||||||
|
- Theme-aware styling for all supported themes
|
||||||
|
- Dynamic line number generation based on content
|
||||||
|
|
||||||
|
**Undo/Redo System** (`src/renderer.js:100-150`)
|
||||||
|
- Stack-based state management for editor history
|
||||||
|
- Keyboard shortcuts: `Ctrl/Cmd+Z` (undo), `Ctrl/Cmd+Shift+Z` (redo)
|
||||||
|
- Intelligent state saving on text changes
|
||||||
|
- Memory-efficient history management
|
||||||
|
|
||||||
|
**Smart Auto-Indentation** (`src/renderer.js:350-400`)
|
||||||
|
- Automatic list continuation on Enter key
|
||||||
|
- Proper indentation handling for nested lists
|
||||||
|
- Support for ordered and unordered lists
|
||||||
|
- Intelligent whitespace management
|
||||||
|
|
||||||
|
**Enhanced Keyboard Shortcuts** (`src/renderer.js:400-450`)
|
||||||
|
- `Tab`/`Shift+Tab` for line indentation/outdentation
|
||||||
|
- `Enter` for auto-continuing lists
|
||||||
|
- `Ctrl/Cmd+F` for find & replace dialog
|
||||||
|
- `Escape` for closing dialogs
|
||||||
|
|
||||||
|
**Word/Character Count** (`src/renderer.js:500-530`)
|
||||||
|
- Live counting displayed in status bar
|
||||||
|
- Updates automatically as content changes
|
||||||
|
- Word and character statistics
|
||||||
|
|
||||||
|
#### 📤 Export & Conversion Features
|
||||||
|
|
||||||
|
**PowerPoint Export** (`src/main.js:330-350`)
|
||||||
|
- Convert markdown to PPTX presentations
|
||||||
|
- Automatic slide-level formatting (`--slide-level=2`)
|
||||||
|
- Smart presentation structure handling
|
||||||
|
|
||||||
|
**Spreadsheet Export** (`src/main.js:370-457`)
|
||||||
|
- Export markdown tables to Excel (XLSX/XLS) and ODS formats
|
||||||
|
- Multi-table support with separate worksheets
|
||||||
|
- Automatic table detection and parsing
|
||||||
|
- Error handling for files without tables
|
||||||
|
|
||||||
|
**Document Import** (`src/main.js:280-315`)
|
||||||
|
- Import DOCX, ODT, RTF, HTML, PDF, PPTX, ODP files
|
||||||
|
- Automatic conversion to markdown format
|
||||||
|
- File dialog with appropriate filters
|
||||||
|
- Success notifications and error handling
|
||||||
|
|
||||||
|
**Table Creation Helper** (`src/renderer.js:600-650`)
|
||||||
|
- Built-in table generator with row/column specification
|
||||||
|
- Automatic markdown table formatting
|
||||||
|
- Proper header separation and alignment
|
||||||
|
|
||||||
|
#### 🎨 Interface & Theming
|
||||||
|
|
||||||
|
**Multi-Theme Support** (`src/styles.css:214-598`)
|
||||||
|
- Light, Dark, Solarized, Monokai, GitHub themes
|
||||||
|
- Complete theming for all UI components
|
||||||
|
- Theme-aware styling for new features (find dialog, line numbers)
|
||||||
|
- Persistent theme selection with local storage
|
||||||
|
|
||||||
|
**Enhanced UI Components** (`src/index.html:94-108`)
|
||||||
|
- Find & replace dialog with modern styling
|
||||||
|
- Toolbar buttons for all new features
|
||||||
|
- Status bar with live statistics
|
||||||
|
- Responsive layout with proper spacing
|
||||||
|
|
||||||
|
## File Structure & Key Components
|
||||||
|
|
||||||
### Main Process (`src/main.js`)
|
### Main Process (`src/main.js`)
|
||||||
- Manages application lifecycle
|
- **Menu System**: Comprehensive menu with file operations, editing, conversion, view options
|
||||||
- Creates and manages windows
|
- **IPC Handlers**: Communication between main and renderer processes
|
||||||
- Handles file operations
|
- **File Operations**: Open, save, import/export functionality
|
||||||
- Manages menu bar
|
- **Theme Management**: Persistent theme storage and application
|
||||||
- Communicates with renderer via IPC
|
- **Spreadsheet Export**: Table extraction and XLSX generation
|
||||||
|
- **About Dialog**: Application information and feature list
|
||||||
|
|
||||||
### Renderer Process (`src/renderer.js`)
|
### Renderer Process (`src/renderer.js`)
|
||||||
- Handles UI interactions
|
- **Editor Initialization**: CodeMirror-like functionality with custom implementation
|
||||||
- Manages editor state
|
- **Find & Replace Engine**: Search algorithms with regex support
|
||||||
- Renders markdown preview
|
- **Undo/Redo Manager**: History stack management
|
||||||
- Handles theme switching
|
- **Auto-indentation Logic**: Smart list continuation
|
||||||
- Communicates with main process via IPC
|
- **Live Preview**: Real-time markdown rendering with DOMPurify
|
||||||
|
- **Event Handling**: Keyboard shortcuts and UI interactions
|
||||||
|
- **Statistics Tracking**: Word/character counting
|
||||||
|
|
||||||
### Key Libraries
|
### Styling (`src/styles.css`)
|
||||||
- **Electron**: Desktop application framework
|
- **Base Styles**: Application layout and typography
|
||||||
- **marked**: Markdown parsing
|
- **Component Styles**: Toolbar, editor, preview, dialogs
|
||||||
- **highlight.js**: Syntax highlighting
|
- **Theme Implementations**: Complete styling for all themes
|
||||||
- **DOMPurify**: HTML sanitization
|
- **Responsive Design**: Flexible layouts and proper spacing
|
||||||
- **electron-store**: Persistent storage
|
- **Animation Support**: Smooth transitions and hover effects
|
||||||
- **electron-builder**: Application packaging
|
|
||||||
|
|
||||||
## Features Implementation
|
### HTML Structure (`src/index.html`)
|
||||||
|
- **Toolbar**: Feature buttons with SVG icons
|
||||||
|
- **Find Dialog**: Search and replace interface
|
||||||
|
- **Editor Container**: Line numbers and text editor
|
||||||
|
- **Preview Pane**: Rendered markdown display
|
||||||
|
- **Status Bar**: Statistics and application status
|
||||||
|
|
||||||
### Markdown Editor
|
## Testing & Quality Assurance
|
||||||
- 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
|
### Manual Testing Checklist
|
||||||
- Updates in real-time as you type
|
- [ ] All keyboard shortcuts work correctly
|
||||||
- Sanitized HTML output for security
|
- [ ] Find & replace functions properly with edge cases
|
||||||
- Syntax highlighting for code blocks
|
- [ ] Line numbers sync correctly with content
|
||||||
- GitHub-flavored markdown support
|
- [ ] Undo/redo preserves cursor position
|
||||||
|
- [ ] Auto-indentation works with various list types
|
||||||
|
- [ ] All themes render correctly for new components
|
||||||
|
- [ ] Export functions work with various document formats
|
||||||
|
- [ ] Table creation and export functionality
|
||||||
|
- [ ] Cross-platform compatibility
|
||||||
|
|
||||||
### Theme System
|
### Known Issues & Limitations
|
||||||
- Themes stored in user preferences
|
- AppImage may require `--no-sandbox` flag on some Linux systems
|
||||||
- Applied via CSS classes on body element
|
- Large files (>1MB) may cause performance issues
|
||||||
- Persists across application restarts
|
- Windows/Mac builds require platform-specific environments
|
||||||
- Five built-in themes
|
- Pandoc must be installed separately for export functionality
|
||||||
|
|
||||||
### Export System
|
## Deployment & Distribution
|
||||||
- Uses Pandoc command-line tool
|
|
||||||
- Supports multiple output formats
|
|
||||||
- Maintains original file structure
|
|
||||||
- Shows error messages if Pandoc not installed
|
|
||||||
|
|
||||||
## Platform-Specific Branches
|
### Release Packages
|
||||||
|
- **Linux AppImage**: Universal Linux package (self-contained)
|
||||||
|
- **Debian Package**: `.deb` for Ubuntu/Debian systems
|
||||||
|
- **Snap Package**: Universal Linux package via Snap Store
|
||||||
|
- **Future**: Windows `.exe` and macOS `.dmg` packages
|
||||||
|
|
||||||
The repository maintains separate branches for platform-specific customizations:
|
### Release Process
|
||||||
- `master`: Main development branch
|
1. Update version in `package.json`, `src/main.js`, and `README.md`
|
||||||
- `linux`: Linux-specific configurations
|
2. Commit changes and push to all platform branches
|
||||||
- `macos`: macOS-specific configurations
|
3. Build platform-specific packages
|
||||||
- `windows`: Windows-specific configurations
|
4. Create Git tag and GitHub release
|
||||||
|
5. Upload packages to GitHub release
|
||||||
|
6. Update documentation and announce release
|
||||||
|
|
||||||
## Debian Package
|
## Contributing Guidelines
|
||||||
|
|
||||||
The application can be built as a `.deb` package for Debian-based systems:
|
### Code Style
|
||||||
```bash
|
- Use consistent indentation (2 spaces)
|
||||||
npm run build:linux
|
- Follow JavaScript ES6+ standards
|
||||||
```
|
- Comment complex functionality
|
||||||
|
- Maintain separation between main and renderer processes
|
||||||
|
- Use descriptive variable and function names
|
||||||
|
|
||||||
The generated `.deb` file will:
|
### Adding New Features
|
||||||
- Install to `/opt/pan-converter`
|
1. Plan feature implementation and UI integration
|
||||||
- Create desktop entry for application menu
|
2. Update relevant files (main.js, renderer.js, styles.css)
|
||||||
- Set up proper file associations
|
3. Test across all supported themes
|
||||||
- Declare Pandoc as dependency
|
4. Update documentation and README
|
||||||
|
5. Test on multiple platforms if possible
|
||||||
|
6. Submit pull request with detailed description
|
||||||
|
|
||||||
## Testing
|
### Bug Reporting
|
||||||
|
- Include steps to reproduce
|
||||||
|
- Specify platform and version information
|
||||||
|
- Attach relevant screenshots or error logs
|
||||||
|
- Check existing issues before creating new ones
|
||||||
|
|
||||||
### Manual Testing
|
## Future Roadmap
|
||||||
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
|
### Planned Features
|
||||||
Test on:
|
- [ ] Collaborative editing capabilities
|
||||||
- Windows 10/11
|
- [ ] Plugin system for extensions
|
||||||
- macOS 11+ (Big Sur and later)
|
- [ ] Advanced markdown extensions (math, diagrams)
|
||||||
- Ubuntu 20.04+, Debian 11+
|
- [ ] Cloud synchronization options
|
||||||
|
- [ ] Mobile companion app
|
||||||
|
- [ ] Advanced export templates
|
||||||
|
- [ ] Spell check and grammar checking
|
||||||
|
- [ ] Version control integration
|
||||||
|
|
||||||
## Known Issues
|
### Technical Improvements
|
||||||
- ICO and ICNS icons need manual conversion from PNG
|
- [ ] Performance optimization for large files
|
||||||
- Pandoc must be installed separately
|
- [ ] Memory usage optimization
|
||||||
- Large files may cause performance issues
|
- [ ] Startup time improvements
|
||||||
|
- [ ] Better error handling and user feedback
|
||||||
|
- [ ] Automated testing suite
|
||||||
|
- [ ] Continuous integration/deployment
|
||||||
|
|
||||||
## 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
|
**Last Updated**: September 1, 2025
|
||||||
MIT License
|
**Claude Assistant**: Development completed for v1.3.3 with tabbed interface, enhanced PDF export, file associations, and improved typography
|
||||||
|
|
||||||
## Support
|
|
||||||
For issues or feature requests, please open an issue on the GitHub repository.
|
|
||||||
+378
@@ -0,0 +1,378 @@
|
|||||||
|
\hypertarget{panconverter}{%
|
||||||
|
\section{PanConverter}\label{panconverter}}
|
||||||
|
|
||||||
|
A cross-platform Markdown editor and converter powered by Pandoc.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\centering
|
||||||
|
\includegraphics{assets/icon.png}
|
||||||
|
\caption{PanConverter}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\hypertarget{features}{%
|
||||||
|
\subsection{Features}\label{features}}
|
||||||
|
|
||||||
|
\hypertarget{advanced-markdown-editor}{%
|
||||||
|
\subsubsection{✨ Advanced Markdown
|
||||||
|
Editor}\label{advanced-markdown-editor}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
🗂️ \textbf{Tabbed Interface} - Work with multiple files simultaneously
|
||||||
|
in separate tabs
|
||||||
|
\item
|
||||||
|
📝 \textbf{Rich Text Editor} - Full-featured editor with syntax
|
||||||
|
highlighting and comprehensive toolbar
|
||||||
|
\item
|
||||||
|
🔍 \textbf{Find \& Replace} - Powerful search and replace with match
|
||||||
|
highlighting and navigation
|
||||||
|
\item
|
||||||
|
🔢 \textbf{Line Numbers} - Toggle line numbers for easier code editing
|
||||||
|
and navigation
|
||||||
|
\item
|
||||||
|
↩️ \textbf{Smart Auto-Indentation} - Automatic list continuation and
|
||||||
|
intelligent indentation
|
||||||
|
\item
|
||||||
|
⏪ \textbf{Undo/Redo} - Full undo/redo support with keyboard shortcuts
|
||||||
|
\item
|
||||||
|
⌨️ \textbf{Advanced Shortcuts} - Tab indentation, line selection, and
|
||||||
|
smart text formatting
|
||||||
|
\item
|
||||||
|
📂 \textbf{File Association Support} - Open markdown files directly
|
||||||
|
from file manager
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{themes-interface}{%
|
||||||
|
\subsubsection{🎨 Themes \& Interface}\label{themes-interface}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
👁️ \textbf{Live Preview} - See your markdown rendered in real-time
|
||||||
|
with synchronized scrolling
|
||||||
|
\item
|
||||||
|
🎨 \textbf{Multiple Themes} - Choose from Light, Dark, Solarized,
|
||||||
|
Monokai, or GitHub themes
|
||||||
|
\item
|
||||||
|
💾 \textbf{Auto-Save} - Never lose your work with automatic saving
|
||||||
|
every 30 seconds
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{export-conversion}{%
|
||||||
|
\subsubsection{📤 Export \& Conversion}\label{export-conversion}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
📄 \textbf{Enhanced PDF Export} - Robust PDF generation with multiple
|
||||||
|
LaTeX engine fallbacks (XeLaTeX, PDFLaTeX, wkhtmltopdf)
|
||||||
|
\item
|
||||||
|
📄 \textbf{Document Export} - Convert to HTML, DOCX, LaTeX, RTF, ODT,
|
||||||
|
EPUB, PowerPoint (PPTX), and OpenDocument Presentation (ODP)
|
||||||
|
\item
|
||||||
|
📊 \textbf{Spreadsheet Export} - Export markdown tables to Excel
|
||||||
|
(XLSX/XLS) and OpenDocument Spreadsheet (ODS) formats
|
||||||
|
\item
|
||||||
|
📥 \textbf{Document Import} - Import DOCX, ODT, RTF, HTML, PDF, and
|
||||||
|
presentation files to markdown
|
||||||
|
\item
|
||||||
|
📋 \textbf{Table Creation Helper} - Built-in table generator for easy
|
||||||
|
markdown table creation
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{platform-support}{%
|
||||||
|
\subsubsection{🖥️ Platform Support}\label{platform-support}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\textbf{Cross-Platform} - Works seamlessly on Windows, macOS, and
|
||||||
|
Linux
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{installation}{%
|
||||||
|
\subsection{Installation}\label{installation}}
|
||||||
|
|
||||||
|
\hypertarget{prerequisites}{%
|
||||||
|
\subsubsection{Prerequisites}\label{prerequisites}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\href{https://pandoc.org/installing.html}{Pandoc} must be installed
|
||||||
|
for export functionality
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\textbf{Ubuntu/Debian}: \texttt{sudo\ apt-get\ install\ pandoc}
|
||||||
|
\item
|
||||||
|
\textbf{macOS}: \texttt{brew\ install\ pandoc}
|
||||||
|
\item
|
||||||
|
\textbf{Windows}: Download installer from Pandoc website
|
||||||
|
\end{itemize}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{pdf-export-requirements}{%
|
||||||
|
\subsubsection{PDF Export Requirements}\label{pdf-export-requirements}}
|
||||||
|
|
||||||
|
For optimal PDF export, install a LaTeX engine (recommended): -
|
||||||
|
\textbf{Ubuntu/Debian}:
|
||||||
|
\texttt{sudo\ apt-get\ install\ texlive-xetex\ texlive-latex-base} -
|
||||||
|
\textbf{macOS}: \texttt{brew\ install\ -\/-cask\ mactex} -
|
||||||
|
\textbf{Windows}: Install MiKTeX or TeX Live - \textbf{Alternative}:
|
||||||
|
\texttt{sudo\ apt-get\ install\ wkhtmltopdf} (fallback option)
|
||||||
|
|
||||||
|
\hypertarget{download}{%
|
||||||
|
\subsubsection{Download}\label{download}}
|
||||||
|
|
||||||
|
Download the latest release for your platform from the
|
||||||
|
\href{https://github.com/amitwh/pan-converter/releases}{Releases} page.
|
||||||
|
|
||||||
|
\hypertarget{linux}{%
|
||||||
|
\paragraph{Linux}\label{linux}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\textbf{AppImage}: \texttt{PanConverter-1.3.1.AppImage} (universal,
|
||||||
|
may require \texttt{-\/-no-sandbox} flag)
|
||||||
|
\item
|
||||||
|
\textbf{Debian Package}: \texttt{pan-converter\_1.3.1\_amd64.deb}
|
||||||
|
\item
|
||||||
|
\textbf{Snap Package}: \texttt{pan-converter\_1.3.1\_amd64.snap}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{install-from-source}{%
|
||||||
|
\subsubsection{Install from Source}\label{install-from-source}}
|
||||||
|
|
||||||
|
\begin{Shaded}
|
||||||
|
\begin{Highlighting}[]
|
||||||
|
\FunctionTok{git}\NormalTok{ clone https://github.com/amitwh/pan{-}converter.git}
|
||||||
|
\BuiltInTok{cd}\NormalTok{ pan{-}converter}
|
||||||
|
\ExtensionTok{npm}\NormalTok{ install}
|
||||||
|
\ExtensionTok{npm}\NormalTok{ start}
|
||||||
|
\end{Highlighting}
|
||||||
|
\end{Shaded}
|
||||||
|
|
||||||
|
\hypertarget{usage}{%
|
||||||
|
\subsection{Usage}\label{usage}}
|
||||||
|
|
||||||
|
\hypertarget{basic-workflow}{%
|
||||||
|
\subsubsection{Basic Workflow}\label{basic-workflow}}
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\def\labelenumi{\arabic{enumi}.}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\textbf{Write} - Use the editor to write your Markdown content
|
||||||
|
\item
|
||||||
|
\textbf{Preview} - Toggle the preview pane to see rendered output
|
||||||
|
\item
|
||||||
|
\textbf{Theme} - Choose your preferred theme from the View menu
|
||||||
|
\item
|
||||||
|
\textbf{Export} - Export your document to various formats
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
\hypertarget{export-options}{%
|
||||||
|
\subsubsection{Export Options}\label{export-options}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\textbf{Documents}: HTML, PDF, DOCX, LaTeX, RTF, ODT, EPUB
|
||||||
|
\item
|
||||||
|
\textbf{Presentations}: PowerPoint (PPTX), OpenDocument Presentation
|
||||||
|
(ODP)
|
||||||
|
\item
|
||||||
|
\textbf{Spreadsheets}: Excel (XLSX/XLS), OpenDocument Spreadsheet
|
||||||
|
(ODS)
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{import-conversion}{%
|
||||||
|
\subsubsection{Import \& Conversion}\label{import-conversion}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\textbf{Import Documents}: Convert DOCX, ODT, RTF, HTML, PDF, and
|
||||||
|
presentation files to Markdown
|
||||||
|
\item
|
||||||
|
\textbf{Cross-Format Conversion}: Convert current file between
|
||||||
|
multiple formats
|
||||||
|
\item
|
||||||
|
\textbf{Smart Presentation Handling}: Automatic slide-level formatting
|
||||||
|
for PPTX/ODP exports
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{table-creation}{%
|
||||||
|
\subsubsection{Table Creation}\label{table-creation}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
Click the table button in the toolbar
|
||||||
|
\item
|
||||||
|
Specify number of rows and columns
|
||||||
|
\item
|
||||||
|
Automatically generates properly formatted Markdown tables
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{keyboard-shortcuts}{%
|
||||||
|
\subsection{Keyboard Shortcuts}\label{keyboard-shortcuts}}
|
||||||
|
|
||||||
|
\hypertarget{file-operations}{%
|
||||||
|
\subsubsection{File Operations}\label{file-operations}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ N} - New file/tab
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ T} - New tab
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ W} - Close current tab
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ Tab} - Switch to next tab
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ O} - Open file
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ S} - Save file
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ Shift\ +\ S} - Save as
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ I} - Import document
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{editor-features}{%
|
||||||
|
\subsubsection{Editor Features}\label{editor-features}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ F} - Find \& Replace
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ Z} - Undo
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ Shift\ +\ Z} - Redo
|
||||||
|
\item
|
||||||
|
\texttt{Tab} - Indent lines or insert 4 spaces
|
||||||
|
\item
|
||||||
|
\texttt{Shift\ +\ Tab} - Outdent selected lines
|
||||||
|
\item
|
||||||
|
\texttt{Enter} - Auto-continue lists with proper indentation
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{view-navigation}{%
|
||||||
|
\subsubsection{View \& Navigation}\label{view-navigation}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ P} - Toggle preview
|
||||||
|
\item
|
||||||
|
\texttt{Ctrl/Cmd\ +\ Enter} - Toggle preview (alternative)
|
||||||
|
\item
|
||||||
|
\texttt{Escape} - Close find dialog
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{building}{%
|
||||||
|
\subsection{Building}\label{building}}
|
||||||
|
|
||||||
|
\begin{Shaded}
|
||||||
|
\begin{Highlighting}[]
|
||||||
|
\CommentTok{\# Install dependencies}
|
||||||
|
\ExtensionTok{npm}\NormalTok{ install}
|
||||||
|
|
||||||
|
\CommentTok{\# Generate icons}
|
||||||
|
\ExtensionTok{npm}\NormalTok{ run generate{-}icons}
|
||||||
|
|
||||||
|
\CommentTok{\# Build for current platform}
|
||||||
|
\ExtensionTok{npm}\NormalTok{ run build}
|
||||||
|
|
||||||
|
\CommentTok{\# Build for specific platform}
|
||||||
|
\ExtensionTok{npm}\NormalTok{ run build:win }\CommentTok{\# Windows}
|
||||||
|
\ExtensionTok{npm}\NormalTok{ run build:mac }\CommentTok{\# macOS }
|
||||||
|
\ExtensionTok{npm}\NormalTok{ run build:linux }\CommentTok{\# Linux (generates .deb, .AppImage, and .snap)}
|
||||||
|
|
||||||
|
\CommentTok{\# Build for all platforms}
|
||||||
|
\ExtensionTok{npm}\NormalTok{ run dist:all}
|
||||||
|
\end{Highlighting}
|
||||||
|
\end{Shaded}
|
||||||
|
|
||||||
|
\hypertarget{version-history}{%
|
||||||
|
\subsection{Version History}\label{version-history}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
\textbf{v1.3.1} - Bug fixes: Fixed file associations for
|
||||||
|
double-clicking .md files, corrected 50/50 layout alignment for
|
||||||
|
editor/preview panes
|
||||||
|
\item
|
||||||
|
\textbf{v1.3.0} - Major update: Tabbed interface for multiple files,
|
||||||
|
enhanced PDF export with LaTeX engines, fixed file associations,
|
||||||
|
removed redundant converter menu, improved UI architecture
|
||||||
|
\item
|
||||||
|
\textbf{v1.2.1} - Comprehensive editor enhancements: Find \& Replace,
|
||||||
|
Line Numbers, Undo/Redo, Auto-indentation, PowerPoint export, document
|
||||||
|
conversion menu, table creation helper, spreadsheet export
|
||||||
|
\item
|
||||||
|
\textbf{v1.1.0} - Added Excel/ODS spreadsheet export, updated author
|
||||||
|
information, renamed to PanConverter
|
||||||
|
\item
|
||||||
|
\textbf{v1.0.0} - Initial release with basic markdown editing, themes,
|
||||||
|
and Pandoc export
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{known-issues}{%
|
||||||
|
\subsection{Known Issues}\label{known-issues}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
AppImage may require \texttt{-\/-no-sandbox} flag on some Linux
|
||||||
|
systems
|
||||||
|
\item
|
||||||
|
Windows/Mac builds require platform-specific build environments
|
||||||
|
\item
|
||||||
|
Large files may cause performance issues
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\hypertarget{contributing}{%
|
||||||
|
\subsection{Contributing}\label{contributing}}
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request to
|
||||||
|
the \href{https://github.com/amitwh/pan-converter}{GitHub repository}.
|
||||||
|
|
||||||
|
\hypertarget{license}{%
|
||||||
|
\subsection{License}\label{license}}
|
||||||
|
|
||||||
|
MIT License - see LICENSE file for details.
|
||||||
|
|
||||||
|
\hypertarget{author}{%
|
||||||
|
\subsection{Author}\label{author}}
|
||||||
|
|
||||||
|
\textbf{Amit Haridas} -
|
||||||
|
\href{mailto:amit.wh@gmail.com}{\nolinkurl{amit.wh@gmail.com}}
|
||||||
|
|
||||||
|
\hypertarget{acknowledgments}{%
|
||||||
|
\subsection{Acknowledgments}\label{acknowledgments}}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\tightlist
|
||||||
|
\item
|
||||||
|
Built with \href{https://www.electronjs.org/}{Electron}
|
||||||
|
\item
|
||||||
|
Markdown parsing by \href{https://marked.js.org/}{marked}
|
||||||
|
\item
|
||||||
|
Export functionality powered by \href{https://pandoc.org/}{Pandoc}
|
||||||
|
\item
|
||||||
|
Syntax highlighting by \href{https://highlightjs.org/}{highlight.js}
|
||||||
|
\item
|
||||||
|
Spreadsheet export by \href{https://www.npmjs.com/package/xlsx}{XLSX}
|
||||||
|
\item
|
||||||
|
HTML sanitization by
|
||||||
|
\href{https://www.npmjs.com/package/dompurify}{DOMPurify}
|
||||||
|
\end{itemize}
|
||||||
@@ -6,24 +6,57 @@ A cross-platform Markdown editor and converter powered by Pandoc.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 📝 **Rich Markdown Editor** - Full-featured editor with syntax highlighting
|
### ✨ Advanced Markdown Editor
|
||||||
- 👁️ **Live Preview** - See your markdown rendered in real-time
|
- 🗂️ **Tabbed Interface** - Work with multiple files simultaneously in separate tabs
|
||||||
|
- 📝 **Rich Text Editor** - Full-featured editor with syntax highlighting and comprehensive toolbar
|
||||||
|
- 🔍 **Find & Replace** - Powerful search and replace with match highlighting and navigation
|
||||||
|
- 🔢 **Line Numbers** - Toggle line numbers for easier code editing and navigation
|
||||||
|
- ↩️ **Smart Auto-Indentation** - Automatic list continuation and intelligent indentation
|
||||||
|
- ⏪ **Undo/Redo** - Full undo/redo support with keyboard shortcuts
|
||||||
|
- ⌨️ **Advanced Shortcuts** - Tab indentation, line selection, and smart text formatting
|
||||||
|
- 📂 **File Association Support** - Open markdown files directly from file manager
|
||||||
|
|
||||||
|
### 🎨 Themes & Interface
|
||||||
|
- 👁️ **Live Preview** - See your markdown rendered in real-time with synchronized scrolling
|
||||||
- 🎨 **Multiple Themes** - Choose from Light, Dark, Solarized, Monokai, or GitHub themes
|
- 🎨 **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 every 30 seconds
|
||||||
- 💾 **Auto-Save** - Never lose your work with automatic saving
|
|
||||||
- 🖥️ **Cross-Platform** - Works on Windows, macOS, and Linux
|
### 📤 Export & Conversion
|
||||||
|
- 📄 **Enhanced PDF Export** - Robust PDF generation with multiple LaTeX engine fallbacks (XeLaTeX, PDFLaTeX, wkhtmltopdf)
|
||||||
|
- 📄 **Document Export** - Convert to HTML, DOCX, LaTeX, RTF, ODT, EPUB, PowerPoint (PPTX), and OpenDocument Presentation (ODP)
|
||||||
|
- 📊 **Spreadsheet Export** - Export markdown tables to Excel (XLSX/XLS) and OpenDocument Spreadsheet (ODS) formats
|
||||||
|
- 📥 **Document Import** - Import DOCX, ODT, RTF, HTML, PDF, and presentation files to markdown
|
||||||
|
- 📋 **Table Creation Helper** - Built-in table generator for easy markdown table creation
|
||||||
|
|
||||||
|
### 🖥️ Platform Support
|
||||||
|
- **Cross-Platform** - Works seamlessly on Windows, macOS, and Linux
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- [Pandoc](https://pandoc.org/installing.html) must be installed for export functionality
|
- [Pandoc](https://pandoc.org/installing.html) must be installed for export functionality
|
||||||
|
- **Ubuntu/Debian**: `sudo apt-get install pandoc`
|
||||||
|
- **macOS**: `brew install pandoc`
|
||||||
|
- **Windows**: Download installer from Pandoc website
|
||||||
|
|
||||||
|
### PDF Export Requirements
|
||||||
|
For optimal PDF export, install a LaTeX engine (recommended):
|
||||||
|
- **Ubuntu/Debian**: `sudo apt-get install texlive-xetex texlive-latex-base`
|
||||||
|
- **macOS**: `brew install --cask mactex`
|
||||||
|
- **Windows**: Install MiKTeX or TeX Live
|
||||||
|
- **Alternative**: `sudo apt-get install wkhtmltopdf` (fallback option)
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
Download the latest release for your platform from the [Releases](https://github.com/yourusername/pan-converter/releases) page.
|
Download the latest release for your platform from the [Releases](https://github.com/amitwh/pan-converter/releases) page.
|
||||||
|
|
||||||
|
#### Linux
|
||||||
|
- **AppImage**: `PanConverter-1.3.1.AppImage` (universal, may require `--no-sandbox` flag)
|
||||||
|
- **Debian Package**: `pan-converter_1.3.1_amd64.deb`
|
||||||
|
- **Snap Package**: `pan-converter_1.3.1_amd64.snap`
|
||||||
|
|
||||||
### Install from Source
|
### Install from Source
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/yourusername/pan-converter.git
|
git clone https://github.com/amitwh/pan-converter.git
|
||||||
cd pan-converter
|
cd pan-converter
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
@@ -31,45 +64,104 @@ npm start
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
### Basic Workflow
|
||||||
1. **Write** - Use the editor to write your Markdown content
|
1. **Write** - Use the editor to write your Markdown content
|
||||||
2. **Preview** - Toggle the preview pane to see rendered output
|
2. **Preview** - Toggle the preview pane to see rendered output
|
||||||
3. **Theme** - Choose your preferred theme from the View menu
|
3. **Theme** - Choose your preferred theme from the View menu
|
||||||
4. **Export** - Export your document to various formats via File > Export
|
4. **Export** - Export your document to various formats
|
||||||
|
|
||||||
|
### Export Options
|
||||||
|
- **Documents**: HTML, PDF, DOCX, LaTeX, RTF, ODT, EPUB
|
||||||
|
- **Presentations**: PowerPoint (PPTX), OpenDocument Presentation (ODP)
|
||||||
|
- **Spreadsheets**: Excel (XLSX/XLS), OpenDocument Spreadsheet (ODS)
|
||||||
|
|
||||||
|
### Import & Conversion
|
||||||
|
- **Import Documents**: Convert DOCX, ODT, RTF, HTML, PDF, and presentation files to Markdown
|
||||||
|
- **Cross-Format Conversion**: Convert current file between multiple formats
|
||||||
|
- **Smart Presentation Handling**: Automatic slide-level formatting for PPTX/ODP exports
|
||||||
|
|
||||||
|
### Table Creation
|
||||||
|
- Click the table button in the toolbar
|
||||||
|
- Specify number of rows and columns
|
||||||
|
- Automatically generates properly formatted Markdown tables
|
||||||
|
|
||||||
## Keyboard Shortcuts
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
- `Ctrl/Cmd + N` - New file
|
### File Operations
|
||||||
|
- `Ctrl/Cmd + N` - New file/tab
|
||||||
|
- `Ctrl/Cmd + T` - New tab
|
||||||
|
- `Ctrl/Cmd + W` - Close current tab
|
||||||
|
- `Ctrl/Cmd + Tab` - Switch to next tab
|
||||||
- `Ctrl/Cmd + O` - Open file
|
- `Ctrl/Cmd + O` - Open file
|
||||||
- `Ctrl/Cmd + S` - Save file
|
- `Ctrl/Cmd + S` - Save file
|
||||||
- `Ctrl/Cmd + Shift + S` - Save as
|
- `Ctrl/Cmd + Shift + S` - Save as
|
||||||
|
- `Ctrl/Cmd + I` - Import document
|
||||||
|
|
||||||
|
### Editor Features
|
||||||
|
- `Ctrl/Cmd + F` - Find & Replace
|
||||||
|
- `Ctrl/Cmd + Z` - Undo
|
||||||
|
- `Ctrl/Cmd + Shift + Z` - Redo
|
||||||
|
- `Tab` - Indent lines or insert 4 spaces
|
||||||
|
- `Shift + Tab` - Outdent selected lines
|
||||||
|
- `Enter` - Auto-continue lists with proper indentation
|
||||||
|
|
||||||
|
### View & Navigation
|
||||||
- `Ctrl/Cmd + P` - Toggle preview
|
- `Ctrl/Cmd + P` - Toggle preview
|
||||||
- `Ctrl/Cmd + Enter` - Toggle preview (alternative)
|
- `Ctrl/Cmd + Enter` - Toggle preview (alternative)
|
||||||
|
- `Escape` - Close find dialog
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Generate icons
|
||||||
|
npm run generate-icons
|
||||||
|
|
||||||
# Build for current platform
|
# Build for current platform
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Build for specific platform
|
# Build for specific platform
|
||||||
npm run build:win
|
npm run build:win # Windows
|
||||||
npm run build:mac
|
npm run build:mac # macOS
|
||||||
npm run build:linux
|
npm run build:linux # Linux (generates .deb, .AppImage, and .snap)
|
||||||
|
|
||||||
# Build for all platforms
|
# Build for all platforms
|
||||||
npm run dist:all
|
npm run dist:all
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
- **v1.3.1** - Bug fixes: Fixed file associations for double-clicking .md files, corrected 50/50 layout alignment for editor/preview panes
|
||||||
|
- **v1.3.0** - Major update: Tabbed interface for multiple files, enhanced PDF export with LaTeX engines, fixed file associations, removed redundant converter menu, improved UI architecture
|
||||||
|
- **v1.2.1** - Comprehensive editor enhancements: Find & Replace, Line Numbers, Undo/Redo, Auto-indentation, PowerPoint export, document conversion menu, table creation helper, spreadsheet export
|
||||||
|
- **v1.1.0** - Added Excel/ODS spreadsheet export, updated author information, renamed to PanConverter
|
||||||
|
- **v1.0.0** - Initial release with basic markdown editing, themes, and Pandoc export
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
- AppImage may require `--no-sandbox` flag on some Linux systems
|
||||||
|
- Windows/Mac builds require platform-specific build environments
|
||||||
|
- Large files may cause performance issues
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
Contributions are welcome! Please feel free to submit a Pull Request to the [GitHub repository](https://github.com/amitwh/pan-converter).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License - see LICENSE file for details
|
MIT License - see LICENSE file for details.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
**Amit Haridas** - [amit.wh@gmail.com](mailto:amit.wh@gmail.com)
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
- Built with [Electron](https://www.electronjs.org/)
|
- Built with [Electron](https://www.electronjs.org/)
|
||||||
- Markdown parsing by [marked](https://marked.js.org/)
|
- Markdown parsing by [marked](https://marked.js.org/)
|
||||||
- Export functionality powered by [Pandoc](https://pandoc.org/)
|
- Export functionality powered by [Pandoc](https://pandoc.org/)
|
||||||
|
- Syntax highlighting by [highlight.js](https://highlightjs.org/)
|
||||||
|
- Spreadsheet export by [XLSX](https://www.npmjs.com/package/xlsx)
|
||||||
|
- HTML sanitization by [DOMPurify](https://www.npmjs.com/package/dompurify)
|
||||||
BIN
Binary file not shown.
+27
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pan-converter",
|
"name": "pan-converter",
|
||||||
"version": "1.0.0",
|
"version": "1.3.4",
|
||||||
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -14,8 +14,13 @@
|
|||||||
"dist:all": "electron-builder -mwl",
|
"dist:all": "electron-builder -mwl",
|
||||||
"generate-icons": "node scripts/generate-icons.js"
|
"generate-icons": "node scripts/generate-icons.js"
|
||||||
},
|
},
|
||||||
"keywords": ["markdown", "pandoc", "converter", "editor"],
|
"keywords": [
|
||||||
"author": "Your Name",
|
"markdown",
|
||||||
|
"pandoc",
|
||||||
|
"converter",
|
||||||
|
"editor"
|
||||||
|
],
|
||||||
|
"author": "Amit Haridas <amit.wh@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^37.4.0",
|
"electron": "^37.4.0",
|
||||||
@@ -27,7 +32,8 @@
|
|||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.2.6",
|
||||||
"electron-store": "^10.1.0",
|
"electron-store": "^10.1.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"marked": "^16.2.1"
|
"marked": "^16.2.1",
|
||||||
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.panconverter.app",
|
"appId": "com.panconverter.app",
|
||||||
@@ -41,6 +47,22 @@
|
|||||||
"node_modules/**/*",
|
"node_modules/**/*",
|
||||||
"package.json"
|
"package.json"
|
||||||
],
|
],
|
||||||
|
"fileAssociations": [
|
||||||
|
{
|
||||||
|
"ext": "md",
|
||||||
|
"name": "Markdown Document",
|
||||||
|
"description": "Markdown Document",
|
||||||
|
"mimeType": "text/markdown",
|
||||||
|
"role": "Editor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": "markdown",
|
||||||
|
"name": "Markdown Document",
|
||||||
|
"description": "Markdown Document",
|
||||||
|
"mimeType": "text/markdown",
|
||||||
|
"role": "Editor"
|
||||||
|
}
|
||||||
|
],
|
||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.productivity",
|
"category": "public.app-category.productivity",
|
||||||
"icon": "assets/icon.icns"
|
"icon": "assets/icon.icns"
|
||||||
@@ -63,7 +85,7 @@
|
|||||||
"pandoc"
|
"pandoc"
|
||||||
],
|
],
|
||||||
"description": "Markdown editor and converter using Pandoc",
|
"description": "Markdown editor and converter using Pandoc",
|
||||||
"maintainer": "Your Name <your.email@example.com>"
|
"maintainer": "Amit Haridas <amit.wh@gmail.com>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+55
-4
@@ -10,6 +10,13 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="tab-bar" id="tab-bar">
|
||||||
|
<div class="tab active" data-tab-id="1">
|
||||||
|
<span class="tab-title">Untitled</span>
|
||||||
|
<button class="tab-close" title="Close tab">×</button>
|
||||||
|
</div>
|
||||||
|
<button class="new-tab-button" id="new-tab-btn" title="New tab">+</button>
|
||||||
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<button id="btn-bold" title="Bold">
|
<button id="btn-bold" title="Bold">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
@@ -59,6 +66,29 @@
|
|||||||
<path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"></path>
|
<path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="btn-table" title="Insert Table">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||||
|
<line x1="3" y1="9" x2="21" y2="9"></line>
|
||||||
|
<line x1="3" y1="15" x2="21" y2="15"></line>
|
||||||
|
<line x1="9" y1="3" x2="9" y2="21"></line>
|
||||||
|
<line x1="15" y1="3" x2="15" y2="21"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="toolbar-separator"></div>
|
||||||
|
<button id="btn-find" title="Find & Replace (Ctrl+F)">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<path d="m21 21-4.35-4.35"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button id="btn-line-numbers" title="Toggle Line Numbers">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M3 6h18"></path>
|
||||||
|
<path d="M3 12h18"></path>
|
||||||
|
<path d="M3 18h18"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<div class="toolbar-separator"></div>
|
<div class="toolbar-separator"></div>
|
||||||
<button id="btn-preview-toggle" title="Toggle Preview">
|
<button id="btn-preview-toggle" title="Toggle Preview">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
@@ -68,12 +98,33 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Find & Replace Dialog -->
|
||||||
|
<div id="find-dialog" class="find-dialog hidden">
|
||||||
|
<div class="find-controls">
|
||||||
|
<input type="text" id="find-input" placeholder="Find...">
|
||||||
|
<input type="text" id="replace-input" placeholder="Replace...">
|
||||||
|
<button id="btn-find-prev" title="Previous">↑</button>
|
||||||
|
<button id="btn-find-next" title="Next">↓</button>
|
||||||
|
<button id="btn-replace" title="Replace">Replace</button>
|
||||||
|
<button id="btn-replace-all" title="Replace All">All</button>
|
||||||
|
<button id="btn-find-close" title="Close">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="find-info">
|
||||||
|
<span id="find-count">0 matches</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="editor-container">
|
<div class="editor-container">
|
||||||
<div id="editor-pane" class="pane">
|
<div class="tab-content active" id="tab-content-1" data-tab-id="1">
|
||||||
<textarea id="editor"></textarea>
|
<div id="editor-pane-1" class="pane">
|
||||||
|
<div class="editor-wrapper">
|
||||||
|
<div id="line-numbers-1" class="line-numbers hidden"></div>
|
||||||
|
<textarea id="editor-1" class="editor-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="preview-pane-1" class="pane">
|
||||||
|
<div id="preview-1" class="preview-content"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="preview-pane" class="pane">
|
|
||||||
<div id="preview"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+266
-8
@@ -2,6 +2,7 @@ const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron')
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
|
const XLSX = require('xlsx');
|
||||||
|
|
||||||
// Simple storage implementation to replace electron-store
|
// Simple storage implementation to replace electron-store
|
||||||
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
||||||
@@ -27,7 +28,7 @@ const store = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
let currentFile = null;
|
let currentFile = null; // This will now represent the active tab's file
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
@@ -47,6 +48,14 @@ function createWindow() {
|
|||||||
mainWindow.on('closed', () => {
|
mainWindow.on('closed', () => {
|
||||||
mainWindow = null;
|
mainWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle pending file from file association
|
||||||
|
if (app.pendingFile) {
|
||||||
|
mainWindow.webContents.once('dom-ready', () => {
|
||||||
|
openFileFromPath(app.pendingFile);
|
||||||
|
app.pendingFile = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMenu() {
|
function createMenu() {
|
||||||
@@ -75,6 +84,11 @@ function createMenu() {
|
|||||||
click: saveAsFile
|
click: saveAsFile
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Import Document...',
|
||||||
|
accelerator: 'CmdOrCtrl+I',
|
||||||
|
click: importDocument
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Export',
|
label: 'Export',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -84,7 +98,14 @@ function createMenu() {
|
|||||||
{ label: 'LaTeX', click: () => exportFile('latex') },
|
{ label: 'LaTeX', click: () => exportFile('latex') },
|
||||||
{ label: 'RTF', click: () => exportFile('rtf') },
|
{ label: 'RTF', click: () => exportFile('rtf') },
|
||||||
{ label: 'ODT', click: () => exportFile('odt') },
|
{ label: 'ODT', click: () => exportFile('odt') },
|
||||||
{ label: 'EPUB', click: () => exportFile('epub') }
|
{ label: 'EPUB', click: () => exportFile('epub') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'PowerPoint (PPTX)', click: () => exportFile('pptx') },
|
||||||
|
{ label: 'OpenDocument Presentation (ODP)', click: () => exportFile('odp') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Excel (XLSX)', click: () => exportSpreadsheet('xlsx') },
|
||||||
|
{ label: 'Excel Legacy (XLS)', click: () => exportSpreadsheet('xls') },
|
||||||
|
{ label: 'OpenDocument Spreadsheet (ODS)', click: () => exportSpreadsheet('ods') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -99,12 +120,18 @@ function createMenu() {
|
|||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
|
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
|
||||||
{ label: 'Redo', accelerator: 'CmdOrCtrl+Y', role: 'redo' },
|
{ label: 'Redo', accelerator: 'CmdOrCtrl+Shift+Z', role: 'redo' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
||||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
||||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
||||||
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' }
|
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Find & Replace',
|
||||||
|
accelerator: 'CmdOrCtrl+F',
|
||||||
|
click: () => mainWindow.webContents.send('toggle-find')
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -126,6 +153,27 @@ function createMenu() {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Font Size',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Increase Font Size',
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+Plus',
|
||||||
|
click: () => mainWindow.webContents.send('adjust-font-size', 'increase')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Decrease Font Size',
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+-',
|
||||||
|
click: () => mainWindow.webContents.send('adjust-font-size', 'decrease')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Reset Font Size',
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+0',
|
||||||
|
click: () => mainWindow.webContents.send('adjust-font-size', 'reset')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
{ label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' },
|
{ label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' },
|
||||||
{ label: 'Toggle DevTools', accelerator: 'F12', role: 'toggleDevTools' },
|
{ label: 'Toggle DevTools', accelerator: 'F12', role: 'toggleDevTools' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -144,14 +192,14 @@ function createMenu() {
|
|||||||
type: 'info',
|
type: 'info',
|
||||||
title: 'About PanConverter',
|
title: 'About PanConverter',
|
||||||
message: 'PanConverter',
|
message: 'PanConverter',
|
||||||
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.0.0\nLicense: MIT',
|
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.3.4\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Enhanced PDF export with LaTeX engines\n• File association support for .md files\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• Multiple themes support\n• Undo/redo functionality',
|
||||||
buttons: ['OK']
|
buttons: ['OK']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Documentation',
|
label: 'Documentation',
|
||||||
click: () => shell.openExternal('https://github.com/yourusername/pan-converter')
|
click: () => shell.openExternal('https://github.com/amitwh/pan-converter')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -206,12 +254,54 @@ function exportFile(format) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (outputFile) {
|
if (outputFile) {
|
||||||
const pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`;
|
let pandocCmd = `pandoc "${currentFile}" -o "${outputFile}"`;
|
||||||
|
|
||||||
|
// Add specific options for PDF export to ensure proper generation
|
||||||
|
if (format === 'pdf') {
|
||||||
|
pandocCmd = `pandoc "${currentFile}" --pdf-engine=xelatex -V geometry:margin=1in -o "${outputFile}"`;
|
||||||
|
// Try with different PDF engines if xelatex fails
|
||||||
|
exec(pandocCmd, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
// Fallback to pdflatex
|
||||||
|
const fallbackCmd = `pandoc "${currentFile}" --pdf-engine=pdflatex -V geometry:margin=1in -o "${outputFile}"`;
|
||||||
|
exec(fallbackCmd, (fallbackError, fallbackStdout, fallbackStderr) => {
|
||||||
|
if (fallbackError) {
|
||||||
|
// Final fallback to wkhtmltopdf
|
||||||
|
const htmlToPdfCmd = `pandoc "${currentFile}" -t html5 | wkhtmltopdf - "${outputFile}"`;
|
||||||
|
exec(htmlToPdfCmd, (finalError) => {
|
||||||
|
if (finalError) {
|
||||||
|
dialog.showErrorBox('PDF Export Error',
|
||||||
|
`Failed to export PDF. Please ensure you have one of the following installed:\n` +
|
||||||
|
`• XeLaTeX (recommended): sudo apt-get install texlive-xetex\n` +
|
||||||
|
`• PDFLaTeX: sudo apt-get install texlive-latex-base\n` +
|
||||||
|
`• wkhtmltopdf: sudo apt-get install wkhtmltopdf\n\n` +
|
||||||
|
`Error: ${finalError.message}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showExportSuccess(outputFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showExportSuccess(outputFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showExportSuccess(outputFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
exec(pandocCmd, (error, stdout, stderr) => {
|
exec(pandocCmd, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
dialog.showErrorBox('Export Error', `Failed to export: ${error.message}\n\nMake sure Pandoc is installed.`);
|
dialog.showErrorBox('Export Error', `Failed to export: ${error.message}\n\nMake sure Pandoc is installed.`);
|
||||||
} else {
|
} else {
|
||||||
|
showExportSuccess(outputFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showExportSuccess(outputFile) {
|
||||||
dialog.showMessageBox(mainWindow, {
|
dialog.showMessageBox(mainWindow, {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: 'Export Complete',
|
title: 'Export Complete',
|
||||||
@@ -219,10 +309,55 @@ function exportFile(format) {
|
|||||||
buttons: ['OK']
|
buttons: ['OK']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportSpreadsheet(format) {
|
||||||
|
if (!currentFile) {
|
||||||
|
dialog.showErrorBox('Error', 'Please save the file first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request content from renderer
|
||||||
|
mainWindow.webContents.send('get-content-for-spreadsheet', format);
|
||||||
|
}
|
||||||
|
|
||||||
|
function importDocument() {
|
||||||
|
const files = dialog.showOpenDialogSync(mainWindow, {
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [
|
||||||
|
{ name: 'Documents', extensions: ['docx', 'odt', 'rtf', 'html', 'tex', 'epub', 'pdf'] },
|
||||||
|
{ name: 'Presentations', extensions: ['pptx', 'odp'] },
|
||||||
|
{ name: 'All Files', extensions: ['*'] }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (files && files[0]) {
|
||||||
|
const inputFile = files[0];
|
||||||
|
const outputFile = inputFile.replace(/\.[^/.]+$/, '.md');
|
||||||
|
|
||||||
|
// Convert to markdown using pandoc
|
||||||
|
const pandocCmd = `pandoc "${inputFile}" -t markdown -o "${outputFile}"`;
|
||||||
|
|
||||||
|
exec(pandocCmd, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
dialog.showErrorBox('Import Error', `Failed to import: ${error.message}\n\nMake sure Pandoc is installed.`);
|
||||||
|
} else {
|
||||||
|
// Open the converted markdown file
|
||||||
|
currentFile = outputFile;
|
||||||
|
const content = fs.readFileSync(outputFile, 'utf-8');
|
||||||
|
mainWindow.webContents.send('file-opened', { path: outputFile, content });
|
||||||
|
|
||||||
|
dialog.showMessageBox(mainWindow, {
|
||||||
|
type: 'info',
|
||||||
|
title: 'Import Complete',
|
||||||
|
message: `Document imported successfully as ${outputFile}`,
|
||||||
|
buttons: ['OK']
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setTheme(theme) {
|
function setTheme(theme) {
|
||||||
store.set('theme', theme);
|
store.set('theme', theme);
|
||||||
mainWindow.webContents.send('theme-changed', theme);
|
mainWindow.webContents.send('theme-changed', theme);
|
||||||
@@ -247,7 +382,108 @@ ipcMain.on('get-theme', (event) => {
|
|||||||
event.reply('theme-changed', theme);
|
event.reply('theme-changed', theme);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.whenReady().then(createWindow);
|
// Handle tab file tracking for exports
|
||||||
|
ipcMain.on('set-current-file', (event, filePath) => {
|
||||||
|
currentFile = filePath;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('export-spreadsheet', (event, { content, format }) => {
|
||||||
|
const outputFile = dialog.showSaveDialogSync(mainWindow, {
|
||||||
|
defaultPath: currentFile.replace(/\.[^/.]+$/, `.${format}`),
|
||||||
|
filters: [
|
||||||
|
{ name: format.toUpperCase(), extensions: [format] }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (outputFile) {
|
||||||
|
try {
|
||||||
|
// Parse markdown content to extract tables
|
||||||
|
const tables = extractTablesFromMarkdown(content);
|
||||||
|
|
||||||
|
if (tables.length === 0) {
|
||||||
|
dialog.showErrorBox('Export Error', 'No tables found in the markdown content');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create workbook
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
tables.forEach((table, index) => {
|
||||||
|
const ws = XLSX.utils.aoa_to_sheet(table);
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, `Table ${index + 1}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write file
|
||||||
|
XLSX.writeFile(wb, outputFile);
|
||||||
|
|
||||||
|
dialog.showMessageBox(mainWindow, {
|
||||||
|
type: 'info',
|
||||||
|
title: 'Export Complete',
|
||||||
|
message: `Spreadsheet exported successfully to ${outputFile}`,
|
||||||
|
buttons: ['OK']
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
dialog.showErrorBox('Export Error', `Failed to export: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to extract tables from markdown
|
||||||
|
function extractTablesFromMarkdown(markdown) {
|
||||||
|
const tables = [];
|
||||||
|
const lines = markdown.split('\n');
|
||||||
|
let currentTable = [];
|
||||||
|
let inTable = false;
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.includes('|')) {
|
||||||
|
if (!inTable) {
|
||||||
|
inTable = true;
|
||||||
|
currentTable = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip separator lines (|---|---|)
|
||||||
|
if (!line.match(/^\s*\|?\s*:?-+:?\s*\|/)) {
|
||||||
|
const cells = line.split('|')
|
||||||
|
.map(cell => cell.trim())
|
||||||
|
.filter(cell => cell !== '');
|
||||||
|
|
||||||
|
if (cells.length > 0) {
|
||||||
|
currentTable.push(cells);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (inTable && line.trim() === '') {
|
||||||
|
// End of table
|
||||||
|
if (currentTable.length > 0) {
|
||||||
|
tables.push(currentTable);
|
||||||
|
}
|
||||||
|
currentTable = [];
|
||||||
|
inTable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add last table if exists
|
||||||
|
if (currentTable.length > 0) {
|
||||||
|
tables.push(currentTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
// Handle file association on app startup
|
||||||
|
// Process all command line arguments except the first two (node and script path)
|
||||||
|
const fileArgs = process.argv.slice(2);
|
||||||
|
for (const arg of fileArgs) {
|
||||||
|
if ((arg.endsWith('.md') || arg.endsWith('.markdown')) && fs.existsSync(arg)) {
|
||||||
|
// Store the file to open after window is ready
|
||||||
|
app.pendingFile = arg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
@@ -260,3 +496,25 @@ app.on('activate', () => {
|
|||||||
createWindow();
|
createWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle file opening on macOS
|
||||||
|
app.on('open-file', (event, filePath) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (mainWindow) {
|
||||||
|
openFileFromPath(filePath);
|
||||||
|
} else {
|
||||||
|
// Store the file path to open after window is created
|
||||||
|
app.pendingFile = filePath;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle file opening from command line or file association
|
||||||
|
function openFileFromPath(filePath) {
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
currentFile = filePath;
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
if (mainWindow && mainWindow.webContents) {
|
||||||
|
mainWindow.webContents.send('file-opened', { path: filePath, content });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+508
-154
@@ -17,202 +17,556 @@ marked.setOptions({
|
|||||||
gfm: true
|
gfm: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Elements
|
// Tab Management
|
||||||
const editor = document.getElementById('editor');
|
class TabManager {
|
||||||
const preview = document.getElementById('preview');
|
constructor() {
|
||||||
const previewPane = document.getElementById('preview-pane');
|
this.tabs = new Map();
|
||||||
const editorPane = document.getElementById('editor-pane');
|
this.activeTabId = 1;
|
||||||
const statusText = document.getElementById('status-text');
|
this.nextTabId = 2;
|
||||||
const wordCount = document.getElementById('word-count');
|
this.isPreviewVisible = true;
|
||||||
|
this.showLineNumbers = false;
|
||||||
|
|
||||||
// State
|
// Initialize first tab
|
||||||
let isPreviewVisible = true;
|
this.tabs.set(1, {
|
||||||
let currentContent = '';
|
id: 1,
|
||||||
let isDirty = false;
|
title: 'Untitled',
|
||||||
|
content: '',
|
||||||
// Initialize
|
filePath: null,
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
isDirty: false,
|
||||||
// Request current theme
|
undoStack: [],
|
||||||
ipcRenderer.send('get-theme');
|
redoStack: [],
|
||||||
|
findMatches: [],
|
||||||
// Set up auto-save interval
|
currentMatchIndex: -1
|
||||||
setInterval(autoSave, 30000); // Auto-save every 30 seconds
|
|
||||||
|
|
||||||
// Initialize with empty content
|
|
||||||
updatePreview();
|
|
||||||
updateWordCount();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Editor input handler
|
this.setupEventListeners();
|
||||||
editor.addEventListener('input', () => {
|
this.updateUI();
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
// Tab bar events
|
||||||
|
document.getElementById('new-tab-btn').addEventListener('click', () => this.createNewTab());
|
||||||
|
document.getElementById('tab-bar').addEventListener('click', (e) => {
|
||||||
|
if (e.target.classList.contains('tab-close')) {
|
||||||
|
e.stopPropagation();
|
||||||
|
const tabId = parseInt(e.target.closest('.tab').dataset.tabId);
|
||||||
|
this.closeTab(tabId);
|
||||||
|
} else if (e.target.closest('.tab')) {
|
||||||
|
const tabId = parseInt(e.target.closest('.tab').dataset.tabId);
|
||||||
|
this.switchToTab(tabId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Editor events for active tab
|
||||||
|
this.setupEditorEvents();
|
||||||
|
|
||||||
|
// Toolbar events
|
||||||
|
this.setupToolbarEvents();
|
||||||
|
|
||||||
|
// Find dialog events
|
||||||
|
this.setupFindEvents();
|
||||||
|
|
||||||
|
// Keyboard shortcuts
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
switch (e.key) {
|
||||||
|
case 'n':
|
||||||
|
e.preventDefault();
|
||||||
|
this.createNewTab();
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
if (this.tabs.size > 1) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.closeTab(this.activeTabId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
e.preventDefault();
|
||||||
|
this.createNewTab();
|
||||||
|
break;
|
||||||
|
case 'Tab':
|
||||||
|
if (this.tabs.size > 1) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.switchToNextTab();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createNewTab() {
|
||||||
|
const newTabId = this.nextTabId++;
|
||||||
|
const tab = {
|
||||||
|
id: newTabId,
|
||||||
|
title: 'Untitled',
|
||||||
|
content: '',
|
||||||
|
filePath: null,
|
||||||
|
isDirty: false,
|
||||||
|
undoStack: [],
|
||||||
|
redoStack: [],
|
||||||
|
findMatches: [],
|
||||||
|
currentMatchIndex: -1
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tabs.set(newTabId, tab);
|
||||||
|
this.createTabElements(tab);
|
||||||
|
this.switchToTab(newTabId);
|
||||||
|
this.updateTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
createTabElements(tab) {
|
||||||
|
// Create tab content container
|
||||||
|
const tabContent = document.createElement('div');
|
||||||
|
tabContent.className = 'tab-content';
|
||||||
|
tabContent.id = `tab-content-${tab.id}`;
|
||||||
|
tabContent.dataset.tabId = tab.id;
|
||||||
|
|
||||||
|
tabContent.innerHTML = `
|
||||||
|
<div id="editor-pane-${tab.id}" class="pane">
|
||||||
|
<div class="editor-wrapper">
|
||||||
|
<div id="line-numbers-${tab.id}" class="line-numbers hidden"></div>
|
||||||
|
<textarea id="editor-${tab.id}" class="editor-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="preview-pane-${tab.id}" class="pane">
|
||||||
|
<div id="preview-${tab.id}" class="preview-content"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.querySelector('.editor-container').appendChild(tabContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToTab(tabId) {
|
||||||
|
if (!this.tabs.has(tabId)) return;
|
||||||
|
|
||||||
|
// Save current tab state before switching
|
||||||
|
if (this.activeTabId && this.tabs.has(this.activeTabId)) {
|
||||||
|
this.saveCurrentTabState();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeTabId = tabId;
|
||||||
|
this.updateUI();
|
||||||
|
this.restoreTabState(tabId);
|
||||||
|
this.focusActiveEditor();
|
||||||
|
|
||||||
|
// Notify main process about current file for exports
|
||||||
|
const tab = this.tabs.get(tabId);
|
||||||
|
if (tab && tab.filePath) {
|
||||||
|
ipcRenderer.send('set-current-file', tab.filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToNextTab() {
|
||||||
|
const tabIds = Array.from(this.tabs.keys());
|
||||||
|
const currentIndex = tabIds.indexOf(this.activeTabId);
|
||||||
|
const nextIndex = (currentIndex + 1) % tabIds.length;
|
||||||
|
this.switchToTab(tabIds[nextIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeTab(tabId) {
|
||||||
|
if (this.tabs.size === 1) return; // Don't close the last tab
|
||||||
|
|
||||||
|
const tab = this.tabs.get(tabId);
|
||||||
|
if (tab.isDirty) {
|
||||||
|
// TODO: Show confirmation dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove tab elements
|
||||||
|
const tabElement = document.querySelector(`[data-tab-id="${tabId}"]`);
|
||||||
|
const tabContent = document.getElementById(`tab-content-${tabId}`);
|
||||||
|
|
||||||
|
if (tabElement && tabElement.classList.contains('tab')) {
|
||||||
|
tabElement.remove();
|
||||||
|
}
|
||||||
|
if (tabContent) {
|
||||||
|
tabContent.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tabs.delete(tabId);
|
||||||
|
|
||||||
|
// Switch to another tab if this was active
|
||||||
|
if (this.activeTabId === tabId) {
|
||||||
|
const remainingTabs = Array.from(this.tabs.keys());
|
||||||
|
this.switchToTab(remainingTabs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTabBar() {
|
||||||
|
const tabBar = document.getElementById('tab-bar');
|
||||||
|
const existingTabs = tabBar.querySelectorAll('.tab');
|
||||||
|
|
||||||
|
// Remove all existing tab elements except the new tab button
|
||||||
|
existingTabs.forEach(tab => tab.remove());
|
||||||
|
|
||||||
|
// Add tabs in order
|
||||||
|
const sortedTabs = Array.from(this.tabs.values()).sort((a, b) => a.id - b.id);
|
||||||
|
const newTabBtn = document.getElementById('new-tab-btn');
|
||||||
|
|
||||||
|
sortedTabs.forEach(tab => {
|
||||||
|
const tabElement = document.createElement('div');
|
||||||
|
tabElement.className = `tab ${tab.id === this.activeTabId ? 'active' : ''}`;
|
||||||
|
tabElement.dataset.tabId = tab.id;
|
||||||
|
|
||||||
|
const title = tab.filePath ?
|
||||||
|
tab.filePath.split('/').pop() :
|
||||||
|
tab.title;
|
||||||
|
|
||||||
|
const dirtyIndicator = tab.isDirty ? ' •' : '';
|
||||||
|
|
||||||
|
tabElement.innerHTML = `
|
||||||
|
<span class="tab-title">${title}${dirtyIndicator}</span>
|
||||||
|
<button class="tab-close" title="Close tab">×</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
tabBar.insertBefore(tabElement, newTabBtn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUI() {
|
||||||
|
// Show/hide tab contents
|
||||||
|
document.querySelectorAll('.tab-content').forEach(content => {
|
||||||
|
content.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeContent = document.getElementById(`tab-content-${this.activeTabId}`);
|
||||||
|
if (activeContent) {
|
||||||
|
activeContent.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update preview visibility
|
||||||
|
this.updatePreviewVisibility();
|
||||||
|
this.updateLineNumbers();
|
||||||
|
this.updateTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCurrentTabState() {
|
||||||
|
const tab = this.tabs.get(this.activeTabId);
|
||||||
|
if (!tab) return;
|
||||||
|
|
||||||
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
|
if (editor) {
|
||||||
|
tab.content = editor.value;
|
||||||
|
tab.isDirty = tab.content !== (tab.originalContent || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreTabState(tabId) {
|
||||||
|
const tab = this.tabs.get(tabId);
|
||||||
|
if (!tab) return;
|
||||||
|
|
||||||
|
const editor = document.getElementById(`editor-${tabId}`);
|
||||||
|
const preview = document.getElementById(`preview-${tabId}`);
|
||||||
|
|
||||||
|
if (editor) {
|
||||||
|
editor.value = tab.content;
|
||||||
|
this.updatePreview(tabId);
|
||||||
|
this.updateWordCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focusActiveEditor() {
|
||||||
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
|
if (editor) {
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
}
|
||||||
// Trigger input event
|
|
||||||
editor.dispatchEvent(new Event('input'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update preview
|
updatePreview(tabId = this.activeTabId) {
|
||||||
function updatePreview() {
|
const tab = this.tabs.get(tabId);
|
||||||
const html = marked.parse(editor.value);
|
const preview = document.getElementById(`preview-${tabId}`);
|
||||||
const clean = DOMPurify.sanitize(html);
|
|
||||||
preview.innerHTML = clean;
|
|
||||||
|
|
||||||
// Re-highlight code blocks
|
if (!tab || !preview) return;
|
||||||
preview.querySelectorAll('pre code').forEach((block) => {
|
|
||||||
hljs.highlightElement(block);
|
try {
|
||||||
});
|
const html = marked.parse(tab.content);
|
||||||
|
const sanitizedHtml = DOMPurify.sanitize(html);
|
||||||
|
preview.innerHTML = sanitizedHtml;
|
||||||
|
} catch (error) {
|
||||||
|
preview.innerHTML = '<p>Error rendering preview</p>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle preview visibility
|
updatePreviewVisibility() {
|
||||||
function togglePreview() {
|
document.querySelectorAll('.tab-content').forEach(content => {
|
||||||
isPreviewVisible = !isPreviewVisible;
|
const previewPane = content.querySelector('.pane:last-child');
|
||||||
|
const editorPane = content.querySelector('.pane:first-child');
|
||||||
|
|
||||||
if (isPreviewVisible) {
|
if (this.isPreviewVisible) {
|
||||||
previewPane.classList.remove('hidden');
|
previewPane.classList.remove('hidden');
|
||||||
editorPane.classList.remove('full-width');
|
editorPane.classList.remove('full-width');
|
||||||
} else {
|
} else {
|
||||||
previewPane.classList.add('hidden');
|
previewPane.classList.add('hidden');
|
||||||
editorPane.classList.add('full-width');
|
editorPane.classList.add('full-width');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update word count
|
updateLineNumbers() {
|
||||||
function updateWordCount() {
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
const text = editor.value;
|
const lineNumbers = document.getElementById(`line-numbers-${this.activeTabId}`);
|
||||||
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
||||||
const chars = text.length;
|
|
||||||
wordCount.textContent = `Words: ${words} | Characters: ${chars}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update status
|
if (!editor || !lineNumbers) return;
|
||||||
function updateStatus(text) {
|
|
||||||
statusText.textContent = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-save function
|
if (this.showLineNumbers) {
|
||||||
function autoSave() {
|
const lines = editor.value.split('\\n');
|
||||||
if (isDirty && currentContent) {
|
lineNumbers.innerHTML = lines.map((_, i) =>
|
||||||
ipcRenderer.send('save-current-file', currentContent);
|
`<div class="line-number">${i + 1}</div>`
|
||||||
isDirty = false;
|
).join('');
|
||||||
updateStatus('Auto-saved');
|
lineNumbers.classList.remove('hidden');
|
||||||
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 {
|
} else {
|
||||||
editor.value = '';
|
lineNumbers.classList.add('hidden');
|
||||||
currentContent = '';
|
}
|
||||||
updatePreview();
|
}
|
||||||
updateWordCount();
|
|
||||||
updateStatus('New file');
|
updateWordCount() {
|
||||||
|
const tab = this.tabs.get(this.activeTabId);
|
||||||
|
if (!tab) return;
|
||||||
|
|
||||||
|
const words = tab.content.trim().split(/\\s+/).filter(word => word.length > 0).length;
|
||||||
|
const chars = tab.content.length;
|
||||||
|
document.getElementById('word-count').textContent = `Words: ${words} | Characters: ${chars}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEditorEvents() {
|
||||||
|
// Set up editor events using event delegation
|
||||||
|
document.addEventListener('input', (e) => {
|
||||||
|
if (e.target.classList.contains('editor-textarea')) {
|
||||||
|
const tabId = parseInt(e.target.id.split('-')[1]);
|
||||||
|
if (tabId === this.activeTabId) {
|
||||||
|
this.handleEditorInput(tabId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('file-opened', (event, { path, content }) => {
|
document.addEventListener('scroll', (e) => {
|
||||||
editor.value = content;
|
if (e.target.classList.contains('editor-textarea')) {
|
||||||
currentContent = content;
|
const tabId = parseInt(e.target.id.split('-')[1]);
|
||||||
isDirty = false;
|
if (tabId === this.activeTabId) {
|
||||||
updatePreview();
|
this.updateLineNumbers();
|
||||||
updateWordCount();
|
}
|
||||||
updateStatus(`Opened: ${path}`);
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEditorInput(tabId) {
|
||||||
|
const tab = this.tabs.get(tabId);
|
||||||
|
if (!tab) return;
|
||||||
|
|
||||||
|
const editor = document.getElementById(`editor-${tabId}`);
|
||||||
|
tab.content = editor.value;
|
||||||
|
tab.isDirty = true;
|
||||||
|
|
||||||
|
this.updatePreview(tabId);
|
||||||
|
this.updateWordCount();
|
||||||
|
this.updateLineNumbers();
|
||||||
|
this.updateTabBar();
|
||||||
|
|
||||||
|
// Add to undo stack
|
||||||
|
this.pushUndoState(tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushUndoState(tabId) {
|
||||||
|
const tab = this.tabs.get(tabId);
|
||||||
|
if (!tab) return;
|
||||||
|
|
||||||
|
tab.undoStack.push(tab.content);
|
||||||
|
if (tab.undoStack.length > 50) {
|
||||||
|
tab.undoStack.shift();
|
||||||
|
}
|
||||||
|
tab.redoStack = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
const tab = this.tabs.get(this.activeTabId);
|
||||||
|
if (!tab || tab.undoStack.length === 0) return;
|
||||||
|
|
||||||
|
tab.redoStack.push(tab.content);
|
||||||
|
tab.content = tab.undoStack.pop();
|
||||||
|
|
||||||
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
|
if (editor) {
|
||||||
|
editor.value = tab.content;
|
||||||
|
this.updatePreview();
|
||||||
|
this.updateWordCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redo() {
|
||||||
|
const tab = this.tabs.get(this.activeTabId);
|
||||||
|
if (!tab || tab.redoStack.length === 0) return;
|
||||||
|
|
||||||
|
tab.undoStack.push(tab.content);
|
||||||
|
tab.content = tab.redoStack.pop();
|
||||||
|
|
||||||
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
|
if (editor) {
|
||||||
|
editor.value = tab.content;
|
||||||
|
this.updatePreview();
|
||||||
|
this.updateWordCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupToolbarEvents() {
|
||||||
|
// Existing toolbar setup...
|
||||||
|
document.getElementById('btn-preview-toggle').addEventListener('click', () => {
|
||||||
|
this.isPreviewVisible = !this.isPreviewVisible;
|
||||||
|
this.updatePreviewVisibility();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-line-numbers').addEventListener('click', () => {
|
||||||
|
this.showLineNumbers = !this.showLineNumbers;
|
||||||
|
this.updateLineNumbers();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add other toolbar events...
|
||||||
|
}
|
||||||
|
|
||||||
|
setupFindEvents() {
|
||||||
|
// Find dialog implementation...
|
||||||
|
document.getElementById('btn-find').addEventListener('click', () => {
|
||||||
|
document.getElementById('find-dialog').classList.remove('hidden');
|
||||||
|
document.getElementById('find-input').focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-find-close').addEventListener('click', () => {
|
||||||
|
document.getElementById('find-dialog').classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// File operations
|
||||||
|
openFile(filePath, content) {
|
||||||
|
let tab = this.tabs.get(this.activeTabId);
|
||||||
|
|
||||||
|
// If current tab is empty and untitled, reuse it
|
||||||
|
if (!tab.filePath && !tab.isDirty && tab.content === '') {
|
||||||
|
tab.filePath = filePath;
|
||||||
|
tab.title = filePath.split('/').pop();
|
||||||
|
tab.content = content;
|
||||||
|
tab.originalContent = content;
|
||||||
|
tab.isDirty = false;
|
||||||
|
} else {
|
||||||
|
// Create new tab for the file
|
||||||
|
this.createNewTab();
|
||||||
|
tab = this.tabs.get(this.activeTabId);
|
||||||
|
tab.filePath = filePath;
|
||||||
|
tab.title = filePath.split('/').pop();
|
||||||
|
tab.content = content;
|
||||||
|
tab.originalContent = content;
|
||||||
|
tab.isDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.restoreTabState(this.activeTabId);
|
||||||
|
this.updateTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentContent() {
|
||||||
|
const tab = this.tabs.get(this.activeTabId);
|
||||||
|
return tab ? tab.content : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentFilePath() {
|
||||||
|
const tab = this.tabs.get(this.activeTabId);
|
||||||
|
return tab ? tab.filePath : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize tab manager
|
||||||
|
let tabManager;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
tabManager = new TabManager();
|
||||||
|
|
||||||
|
// Request current theme
|
||||||
|
ipcRenderer.send('get-theme');
|
||||||
|
|
||||||
|
// Set up auto-save interval
|
||||||
|
setInterval(() => {
|
||||||
|
// Auto-save logic for all tabs
|
||||||
|
tabManager.tabs.forEach(tab => {
|
||||||
|
if (tab.isDirty && tab.filePath) {
|
||||||
|
ipcRenderer.send('save-current-file', tab.content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 30000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// IPC event listeners
|
||||||
|
ipcRenderer.on('file-new', () => {
|
||||||
|
tabManager.createNewTab();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('file-opened', (event, data) => {
|
||||||
|
tabManager.openFile(data.path, data.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('file-save', () => {
|
ipcRenderer.on('file-save', () => {
|
||||||
ipcRenderer.send('save-current-file', editor.value);
|
const currentContent = tabManager.getCurrentContent();
|
||||||
isDirty = false;
|
const currentFilePath = tabManager.getCurrentFilePath();
|
||||||
updateStatus('Saved');
|
if (currentFilePath) {
|
||||||
|
ipcRenderer.send('save-current-file', currentContent);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('get-content-for-save', (event, path) => {
|
ipcRenderer.on('get-content-for-save', (event, filePath) => {
|
||||||
ipcRenderer.send('save-file', { path, content: editor.value });
|
const currentContent = tabManager.getCurrentContent();
|
||||||
isDirty = false;
|
ipcRenderer.send('save-file', { path: filePath, content: currentContent });
|
||||||
updateStatus(`Saved: ${path}`);
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('get-content-for-spreadsheet', (event, format) => {
|
||||||
|
const currentContent = tabManager.getCurrentContent();
|
||||||
|
ipcRenderer.send('export-spreadsheet', { content: currentContent, format });
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-preview', () => {
|
ipcRenderer.on('toggle-preview', () => {
|
||||||
togglePreview();
|
tabManager.isPreviewVisible = !tabManager.isPreviewVisible;
|
||||||
|
tabManager.updatePreviewVisibility();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('toggle-find', () => {
|
||||||
|
const findDialog = document.getElementById('find-dialog');
|
||||||
|
if (findDialog.classList.contains('hidden')) {
|
||||||
|
findDialog.classList.remove('hidden');
|
||||||
|
document.getElementById('find-input').focus();
|
||||||
|
} else {
|
||||||
|
findDialog.classList.add('hidden');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('theme-changed', (event, theme) => {
|
ipcRenderer.on('theme-changed', (event, theme) => {
|
||||||
// Remove all theme classes
|
document.body.className = `theme-${theme}`;
|
||||||
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
|
// Font size adjustment
|
||||||
document.addEventListener('keydown', (e) => {
|
let currentFontSize = parseInt(localStorage.getItem('fontSize')) || 15;
|
||||||
// Ctrl/Cmd + Enter to toggle preview
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
||||||
togglePreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tab key in editor
|
function updateFontSizes(size) {
|
||||||
if (e.key === 'Tab' && e.target === editor) {
|
const editors = document.querySelectorAll('#editor, .editor-textarea');
|
||||||
e.preventDefault();
|
const previews = document.querySelectorAll('#preview, .preview-content');
|
||||||
const start = editor.selectionStart;
|
|
||||||
const end = editor.selectionEnd;
|
|
||||||
|
|
||||||
editor.value = editor.value.substring(0, start) + ' ' + editor.value.substring(end);
|
editors.forEach(editor => {
|
||||||
editor.selectionStart = editor.selectionEnd = start + 4;
|
editor.style.fontSize = `${size}px`;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prevent accidental navigation
|
previews.forEach(preview => {
|
||||||
window.addEventListener('beforeunload', (e) => {
|
preview.style.fontSize = `${size}px`;
|
||||||
if (isDirty) {
|
});
|
||||||
e.preventDefault();
|
|
||||||
e.returnValue = '';
|
localStorage.setItem('fontSize', size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply saved font size on load
|
||||||
|
updateFontSizes(currentFontSize);
|
||||||
|
|
||||||
|
ipcRenderer.on('adjust-font-size', (event, action) => {
|
||||||
|
if (action === 'increase' && currentFontSize < 24) {
|
||||||
|
currentFontSize++;
|
||||||
|
} else if (action === 'decrease' && currentFontSize > 10) {
|
||||||
|
currentFontSize--;
|
||||||
|
} else if (action === 'reset') {
|
||||||
|
currentFontSize = 15;
|
||||||
|
}
|
||||||
|
updateFontSizes(currentFontSize);
|
||||||
});
|
});
|
||||||
+470
-48
@@ -16,6 +16,93 @@ body {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tab Bar */
|
||||||
|
.tab-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding: 0 8px;
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #e8e8e8;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
margin-right: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #999;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-title {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-close:hover {
|
||||||
|
background: #ddd;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-tab-button {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-tab-button:hover {
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab Content */
|
||||||
|
.tab-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content:not(.active) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Toolbar */
|
/* Toolbar */
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -65,8 +152,40 @@ body {
|
|||||||
.pane {
|
.pane {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pane:first-child {
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane:last-child {
|
||||||
|
padding: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px 24px 24px 24px;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy support for old selectors */
|
||||||
#editor-pane {
|
#editor-pane {
|
||||||
border-right: 1px solid #ddd;
|
border-right: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
@@ -76,7 +195,7 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -91,6 +210,7 @@ body {
|
|||||||
#preview {
|
#preview {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status Bar */
|
/* Status Bar */
|
||||||
@@ -105,31 +225,33 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Preview Styles */
|
/* Preview Styles */
|
||||||
#preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6 {
|
#preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6,
|
||||||
|
.preview-content h1, .preview-content h2, .preview-content h3, .preview-content h4, .preview-content h5, .preview-content h6 {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview h1 {
|
#preview h1, .preview-content h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
border-bottom: 1px solid #eaecef;
|
border-bottom: 1px solid #eaecef;
|
||||||
padding-bottom: 0.3em;
|
padding-bottom: 0.3em;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview h2 {
|
#preview h2, .preview-content h2 {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
border-bottom: 1px solid #eaecef;
|
border-bottom: 1px solid #eaecef;
|
||||||
padding-bottom: 0.3em;
|
padding-bottom: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview p {
|
#preview p, .preview-content p {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview code {
|
#preview code, .preview-content code {
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
@@ -138,7 +260,7 @@ body {
|
|||||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview pre {
|
#preview pre, .preview-content pre {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
@@ -148,7 +270,7 @@ body {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview pre code {
|
#preview pre code, .preview-content pre code {
|
||||||
display: inline;
|
display: inline;
|
||||||
max-width: auto;
|
max-width: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -160,54 +282,54 @@ body {
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview blockquote {
|
#preview blockquote, .preview-content blockquote {
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
color: #6a737d;
|
color: #6a737d;
|
||||||
border-left: 0.25em solid #dfe2e5;
|
border-left: 0.25em solid #dfe2e5;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview ul, #preview ol {
|
#preview ul, #preview ol, .preview-content ul, .preview-content ol {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview li {
|
#preview li, .preview-content li {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview a {
|
#preview a, .preview-content a {
|
||||||
color: #0366d6;
|
color: #0366d6;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview a:hover {
|
#preview a:hover, .preview-content a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview img {
|
#preview img, .preview-content img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview table {
|
#preview table, .preview-content table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview table th,
|
#preview table th, #preview table td,
|
||||||
#preview table td {
|
.preview-content table th, .preview-content table td {
|
||||||
padding: 6px 13px;
|
padding: 6px 13px;
|
||||||
border: 1px solid #dfe2e5;
|
border: 1px solid #dfe2e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview table th {
|
#preview table th, .preview-content table th {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background-color: #f6f8fa;
|
background-color: #f6f8fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
#preview table tr:nth-child(2n) {
|
#preview table tr:nth-child(2n), .preview-content table tr:nth-child(2n) {
|
||||||
background-color: #f6f8fa;
|
background-color: #f6f8fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +339,40 @@ body.theme-dark {
|
|||||||
color: #d4d4d4;
|
color: #d4d4d4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.theme-dark .tab-bar {
|
||||||
|
background: #2d2d30;
|
||||||
|
border-bottom-color: #3e3e42;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .tab {
|
||||||
|
background: #3c3c3c;
|
||||||
|
border-color: #555;
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .tab.active {
|
||||||
|
background: #1e1e1e;
|
||||||
|
border-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .tab-close {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .tab-close:hover {
|
||||||
|
background: #555;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .new-tab-button {
|
||||||
|
border-color: #555;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .new-tab-button:hover {
|
||||||
|
background: #3c3c3c;
|
||||||
|
}
|
||||||
|
|
||||||
body.theme-dark .toolbar {
|
body.theme-dark .toolbar {
|
||||||
background: #2d2d30;
|
background: #2d2d30;
|
||||||
border-bottom-color: #3e3e42;
|
border-bottom-color: #3e3e42;
|
||||||
@@ -235,6 +391,20 @@ body.theme-dark .toolbar-separator {
|
|||||||
background: #3e3e42;
|
background: #3e3e42;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.theme-dark .pane:first-child {
|
||||||
|
border-right-color: #3e3e42;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .editor-textarea {
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .pane:last-child {
|
||||||
|
background: #1e1e1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy support */
|
||||||
body.theme-dark #editor-pane {
|
body.theme-dark #editor-pane {
|
||||||
border-right-color: #3e3e42;
|
border-right-color: #3e3e42;
|
||||||
}
|
}
|
||||||
@@ -248,42 +418,42 @@ body.theme-dark #preview-pane {
|
|||||||
background: #252526;
|
background: #252526;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview {
|
body.theme-dark #preview, body.theme-dark .preview-content {
|
||||||
color: #d4d4d4;
|
color: #d4d4d4;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview h1,
|
body.theme-dark #preview h1, body.theme-dark #preview h2,
|
||||||
body.theme-dark #preview h2 {
|
body.theme-dark .preview-content h1, body.theme-dark .preview-content h2 {
|
||||||
border-bottom-color: #3e3e42;
|
border-bottom-color: #3e3e42;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview code {
|
body.theme-dark #preview code, body.theme-dark .preview-content code {
|
||||||
background-color: rgba(255,255,255,0.1);
|
background-color: rgba(255,255,255,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview pre {
|
body.theme-dark #preview pre, body.theme-dark .preview-content pre {
|
||||||
background-color: #2d2d30;
|
background-color: #2d2d30;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview blockquote {
|
body.theme-dark #preview blockquote, body.theme-dark .preview-content blockquote {
|
||||||
color: #808080;
|
color: #808080;
|
||||||
border-left-color: #3e3e42;
|
border-left-color: #3e3e42;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview a {
|
body.theme-dark #preview a, body.theme-dark .preview-content a {
|
||||||
color: #569cd6;
|
color: #569cd6;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview table th,
|
body.theme-dark #preview table th, body.theme-dark #preview table td,
|
||||||
body.theme-dark #preview table td {
|
body.theme-dark .preview-content table th, body.theme-dark .preview-content table td {
|
||||||
border-color: #3e3e42;
|
border-color: #3e3e42;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview table th {
|
body.theme-dark #preview table th, body.theme-dark .preview-content table th {
|
||||||
background-color: #2d2d30;
|
background-color: #2d2d30;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dark #preview table tr:nth-child(2n) {
|
body.theme-dark #preview table tr:nth-child(2n), body.theme-dark .preview-content table tr:nth-child(2n) {
|
||||||
background-color: #2d2d30;
|
background-color: #2d2d30;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +469,40 @@ body.theme-solarized {
|
|||||||
color: #657b83;
|
color: #657b83;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.theme-solarized .tab-bar {
|
||||||
|
background: #eee8d5;
|
||||||
|
border-bottom-color: #93a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-solarized .tab {
|
||||||
|
background: #fdf6e3;
|
||||||
|
border-color: #93a1a1;
|
||||||
|
color: #657b83;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-solarized .tab.active {
|
||||||
|
background: #fdf6e3;
|
||||||
|
border-color: #859900;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-solarized .tab-close {
|
||||||
|
color: #657b83;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-solarized .tab-close:hover {
|
||||||
|
background: #93a1a1;
|
||||||
|
color: #dc322f;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-solarized .new-tab-button {
|
||||||
|
border-color: #93a1a1;
|
||||||
|
color: #657b83;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-solarized .new-tab-button:hover {
|
||||||
|
background: #eee8d5;
|
||||||
|
}
|
||||||
|
|
||||||
body.theme-solarized .toolbar {
|
body.theme-solarized .toolbar {
|
||||||
background: #eee8d5;
|
background: #eee8d5;
|
||||||
border-bottom-color: #93a1a1;
|
border-bottom-color: #93a1a1;
|
||||||
@@ -318,19 +522,19 @@ body.theme-solarized #editor {
|
|||||||
color: #657b83;
|
color: #657b83;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-solarized #preview {
|
body.theme-solarized #preview, body.theme-solarized .preview-content {
|
||||||
color: #586e75;
|
color: #586e75;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-solarized #preview code {
|
body.theme-solarized #preview code, body.theme-solarized .preview-content code {
|
||||||
background-color: #eee8d5;
|
background-color: #eee8d5;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-solarized #preview pre {
|
body.theme-solarized #preview pre, body.theme-solarized .preview-content pre {
|
||||||
background-color: #eee8d5;
|
background-color: #eee8d5;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-solarized #preview a {
|
body.theme-solarized #preview a, body.theme-solarized .preview-content a {
|
||||||
color: #268bd2;
|
color: #268bd2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +544,40 @@ body.theme-monokai {
|
|||||||
color: #f8f8f2;
|
color: #f8f8f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .tab-bar {
|
||||||
|
background: #3e3d32;
|
||||||
|
border-bottom-color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .tab {
|
||||||
|
background: #49483e;
|
||||||
|
border-color: #75715e;
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .tab.active {
|
||||||
|
background: #272822;
|
||||||
|
border-color: #a6e22e;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .tab-close {
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .tab-close:hover {
|
||||||
|
background: #75715e;
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .new-tab-button {
|
||||||
|
border-color: #75715e;
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .new-tab-button:hover {
|
||||||
|
background: #49483e;
|
||||||
|
}
|
||||||
|
|
||||||
body.theme-monokai .toolbar {
|
body.theme-monokai .toolbar {
|
||||||
background: #3e3d32;
|
background: #3e3d32;
|
||||||
border-bottom-color: #75715e;
|
border-bottom-color: #75715e;
|
||||||
@@ -359,33 +597,38 @@ body.theme-monokai #editor {
|
|||||||
color: #f8f8f2;
|
color: #f8f8f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .editor-textarea {
|
||||||
|
background: #272822;
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
body.theme-monokai #preview-pane {
|
body.theme-monokai #preview-pane {
|
||||||
background: #272822;
|
background: #272822;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-monokai #preview {
|
body.theme-monokai #preview, body.theme-monokai .preview-content {
|
||||||
color: #f8f8f2;
|
color: #f8f8f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-monokai #preview h1,
|
body.theme-monokai #preview h1, body.theme-monokai #preview h2,
|
||||||
body.theme-monokai #preview h2 {
|
body.theme-monokai .preview-content h1, body.theme-monokai .preview-content h2 {
|
||||||
border-bottom-color: #75715e;
|
border-bottom-color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-monokai #preview code {
|
body.theme-monokai #preview code, body.theme-monokai .preview-content code {
|
||||||
background-color: #3e3d32;
|
background-color: #3e3d32;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-monokai #preview pre {
|
body.theme-monokai #preview pre, body.theme-monokai .preview-content pre {
|
||||||
background-color: #3e3d32;
|
background-color: #3e3d32;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-monokai #preview blockquote {
|
body.theme-monokai #preview blockquote, body.theme-monokai .preview-content blockquote {
|
||||||
color: #75715e;
|
color: #75715e;
|
||||||
border-left-color: #75715e;
|
border-left-color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-monokai #preview a {
|
body.theme-monokai #preview a, body.theme-monokai .preview-content a {
|
||||||
color: #66d9ef;
|
color: #66d9ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +644,40 @@ body.theme-github {
|
|||||||
color: #24292e;
|
color: #24292e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.theme-github .tab-bar {
|
||||||
|
background: #fafbfc;
|
||||||
|
border-bottom-color: #e1e4e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-github .tab {
|
||||||
|
background: #f6f8fa;
|
||||||
|
border-color: #e1e4e8;
|
||||||
|
color: #24292e;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-github .tab.active {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #0366d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-github .tab-close {
|
||||||
|
color: #586069;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-github .tab-close:hover {
|
||||||
|
background: #e1e4e8;
|
||||||
|
color: #cb2431;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-github .new-tab-button {
|
||||||
|
border-color: #e1e4e8;
|
||||||
|
color: #586069;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-github .new-tab-button:hover {
|
||||||
|
background: #f6f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
body.theme-github .toolbar {
|
body.theme-github .toolbar {
|
||||||
background: #fafbfc;
|
background: #fafbfc;
|
||||||
border-bottom-color: #e1e4e8;
|
border-bottom-color: #e1e4e8;
|
||||||
@@ -420,29 +697,29 @@ body.theme-github #editor {
|
|||||||
color: #24292e;
|
color: #24292e;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-github #preview {
|
body.theme-github #preview, body.theme-github .preview-content {
|
||||||
color: #24292e;
|
color: #24292e;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-github #preview h1,
|
body.theme-github #preview h1, body.theme-github #preview h2,
|
||||||
body.theme-github #preview h2 {
|
body.theme-github .preview-content h1, body.theme-github .preview-content h2 {
|
||||||
border-bottom-color: #eaecef;
|
border-bottom-color: #eaecef;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-github #preview code {
|
body.theme-github #preview code, body.theme-github .preview-content code {
|
||||||
background-color: rgba(27,31,35,0.05);
|
background-color: rgba(27,31,35,0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-github #preview pre {
|
body.theme-github #preview pre, body.theme-github .preview-content pre {
|
||||||
background-color: #f6f8fa;
|
background-color: #f6f8fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-github #preview blockquote {
|
body.theme-github #preview blockquote, body.theme-github .preview-content blockquote {
|
||||||
color: #6a737d;
|
color: #6a737d;
|
||||||
border-left-color: #dfe2e5;
|
border-left-color: #dfe2e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-github #preview a {
|
body.theme-github #preview a, body.theme-github .preview-content a {
|
||||||
color: #0366d6;
|
color: #0366d6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +730,15 @@ body.theme-github .status-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Hide preview pane by default */
|
/* Hide preview pane by default */
|
||||||
|
.pane.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane.full-width {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy support */
|
||||||
#preview-pane.hidden {
|
#preview-pane.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -460,3 +746,139 @@ body.theme-github .status-bar {
|
|||||||
#editor-pane.full-width {
|
#editor-pane.full-width {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find & Replace Dialog */
|
||||||
|
.find-dialog {
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
right: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
z-index: 1000;
|
||||||
|
min-width: 400px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-dialog.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-controls input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-controls button {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-controls button:hover {
|
||||||
|
background: #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-info {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor Wrapper with Line Numbers */
|
||||||
|
.editor-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers {
|
||||||
|
min-width: 40px;
|
||||||
|
padding: 8px 4px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #666;
|
||||||
|
text-align: right;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers .line-number {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust editor when line numbers are shown */
|
||||||
|
.editor-wrapper #editor {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme support for find dialog */
|
||||||
|
body.theme-dark .find-dialog {
|
||||||
|
background: #2d2d2d;
|
||||||
|
border-color: #555;
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .find-controls input {
|
||||||
|
background: #333;
|
||||||
|
border-color: #555;
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .find-controls button {
|
||||||
|
background: #404040;
|
||||||
|
border-color: #555;
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .find-controls button:hover {
|
||||||
|
background: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-dark .line-numbers {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border-right-color: #555;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Additional theme support for line numbers */
|
||||||
|
body.theme-solarized .line-numbers {
|
||||||
|
background: #fdf6e3;
|
||||||
|
border-right-color: #eee8d5;
|
||||||
|
color: #93a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-monokai .line-numbers {
|
||||||
|
background: #2f2f2f;
|
||||||
|
border-right-color: #555;
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.theme-github .line-numbers {
|
||||||
|
background: #fafbfc;
|
||||||
|
border-right-color: #e1e4e8;
|
||||||
|
color: #586069;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user