diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0959030..669213b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -21,7 +21,13 @@ "Bash(findstr:*)", "Bash(cmd /c \"dir /b H:\\Coding\\pan-converter\\dist\\*.exe H:\\Coding\\pan-converter\\dist\\*.zip 2>nul | findstr 1.9.1\")", "Bash(cmd /c \"dir H:\\Coding\\pan-converter\\dist\\*1.9.1*\")", - "Bash(gh release create v1.9.1 --title \"v1.9.1: ASCII Art & Code Block Preservation\" --notes \"$(cat <<''EOF''\n# Release v1.9.1: ASCII Art & Code Block Preservation\n\n## šŸŽÆ Overview\nThis release fixes the export of ASCII art, charts, tables, and flowcharts to match the preview exactly. Code blocks now export with perfect monospace formatting, preserving all line breaks, spacing, and box-drawing characters.\n\n## šŸŽØ What''s Fixed\n\n### ASCII Art & Code Block Preservation\n- **Code blocks now export exactly as shown in preview**\n- Each line rendered separately to preserve exact spacing and alignment\n- Monospace font (Consolas) used consistently across all export formats\n- Box-drawing characters, arrows, and flowcharts render correctly\n- No more text wrapping or broken formatting in exports\n\n### Export Format Improvements\n\n#### DOCX (Enhanced) - Word Template Exporter\n- Each code line rendered as separate paragraph with exact spacing\n- Added `` and `` XML properties\n- Light gray background (#F5F5F5) for visual distinction\n- Perfect alignment preserved for ASCII art and flowcharts\n\n#### PDF Export (All Methods)\n- Added `-V monofont=\"Consolas\"` for proper code block rendering\n- Added `--highlight-style=tango` for syntax highlighting\n- Works with XeLaTeX, PDFLaTeX, and LuaLaTeX engines\n\n#### HTML & Electron PDF Fallback\n- Enhanced CSS: `white-space: pre`, `word-wrap: normal`\n- Print media queries preserve code block formatting\n- `overflow: visible` prevents content truncation\n\n### Improved ASCII Art Detection\n- Extended Unicode box-drawing character support\n- Added rounded corners: ╭╮╯╰\n- Added heavy box characters: ā”ƒā”ā”ā”“ā”—ā”›ā”£ā”«ā”³ā”»ā•‹\n- Added more arrow types: ↔↕⇒⇐⇓⇑\n- Additional ASCII patterns detected\n\n## šŸ“¦ Available Downloads\n\nChoose the package that suits your needs:\n\n1. **PanConverter-Setup-1.9.1.exe** (Recommended)\n - Full installer with uninstaller\n - File associations and Start Menu shortcuts\n - ~104 MB\n\n2. **PanConverter-1.9.1-x64.exe**\n - Portable executable, no installation required\n - Run directly from any folder\n - ~104 MB\n\n3. **PanConverter-1.9.1-x64.zip**\n - ZIP archive with unpacked application\n - For manual extraction and deployment\n - ~145 MB (uncompressed files)\n\n## šŸ”§ Technical Changes\n\n### Code Changes\n- `src/wordTemplateExporter.js`: Enhanced `createCodeBlockXml()` - renders each line separately\n- `src/wordTemplateExporter.js`: Improved `isAsciiArt()` - extended character detection\n- `src/main.js`: Added monofont settings to all PDF export paths\n- `src/main.js`: Enhanced HTML/Electron PDF export CSS\n\n### Key Properties for ASCII Preservation\n```css\npre {\n white-space: pre;\n word-wrap: normal;\n font-family: Consolas, Monaco, ''Courier New'', monospace;\n}\n```\n\n```xml\n\n \n \n \n\n```\n\n## šŸ”„ Upgrade Notes\n\nIf upgrading from v1.9.0 or earlier:\n- All your settings and preferences will be preserved\n- Recent files list will remain intact\n- Word template selections will be maintained\n- No breaking changes to existing workflows\n\n## šŸ“ Installation Instructions\n\n### Using the Installer (Recommended)\n1. Download `PanConverter-Setup-1.9.1.exe`\n2. Run the installer\n3. Follow the installation wizard\n4. Launch from Start Menu or Desktop shortcut\n\n### Using Portable Version\n1. Download `PanConverter-1.9.1-x64.exe`\n2. Save to any folder (e.g., Documents, Desktop)\n3. Double-click to run (no installation needed)\n4. Create your own shortcut if desired\n\n## šŸ’¬ Feedback\nHave suggestions or found a bug? Please [open an issue](https://github.com/amitwh/pan-converter/issues).\n\n---\n\n**Full Changelog**: https://github.com/amitwh/pan-converter/compare/v1.9.0...v1.9.1\n\nšŸ¤– Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude \nEOF\n)\" \"dist/PanConverter-1.9.1-x64.exe\" \"dist/PanConverter-1.9.1-x64.zip\" \"dist/PanConverter-Setup-1.9.1.exe\")" + "Bash(gh release create v1.9.1 --title \"v1.9.1: ASCII Art & Code Block Preservation\" --notes \"$(cat <<''EOF''\n# Release v1.9.1: ASCII Art & Code Block Preservation\n\n## šŸŽÆ Overview\nThis release fixes the export of ASCII art, charts, tables, and flowcharts to match the preview exactly. Code blocks now export with perfect monospace formatting, preserving all line breaks, spacing, and box-drawing characters.\n\n## šŸŽØ What''s Fixed\n\n### ASCII Art & Code Block Preservation\n- **Code blocks now export exactly as shown in preview**\n- Each line rendered separately to preserve exact spacing and alignment\n- Monospace font (Consolas) used consistently across all export formats\n- Box-drawing characters, arrows, and flowcharts render correctly\n- No more text wrapping or broken formatting in exports\n\n### Export Format Improvements\n\n#### DOCX (Enhanced) - Word Template Exporter\n- Each code line rendered as separate paragraph with exact spacing\n- Added `` and `` XML properties\n- Light gray background (#F5F5F5) for visual distinction\n- Perfect alignment preserved for ASCII art and flowcharts\n\n#### PDF Export (All Methods)\n- Added `-V monofont=\"Consolas\"` for proper code block rendering\n- Added `--highlight-style=tango` for syntax highlighting\n- Works with XeLaTeX, PDFLaTeX, and LuaLaTeX engines\n\n#### HTML & Electron PDF Fallback\n- Enhanced CSS: `white-space: pre`, `word-wrap: normal`\n- Print media queries preserve code block formatting\n- `overflow: visible` prevents content truncation\n\n### Improved ASCII Art Detection\n- Extended Unicode box-drawing character support\n- Added rounded corners: ╭╮╯╰\n- Added heavy box characters: ā”ƒā”ā”ā”“ā”—ā”›ā”£ā”«ā”³ā”»ā•‹\n- Added more arrow types: ↔↕⇒⇐⇓⇑\n- Additional ASCII patterns detected\n\n## šŸ“¦ Available Downloads\n\nChoose the package that suits your needs:\n\n1. **PanConverter-Setup-1.9.1.exe** (Recommended)\n - Full installer with uninstaller\n - File associations and Start Menu shortcuts\n - ~104 MB\n\n2. **PanConverter-1.9.1-x64.exe**\n - Portable executable, no installation required\n - Run directly from any folder\n - ~104 MB\n\n3. **PanConverter-1.9.1-x64.zip**\n - ZIP archive with unpacked application\n - For manual extraction and deployment\n - ~145 MB (uncompressed files)\n\n## šŸ”§ Technical Changes\n\n### Code Changes\n- `src/wordTemplateExporter.js`: Enhanced `createCodeBlockXml()` - renders each line separately\n- `src/wordTemplateExporter.js`: Improved `isAsciiArt()` - extended character detection\n- `src/main.js`: Added monofont settings to all PDF export paths\n- `src/main.js`: Enhanced HTML/Electron PDF export CSS\n\n### Key Properties for ASCII Preservation\n```css\npre {\n white-space: pre;\n word-wrap: normal;\n font-family: Consolas, Monaco, ''Courier New'', monospace;\n}\n```\n\n```xml\n\n \n \n \n\n```\n\n## šŸ”„ Upgrade Notes\n\nIf upgrading from v1.9.0 or earlier:\n- All your settings and preferences will be preserved\n- Recent files list will remain intact\n- Word template selections will be maintained\n- No breaking changes to existing workflows\n\n## šŸ“ Installation Instructions\n\n### Using the Installer (Recommended)\n1. Download `PanConverter-Setup-1.9.1.exe`\n2. Run the installer\n3. Follow the installation wizard\n4. Launch from Start Menu or Desktop shortcut\n\n### Using Portable Version\n1. Download `PanConverter-1.9.1-x64.exe`\n2. Save to any folder (e.g., Documents, Desktop)\n3. Double-click to run (no installation needed)\n4. Create your own shortcut if desired\n\n## šŸ’¬ Feedback\nHave suggestions or found a bug? Please [open an issue](https://github.com/amitwh/pan-converter/issues).\n\n---\n\n**Full Changelog**: https://github.com/amitwh/pan-converter/compare/v1.9.0...v1.9.1\n\nšŸ¤– Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude \nEOF\n)\" \"dist/PanConverter-1.9.1-x64.exe\" \"dist/PanConverter-1.9.1-x64.zip\" \"dist/PanConverter-Setup-1.9.1.exe\")", + "Bash(wc:*)", + "Bash(npm test)", + "Bash(npm run generate-icons:*)", + "Bash(node --check:*)", + "Bash(timeout /t 3)", + "Bash(timeout /t 8)" ], "deny": [], "ask": [] diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0256aae --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "useTabs": false, + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "always", + "printWidth": 100, + "endOfLine": "auto" +} diff --git a/CLAUDE.md b/CLAUDE.md index e02aac4..9996117 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,11 +1,11 @@ -# PanConverter - Claude Development Guide +# MarkdownConverter - Claude Development Guide ## Project Overview -**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. +**MarkdownConverter** (formerly PanConverter) is a cross-platform Markdown editor and universal file converter powered by Pandoc, built with Electron. It provides professional-grade editing capabilities with comprehensive export options, media conversion, and PDF editing tools. -**Current Version**: v1.9.2 -**Author**: Amit Haridas (amit.wh@gmail.com) +**Current Version**: v3.0.0 +**Author**: Amit Haridas / ConcreteInfo (amit.wh@gmail.com) **License**: MIT **Repository**: https://github.com/amitwh/pan-converter @@ -25,17 +25,28 @@ ### 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 # Base styling with multi-theme support -└── styles-modern.css # Modern glassmorphism UI design system (v1.7.1) +ā”œā”€ā”€ main.js # Electron main process, menu system, IPC handlers +ā”œā”€ā”€ preload.js # Secure IPC bridge for context isolation (v2.2.0) +ā”œā”€ā”€ renderer.js # TabManager class, multi-file editing, event handling +ā”œā”€ā”€ index.html # Application layout with tabbed interface +ā”œā”€ā”€ ascii-generator.html # Separate window for ASCII Art Generator (v3.0.0) +ā”œā”€ā”€ table-generator.html # Separate window for Table Generator (v3.0.0) +ā”œā”€ā”€ styles.css # Base styling with multi-theme support +ā”œā”€ā”€ styles-modern.css # Modern glassmorphism UI design system (v1.7.1) +ā”œā”€ā”€ styles-concreteinfo.css # ConcreteInfo brand theme (v3.0.0) +└── wordTemplateExporter.js # Word template-based export (v1.7.9) assets/ -└── icon.png # Application icon +ā”œā”€ā”€ icon.png # Application icon (512x512) +ā”œā”€ā”€ logo.png # ConcreteInfo logo +ā”œā”€ā”€ NewIcon.jpg # Source image for icon generation +└── icons/ # Generated icons (16x16 to 1024x1024) -package.json # Dependencies and build configuration -CLAUDE.md # Development documentation for AI assistants +scripts/ +└── generate-icons.js # Icon generation script + +package.json # Dependencies and build configuration +CLAUDE.md # Development documentation for AI assistants ``` ## Development Commands @@ -113,7 +124,115 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \ ## Feature Implementation Guide -### v1.9.1 ASCII Art & Code Block Preservation (Latest) +### v3.0.0 Major UI Overhaul & Media Converters (Latest) + +#### Rebranding & UI Improvements +**Renamed to MarkdownConverter with ConcreteInfo Branding** (`src/index.html`, `package.json`) + +**Changes:** +- Renamed application from "PanConverter" to "MarkdownConverter" +- Added ConcreteInfo logo in app header +- Removed preview popout button (per user feedback) +- Version updated to 3.0.0 + +#### ConcreteInfo Theme System +**New Theme Based on Logo Palette** (`src/styles-concreteinfo.css`) + +**Color Palette:** +- Dark Gray: #464646 +- Medium Gray: #9a9696 +- Accent (Red): #e5461f +- Light Gray: #e3e3e3 +- Black: #0d0b09 + +**Theme Variants:** +- Concrete Dark - Dark mode with red accents +- Concrete Light - Clean light mode +- Concrete Warm - Warm tones with gray toolbar + +#### Separate Generator Windows +**ASCII Art & Table Generators in Dedicated Windows** (`src/ascii-generator.html`, `src/table-generator.html`, `src/main.js`) + +**ASCII Art Generator Features:** +- Text Banner mode with 5 font styles (Standard, Banner, Block, Bubble, Digital) +- Box/Frame mode with 5 styles (Single, Double, Rounded, Bold, ASCII) +- 16 professional templates (arrows, flowcharts, diagrams, decorative) +- Real-time preview with terminal-style display +- One-click insert to editor + +**Table Generator Features:** +- Adjustable rows and columns (1-50 rows, 1-20 columns) +- Quick templates (2-column, 3-column, 4x4 grid, etc.) +- Left/Center/Right alignment +- Optional header row +- Fill sample data option +- Live Markdown preview +- Copy or insert to editor + +**Access:** +- Tools → ASCII Art Generator (Ctrl+Shift+A) +- Tools → Table Generator (Ctrl+Shift+T) + +#### Media Converter Menus +**Separate Menus for Image, Audio, and Video Conversion** (`src/main.js`) + +**Image Menu:** +- Convert Image... (single file) +- Batch Convert Images... +- Resize Image... +- Compress Image... +- Rotate Image... + +**Audio Menu:** +- Convert Audio... +- Batch Convert Audio... +- Extract Audio from Video... +- Trim Audio... +- Merge Audio Files... + +**Video Menu:** +- Convert Video... +- Batch Convert Videos... +- Compress Video... +- Trim Video... +- Extract Frames... +- Convert to GIF... + +**Batch Menu:** +- Convert Markdown Folder... +- Batch Image Conversion... +- Batch Audio Conversion... +- Batch Video Conversion... +- Batch PDF Conversion... + +#### Enhanced PDF Editor +**Open PDF Files Directly** (`src/main.js`) + +- New "Open PDF File..." option in PDF Editor menu +- View and edit PDFs within the application +- All PDF operations available (merge, split, compress, rotate, etc.) + +#### About Menu & Dependencies +**Updated Help Menu** (`src/main.js`) + +- "About MarkdownConverter" shows app info with ConcreteInfo logo +- "Dependencies & Requirements" lists all required tools with download links: + - Pandoc (required) + - FFmpeg (optional, for media conversion) + - ImageMagick (optional, for image processing) + - LibreOffice (optional, for document conversion) + - MiKTeX/TeX Live (optional, for PDF via LaTeX) + +#### Security Enhancements (v2.2.0 continued) +**Secure Preload Script** (`src/preload.js`) + +- New IPC channels for media converters +- Secure generator window communication +- Insert content from generator windows + +--- + +### v1.9.1 ASCII Art & Code Block Preservation #### šŸŽØ Preserved ASCII Art in Exports **ASCII Art, Charts, Tables & Flowcharts Now Export Exactly as Previewed** (`src/wordTemplateExporter.js`, `src/main.js`) diff --git a/README.md b/README.md index 836065e..49f336f 100644 --- a/README.md +++ b/README.md @@ -1,162 +1,141 @@ # PanConverter -A cross-platform Markdown editor and converter powered by Pandoc. - -![PanConverter](assets/icon.png) +A powerful cross-platform Markdown editor and document converter powered by Pandoc, built with Electron. ## Features -### ✨ Advanced Markdown Editor -- šŸ—‚ļø **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 +### Markdown Editor +- **Multi-tab editing** - Work on multiple files simultaneously +- **Live preview** - Real-time markdown rendering with syntax highlighting +- **Dynamic splitter** - Drag to resize editor and preview panes +- **25+ themes** - Light and dark themes including Atom One Light, Dracula, Nord, Sepia, and more +- **Find & Replace** - Search and replace with regex support +- **Line numbers** - Toggle line numbers in the editor +- **Auto-save** - Automatic saving every 30 seconds +- **Math support** - KaTeX integration for mathematical expressions -### šŸŽØ 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 -- šŸ’¾ **Auto-Save** - Never lose your work with automatic saving every 30 seconds -- šŸ“Š **Enhanced Statistics** - Detailed document statistics including word count, character count, lines, paragraphs, sentences, and estimated reading time -- šŸ•’ **Recent Files** - Quick access to recently opened files via File menu -- 🧮 **Math Support** - Render mathematical expressions using KaTeX (supports $$, $, \\[\\], \\(\\) delimiters) +### PDF Viewer & Editor +- **Built-in PDF viewer** - Open and view PDF files directly in the app +- **Page navigation** - Navigate pages with keyboard or buttons +- **Zoom controls** - Zoom in/out, fit to width, fit to page +- **Rotation** - Rotate pages left or right +- **PDF Editor tools**: + - Merge multiple PDFs + - Split PDFs by page range + - Compress PDFs + - Rotate pages + - Delete pages + - Reorder pages + - Add watermarks + - Password protection + - Remove passwords + - Set permissions -### šŸ“¤ 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 CSV format for compatibility with any spreadsheet application -- šŸ“„ **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 +### Export Options +- **PDF** - Export to PDF with customizable page sizes and orientation +- **DOCX** - Standard and Enhanced (template-based) Word export +- **ODT** - OpenDocument format +- **HTML** - Web-ready HTML export +- **PowerPoint** - PPTX presentation export +- **EPUB** - E-book format +- **LaTeX** - Academic document format +- **RTF** - Rich Text Format -### šŸ–„ļø Platform Support -- **Cross-Platform** - Works seamlessly on Windows, macOS, and Linux +### Advanced Features +- **Custom headers & footers** - Add headers/footers to exports with dynamic fields +- **Page size configuration** - A3, A4, A5, B4, B5, Letter, Legal, Tabloid, or custom sizes +- **Batch conversion** - Convert entire folders of markdown files +- **ASCII Art Generator** - Create text banners and diagrams +- **Word templates** - Use custom Word templates for enhanced exports +- **Import documents** - Import from 30+ formats (DOCX, PDF, HTML, etc.) ## Installation ### Prerequisites -- [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 +- [Node.js](https://nodejs.org/) (v16 or later) +- [Pandoc](https://pandoc.org/installing.html) (required for export functionality) -### 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 the latest release for your platform from the [Releases](https://github.com/amitwh/pan-converter/releases) page. - -#### Linux -- **AppImage**: `PanConverter-1.5.0.AppImage` (universal, may require `--no-sandbox` flag) -- **Debian Package**: `pan-converter_1.5.6_amd64.deb` -- **Snap Package**: `pan-converter_1.5.6_amd64.snap` - -### Install from Source +### Install Dependencies ```bash -git clone https://github.com/amitwh/pan-converter.git -cd pan-converter npm install +``` + +### Run the Application +```bash npm start ``` -## Usage +### Build for Distribution +```bash +# Windows +npm run build:win -### Basic Workflow -1. **Write** - Use the editor to write your Markdown content -2. **Preview** - Toggle the preview pane to see rendered output -3. **Theme** - Choose your preferred theme from the View menu -4. **Export** - Export your document to various formats +# macOS +npm run build:mac -### Export Options -- **Documents**: HTML, PDF, DOCX, LaTeX, RTF, ODT, EPUB -- **Presentations**: PowerPoint (PPTX), OpenDocument Presentation (ODP) -- **Spreadsheets**: CSV format for tables (compatible with Excel, LibreOffice, Google Sheets) -- **Advanced Export Options**: Optional template support, metadata insertion, table of contents, section numbering, and PDF-specific settings -- **Batch Conversion**: Convert entire folders of markdown files with progress tracking - -### 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 +# Linux +npm run build:linux +``` ## Keyboard Shortcuts -### 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 + S` - Save file -- `Ctrl/Cmd + Shift + S` - Save as -- `Ctrl/Cmd + I` - Import document +| Action | Shortcut | +|--------|----------| +| New File | Ctrl+N | +| Open File | Ctrl+O | +| Open PDF | Ctrl+Shift+O | +| Save | Ctrl+S | +| Save As | Ctrl+Shift+S | +| Export | Ctrl+E | +| Print | Ctrl+P | +| Find | Ctrl+F | +| Undo | Ctrl+Z | +| Redo | Ctrl+Shift+Z | +| New Tab | Ctrl+T | +| Close Tab | Ctrl+W | +| Toggle Preview | Ctrl+Shift+P | +| Zoom In | Ctrl+Shift++ | +| Zoom Out | Ctrl+Shift+- | -### 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 +## Themes -### View & Navigation -- `Ctrl/Cmd + P` - Toggle preview -- `Ctrl/Cmd + Enter` - Toggle preview (alternative) -- `Escape` - Close find dialog +### Light Themes +- Atom One Light (Default) +- GitHub Light +- Light +- Solarized Light +- Gruvbox Light +- Ayu Light +- Sepia +- Paper +- Rose Pine Dawn +- Concrete Light -## Building +### Dark Themes +- Dark +- One Dark +- Dracula +- Nord +- Monokai +- Material +- Gruvbox Dark +- Tokyo Night +- Palenight +- Ayu Dark +- Ayu Mirage +- Oceanic Next +- Cobalt2 +- Concrete Dark +- Concrete Warm -```bash -# Install dependencies -npm install +## PDF Viewer -# Generate icons -npm run generate-icons - -# Build for current platform -npm run build - -# Build for specific platform -npm run build:win # Windows -npm run build:mac # macOS -npm run build:linux # Linux (generates .deb, .AppImage, and .snap) - -# Build for all platforms -npm run dist:all -``` - -## Version History - -- **v1.8.0** - Added enhanced Word export with template support to batch converter -- **v1.7.9** - Enhanced Word Export with Template Support: Template-based DOCX generation with custom templates, table styling, ASCII art support, and red-colored flowchart arrows -- **v1.5.6** - (Your release notes here) -- **v1.5.5** - Refactored PDF export, simplified Pandoc pathing, and removed XLSX dependency. -- **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 - -Contributions are welcome! Please feel free to submit a Pull Request to the [GitHub repository](https://github.com/amitwh/pan-converter). +Open PDF files directly in PanConverter: +- **File > Open PDF** or **Ctrl+Shift+O** +- Navigate pages with arrow buttons or page input +- Zoom controls: +/- buttons, Fit Width, Fit Page +- Rotate pages left or right +- Close PDF to return to editor ## License @@ -164,13 +143,8 @@ MIT License - see LICENSE file for details. ## Author -**Amit Haridas** - [amit.wh@gmail.com](mailto:amit.wh@gmail.com) +Amit Haridas (amit.wh@gmail.com) -## Acknowledgments +## Version -- Built with [Electron](https://www.electronjs.org/) -- Markdown parsing by [marked](https://marked.js.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) \ No newline at end of file +v3.0.0 diff --git a/assets/docico1.png b/assets/docico1.png new file mode 100644 index 0000000..e4cc5c7 Binary files /dev/null and b/assets/docico1.png differ diff --git a/assets/favicon.png b/assets/favicon.png new file mode 100644 index 0000000..f7bc0bc Binary files /dev/null and b/assets/favicon.png differ diff --git a/assets/icon-proper.ico b/assets/icon-proper.ico deleted file mode 100644 index 46ffe19..0000000 Binary files a/assets/icon-proper.ico and /dev/null differ diff --git a/assets/icon.icns b/assets/icon.icns deleted file mode 100644 index 68324cd..0000000 Binary files a/assets/icon.icns and /dev/null differ diff --git a/assets/icon.ico b/assets/icon.ico deleted file mode 100644 index 46ffe19..0000000 Binary files a/assets/icon.ico and /dev/null differ diff --git a/assets/icon.png b/assets/icon.png index 68324cd..a87dedf 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/icon.svg b/assets/icon.svg deleted file mode 100644 index 5790d80..0000000 --- a/assets/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - PAN - \ No newline at end of file diff --git a/assets/icon@2x.png b/assets/icon@2x.png index f4cd1ac..7a87141 100644 Binary files a/assets/icon@2x.png and b/assets/icon@2x.png differ diff --git a/assets/icons/1024x1024.png b/assets/icons/1024x1024.png new file mode 100644 index 0000000..7a87141 Binary files /dev/null and b/assets/icons/1024x1024.png differ diff --git a/assets/icons/128x128.png b/assets/icons/128x128.png new file mode 100644 index 0000000..b33d9b6 Binary files /dev/null and b/assets/icons/128x128.png differ diff --git a/assets/icons/16x16.png b/assets/icons/16x16.png new file mode 100644 index 0000000..6ad7303 Binary files /dev/null and b/assets/icons/16x16.png differ diff --git a/assets/icons/24x24.png b/assets/icons/24x24.png new file mode 100644 index 0000000..70a61f0 Binary files /dev/null and b/assets/icons/24x24.png differ diff --git a/assets/icons/256x256.png b/assets/icons/256x256.png new file mode 100644 index 0000000..b9b10b9 Binary files /dev/null and b/assets/icons/256x256.png differ diff --git a/assets/icons/32x32.png b/assets/icons/32x32.png new file mode 100644 index 0000000..f7bc0bc Binary files /dev/null and b/assets/icons/32x32.png differ diff --git a/assets/icons/48x48.png b/assets/icons/48x48.png new file mode 100644 index 0000000..df2f49c Binary files /dev/null and b/assets/icons/48x48.png differ diff --git a/assets/icons/512x512.png b/assets/icons/512x512.png new file mode 100644 index 0000000..a87dedf Binary files /dev/null and b/assets/icons/512x512.png differ diff --git a/assets/icons/64x64.png b/assets/icons/64x64.png new file mode 100644 index 0000000..7291f26 Binary files /dev/null and b/assets/icons/64x64.png differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..b1871d2 Binary files /dev/null and b/assets/logo.png differ diff --git a/assets/tray-icon.png b/assets/tray-icon.png new file mode 100644 index 0000000..70a61f0 Binary files /dev/null and b/assets/tray-icon.png differ diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..fcb2039 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,89 @@ +/** + * ESLint Configuration for PanConverter + * Uses flat config format (ESLint 9+) + */ + +module.exports = [ + { + // Global ignores + ignores: [ + 'node_modules/**', + 'dist/**', + 'coverage/**', + '*.min.js' + ] + }, + { + // JavaScript files + files: ['**/*.js'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { + // Node.js + require: 'readonly', + module: 'readonly', + exports: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + process: 'readonly', + Buffer: 'readonly', + console: 'readonly', + setTimeout: 'readonly', + setInterval: 'readonly', + clearTimeout: 'readonly', + clearInterval: 'readonly', + // Browser + window: 'readonly', + document: 'readonly', + localStorage: 'readonly', + alert: 'readonly', + Event: 'readonly', + CustomEvent: 'readonly', + HTMLElement: 'readonly', + MutationObserver: 'readonly', + // Electron + electronAPI: 'readonly', + // Libraries + marked: 'readonly', + DOMPurify: 'readonly', + hljs: 'readonly', + mermaid: 'readonly', + // Jest + jest: 'readonly', + describe: 'readonly', + test: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + beforeAll: 'readonly', + afterAll: 'readonly' + } + }, + rules: { + // Error prevention + 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + 'no-undef': 'error', + 'no-console': 'off', // Allow console for Electron apps + + // Code quality + 'eqeqeq': ['warn', 'always'], + 'no-var': 'warn', + 'prefer-const': 'warn', + + // Style (handled by Prettier) + 'semi': 'off', + 'quotes': 'off', + 'indent': 'off', + + // Async handling + 'no-async-promise-executor': 'warn', + 'require-await': 'off', + + // Security + 'no-eval': 'error', + 'no-implied-eval': 'error', + 'no-new-func': 'error' + } + } +]; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..2bf5a07 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,59 @@ +/** + * Jest Configuration for PanConverter + * @version 2.2.0 + */ + +module.exports = { + // Test environment + testEnvironment: 'jsdom', + + // Root directory + rootDir: '.', + + // Test file patterns + testMatch: [ + '**/tests/**/*.test.js', + '**/tests/**/*.spec.js' + ], + + // Coverage configuration + collectCoverageFrom: [ + 'src/**/*.js', + '!src/main.js', // Main process needs electron-mock + '!**/node_modules/**' + ], + + // Coverage thresholds (start low, increase over time) + coverageThreshold: { + global: { + branches: 10, + functions: 10, + lines: 10, + statements: 10 + } + }, + + // Transform settings (no transpilation needed for vanilla JS) + transform: {}, + + // Module paths + moduleDirectories: ['node_modules', 'src'], + + // Setup files + setupFilesAfterEnv: ['/tests/setup.js'], + + // Ignore patterns + testPathIgnorePatterns: [ + '/node_modules/', + '/dist/' + ], + + // Verbose output + verbose: true, + + // Clear mocks between tests + clearMocks: true, + + // Reset modules between tests + resetModules: true +}; diff --git a/package.json b/package.json index 232aef0..976260c 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,20 @@ { - "name": "pan-converter", - "version": "2.1.0", - "description": "Cross-platform Markdown editor and converter using Pandoc", + "name": "markdown-converter", + "version": "3.0.0", + "description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting", "main": "src/main.js", "scripts": { "start": "electron .", - "test": "echo \"Error: no test specified\" && exit 1", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "lint": "eslint src tests", + "lint:fix": "eslint src tests --fix", + "format": "prettier --write src tests", + "format:check": "prettier --check src tests", "build": "electron-builder", - "build:win": "cross-env CSC_LINK=code-signing-cert.pfx CSC_KEY_PASSWORD=PanConverter2024! electron-builder --win", + "build:win": "electron-builder --win", + "build:win-signed": "cross-env CSC_LINK=code-signing-cert.pfx CSC_KEY_PASSWORD=%CSC_KEY_PASSWORD% electron-builder --win", "build:win-unsigned": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --win", "build:mac": "electron-builder --mac", "build:linux": "electron-builder --linux", @@ -19,14 +26,29 @@ "markdown", "pandoc", "converter", - "editor" + "editor", + "pdf", + "audio", + "video", + "image" ], - "author": "Amit Haridas ", + "author": "ConcreteInfo ", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/amitwh/markdown-converter" + }, "devDependencies": { + "@testing-library/dom": "^10.4.1", "cross-env": "^10.0.0", "electron": "^37.4.0", "electron-builder": "^26.0.12", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "jest": "^30.2.0", + "jest-environment-jsdom": "^30.2.0", + "prettier": "^3.7.4", "sharp": "^0.34.3" }, "dependencies": { @@ -40,14 +62,15 @@ "html2pdf.js": "^0.10.1", "marked": "^16.2.1", "pdf-lib": "^1.17.1", + "pdfjs-dist": "^3.11.174", "pdfkit": "^0.14.0", "pizzip": "^3.2.0", "tslib": "^2.8.1", "xlsx": "^0.18.5" }, "build": { - "appId": "com.panconverter.app", - "productName": "PanConverter", + "appId": "com.concreteinfo.markdownconverter", + "productName": "MarkdownConverter", "directories": { "output": "dist" }, @@ -73,10 +96,18 @@ "description": "Markdown Document", "mimeType": "text/markdown", "role": "Editor" + }, + { + "ext": "pdf", + "name": "PDF Document", + "description": "PDF Document", + "mimeType": "application/pdf", + "role": "Editor" } ], "mac": { - "category": "public.app-category.productivity" + "category": "public.app-category.productivity", + "identity": null }, "win": { "target": [ @@ -110,7 +141,7 @@ "displayLanguageSelector": true, "createDesktopShortcut": true, "createStartMenuShortcut": true, - "shortcutName": "PanConverter", + "shortcutName": "MarkdownConverter", "runAfterFinish": true, "menuCategory": "Productivity", "license": "LICENSE", @@ -123,16 +154,29 @@ "target": [ "deb", "AppImage", - "snap" + "snap", + "rpm" ], - "category": "Utility" + "category": "Utility", + "maintainer": "ConcreteInfo " }, "deb": { "depends": [ - "pandoc" + "pandoc", + "ffmpeg", + "imagemagick", + "libreoffice-common" ], - "description": "Markdown editor and converter using Pandoc", - "maintainer": "Amit Haridas " + "description": "Professional Markdown editor and universal file converter", + "maintainer": "ConcreteInfo " + }, + "rpm": { + "depends": [ + "pandoc", + "ffmpeg", + "ImageMagick", + "libreoffice-core" + ] } } } diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js index 46d1f69..fd3d7c1 100644 --- a/scripts/generate-icons.js +++ b/scripts/generate-icons.js @@ -1,43 +1,82 @@ +/** + * Icon Generator for MarkdownConverter + * Generates all required icon sizes from NewIcon.jpg + */ + const sharp = require('sharp'); const fs = require('fs'); const path = require('path'); -const sizes = { - 'icon.png': 512, - 'icon@2x.png': 1024, - 'icon.ico': 256, - 'icon.icns': 512 -}; +// Source image +const sourceImage = path.join(__dirname, '..', 'assets', 'NewIcon.jpg'); +const assetsDir = path.join(__dirname, '..', 'assets'); -const svgPath = path.join(__dirname, '..', 'assets', 'icon.svg'); -const svgBuffer = fs.readFileSync(svgPath); +// Icon sizes needed for different platforms +const iconSizes = [16, 24, 32, 48, 64, 128, 256, 512, 1024]; async function generateIcons() { - for (const [filename, size] of Object.entries(sizes)) { - const outputPath = path.join(__dirname, '..', 'assets', filename); - - if (filename.endsWith('.png')) { - await sharp(svgBuffer) - .resize(size, size) - .png() - .toFile(outputPath); - console.log(`Generated ${filename}`); - } else if (filename.endsWith('.ico')) { - // For ICO, we'll just use the PNG version - await sharp(svgBuffer) - .resize(size, size) - .png() - .toFile(outputPath.replace('.ico', '.png')); - console.log(`Generated ${filename.replace('.ico', '.png')} (use png2ico to convert)`); - } else if (filename.endsWith('.icns')) { - // For ICNS, we'll just use the PNG version - await sharp(svgBuffer) - .resize(size, size) - .png() - .toFile(outputPath.replace('.icns', '.png')); - console.log(`Generated ${filename.replace('.icns', '.png')} (use png2icns to convert)`); - } + console.log('Generating icons from NewIcon.jpg...'); + + // Check if source exists + if (!fs.existsSync(sourceImage)) { + console.error('Source image not found:', sourceImage); + process.exit(1); + } + + try { + // Generate main icon.png (512x512) + await sharp(sourceImage) + .resize(512, 512, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } }) + .png() + .toFile(path.join(assetsDir, 'icon.png')); + console.log('Generated icon.png (512x512)'); + + // Generate icon@2x.png (1024x1024) + await sharp(sourceImage) + .resize(1024, 1024, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } }) + .png() + .toFile(path.join(assetsDir, 'icon@2x.png')); + console.log('Generated icon@2x.png (1024x1024)'); + + // Create icons directory for multi-size icons + const iconsDir = path.join(assetsDir, 'icons'); + if (!fs.existsSync(iconsDir)) { + fs.mkdirSync(iconsDir, { recursive: true }); } + + // Generate all sizes + for (const size of iconSizes) { + await sharp(sourceImage) + .resize(size, size, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } }) + .png() + .toFile(path.join(iconsDir, `${size}x${size}.png`)); + console.log(`Generated icons/${size}x${size}.png`); + } + + // Generate favicon + await sharp(sourceImage) + .resize(32, 32, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } }) + .png() + .toFile(path.join(assetsDir, 'favicon.png')); + console.log('Generated favicon.png (32x32)'); + + // Generate tray icon (smaller, for system tray) + await sharp(sourceImage) + .resize(24, 24, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } }) + .png() + .toFile(path.join(assetsDir, 'tray-icon.png')); + console.log('Generated tray-icon.png (24x24)'); + + console.log('\nIcon generation complete!'); + console.log('\nFor Windows .ico file, use an online converter or:'); + console.log(' magick convert assets/icons/*.png assets/icon.ico'); + console.log('\nFor macOS .icns file, use:'); + console.log(' iconutil -c icns assets/icon.iconset -o assets/icon.icns'); + + } catch (error) { + console.error('Error generating icons:', error); + process.exit(1); + } } -generateIcons().catch(console.error); \ No newline at end of file +generateIcons(); diff --git a/src/ascii-generator.html b/src/ascii-generator.html new file mode 100644 index 0000000..2543ae1 --- /dev/null +++ b/src/ascii-generator.html @@ -0,0 +1,601 @@ + + + + + + ASCII Art Generator - MarkdownConverter + + + + +
+

ASCII Art Generator

+
+ +
+
+
+ + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
Arrows & Flow
+
+ + + + +
+
+
+
Diagrams
+
+ + + + +
+
+
+
Boxes & Containers
+
+ + + + +
+
+
+
Decorative
+
+ + + + +
+
+
+
+ +
+
Preview
+
+
+
+
+
+ + + + + + diff --git a/src/index.html b/src/index.html index 81a4a08..7fa1327 100644 --- a/src/index.html +++ b/src/index.html @@ -3,12 +3,13 @@ - PanConverter - Markdown Editor + MarkdownConverter + @@ -16,6 +17,16 @@
+ +
+
+ + MarkdownConverter +
+
+ v3.0.0 +
+
Untitled @@ -870,10 +881,13 @@
- + +
+
+
- - + +
@@ -901,10 +915,13 @@
- + +
+
+
- - + +
@@ -924,10 +941,13 @@ Separate page numbers/ranges with commas
- + +
+
+
- - + +
@@ -953,10 +973,13 @@
- + +
+
+
- - + +
@@ -1009,10 +1032,13 @@
- + +
+
+
- - + +
@@ -1058,10 +1084,13 @@ These permissions apply when owner password is set
- + +
+
+
- - + +
@@ -1081,10 +1110,13 @@ Enter the password to unlock and remove protection
- + +
+
+
- - + +
@@ -1121,10 +1153,13 @@
- + +
+
+
- - + +
@@ -1271,12 +1306,104 @@
-
- -
+ + +
diff --git a/src/main.js b/src/main.js index d9a0b33..e55ae1c 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,7 @@ const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron'); const path = require('path'); const fs = require('fs'); -const { exec } = require('child_process'); +const { exec, execFile, spawn } = require('child_process'); const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib'); const WordTemplateExporter = require('./wordTemplateExporter'); @@ -25,12 +25,95 @@ function getPandocPath() { function checkPandocAvailable() { return new Promise((resolve) => { const pandocPath = getPandocPath(); - exec(`${pandocPath} --version`, (error) => { + execFile(pandocPath, ['--version'], (error) => { resolve(!error); }); }); } +/** + * Safe command execution using execFile (no shell injection risk) + * @param {string} command - The command to execute + * @param {string[]} args - Array of arguments + * @param {object} options - Options for execFile + * @returns {Promise<{stdout: string, stderr: string}>} + */ +function safeExecFile(command, args, options = {}) { + return new Promise((resolve, reject) => { + execFile(command, args, { maxBuffer: 10 * 1024 * 1024, ...options }, (error, stdout, stderr) => { + if (error) { + reject({ error, stdout, stderr }); + } else { + resolve({ stdout, stderr }); + } + }); + }); +} + +/** + * Run Pandoc command safely with execFile + * @param {string[]} args - Pandoc arguments array + * @param {Function} callback - Callback function (error, stdout, stderr) + */ +function runPandoc(args, callback) { + const pandocPath = getPandocPath(); + execFile(pandocPath, args, { maxBuffer: 10 * 1024 * 1024 }, callback); +} + +/** + * Run a Pandoc command string safely using execFile + * Parses the command string and uses execFile to prevent shell injection + * @param {string} cmdString - Full Pandoc command string (e.g., 'pandoc "input.md" -o "output.pdf"') + * @param {Function} callback - Callback function (error, stdout, stderr) + */ +function runPandocCmd(cmdString, callback) { + const parsed = parseCommand(cmdString); + // Skip 'pandoc' if it's the first element (command itself) + const args = parsed.command === 'pandoc' ? parsed.args : [parsed.command, ...parsed.args]; + const pandocPath = getPandocPath(); + execFile(pandocPath, args, { maxBuffer: 10 * 1024 * 1024 }, callback); +} + +/** + * Parse a command string into command and arguments array + * This helps transition from exec() to execFile() safely + * @param {string} cmdString - Full command string + * @returns {{command: string, args: string[]}} + */ +function parseCommand(cmdString) { + // Handle quoted strings properly + const parts = []; + let current = ''; + let inQuotes = false; + let quoteChar = ''; + + for (let i = 0; i < cmdString.length; i++) { + const char = cmdString[i]; + if ((char === '"' || char === "'") && !inQuotes) { + inQuotes = true; + quoteChar = char; + } else if (char === quoteChar && inQuotes) { + inQuotes = false; + quoteChar = ''; + } else if (char === ' ' && !inQuotes) { + if (current) { + parts.push(current); + current = ''; + } + } else { + current += char; + } + } + if (current) { + parts.push(current); + } + + return { + command: parts[0], + args: parts.slice(1) + }; +} + // Simple storage implementation to replace electron-store const settingsPath = path.join(app.getPath('userData'), 'settings.json'); const store = { @@ -180,15 +263,15 @@ if (!gotTheLock) { }); } -// Check if pandoc is available +// Check if pandoc is available (using execFile for consistency) function checkPandocAvailability() { return new Promise((resolve) => { if (pandocAvailable !== null) { resolve(pandocAvailable); return; } - - exec('pandoc --version', (error, stdout, stderr) => { + + execFile('pandoc', ['--version'], (error, stdout, stderr) => { pandocAvailable = !error; resolve(pandocAvailable); }); @@ -284,6 +367,11 @@ function createMenu() { accelerator: 'CmdOrCtrl+O', click: openFile }, + { + label: 'Open PDF', + accelerator: 'CmdOrCtrl+Shift+O', + click: openPdfFile + }, { label: 'Save', accelerator: 'CmdOrCtrl+S', @@ -401,29 +489,33 @@ function createMenu() { { label: 'Theme', submenu: [ + // Light Themes (grouped first) + { label: 'Atom One Light (Default)', click: () => setTheme('atomonelight') }, + { label: 'GitHub Light', click: () => setTheme('github') }, { label: 'Light', click: () => setTheme('light') }, - { label: 'Dark', click: () => setTheme('dark') }, - { label: 'Solarized', click: () => setTheme('solarized') }, - { label: 'Monokai', click: () => setTheme('monokai') }, - { label: 'GitHub', click: () => setTheme('github') }, + { label: 'Solarized Light', click: () => setTheme('solarized') }, + { label: 'Gruvbox Light', click: () => setTheme('gruvbox-light') }, + { label: 'Ayu Light', click: () => setTheme('ayu-light') }, + { label: 'Sepia', click: () => setTheme('sepia') }, + { label: 'Paper', click: () => setTheme('paper') }, + { label: 'Rose Pine Dawn', click: () => setTheme('rosepine-dawn') }, + { label: 'Concrete Light', click: () => setTheme('concrete-light') }, { type: 'separator' }, + // Dark Themes + { label: 'Dark', click: () => setTheme('dark') }, + { label: 'One Dark', click: () => setTheme('onedark') }, { label: 'Dracula', click: () => setTheme('dracula') }, { label: 'Nord', click: () => setTheme('nord') }, - { label: 'One Dark', click: () => setTheme('onedark') }, - { label: 'Atom One Light', click: () => setTheme('atomonelight') }, + { label: 'Monokai', click: () => setTheme('monokai') }, { label: 'Material', click: () => setTheme('material') }, { label: 'Gruvbox Dark', click: () => setTheme('gruvbox-dark') }, - { label: 'Gruvbox Light', click: () => setTheme('gruvbox-light') }, { label: 'Tokyo Night', click: () => setTheme('tokyonight') }, { label: 'Palenight', click: () => setTheme('palenight') }, { label: 'Ayu Dark', click: () => setTheme('ayu-dark') }, - { label: 'Ayu Light', click: () => setTheme('ayu-light') }, { label: 'Ayu Mirage', click: () => setTheme('ayu-mirage') }, { label: 'Oceanic Next', click: () => setTheme('oceanic-next') }, { label: 'Cobalt2', click: () => setTheme('cobalt2') }, - { type: 'separator' }, { label: 'Concrete Dark', click: () => setTheme('concrete-dark') }, - { label: 'Concrete Light', click: () => setTheme('concrete-light') }, { label: 'Concrete Warm', click: () => setTheme('concrete-warm') } ] }, @@ -461,8 +553,25 @@ function createMenu() { label: 'Batch', submenu: [ { - label: 'Convert Folder...', + label: 'Convert Markdown Folder...', click: () => showBatchConversionDialog() + }, + { type: 'separator' }, + { + label: 'Batch Image Conversion...', + click: () => mainWindow.webContents.send('show-batch-converter', 'image') + }, + { + label: 'Batch Audio Conversion...', + click: () => mainWindow.webContents.send('show-batch-converter', 'audio') + }, + { + label: 'Batch Video Conversion...', + click: () => mainWindow.webContents.send('show-batch-converter', 'video') + }, + { + label: 'Batch PDF Conversion...', + click: () => mainWindow.webContents.send('show-batch-converter', 'pdf') } ] }, @@ -473,27 +582,18 @@ function createMenu() { label: 'Universal File Converter...', accelerator: 'CmdOrCtrl+Shift+C', click: () => showUniversalConverterDialog() - }, - { - type: 'separator' - }, - { - label: 'About Converter', - click: () => { - dialog.showMessageBox(mainWindow, { - type: 'info', - title: 'About Universal Converter', - message: 'Open-Source File Converter', - detail: 'PanConverter includes a powerful open-source file conversion system.\n\nSupported Converters:\n• LibreOffice - Document conversions (DOCX, PDF, ODT, etc.)\n• ImageMagick - Image format conversions\n• FFmpeg - Media file conversions\n• Pandoc - Document markup conversions\n\nFeatures:\n• 100% open-source and free\n• No API keys required\n• Runs completely offline\n• Supports 100+ file formats\n• High-quality professional conversions', - buttons: ['OK'] - }); - } } ] }, { label: 'PDF Editor', submenu: [ + { + label: 'Open PDF File...', + accelerator: 'CmdOrCtrl+Shift+O', + click: () => openPDFFile() + }, + { type: 'separator' }, { label: 'Merge PDFs...', click: () => showPDFEditorDialog('merge') @@ -570,12 +670,15 @@ function createMenu() { submenu: [ { label: 'Table Generator', - click: () => mainWindow.webContents.send('show-table-generator') + accelerator: 'CmdOrCtrl+Shift+T', + click: () => openTableGenerator() }, { label: 'ASCII Art Generator', - click: () => mainWindow.webContents.send('show-ascii-generator') + accelerator: 'CmdOrCtrl+Shift+A', + click: () => openAsciiGenerator() }, + { type: 'separator' }, { label: 'Document Compare', click: () => mainWindow.webContents.send('show-document-compare') @@ -586,20 +689,26 @@ function createMenu() { label: 'Help', submenu: [ { - label: 'About', - click: () => { - dialog.showMessageBox(mainWindow, { - type: 'info', - title: 'About PanConverter', - message: 'PanConverter', - detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 2.1.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\n✨ New in v2.1.0:\n• Subtle & Small Preview Popout Button - Cleaner, minimalist design\n• Simplified Table Headers - Light gray background with better readability\n• Comprehensive Format-to-Markdown Conversion - Support for 30+ formats (RST, Textile, MediaWiki, Org-mode, AsciiDoc, CSV, JSON, and more)\n• Exhaustive ASCII Art Generator - 5 text banner styles (Standard, Banner, Block, Bubble, Digital) and 19 professional templates\n• Enhanced Templates - Flowcharts, sequence diagrams, network diagrams, hierarchy trees, timelines, note boxes, warning boxes, and decorative elements\n\n✨ Features from v2.0.0:\n• Export Profiles - Save and load export configurations\n• Mermaid.js diagram support - Render flowcharts, sequence diagrams, and more\n• Command Palette (Ctrl+Shift+P) - Quick access to all commands\n• GitHub Light/Dark preview themes - Beautiful code preview styling\n• Table Generator - Interactive table creation tool\n• Resizable Preview Pane - Drag divider to adjust editor/preview sizes\n• Pop-out Preview Window - Open preview in separate window with live sync\n\nCore Features:\n• Configurable page sizes (A3, A4, A5, B4, B5, Letter, Legal, Tabloid, Custom) with orientation support\n• Page size support for all export formats (PDF, DOCX, ODT, PowerPoint) and batch conversion\n• Preserved ASCII art, charts, tables, flowcharts in exports with full-width background\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Undo/redo functionality\n• Live word count and statistics', - buttons: ['OK'] - }); - } + label: 'About MarkdownConverter', + click: () => showAboutDialog() }, + { type: 'separator' }, + { + label: 'Dependencies & Requirements', + click: () => showDependenciesDialog() + }, + { type: 'separator' }, { label: 'Documentation', - click: () => shell.openExternal('https://github.com/amitwh/pan-converter') + click: () => shell.openExternal('https://github.com/amitwh/markdown-converter') + }, + { + label: 'Report Issue', + click: () => shell.openExternal('https://github.com/amitwh/markdown-converter/issues') + }, + { + label: 'Check for Updates', + click: () => shell.openExternal('https://github.com/amitwh/markdown-converter/releases') } ] } @@ -609,6 +718,237 @@ function createMenu() { Menu.setApplicationMenu(menu); } +// Show About Dialog with logo +function showAboutDialog() { + const aboutWindow = new BrowserWindow({ + width: 500, + height: 600, + parent: mainWindow, + modal: true, + resizable: false, + minimizable: false, + maximizable: false, + webPreferences: { + nodeIntegration: false, + contextIsolation: true + }, + icon: path.join(__dirname, '../assets/icon.png') + }); + + // Convert images to base64 for data URL compatibility + let logoBase64 = ''; + let iconBase64 = ''; + try { + const logoPath = path.join(__dirname, '../assets/logo.png'); + const iconPath = path.join(__dirname, '../assets/icon.png'); + if (fs.existsSync(logoPath)) { + logoBase64 = 'data:image/png;base64,' + fs.readFileSync(logoPath).toString('base64'); + } + if (fs.existsSync(iconPath)) { + iconBase64 = 'data:image/png;base64,' + fs.readFileSync(iconPath).toString('base64'); + } + } catch (e) { + console.error('Error loading about dialog images:', e); + } + + const aboutHTML = ` + + + + + About MarkdownConverter + + + + +

MarkdownConverter

+
Version 3.0.0
+ +
+ by + +
+ +
+

Features

+
    +
  • Professional Markdown editing with syntax highlighting
  • +
  • Universal file converter (Image, Audio, Video, PDF)
  • +
  • Batch conversion for all media types
  • +
  • Advanced PDF Editor (merge, split, watermark, encrypt)
  • +
  • Export to PDF, DOCX, HTML, LaTeX, EPUB & more
  • +
  • 23+ beautiful themes including ConcreteInfo theme
  • +
  • ASCII Art & Table generators
  • +
  • Code file syntax highlighting
  • +
+
+ +
+

Contact

+

Email: amit.wh@gmail.com

+

Website: GitHub Repository

+
+ + + +`; + + aboutWindow.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(aboutHTML)); + aboutWindow.setMenuBarVisibility(false); +} + +// Show Dependencies Dialog +function showDependenciesDialog() { + const depsWindow = new BrowserWindow({ + width: 600, + height: 700, + parent: mainWindow, + modal: true, + resizable: true, + webPreferences: { + nodeIntegration: false, + contextIsolation: true + }, + icon: path.join(__dirname, '../assets/icon.png') + }); + + const depsHTML = ` + + + + + Dependencies & Requirements + + + +

Dependencies & Requirements

+ +

Required Dependencies

+ +
+
Pandoc Required
+
Universal document converter. Required for most export formats (PDF, DOCX, LaTeX, EPUB).
+ https://pandoc.org/installing.html +
+ +

Optional Dependencies (for extended features)

+ +
+
FFmpeg Optional
+
Required for audio/video conversion and processing.
+ https://ffmpeg.org/download.html +
+ +
+
ImageMagick Optional
+
Required for image format conversion and processing.
+ https://imagemagick.org/script/download.php +
+ +
+
LibreOffice Optional
+
Required for enhanced document conversion (Office formats).
+ https://www.libreoffice.org/download/download/ +
+ +
+
MiKTeX / TeX Live Optional
+
Required for PDF export via LaTeX (higher quality PDFs).
+ https://miktex.org/download +
+ +

Bundled Libraries

+ +
+
pdf-lib
+
PDF manipulation library for merge, split, watermark, and encryption features.
+
+ +
+
marked
+
Markdown parser for preview rendering.
+
+ +
+
highlight.js
+
Syntax highlighting for code blocks.
+
+ +
+
DOMPurify
+
HTML sanitization for security.
+
+ +`; + + depsWindow.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(depsHTML)); + depsWindow.setMenuBarVisibility(false); +} + +// Open PDF File for viewing/editing +function openPDFFile() { + const files = dialog.showOpenDialogSync(mainWindow, { + properties: ['openFile'], + filters: [ + { name: 'PDF Files', extensions: ['pdf'] } + ] + }); + + if (files && files[0]) { + mainWindow.webContents.send('open-pdf-viewer', files[0]); + } +} + function openFile() { const files = dialog.showOpenDialogSync(mainWindow, { properties: ['openFile'], @@ -625,6 +965,20 @@ function openFile() { } } +function openPdfFile() { + const files = dialog.showOpenDialogSync(mainWindow, { + properties: ['openFile'], + filters: [ + { name: 'PDF Files', extensions: ['pdf'] }, + { name: 'All Files', extensions: ['*'] } + ] + }); + + if (files && files[0]) { + mainWindow.webContents.send('open-pdf-file', files[0]); + } +} + function saveAsFile() { const file = dialog.showSaveDialogSync(mainWindow, { defaultExt: '.md', @@ -1133,15 +1487,15 @@ async function exportPDFViaWordTemplate() { const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage); await exporter.convert(content, tempDocxPath); - // Step 2: Convert DOCX to PDF using LibreOffice + // Step 2: Convert DOCX to PDF using LibreOffice (using execFile for safety) const soffice = process.platform === 'win32' - ? '"C:\\Program Files\\LibreOffice\\program\\soffice.exe"' + ? 'C:\\Program Files\\LibreOffice\\program\\soffice.exe' : 'soffice'; const outputDir = path.dirname(result.filePath); - const convertCmd = `${soffice} --headless --convert-to pdf --outdir "${outputDir}" "${tempDocxPath}"`; + const sofficeArgs = ['--headless', '--convert-to', 'pdf', '--outdir', outputDir, tempDocxPath]; - exec(convertCmd, (error, stdout, stderr) => { + execFile(soffice, sofficeArgs, (error, stdout, stderr) => { // Clean up temporary DOCX file try { fs.unlinkSync(tempDocxPath); @@ -1190,26 +1544,34 @@ function showPDFEditorDialog(operation) { mainWindow.webContents.send('show-pdf-editor-dialog', operation); } -// Check if conversion tool is available +// Handle PDF editor from toolbar (with optional file path) +ipcMain.on('show-pdf-editor-from-toolbar', (event, { operation, filePath }) => { + mainWindow.webContents.send('show-pdf-editor-dialog', operation, filePath); +}); + +// Check if conversion tool is available (using execFile for safety) function checkConverterAvailable(tool) { return new Promise((resolve) => { - let command; + const isWin = process.platform === 'win32'; + const locateCmd = isWin ? 'where' : 'which'; + let toolName; + switch (tool) { case 'libreoffice': - command = process.platform === 'win32' ? 'where soffice' : 'which soffice'; + toolName = 'soffice'; break; case 'imagemagick': - command = process.platform === 'win32' ? 'where magick' : 'which convert'; + toolName = isWin ? 'magick' : 'convert'; break; case 'ffmpeg': - command = process.platform === 'win32' ? 'where ffmpeg' : 'which ffmpeg'; + toolName = 'ffmpeg'; break; default: resolve(false); return; } - exec(command, (error) => { + execFile(locateCmd, [toolName], (error) => { resolve(!error); }); }); @@ -1230,26 +1592,27 @@ ipcMain.on('universal-convert', async (event, { tool, fromFormat, toFormat, file mainWindow.webContents.send('conversion-status', 'Converting file...'); const outputPath = filePath.replace(/\.[^/.]+$/, `.${toFormat}`); - let conversionCmd; + let conversionInfo; switch (tool) { case 'libreoffice': - conversionCmd = convertWithLibreOffice(filePath, toFormat, outputPath); + conversionInfo = convertWithLibreOffice(filePath, toFormat, outputPath); break; case 'imagemagick': - conversionCmd = convertWithImageMagick(filePath, outputPath); + conversionInfo = convertWithImageMagick(filePath, outputPath); break; case 'ffmpeg': - conversionCmd = convertWithFFmpeg(filePath, outputPath); + conversionInfo = convertWithFFmpeg(filePath, outputPath); break; case 'pandoc': - conversionCmd = `pandoc "${filePath}" -o "${outputPath}"`; + conversionInfo = convertWithPandoc(filePath, outputPath); break; default: throw new Error(`Unknown conversion tool: ${tool}`); } - exec(conversionCmd, (error, stdout, stderr) => { + // Use execFile for safety (prevents command injection) + execFile(conversionInfo.command, conversionInfo.args, (error, stdout, stderr) => { if (error) { mainWindow.webContents.send('conversion-complete', { success: false, @@ -1294,11 +1657,11 @@ ipcMain.on('universal-convert', async (event, { tool, fromFormat, toFormat, file } }); -// LibreOffice conversion command builder +// LibreOffice conversion - returns {command, args} for execFile (safer than exec) function convertWithLibreOffice(inputFile, outputFormat, outputPath) { const outputDir = path.dirname(outputPath); const soffice = process.platform === 'win32' - ? '"C:\\Program Files\\LibreOffice\\program\\soffice.exe"' + ? 'C:\\Program Files\\LibreOffice\\program\\soffice.exe' : 'soffice'; // LibreOffice conversion format mapping @@ -1321,19 +1684,36 @@ function convertWithLibreOffice(inputFile, outputFormat, outputPath) { const format = formatMap[outputFormat] || outputFormat; - // Use headless mode for conversion - return `${soffice} --headless --convert-to ${format} --outdir "${outputDir}" "${inputFile}"`; + // Return command and args for execFile + return { + command: soffice, + args: ['--headless', '--convert-to', format, '--outdir', outputDir, inputFile] + }; } -// ImageMagick conversion command builder +// ImageMagick conversion - returns {command, args} for execFile function convertWithImageMagick(inputFile, outputPath) { const magick = process.platform === 'win32' ? 'magick' : 'convert'; - return `${magick} "${inputFile}" "${outputPath}"`; + return { + command: magick, + args: [inputFile, outputPath] + }; } -// FFmpeg conversion command builder +// FFmpeg conversion - returns {command, args} for execFile function convertWithFFmpeg(inputFile, outputPath) { - return `ffmpeg -i "${inputFile}" "${outputPath}" -y`; + return { + command: 'ffmpeg', + args: ['-i', inputFile, outputPath, '-y'] + }; +} + +// Pandoc conversion - returns {command, args} for execFile +function convertWithPandoc(inputFile, outputPath) { + return { + command: 'pandoc', + args: [inputFile, '-o', outputPath] + }; } function performExportWithOptions(format, options) { @@ -1449,8 +1829,8 @@ function performExportWithOptions(format, options) { pandocCmd += ' --variable header-includes="\\\\usepackage{lastpage}"'; } - // Try with specified PDF engine - exec(pandocCmd, (error) => { + // Try with specified PDF engine (using runPandocCmd for safety) + runPandocCmd(pandocCmd, (error) => { if (error) { // Try fallback engines if the specified one fails const fallbackEngines = ['pdflatex', 'lualatex']; @@ -1544,7 +1924,8 @@ function tryPdfFallback(inputFile, outputFile, engines, index, options, lastErro } } - exec(pandocCmd, (error) => { + // Use runPandocCmd for safety (prevents command injection) + runPandocCmd(pandocCmd, (error) => { if (error) { tryPdfFallback(inputFile, outputFile, engines, index + 1, options, error); } else { @@ -1562,11 +1943,11 @@ function showExportSuccess(outputFile) { }); } -// Helper function to export with pandoc (general) +// Helper function to export with pandoc (general) - uses runPandocCmd for safety function exportWithPandoc(pandocCmd, outputFile, format) { console.log(`Executing Pandoc command: ${pandocCmd}`); - exec(pandocCmd, async (error, stdout, stderr) => { + runPandocCmd(pandocCmd, async (error, stdout, stderr) => { if (error) { console.error(`Pandoc error for ${format}:`, error); console.error(`Pandoc stderr:`, stderr); @@ -1642,14 +2023,14 @@ function exportWithPandoc(pandocCmd, outputFile, format) { }); } -// Helper function to export PDF with pandoc (with fallbacks) +// Helper function to export PDF with pandoc (with fallbacks) - uses runPandocCmd for safety function exportWithPandocPDF(pandocCmd, outputFile) { - exec(pandocCmd, (error, stdout, stderr) => { + runPandocCmd(pandocCmd, (error, stdout, stderr) => { if (error) { console.log('XeLaTeX failed, trying PDFLaTeX...'); // Fallback to pdflatex const fallbackCmd = pandocCmd.replace('--pdf-engine=xelatex', '--pdf-engine=pdflatex'); - exec(fallbackCmd, (fallbackError, fallbackStdout, fallbackStderr) => { + runPandocCmd(fallbackCmd, (fallbackError, fallbackStdout, fallbackStderr) => { if (fallbackError) { console.log('PDFLaTeX failed, trying Electron PDF...'); // Final fallback to Electron PDF @@ -1970,10 +2351,10 @@ function importDocument() { additionalOptions = '--from=json -t markdown'; } - // Convert to markdown using pandoc + // Convert to markdown using pandoc (using runPandocCmd for safety) const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown ${additionalOptions} -o "${outputFile}"`; - exec(pandocCmd, (error, stdout, stderr) => { + runPandocCmd(pandocCmd, (error, stdout, stderr) => { if (error) { dialog.showErrorBox('Import Error', `Failed to import: ${error.message}\n\nMake sure Pandoc is installed.\n\nSupported formats: DOCX, ODT, RTF, HTML, LaTeX, EPUB, PDF, PPTX, ODP, RST, Textile, MediaWiki, Org-mode, AsciiDoc, CSV, and more.`); } else { @@ -2014,7 +2395,7 @@ ipcMain.on('save-current-file', (event, content) => { }); ipcMain.on('get-theme', (event) => { - const theme = store.get('theme', 'github'); + const theme = store.get('theme', 'atomonelight'); event.reply('theme-changed', theme); }); @@ -2288,15 +2669,15 @@ function performBatchConversion(inputFolder, outputFolder, format, options) { const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage); await exporter.convert(content, tempDocxPath); - // Step 2: Convert DOCX to PDF using LibreOffice + // Step 2: Convert DOCX to PDF using LibreOffice (using execFile for safety) const soffice = process.platform === 'win32' - ? '"C:\\Program Files\\LibreOffice\\program\\soffice.exe"' + ? 'C:\\Program Files\\LibreOffice\\program\\soffice.exe' : 'soffice'; const outputDir = path.dirname(outputFile); - const convertCmd = `${soffice} --headless --convert-to pdf --outdir "${outputDir}" "${tempDocxPath}"`; + const sofficeArgs = ['--headless', '--convert-to', 'pdf', '--outdir', outputDir, tempDocxPath]; - exec(convertCmd, (error, stdout, stderr) => { + execFile(soffice, sofficeArgs, (error, stdout, stderr) => { // Clean up temporary DOCX file try { fs.unlinkSync(tempDocxPath); @@ -2447,8 +2828,8 @@ function performBatchConversion(inputFolder, outputFolder, format, options) { pandocCmd += ` --variable footer="${footerText}"`; } - // Execute conversion - exec(pandocCmd, async (error, stdout, stderr) => { + // Execute conversion (using runPandocCmd for safety) + runPandocCmd(pandocCmd, async (error, stdout, stderr) => { if (!error) { // Add headers/footers to DOCX if enabled if (format === 'docx' && headerFooterSettings.enabled) { @@ -2563,24 +2944,25 @@ function performCLIConversion(inputPath, format) { console.log(`Converting "${path.basename(inputPath)}" to ${format.toUpperCase()}...`); - // Use existing export functions but with CLI output + // Use existing export functions but with CLI output (using runPandocCmd for safety) const pandocCommand = buildPandocCommand(content, format, outputPath); - - exec(pandocCommand, (error, stdout, stderr) => { + + runPandocCmd(pandocCommand, (error, stdout, stderr) => { if (error) { console.error(`Conversion failed: ${error.message}`); if (stderr) console.error(`Details: ${stderr}`); app.quit(); return; } - + console.log(`Successfully converted to: ${outputPath}`); - - // Show Windows notification + + // Show Windows notification (using exec for PowerShell is acceptable here - hardcoded command) if (process.platform === 'win32') { - exec(`powershell -Command "New-BurntToastNotification -Text 'PanConverter', 'File converted to ${format.toUpperCase()}' -AppLogo '${path.join(__dirname, '../assets/icon.png')}'"`, () => {}); + const iconPath = path.join(__dirname, '../assets/icon.png'); + execFile('powershell', ['-Command', `New-BurntToastNotification -Text 'PanConverter', 'File converted to ${format.toUpperCase()}' -AppLogo '${iconPath}'`], () => {}); } - + app.quit(); }); } catch (error) { @@ -3329,4 +3711,85 @@ ipcMain.on('select-pdf-folder', (event, inputId) => { if (folder && folder[0]) { event.reply('pdf-folder-selected', { inputId, path: folder[0] }); } +}); + +// ============================================ +// ASCII Art Generator Window +// ============================================ +let asciiGeneratorWindow = null; + +function openAsciiGenerator() { + if (asciiGeneratorWindow) { + asciiGeneratorWindow.focus(); + return; + } + + asciiGeneratorWindow = new BrowserWindow({ + width: 800, + height: 700, + parent: mainWindow, + modal: false, + title: 'ASCII Art Generator', + icon: path.join(__dirname, '../assets/icon.png'), + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') + } + }); + + asciiGeneratorWindow.loadFile(path.join(__dirname, 'ascii-generator.html')); + asciiGeneratorWindow.setMenuBarVisibility(false); + + asciiGeneratorWindow.on('closed', () => { + asciiGeneratorWindow = null; + }); +} + +ipcMain.on('open-ascii-generator', () => { + openAsciiGenerator(); +}); + +// ============================================ +// Table Generator Window +// ============================================ +let tableGeneratorWindow = null; + +function openTableGenerator() { + if (tableGeneratorWindow) { + tableGeneratorWindow.focus(); + return; + } + + tableGeneratorWindow = new BrowserWindow({ + width: 900, + height: 700, + parent: mainWindow, + modal: false, + title: 'Table Generator', + icon: path.join(__dirname, '../assets/icon.png'), + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js') + } + }); + + tableGeneratorWindow.loadFile(path.join(__dirname, 'table-generator.html')); + tableGeneratorWindow.setMenuBarVisibility(false); + + tableGeneratorWindow.on('closed', () => { + tableGeneratorWindow = null; + }); +} + +ipcMain.on('open-table-generator', () => { + openTableGenerator(); +}); + +// IPC Handler to receive generated content from generator windows +ipcMain.on('insert-generated-content', (event, content) => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('insert-content', content); + } }); \ No newline at end of file diff --git a/src/preload.js b/src/preload.js new file mode 100644 index 0000000..bd04aa4 --- /dev/null +++ b/src/preload.js @@ -0,0 +1,376 @@ +/** + * Preload Script for PanConverter + * + * This script creates a secure bridge between the main process and renderer process. + * It exposes only specific IPC channels, preventing direct Node.js access in the renderer. + * + * Security Benefits: + * - No direct access to Node.js APIs (fs, path, child_process, etc.) + * - All IPC channels are explicitly whitelisted + * - Prevents XSS from escalating to full system access + * + * @version 2.2.0 + */ + +const { contextBridge, ipcRenderer } = require('electron'); + +// Define allowed IPC channels for security +const ALLOWED_SEND_CHANNELS = [ + // File operations + 'save-file', + 'save-current-file', + 'set-current-file', + 'save-recent-files', + 'clear-recent-files', + 'renderer-ready', + + // Theme + 'get-theme', + + // Print + 'do-print', + + // Export + 'export-with-options', + 'export-spreadsheet', + + // Batch conversion + 'batch-convert', + 'select-folder', + + // Universal converter + 'universal-convert', + 'universal-convert-batch', + + // Image converter + 'image-convert', + 'image-batch-convert', + 'image-resize', + 'image-compress', + 'image-rotate', + + // Audio converter + 'audio-convert', + 'audio-batch-convert', + 'audio-extract', + 'audio-trim', + 'audio-merge', + + // Video converter + 'video-convert', + 'video-batch-convert', + 'video-compress', + 'video-trim', + 'video-frames', + 'video-gif', + + // Header/Footer + 'get-header-footer-settings', + 'save-header-footer-settings', + 'browse-header-footer-logo', + 'save-header-footer-logo', + 'clear-header-footer-logo', + + // Page settings + 'get-page-settings', + 'update-page-settings', + + // Template settings + 'set-custom-start-page', + + // PDF operations + 'process-pdf-operation', + 'get-pdf-page-count', + 'select-pdf-folder', + + // ASCII generator (separate window) + 'open-ascii-generator', + + // Table generator (separate window) + 'open-table-generator', + + // Insert generated content + 'insert-generated-content' +]; + +const ALLOWED_RECEIVE_CHANNELS = [ + // File operations + 'file-new', + 'file-opened', + 'file-save', + 'get-content-for-save', + 'get-content-for-spreadsheet', + 'recent-files-cleared', + + // UI toggles + 'toggle-preview', + 'toggle-find', + + // Theme + 'theme-changed', + 'theme-data', + + // Edit operations + 'undo', + 'redo', + + // Font + 'adjust-font-size', + + // Print + 'print-preview', + 'print-preview-styled', + + // Export dialogs + 'show-export-dialog', + 'show-batch-dialog', + 'show-universal-converter-dialog', + 'show-table-generator', + 'show-pdf-editor-dialog', + + // Converter dialogs + 'show-image-converter', + 'show-audio-converter', + 'show-video-converter', + + // PDF viewer + 'open-pdf-viewer', + + // Conversion status + 'conversion-status', + 'conversion-complete', + 'batch-progress', + 'image-conversion-complete', + 'audio-conversion-complete', + 'video-conversion-complete', + + // Folder selection + 'folder-selected', + 'pdf-folder-selected', + + // Header/Footer + 'header-footer-settings-data', + 'header-footer-logo-selected', + 'header-footer-logo-saved', + + // Page settings + 'page-settings-data', + + // PDF operations + 'pdf-page-count', + 'pdf-operation-complete', + 'pdf-operation-error', + + // ASCII Art Generator + 'show-ascii-generator-window', + 'show-ascii-generator', + + // Table Generator + 'show-table-generator-window', + + // Header/Footer dialog + 'open-header-footer-dialog', + 'header-footer-logo-cleared', + + // PDF operation progress + 'pdf-operation-progress', + + // Insert content from generator windows + 'insert-content' +]; + +/** + * Secure API exposed to renderer process + * Access via window.electronAPI in renderer + */ +contextBridge.exposeInMainWorld('electronAPI', { + // ============================================ + // SEND METHODS (Renderer -> Main) + // ============================================ + + /** + * Send a message to the main process + * @param {string} channel - IPC channel name + * @param {any} data - Data to send + */ + send: (channel, data) => { + if (ALLOWED_SEND_CHANNELS.includes(channel)) { + ipcRenderer.send(channel, data); + } else { + console.warn(`[Preload] Blocked send to unauthorized channel: ${channel}`); + } + }, + + /** + * Invoke a main process handler and get a response + * @param {string} channel - IPC channel name + * @param {any} data - Data to send + * @returns {Promise} Response from main process + */ + invoke: async (channel, data) => { + if (ALLOWED_SEND_CHANNELS.includes(channel)) { + return await ipcRenderer.invoke(channel, data); + } else { + console.warn(`[Preload] Blocked invoke to unauthorized channel: ${channel}`); + return null; + } + }, + + // ============================================ + // RECEIVE METHODS (Main -> Renderer) + // ============================================ + + /** + * Register a listener for messages from main process + * @param {string} channel - IPC channel name + * @param {Function} callback - Function to call with received data + * @returns {Function} Cleanup function to remove listener + */ + on: (channel, callback) => { + if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) { + const subscription = (event, ...args) => callback(...args); + ipcRenderer.on(channel, subscription); + + // Return cleanup function + return () => { + ipcRenderer.removeListener(channel, subscription); + }; + } else { + console.warn(`[Preload] Blocked listener for unauthorized channel: ${channel}`); + return () => {}; // No-op cleanup + } + }, + + /** + * Register a one-time listener for messages from main process + * @param {string} channel - IPC channel name + * @param {Function} callback - Function to call with received data + */ + once: (channel, callback) => { + if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) { + ipcRenderer.once(channel, (event, ...args) => callback(...args)); + } else { + console.warn(`[Preload] Blocked once listener for unauthorized channel: ${channel}`); + } + }, + + /** + * Remove all listeners for a channel + * @param {string} channel - IPC channel name + */ + removeAllListeners: (channel) => { + if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) { + ipcRenderer.removeAllListeners(channel); + } + }, + + // ============================================ + // CONVENIENCE METHODS + // ============================================ + + // File Operations + file: { + save: (filePath, content) => ipcRenderer.send('save-file', { path: filePath, content }), + saveCurrent: (content) => ipcRenderer.send('save-current-file', content), + setCurrent: (filePath) => ipcRenderer.send('set-current-file', filePath), + saveRecent: (recentFiles) => ipcRenderer.send('save-recent-files', recentFiles), + clearRecent: () => ipcRenderer.send('clear-recent-files'), + rendererReady: () => ipcRenderer.send('renderer-ready') + }, + + // Theme Operations + theme: { + get: () => ipcRenderer.send('get-theme') + }, + + // Print Operations + print: { + doPrint: (options) => ipcRenderer.send('do-print', options) + }, + + // Export Operations + export: { + withOptions: (format, options) => ipcRenderer.send('export-with-options', { format, options }), + spreadsheet: (content, format) => ipcRenderer.send('export-spreadsheet', { content, format }) + }, + + // Batch Conversion + batch: { + convert: (inputFolder, outputFolder, format, options) => { + ipcRenderer.send('batch-convert', { inputFolder, outputFolder, format, options }); + }, + selectFolder: (type) => ipcRenderer.send('select-folder', type) + }, + + // Universal Converter + converter: { + convert: (tool, fromFormat, toFormat, filePath) => { + ipcRenderer.send('universal-convert', { tool, fromFormat, toFormat, filePath }); + }, + convertBatch: (tool, fromFormat, toFormat, inputFolder, outputFolder) => { + ipcRenderer.send('universal-convert-batch', { tool, fromFormat, toFormat, inputFolder, outputFolder }); + } + }, + + // Header/Footer Operations + headerFooter: { + getSettings: () => ipcRenderer.send('get-header-footer-settings'), + saveSettings: (settings) => ipcRenderer.send('save-header-footer-settings', settings), + browseLogo: (position) => ipcRenderer.send('browse-header-footer-logo', position), + saveLogo: (position, filePath) => ipcRenderer.send('save-header-footer-logo', { position, filePath }), + clearLogo: (position) => ipcRenderer.send('clear-header-footer-logo', position) + }, + + // Page Settings + page: { + getSettings: () => ipcRenderer.send('get-page-settings'), + updateSettings: (settings) => ipcRenderer.send('update-page-settings', settings), + setCustomStartPage: (pageNumber) => ipcRenderer.send('set-custom-start-page', pageNumber) + }, + + // PDF Operations + pdf: { + processOperation: (data) => ipcRenderer.send('process-pdf-operation', data), + getPageCount: (filePath) => ipcRenderer.send('get-pdf-page-count', filePath), + selectFolder: (inputId) => ipcRenderer.send('select-pdf-folder', inputId) + }, + + // Image Converter Operations + image: { + convert: (data) => ipcRenderer.send('image-convert', data), + batchConvert: (data) => ipcRenderer.send('image-batch-convert', data), + resize: (data) => ipcRenderer.send('image-resize', data), + compress: (data) => ipcRenderer.send('image-compress', data), + rotate: (data) => ipcRenderer.send('image-rotate', data) + }, + + // Audio Converter Operations + audio: { + convert: (data) => ipcRenderer.send('audio-convert', data), + batchConvert: (data) => ipcRenderer.send('audio-batch-convert', data), + extract: (data) => ipcRenderer.send('audio-extract', data), + trim: (data) => ipcRenderer.send('audio-trim', data), + merge: (data) => ipcRenderer.send('audio-merge', data) + }, + + // Video Converter Operations + video: { + convert: (data) => ipcRenderer.send('video-convert', data), + batchConvert: (data) => ipcRenderer.send('video-batch-convert', data), + compress: (data) => ipcRenderer.send('video-compress', data), + trim: (data) => ipcRenderer.send('video-trim', data), + extractFrames: (data) => ipcRenderer.send('video-frames', data), + toGif: (data) => ipcRenderer.send('video-gif', data) + }, + + // Generator Windows + generators: { + openAscii: () => ipcRenderer.send('open-ascii-generator'), + openTable: () => ipcRenderer.send('open-table-generator') + } +}); + +// Log successful preload initialization +console.log('[Preload] Secure IPC bridge initialized'); +console.log('[Preload] Allowed send channels:', ALLOWED_SEND_CHANNELS.length); +console.log('[Preload] Allowed receive channels:', ALLOWED_RECEIVE_CHANNELS.length); diff --git a/src/renderer.js b/src/renderer.js index d8aca81..a128a64 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -1,3 +1,8 @@ +/** + * MarkdownConverter Renderer Process + * @version 3.0.0 + */ + const { ipcRenderer } = require('electron'); const marked = require('marked'); const DOMPurify = require('dompurify'); @@ -10,7 +15,6 @@ marked.setOptions({ try { return hljs.highlight(code, { language: lang }).value; } catch (err) { - // Fallback to auto highlighting if language-specific highlighting fails console.warn('Syntax highlighting failed for language:', lang, err.message); } } @@ -308,6 +312,11 @@ class TabManager { if (!tab || !preview) return; try { + // Check if libraries are available + if (!marked || !DOMPurify) { + preview.innerHTML = '

Error: Required libraries (marked/DOMPurify) not loaded. Check internet connection.

'; + return; + } const html = marked.parse(tab.content); const sanitizedHtml = DOMPurify.sanitize(html); preview.innerHTML = sanitizedHtml; @@ -1043,7 +1052,6 @@ class TabManager { this.updateTabBar(); // Notify main process about current file for exports - const { ipcRenderer } = require('electron'); ipcRenderer.send('set-current-file', filePath); console.log('File opened successfully'); @@ -2308,12 +2316,12 @@ ipcRenderer.on('show-table-generator', () => { }); // Show PDF Editor Dialog -ipcRenderer.on('show-pdf-editor-dialog', (event, operation) => { +ipcRenderer.on('show-pdf-editor-dialog', (event, operation, openedFilePath) => { currentPDFOperation = operation; - showPDFEditorDialog(operation); + showPDFEditorDialog(operation, openedFilePath); }); -function showPDFEditorDialog(operation) { +function showPDFEditorDialog(operation, openedFilePath = null) { const dialog = document.getElementById('pdf-editor-dialog'); const title = document.getElementById('pdf-editor-title'); @@ -2329,43 +2337,82 @@ function showPDFEditorDialog(operation) { sectionId = 'pdf-merge-section'; titleText = 'Merge PDFs'; mergeFilePaths = []; + // If we have an opened file, add it as the first file to merge + if (openedFilePath) { + mergeFilePaths.push(openedFilePath); + } updateMergeFilesList(); break; case 'split': sectionId = 'pdf-split-section'; titleText = 'Split PDF'; + // Pre-fill input path if we have an opened file + if (openedFilePath) { + document.getElementById('split-input-path').value = openedFilePath; + } break; case 'compress': sectionId = 'pdf-compress-section'; titleText = 'Compress PDF'; + if (openedFilePath) { + document.getElementById('compress-input-path').value = openedFilePath; + } break; case 'rotate': sectionId = 'pdf-rotate-section'; titleText = 'Rotate Pages'; + if (openedFilePath) { + const rotateInput = document.getElementById('rotate-input-path'); + if (rotateInput) rotateInput.value = openedFilePath; + } break; case 'delete': sectionId = 'pdf-delete-section'; titleText = 'Delete Pages'; + if (openedFilePath) { + const deleteInput = document.getElementById('delete-input-path'); + if (deleteInput) deleteInput.value = openedFilePath; + } break; case 'reorder': sectionId = 'pdf-reorder-section'; titleText = 'Reorder Pages'; + if (openedFilePath) { + const reorderInput = document.getElementById('reorder-input-path'); + if (reorderInput) reorderInput.value = openedFilePath; + } break; case 'watermark': sectionId = 'pdf-watermark-section'; titleText = 'Add Watermark'; + if (openedFilePath) { + const watermarkInput = document.getElementById('watermark-input-path'); + if (watermarkInput) watermarkInput.value = openedFilePath; + } break; case 'encrypt': sectionId = 'pdf-encrypt-section'; titleText = 'Password Protection'; + if (openedFilePath) { + const encryptInput = document.getElementById('encrypt-input-path'); + if (encryptInput) encryptInput.value = openedFilePath; + } break; case 'decrypt': sectionId = 'pdf-decrypt-section'; titleText = 'Remove Password'; + if (openedFilePath) { + const decryptInput = document.getElementById('decrypt-input-path'); + if (decryptInput) decryptInput.value = openedFilePath; + } break; case 'permissions': sectionId = 'pdf-permissions-section'; titleText = 'Set Permissions'; + if (openedFilePath) { + const permInput = document.getElementById('permissions-input-path'); + if (permInput) permInput.value = openedFilePath; + } break; } @@ -2545,6 +2592,32 @@ document.addEventListener('DOMContentLoaded', () => { }); } + // Overwrite checkbox handlers - toggle Save As section visibility + const overwriteCheckboxes = [ + { checkbox: 'compress-overwrite', section: 'compress-saveas-section' }, + { checkbox: 'rotate-overwrite', section: 'rotate-saveas-section' }, + { checkbox: 'delete-overwrite', section: 'delete-saveas-section' }, + { checkbox: 'reorder-overwrite', section: 'reorder-saveas-section' }, + { checkbox: 'watermark-overwrite', section: 'watermark-saveas-section' }, + { checkbox: 'encrypt-overwrite', section: 'encrypt-saveas-section' }, + { checkbox: 'decrypt-overwrite', section: 'decrypt-saveas-section' }, + { checkbox: 'permissions-overwrite', section: 'permissions-saveas-section' } + ]; + + overwriteCheckboxes.forEach(item => { + const checkbox = document.getElementById(item.checkbox); + const section = document.getElementById(item.section); + if (checkbox && section) { + checkbox.addEventListener('change', (e) => { + if (e.target.checked) { + section.classList.add('hidden'); + } else { + section.classList.remove('hidden'); + } + }); + } + }); + // Load current page order button const loadCurrentOrder = document.getElementById('load-current-order'); if (loadCurrentOrder) { @@ -2619,33 +2692,36 @@ function processPDFOperation() { case 'compress': operationData.inputPath = document.getElementById('compress-input-path').value.trim(); - operationData.outputPath = document.getElementById('compress-output-path').value.trim(); + operationData.overwrite = document.getElementById('compress-overwrite').checked; + operationData.outputPath = operationData.overwrite ? operationData.inputPath : document.getElementById('compress-output-path').value.trim(); operationData.compressionLevel = document.getElementById('compress-level').value; operationData.compressImages = document.getElementById('compress-images').checked; operationData.removeDuplicates = document.getElementById('compress-remove-duplicates').checked; operationData.optimizeFonts = document.getElementById('compress-optimize-fonts').checked; if (!operationData.inputPath || !operationData.outputPath) { - alert('Please select input and output file paths'); + alert('Please select input file' + (operationData.overwrite ? '' : ' and output file paths')); return; } break; case 'rotate': operationData.inputPath = document.getElementById('rotate-input-path').value.trim(); - operationData.outputPath = document.getElementById('rotate-output-path').value.trim(); + operationData.overwrite = document.getElementById('rotate-overwrite').checked; + operationData.outputPath = operationData.overwrite ? operationData.inputPath : document.getElementById('rotate-output-path').value.trim(); operationData.pages = document.getElementById('rotate-pages').value.trim(); operationData.angle = parseInt(document.getElementById('rotate-angle').value); if (!operationData.inputPath || !operationData.outputPath) { - alert('Please select input and output file paths'); + alert('Please select input file' + (operationData.overwrite ? '' : ' and output file')); return; } break; case 'delete': operationData.inputPath = document.getElementById('delete-input-path').value.trim(); - operationData.outputPath = document.getElementById('delete-output-path').value.trim(); + operationData.overwrite = document.getElementById('delete-overwrite').checked; + operationData.outputPath = operationData.overwrite ? operationData.inputPath : document.getElementById('delete-output-path').value.trim(); operationData.pages = document.getElementById('delete-pages').value.trim(); if (!operationData.inputPath || !operationData.outputPath || !operationData.pages) { @@ -2656,7 +2732,8 @@ function processPDFOperation() { case 'reorder': operationData.inputPath = document.getElementById('reorder-input-path').value.trim(); - operationData.outputPath = document.getElementById('reorder-output-path').value.trim(); + operationData.overwrite = document.getElementById('reorder-overwrite').checked; + operationData.outputPath = operationData.overwrite ? operationData.inputPath : document.getElementById('reorder-output-path').value.trim(); operationData.newOrder = document.getElementById('reorder-pages').value.trim(); if (!operationData.inputPath || !operationData.outputPath || !operationData.newOrder) { @@ -2667,7 +2744,8 @@ function processPDFOperation() { case 'watermark': operationData.inputPath = document.getElementById('watermark-input-path').value.trim(); - operationData.outputPath = document.getElementById('watermark-output-path').value.trim(); + operationData.overwrite = document.getElementById('watermark-overwrite').checked; + operationData.outputPath = operationData.overwrite ? operationData.inputPath : document.getElementById('watermark-output-path').value.trim(); operationData.text = document.getElementById('watermark-text').value.trim(); operationData.fontSize = parseInt(document.getElementById('watermark-font-size').value); operationData.opacity = parseInt(document.getElementById('watermark-opacity').value) / 100; @@ -2687,7 +2765,8 @@ function processPDFOperation() { case 'encrypt': operationData.inputPath = document.getElementById('encrypt-input-path').value.trim(); - operationData.outputPath = document.getElementById('encrypt-output-path').value.trim(); + operationData.overwrite = document.getElementById('encrypt-overwrite').checked; + operationData.outputPath = operationData.overwrite ? operationData.inputPath : document.getElementById('encrypt-output-path').value.trim(); operationData.userPassword = document.getElementById('encrypt-user-password').value; operationData.ownerPassword = document.getElementById('encrypt-owner-password').value; operationData.encryptionLevel = parseInt(document.getElementById('encrypt-level').value); @@ -2704,14 +2783,15 @@ function processPDFOperation() { }; if (!operationData.inputPath || !operationData.outputPath || !operationData.userPassword) { - alert('Please select files and enter a user password'); + alert('Please select file and enter a user password'); return; } break; case 'decrypt': operationData.inputPath = document.getElementById('decrypt-input-path').value.trim(); - operationData.outputPath = document.getElementById('decrypt-output-path').value.trim(); + operationData.overwrite = document.getElementById('decrypt-overwrite').checked; + operationData.outputPath = operationData.overwrite ? operationData.inputPath : document.getElementById('decrypt-output-path').value.trim(); operationData.password = document.getElementById('decrypt-password').value; if (!operationData.inputPath || !operationData.outputPath || !operationData.password) { @@ -2722,7 +2802,8 @@ function processPDFOperation() { case 'permissions': operationData.inputPath = document.getElementById('permissions-input-path').value.trim(); - operationData.outputPath = document.getElementById('permissions-output-path').value.trim(); + operationData.overwrite = document.getElementById('permissions-overwrite').checked; + operationData.outputPath = operationData.overwrite ? operationData.inputPath : document.getElementById('permissions-output-path').value.trim(); operationData.currentPassword = document.getElementById('permissions-current-password').value; operationData.ownerPassword = document.getElementById('permissions-owner-password').value; @@ -4057,3 +4138,377 @@ document.addEventListener('click', (e) => { } }); +// ============================================ +// Insert Content from Generator Windows +// ============================================ +ipcRenderer.on('insert-content', (event, content) => { + if (tabManager) { + const activeTabId = tabManager.activeTabId; + const editor = document.getElementById(`editor-${activeTabId}`); + if (editor) { + const cursorPos = editor.selectionStart; + const textBefore = editor.value.substring(0, cursorPos); + const textAfter = editor.value.substring(cursorPos); + + editor.value = textBefore + content + textAfter; + editor.focus(); + + const newPos = cursorPos + content.length; + editor.setSelectionRange(newPos, newPos); + + // Update state and preview + tabManager.tabs.get(activeTabId).content = editor.value; + tabManager.tabs.get(activeTabId).modified = true; + tabManager.updatePreview(activeTabId); + tabManager.updateTabTitle(activeTabId); + } + } +}); + +// ============================================ +// PDF VIEWER FUNCTIONALITY +// ============================================ + +let pdfDoc = null; +let pdfCurrentPage = 1; +let pdfZoomLevel = 1.0; +let pdfRotation = 0; +let pdfFilePath = null; +let isPdfViewerActive = false; // Track if PDF viewer is currently shown + +// Initialize PDF.js +const pdfjsLib = require('pdfjs-dist'); +pdfjsLib.GlobalWorkerOptions.workerSrc = require.resolve('pdfjs-dist/build/pdf.worker.js'); + +// Open PDF file +async function openPdfFile(filePath) { + // Prevent multiple simultaneous PDF loads + if (isPdfViewerActive && pdfFilePath === filePath) { + console.log('PDF already open:', filePath); + return; + } + + // Close any existing PDF first + if (pdfDoc) { + try { + await pdfDoc.destroy(); + } catch (e) { + console.warn('Error destroying previous PDF:', e); + } + pdfDoc = null; + } + + try { + // Show loading state + document.getElementById('status-text').textContent = 'Loading PDF...'; + + const loadingTask = pdfjsLib.getDocument(filePath); + pdfDoc = await loadingTask.promise; + pdfFilePath = filePath; + pdfCurrentPage = 1; + pdfZoomLevel = 1.0; + pdfRotation = 0; + isPdfViewerActive = true; + + // Update UI + document.getElementById('pdf-total-pages').textContent = pdfDoc.numPages; + document.getElementById('pdf-page-input').value = 1; + document.getElementById('pdf-page-input').max = pdfDoc.numPages; + document.getElementById('pdf-filename').textContent = require('path').basename(filePath); + document.getElementById('pdf-zoom-level').textContent = '100%'; + + // Hide markdown toolbar, tabs, show PDF viewer + document.querySelector('.toolbar').classList.add('hidden'); + document.getElementById('tab-bar').classList.add('hidden'); + document.querySelectorAll('.tab-content').forEach(tc => tc.classList.add('hidden')); + document.getElementById('pdf-viewer-container').classList.remove('hidden'); + + // Render first page + await renderPdfPage(pdfCurrentPage); + + // Update status + document.getElementById('status-text').textContent = `PDF: ${require('path').basename(filePath)} (${pdfDoc.numPages} pages)`; + } catch (error) { + console.error('Error loading PDF:', error); + isPdfViewerActive = false; + pdfDoc = null; + pdfFilePath = null; + document.getElementById('status-text').textContent = 'Error loading PDF'; + alert('Error loading PDF: ' + error.message); + } +} + +// Render PDF page +async function renderPdfPage(pageNum) { + if (!pdfDoc) return; + + try { + const page = await pdfDoc.getPage(pageNum); + const canvas = document.getElementById('pdf-canvas'); + const ctx = canvas.getContext('2d'); + + // Calculate viewport with zoom and rotation + const viewport = page.getViewport({ scale: pdfZoomLevel, rotation: pdfRotation }); + + canvas.width = viewport.width; + canvas.height = viewport.height; + + const renderContext = { + canvasContext: ctx, + viewport: viewport + }; + + await page.render(renderContext).promise; + + // Update page input + document.getElementById('pdf-page-input').value = pageNum; + } catch (error) { + console.error('Error rendering PDF page:', error); + } +} + +// PDF navigation handlers +document.getElementById('pdf-prev-page')?.addEventListener('click', async () => { + if (pdfCurrentPage > 1) { + pdfCurrentPage--; + await renderPdfPage(pdfCurrentPage); + } +}); + +document.getElementById('pdf-next-page')?.addEventListener('click', async () => { + if (pdfDoc && pdfCurrentPage < pdfDoc.numPages) { + pdfCurrentPage++; + await renderPdfPage(pdfCurrentPage); + } +}); + +document.getElementById('pdf-page-input')?.addEventListener('change', async (e) => { + const pageNum = parseInt(e.target.value); + if (pdfDoc && pageNum >= 1 && pageNum <= pdfDoc.numPages) { + pdfCurrentPage = pageNum; + await renderPdfPage(pdfCurrentPage); + } +}); + +// PDF zoom handlers +document.getElementById('pdf-zoom-out')?.addEventListener('click', async () => { + if (pdfZoomLevel > 0.25) { + pdfZoomLevel -= 0.25; + document.getElementById('pdf-zoom-level').textContent = Math.round(pdfZoomLevel * 100) + '%'; + await renderPdfPage(pdfCurrentPage); + } +}); + +document.getElementById('pdf-zoom-in')?.addEventListener('click', async () => { + if (pdfZoomLevel < 4.0) { + pdfZoomLevel += 0.25; + document.getElementById('pdf-zoom-level').textContent = Math.round(pdfZoomLevel * 100) + '%'; + await renderPdfPage(pdfCurrentPage); + } +}); + +document.getElementById('pdf-fit-width')?.addEventListener('click', async () => { + if (!pdfDoc) return; + const page = await pdfDoc.getPage(pdfCurrentPage); + const viewport = page.getViewport({ scale: 1, rotation: pdfRotation }); + const containerWidth = document.getElementById('pdf-viewer').clientWidth - 40; + pdfZoomLevel = containerWidth / viewport.width; + document.getElementById('pdf-zoom-level').textContent = Math.round(pdfZoomLevel * 100) + '%'; + await renderPdfPage(pdfCurrentPage); +}); + +document.getElementById('pdf-fit-page')?.addEventListener('click', async () => { + if (!pdfDoc) return; + const page = await pdfDoc.getPage(pdfCurrentPage); + const viewport = page.getViewport({ scale: 1, rotation: pdfRotation }); + const container = document.getElementById('pdf-viewer'); + const containerWidth = container.clientWidth - 40; + const containerHeight = container.clientHeight - 40; + const scaleX = containerWidth / viewport.width; + const scaleY = containerHeight / viewport.height; + pdfZoomLevel = Math.min(scaleX, scaleY); + document.getElementById('pdf-zoom-level').textContent = Math.round(pdfZoomLevel * 100) + '%'; + await renderPdfPage(pdfCurrentPage); +}); + +// PDF rotation handlers +document.getElementById('pdf-rotate-left')?.addEventListener('click', async () => { + pdfRotation = (pdfRotation - 90 + 360) % 360; + await renderPdfPage(pdfCurrentPage); +}); + +document.getElementById('pdf-rotate-right')?.addEventListener('click', async () => { + pdfRotation = (pdfRotation + 90) % 360; + await renderPdfPage(pdfCurrentPage); +}); + +// Close PDF viewer +document.getElementById('pdf-close')?.addEventListener('click', () => { + closePdfViewer(); +}); + +async function closePdfViewer() { + // Destroy PDF document to free memory + if (pdfDoc) { + try { + await pdfDoc.destroy(); + } catch (e) { + console.warn('Error destroying PDF:', e); + } + } + + pdfDoc = null; + pdfFilePath = null; + isPdfViewerActive = false; + + // Hide PDF viewer + document.getElementById('pdf-viewer-container').classList.add('hidden'); + + // Show markdown tabs, tab bar, and toolbar + document.getElementById('tab-bar').classList.remove('hidden'); + document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('hidden')); + document.querySelector('.toolbar').classList.remove('hidden'); + + // Activate the correct markdown tab + if (tabManager) { + const activeTab = document.querySelector(`.tab-content[data-tab-id="${tabManager.activeTabId}"]`); + if (activeTab) activeTab.classList.add('active'); + // Refresh the active tab + tabManager.updatePreview(tabManager.activeTabId); + } + + document.getElementById('status-text').textContent = 'Ready'; +} + +// Handle PDF file open from main process +ipcRenderer.on('open-pdf-file', (event, filePath) => { + openPdfFile(filePath); +}); + +// ============================================ +// PDF EDITOR TOOLBAR BUTTONS +// ============================================ + +// Helper function to trigger PDF editor dialog +function openPdfEditorDialog(operation) { + // Use the opened PDF file if available, otherwise prompt + if (pdfFilePath) { + ipcRenderer.send('show-pdf-editor-from-toolbar', { operation, filePath: pdfFilePath }); + } else { + ipcRenderer.send('show-pdf-editor-from-toolbar', { operation, filePath: null }); + } +} + +// PDF Editor toolbar button handlers +document.getElementById('pdf-tb-merge')?.addEventListener('click', () => { + openPdfEditorDialog('merge'); +}); + +document.getElementById('pdf-tb-split')?.addEventListener('click', () => { + openPdfEditorDialog('split'); +}); + +document.getElementById('pdf-tb-compress')?.addEventListener('click', () => { + openPdfEditorDialog('compress'); +}); + +document.getElementById('pdf-tb-rotate')?.addEventListener('click', () => { + openPdfEditorDialog('rotate'); +}); + +document.getElementById('pdf-tb-delete')?.addEventListener('click', () => { + openPdfEditorDialog('delete'); +}); + +document.getElementById('pdf-tb-reorder')?.addEventListener('click', () => { + openPdfEditorDialog('reorder'); +}); + +document.getElementById('pdf-tb-watermark')?.addEventListener('click', () => { + openPdfEditorDialog('watermark'); +}); + +document.getElementById('pdf-tb-encrypt')?.addEventListener('click', () => { + openPdfEditorDialog('encrypt'); +}); + +document.getElementById('pdf-tb-decrypt')?.addEventListener('click', () => { + openPdfEditorDialog('decrypt'); +}); + +// ============================================ +// DYNAMIC PANE RESIZER +// ============================================ + +function initPaneResizer(tabId) { + const resizer = document.getElementById(`pane-resizer-${tabId}`); + const editorPane = document.getElementById(`editor-pane-${tabId}`); + const previewPane = document.getElementById(`preview-pane-${tabId}`); + const tabContent = document.getElementById(`tab-content-${tabId}`); + + if (!resizer || !editorPane || !previewPane) return; + + let isResizing = false; + let startX = 0; + let startEditorWidth = 0; + + resizer.addEventListener('mousedown', (e) => { + isResizing = true; + startX = e.clientX; + startEditorWidth = editorPane.offsetWidth; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + e.preventDefault(); + }); + + document.addEventListener('mousemove', (e) => { + if (!isResizing) return; + + const containerWidth = tabContent.offsetWidth; + const deltaX = e.clientX - startX; + let newEditorWidth = startEditorWidth + deltaX; + + // Minimum widths + const minWidth = 150; + const maxWidth = containerWidth - minWidth - 6; // 6px for resizer + + newEditorWidth = Math.max(minWidth, Math.min(maxWidth, newEditorWidth)); + + const editorPercent = (newEditorWidth / containerWidth) * 100; + const previewPercent = 100 - editorPercent - 1; // 1% for resizer + + editorPane.style.flex = `0 0 ${editorPercent}%`; + previewPane.style.flex = `0 0 ${previewPercent}%`; + }); + + document.addEventListener('mouseup', () => { + if (isResizing) { + isResizing = false; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + } + }); +} + +// Initialize resizer for first tab +document.addEventListener('DOMContentLoaded', () => { + initPaneResizer(1); +}); + +// ============================================ +// IMAGE/AUDIO/VIDEO CONVERTER HANDLERS +// These require external tools (ImageMagick, FFmpeg) +// ============================================ + +ipcRenderer.on('show-image-tool', (event, tool) => { + alert(`Image ${tool} tool requires ImageMagick to be installed.\n\nPlease install ImageMagick from: https://imagemagick.org/\n\nThis feature will be available in a future update with built-in support.`); +}); + +ipcRenderer.on('show-audio-tool', (event, tool) => { + alert(`Audio ${tool} tool requires FFmpeg to be installed.\n\nPlease install FFmpeg from: https://ffmpeg.org/\n\nThis feature will be available in a future update with built-in support.`); +}); + +ipcRenderer.on('show-video-tool', (event, tool) => { + alert(`Video ${tool} tool requires FFmpeg to be installed.\n\nPlease install FFmpeg from: https://ffmpeg.org/\n\nThis feature will be available in a future update with built-in support.`); +}); + diff --git a/src/styles-concreteinfo.css b/src/styles-concreteinfo.css new file mode 100644 index 0000000..aa80553 --- /dev/null +++ b/src/styles-concreteinfo.css @@ -0,0 +1,969 @@ +/** + * ConcreteInfo Theme for MarkdownConverter + * Based on logo palette: #464646, #9a9696, #e5461f, #e3e3e3, #0d0b09 + * Version: 3.0.0 + */ + +/* ============================================ + CSS Variables - ConcreteInfo Theme + ============================================ */ +:root { + /* Primary Colors from Logo Palette */ + --ci-dark-gray: #464646; + --ci-medium-gray: #9a9696; + --ci-accent: #e5461f; + --ci-light-gray: #e3e3e3; + --ci-black: #0d0b09; + + /* Extended Palette */ + --ci-accent-hover: #c93a18; + --ci-accent-light: rgba(229, 70, 31, 0.1); + --ci-white: #ffffff; + --ci-bg: #f5f5f5; + --ci-border: #d0d0d0; + + /* Semantic Colors */ + --ci-success: #28a745; + --ci-warning: #ffc107; + --ci-danger: #dc3545; + --ci-info: #17a2b8; + + /* Typography */ + --font-sans: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif; + --font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + + /* Spacing */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 24px; + --spacing-xl: 32px; + + /* Border Radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15); + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-normal: 250ms ease; + --transition-slow: 350ms ease; +} + +/* ============================================ + ConcreteInfo Theme Application + ============================================ */ +body.theme-concreteinfo { + background: var(--ci-bg); + color: var(--ci-dark-gray); +} + +body.theme-concreteinfo .toolbar { + background: linear-gradient(135deg, var(--ci-dark-gray) 0%, var(--ci-black) 100%); + border-bottom: 3px solid var(--ci-accent); +} + +body.theme-concreteinfo .toolbar button { + color: var(--ci-white); + background: transparent; + border: 1px solid transparent; + transition: all var(--transition-fast); +} + +body.theme-concreteinfo .toolbar button:hover { + background: var(--ci-accent); + border-color: var(--ci-accent); + transform: translateY(-1px); +} + +body.theme-concreteinfo .tab-bar { + background: var(--ci-dark-gray); + border-bottom: 2px solid var(--ci-accent); +} + +body.theme-concreteinfo .tab { + background: var(--ci-medium-gray); + color: var(--ci-white); + border: none; + margin: 2px; + border-radius: var(--radius-sm) var(--radius-sm) 0 0; + transition: all var(--transition-fast); +} + +body.theme-concreteinfo .tab:hover { + background: var(--ci-accent-light); +} + +body.theme-concreteinfo .tab.active { + background: var(--ci-accent); + color: var(--ci-white); +} + +body.theme-concreteinfo .editor-textarea { + background: var(--ci-white); + color: var(--ci-black); + border: 1px solid var(--ci-border); + font-family: var(--font-mono); +} + +body.theme-concreteinfo .preview-content { + background: var(--ci-white); + color: var(--ci-dark-gray); + border-left: 3px solid var(--ci-accent); +} + +body.theme-concreteinfo .status-bar { + background: var(--ci-dark-gray); + color: var(--ci-light-gray); + border-top: 2px solid var(--ci-accent); +} + +/* ============================================ + Modern Modal Styles + ============================================ */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(13, 11, 9, 0.7); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + opacity: 0; + visibility: hidden; + transition: all var(--transition-normal); +} + +.modal-overlay.active { + opacity: 1; + visibility: visible; +} + +.modal-content { + background: var(--ci-white); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xl); + max-width: 600px; + width: 90%; + max-height: 85vh; + overflow: hidden; + transform: scale(0.9) translateY(20px); + transition: transform var(--transition-normal); +} + +.modal-overlay.active .modal-content { + transform: scale(1) translateY(0); +} + +.modal-header { + background: linear-gradient(135deg, var(--ci-dark-gray) 0%, var(--ci-black) 100%); + color: var(--ci-white); + padding: var(--spacing-md) var(--spacing-lg); + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 3px solid var(--ci-accent); +} + +.modal-header h3 { + margin: 0; + font-size: 1.25rem; + font-weight: 600; + font-family: var(--font-sans); +} + +.modal-close { + background: transparent; + border: none; + color: var(--ci-white); + font-size: 1.5rem; + cursor: pointer; + width: 32px; + height: 32px; + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-fast); +} + +.modal-close:hover { + background: var(--ci-accent); + transform: rotate(90deg); +} + +.modal-body { + padding: var(--spacing-lg); + overflow-y: auto; + max-height: 60vh; +} + +.modal-footer { + padding: var(--spacing-md) var(--spacing-lg); + background: var(--ci-light-gray); + display: flex; + justify-content: flex-end; + gap: var(--spacing-sm); + border-top: 1px solid var(--ci-border); +} + +/* ============================================ + Button Styles + ============================================ */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--spacing-xs); + padding: var(--spacing-sm) var(--spacing-md); + font-family: var(--font-sans); + font-size: 0.875rem; + font-weight: 500; + border-radius: var(--radius-md); + border: none; + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn-primary { + background: var(--ci-accent); + color: var(--ci-white); +} + +.btn-primary:hover { + background: var(--ci-accent-hover); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.btn-secondary { + background: var(--ci-medium-gray); + color: var(--ci-white); +} + +.btn-secondary:hover { + background: var(--ci-dark-gray); +} + +.btn-outline { + background: transparent; + border: 2px solid var(--ci-accent); + color: var(--ci-accent); +} + +.btn-outline:hover { + background: var(--ci-accent); + color: var(--ci-white); +} + +.btn-ghost { + background: transparent; + color: var(--ci-dark-gray); +} + +.btn-ghost:hover { + background: var(--ci-light-gray); +} + +/* ============================================ + Form Controls + ============================================ */ +.form-group { + margin-bottom: var(--spacing-md); +} + +.form-label { + display: block; + margin-bottom: var(--spacing-xs); + font-weight: 500; + color: var(--ci-dark-gray); + font-size: 0.875rem; +} + +.form-input, +.form-select { + width: 100%; + padding: var(--spacing-sm) var(--spacing-md); + font-family: var(--font-sans); + font-size: 0.875rem; + border: 2px solid var(--ci-border); + border-radius: var(--radius-md); + background: var(--ci-white); + color: var(--ci-dark-gray); + transition: all var(--transition-fast); +} + +.form-input:focus, +.form-select:focus { + outline: none; + border-color: var(--ci-accent); + box-shadow: 0 0 0 3px var(--ci-accent-light); +} + +.form-input::placeholder { + color: var(--ci-medium-gray); +} + +/* ============================================ + App Header with Logo + ============================================ */ +.app-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 16px; + background: #ffffff; + border-bottom: 2px solid #e1e4e8; + min-height: 36px; +} + +.app-header-left { + display: flex; + align-items: center; + gap: 12px; +} + +.app-header-right { + display: flex; + align-items: center; + gap: 12px; +} + +.app-logo { + height: 22px; + width: auto; +} + +.app-title { + color: #24292e; + font-size: 0.95rem; + font-weight: 600; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; +} + +.app-version { + color: #6a737d; + font-size: 0.7rem; + font-family: 'SFMono-Regular', Consolas, monospace; +} + +/* Dark theme header adjustments */ +body.theme-dark .app-header, +body.theme-dracula .app-header, +body.theme-onedark .app-header, +body.theme-tokyonight .app-header, +body.theme-monokai .app-header, +body.theme-cobalt2 .app-header, +body.theme-gruvbox-dark .app-header, +body.theme-ayu-dark .app-header, +body.theme-ayu-mirage .app-header, +body.theme-palenight .app-header, +body.theme-oceanic-next .app-header, +body.theme-nord .app-header, +body.theme-concrete-dark .app-header { + background: #1f2937; + border-bottom-color: #374151; +} + +body.theme-dark .app-title, +body.theme-dracula .app-title, +body.theme-onedark .app-title, +body.theme-tokyonight .app-title, +body.theme-monokai .app-title, +body.theme-cobalt2 .app-title, +body.theme-gruvbox-dark .app-title, +body.theme-ayu-dark .app-title, +body.theme-ayu-mirage .app-title, +body.theme-palenight .app-title, +body.theme-oceanic-next .app-title, +body.theme-nord .app-title, +body.theme-concrete-dark .app-title { + color: #f3f4f6; +} + +body.theme-dark .app-version, +body.theme-dracula .app-version, +body.theme-onedark .app-version, +body.theme-tokyonight .app-version, +body.theme-monokai .app-version, +body.theme-cobalt2 .app-version, +body.theme-gruvbox-dark .app-version, +body.theme-ayu-dark .app-version, +body.theme-ayu-mirage .app-version, +body.theme-palenight .app-version, +body.theme-oceanic-next .app-version, +body.theme-nord .app-version, +body.theme-concrete-dark .app-version { + color: #9ca3af; +} + +body.theme-dark .app-logo, +body.theme-dracula .app-logo, +body.theme-onedark .app-logo, +body.theme-tokyonight .app-logo, +body.theme-monokai .app-logo, +body.theme-cobalt2 .app-logo, +body.theme-gruvbox-dark .app-logo, +body.theme-ayu-dark .app-logo, +body.theme-ayu-mirage .app-logo, +body.theme-palenight .app-logo, +body.theme-oceanic-next .app-logo, +body.theme-nord .app-logo, +body.theme-concrete-dark .app-logo { + filter: none; +} + +/* ============================================ + Converter Cards + ============================================ */ +.converter-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--spacing-md); + padding: var(--spacing-md); +} + +.converter-card { + background: var(--ci-white); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + text-align: center; + border: 2px solid var(--ci-border); + transition: all var(--transition-fast); + cursor: pointer; +} + +.converter-card:hover { + border-color: var(--ci-accent); + transform: translateY(-4px); + box-shadow: var(--shadow-lg); +} + +.converter-card-icon { + font-size: 2.5rem; + margin-bottom: var(--spacing-md); +} + +.converter-card-title { + font-weight: 600; + color: var(--ci-dark-gray); + margin-bottom: var(--spacing-xs); +} + +.converter-card-desc { + font-size: 0.875rem; + color: var(--ci-medium-gray); +} + +/* ============================================ + Progress Bar + ============================================ */ +.progress-bar { + height: 8px; + background: var(--ci-light-gray); + border-radius: var(--radius-full); + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--ci-accent) 0%, var(--ci-accent-hover) 100%); + border-radius: var(--radius-full); + transition: width var(--transition-normal); +} + +/* ============================================ + File Drop Zone + ============================================ */ +.drop-zone { + border: 2px dashed var(--ci-border); + border-radius: var(--radius-lg); + padding: var(--spacing-xl); + text-align: center; + transition: all var(--transition-fast); + cursor: pointer; +} + +.drop-zone:hover, +.drop-zone.drag-over { + border-color: var(--ci-accent); + background: var(--ci-accent-light); +} + +.drop-zone-icon { + font-size: 3rem; + color: var(--ci-medium-gray); + margin-bottom: var(--spacing-md); +} + +.drop-zone-text { + color: var(--ci-dark-gray); + font-weight: 500; +} + +.drop-zone-hint { + color: var(--ci-medium-gray); + font-size: 0.875rem; + margin-top: var(--spacing-xs); +} + +/* ============================================ + Syntax Highlighting for Editor + ============================================ */ +.editor-syntax .md-heading { + color: var(--ci-accent); + font-weight: bold; +} + +.editor-syntax .md-bold { + color: var(--ci-dark-gray); + font-weight: bold; +} + +.editor-syntax .md-italic { + color: var(--ci-dark-gray); + font-style: italic; +} + +.editor-syntax .md-code { + background: var(--ci-light-gray); + color: var(--ci-accent); + font-family: var(--font-mono); + padding: 2px 4px; + border-radius: var(--radius-sm); +} + +.editor-syntax .md-link { + color: var(--ci-info); + text-decoration: underline; +} + +.editor-syntax .md-list { + color: var(--ci-accent); +} + +.editor-syntax .md-blockquote { + color: var(--ci-medium-gray); + border-left: 3px solid var(--ci-accent); + padding-left: var(--spacing-md); +} + +/* ============================================ + ASCII Art Generator Styles + ============================================ */ +.ascii-preview { + font-family: var(--font-mono); + font-size: 12px; + line-height: 1.2; + background: var(--ci-black); + color: #00ff00; + padding: var(--spacing-md); + border-radius: var(--radius-md); + overflow-x: auto; + white-space: pre; +} + +.ascii-controls { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-sm); + margin-bottom: var(--spacing-md); +} + +/* ============================================ + Table Generator Styles + ============================================ */ +.table-generator-grid { + display: grid; + gap: 1px; + background: var(--ci-border); + border: 1px solid var(--ci-border); + border-radius: var(--radius-md); + overflow: hidden; +} + +.table-generator-cell { + background: var(--ci-white); + padding: var(--spacing-sm); + min-width: 80px; + min-height: 32px; +} + +.table-generator-cell input { + width: 100%; + border: none; + background: transparent; + text-align: center; + font-family: var(--font-mono); +} + +.table-generator-cell.header { + background: var(--ci-light-gray); + font-weight: 600; +} + +/* ============================================ + PDF Viewer/Editor Styles + ============================================ */ +.pdf-viewer { + background: var(--ci-dark-gray); + padding: var(--spacing-md); + border-radius: var(--radius-md); + min-height: 400px; + display: flex; + align-items: center; + justify-content: center; +} + +.pdf-page { + background: var(--ci-white); + box-shadow: var(--shadow-lg); + max-width: 100%; +} + +.pdf-toolbar { + display: flex; + align-items: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm); + background: var(--ci-light-gray); + border-radius: var(--radius-md); + margin-bottom: var(--spacing-md); +} + +/* ============================================ + Dark Mode for ConcreteInfo Theme + ============================================ */ +body.theme-concreteinfo-dark { + --ci-bg: #1a1a1a; + --ci-white: #2a2a2a; + --ci-light-gray: #3a3a3a; + --ci-border: #4a4a4a; +} + +body.theme-concreteinfo-dark .editor-textarea, +body.theme-concreteinfo-dark .preview-content { + background: #2a2a2a; + color: #e0e0e0; +} + +body.theme-concreteinfo-dark .modal-content { + background: #2a2a2a; + color: #e0e0e0; +} + +body.theme-concreteinfo-dark .modal-body { + background: #2a2a2a; +} + +body.theme-concreteinfo-dark .modal-footer { + background: #1a1a1a; +} + +/* ============================================ + Animations + ============================================ */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { transform: translateY(20px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.animate-fade-in { + animation: fadeIn var(--transition-normal); +} + +.animate-slide-up { + animation: slideUp var(--transition-normal); +} + +.animate-pulse { + animation: pulse 2s infinite; +} + +/* ============================================ + Responsive Design + ============================================ */ +@media (max-width: 768px) { + .modal-content { + width: 95%; + max-height: 90vh; + } + + .converter-grid { + grid-template-columns: 1fr; + } + + .app-header { + flex-wrap: wrap; + } +} + +/* ============================================ + Concrete Dark Theme + ============================================ */ +body.theme-concrete-dark { + background: #1a1a1a; + color: #e3e3e3; +} + +body.theme-concrete-dark .toolbar { + background: linear-gradient(135deg, #0d0b09 0%, #1a1a1a 100%); + border-bottom: 3px solid #e5461f; +} + +body.theme-concrete-dark .toolbar button { + color: #e3e3e3; +} + +body.theme-concrete-dark .toolbar button:hover { + background: #e5461f; +} + +body.theme-concrete-dark .tab-bar { + background: #0d0b09; + border-bottom: 2px solid #e5461f; +} + +body.theme-concrete-dark .tab { + background: #2a2a2a; + color: #e3e3e3; + border-radius: 4px 4px 0 0; +} + +body.theme-concrete-dark .tab.active { + background: #464646; + border-bottom: 2px solid #e5461f; +} + +body.theme-concrete-dark .editor-textarea, +body.theme-concrete-dark .editor-pane { + background: #1a1a1a; + color: #e3e3e3; +} + +body.theme-concrete-dark .preview-content { + background: #1a1a1a; + color: #e3e3e3; +} + +body.theme-concrete-dark .status-bar { + background: #0d0b09; + color: #9a9696; + border-top: 1px solid #464646; +} + +body.theme-concrete-dark .export-dialog-content, +body.theme-concrete-dark .batch-dialog-content { + background: #2a2a2a; + color: #e3e3e3; +} + +body.theme-concrete-dark .export-dialog-header, +body.theme-concrete-dark .batch-dialog-header { + background: #1a1a1a; + border-bottom: 2px solid #e5461f; +} + +body.theme-concrete-dark .export-dialog-footer, +body.theme-concrete-dark .batch-dialog-footer { + background: #1a1a1a; +} + +body.theme-concrete-dark input, +body.theme-concrete-dark select, +body.theme-concrete-dark textarea { + background: #1a1a1a; + color: #e3e3e3; + border-color: #464646; +} + +body.theme-concrete-dark input:focus, +body.theme-concrete-dark select:focus, +body.theme-concrete-dark textarea:focus { + border-color: #e5461f; +} + +body.theme-concrete-dark .line-numbers { + background: #0d0b09; + color: #9a9696; +} + +/* ============================================ + Concrete Light Theme + ============================================ */ +body.theme-concrete-light { + background: #f5f5f5; + color: #464646; +} + +body.theme-concrete-light .toolbar { + background: #ffffff; + border-bottom: 3px solid #e5461f; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +body.theme-concrete-light .toolbar button { + color: #464646; +} + +body.theme-concrete-light .toolbar button:hover { + background: #e5461f; + color: #ffffff; +} + +body.theme-concrete-light .tab-bar { + background: #ffffff; + border-bottom: 2px solid #e3e3e3; +} + +body.theme-concrete-light .tab { + background: #f5f5f5; + color: #464646; + border-radius: 4px 4px 0 0; +} + +body.theme-concrete-light .tab.active { + background: #ffffff; + border-bottom: 2px solid #e5461f; + color: #e5461f; +} + +body.theme-concrete-light .editor-textarea, +body.theme-concrete-light .editor-pane { + background: #ffffff; + color: #464646; +} + +body.theme-concrete-light .preview-content { + background: #ffffff; + color: #464646; +} + +body.theme-concrete-light .status-bar { + background: #ffffff; + color: #9a9696; + border-top: 1px solid #e3e3e3; +} + +body.theme-concrete-light .export-dialog-content, +body.theme-concrete-light .batch-dialog-content { + background: #ffffff; + color: #464646; +} + +body.theme-concrete-light .export-dialog-header, +body.theme-concrete-light .batch-dialog-header { + background: #f5f5f5; + border-bottom: 2px solid #e5461f; +} + +body.theme-concrete-light .line-numbers { + background: #f5f5f5; + color: #9a9696; +} + +/* ============================================ + Concrete Warm Theme + ============================================ */ +body.theme-concrete-warm { + background: #faf8f5; + color: #464646; +} + +body.theme-concrete-warm .toolbar { + background: linear-gradient(135deg, #464646 0%, #5a5a5a 100%); + border-bottom: 3px solid #e5461f; +} + +body.theme-concrete-warm .toolbar button { + color: #faf8f5; +} + +body.theme-concrete-warm .toolbar button:hover { + background: #e5461f; +} + +body.theme-concrete-warm .tab-bar { + background: #464646; + border-bottom: 2px solid #e5461f; +} + +body.theme-concrete-warm .tab { + background: #6a6a6a; + color: #faf8f5; + border-radius: 4px 4px 0 0; +} + +body.theme-concrete-warm .tab.active { + background: #faf8f5; + color: #e5461f; + border-bottom: 2px solid #e5461f; +} + +body.theme-concrete-warm .editor-textarea, +body.theme-concrete-warm .editor-pane { + background: #faf8f5; + color: #464646; +} + +body.theme-concrete-warm .preview-content { + background: #faf8f5; + color: #464646; +} + +body.theme-concrete-warm .status-bar { + background: #f0ebe4; + color: #9a9696; + border-top: 1px solid #e3e3e3; +} + +body.theme-concrete-warm .export-dialog-content, +body.theme-concrete-warm .batch-dialog-content { + background: #faf8f5; + color: #464646; +} + +body.theme-concrete-warm .export-dialog-header, +body.theme-concrete-warm .batch-dialog-header { + background: #f0ebe4; + border-bottom: 2px solid #e5461f; +} + +body.theme-concrete-warm h1, +body.theme-concrete-warm h2, +body.theme-concrete-warm h3, +body.theme-concrete-warm h4, +body.theme-concrete-warm h5, +body.theme-concrete-warm h6 { + color: #e5461f; +} + +body.theme-concrete-warm a { + color: #e5461f; +} + +body.theme-concrete-warm .line-numbers { + background: #f0ebe4; + color: #9a9696; +} diff --git a/src/styles-modern.css b/src/styles-modern.css index 8cd6c0d..52445b2 100644 --- a/src/styles-modern.css +++ b/src/styles-modern.css @@ -50,9 +50,7 @@ body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; overflow: hidden; height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); - background-size: 200% 200%; - animation: gradientShift 15s ease infinite; + /* Background controlled by themes - do not set here */ color: var(--gray-900); } @@ -66,8 +64,8 @@ body { display: flex; flex-direction: column; height: 100vh; - padding: 12px; - gap: 12px; + padding: 4px 8px; + gap: 2px; } /* Glass Container for main content */ @@ -90,10 +88,11 @@ body { align-items: center; background: rgba(255, 255, 255, 0.5); backdrop-filter: blur(10px); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - padding: 8px 12px; - min-height: 48px; - gap: 6px; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + padding: 4px 8px; + min-height: 36px; + gap: 4px; + border-radius: 8px; } .tab { @@ -114,17 +113,14 @@ body { } .tab:hover { - background: rgba(255, 255, 255, 0.5); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + background: rgba(0, 0, 0, 0.05); } .tab.active { - background: linear-gradient(135deg, rgba(102, 126, 234, 0.9), rgba(118, 75, 162, 0.9)); - color: white; - border-color: rgba(255, 255, 255, 0.5); - box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); - transform: translateY(-2px); + background: #ffffff; + color: #24292e; + border-bottom: 2px solid #0366d6; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); } .tab-title { @@ -185,38 +181,36 @@ body { .toolbar { display: flex; align-items: center; - padding: 10px 16px; + padding: 4px 8px; background: rgba(255, 255, 255, 0.4); backdrop-filter: blur(10px); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - gap: 6px; + border-bottom: 1px solid rgba(0, 0, 0, 0.06); + gap: 2px; + border-radius: 8px; } .toolbar button { display: flex; align-items: center; justify-content: center; - width: 36px; - height: 36px; - border: 1px solid rgba(255, 255, 255, 0.3); - background: rgba(255, 255, 255, 0.3); - backdrop-filter: blur(8px); - border-radius: 10px; + width: 32px; + height: 32px; + border: 1px solid transparent; + background: transparent; + border-radius: 6px; cursor: pointer; - transition: all var(--transition-speed) var(--transition-ease); - color: var(--gray-700); + transition: all 0.15s ease; + color: #586069; } .toolbar button:hover { - background: var(--primary-gradient); - border-color: transparent; - color: white; - transform: translateY(-2px) scale(1.05); - box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3); + background: rgba(0, 0, 0, 0.06); + color: #24292e; } .toolbar button:active { - transform: translateY(0) scale(0.98); + background: rgba(0, 0, 0, 0.1); + transform: scale(0.95); } .toolbar-separator { @@ -272,14 +266,14 @@ body { .pane::-webkit-scrollbar-thumb, .editor-textarea::-webkit-scrollbar-thumb { - background: var(--primary-gradient); + background: rgba(0, 0, 0, 0.25); border-radius: 10px; border: 2px solid rgba(255, 255, 255, 0.2); } .pane::-webkit-scrollbar-thumb:hover, .editor-textarea::-webkit-scrollbar-thumb:hover { - background: linear-gradient(135deg, #8b9aff, #9d6fd8); + background: rgba(0, 0, 0, 0.4); } .editor-textarea { @@ -293,13 +287,12 @@ body { outline: none; resize: none; background: transparent; - color: var(--gray-900); + /* Color controlled by theme */ } .pane:last-child { padding: 0; - background: rgba(255, 255, 255, 0.5); - backdrop-filter: blur(15px); + /* Background controlled by theme */ } .preview-content { @@ -308,30 +301,23 @@ body { padding: 24px 32px; line-height: 1.8; font-size: 15px; - color: var(--gray-900); + /* Color controlled by theme */ } -/* Status Bar - Glassmorphism */ +/* Status Bar */ .status-bar { display: flex; justify-content: space-between; align-items: center; padding: 8px 20px; - background: rgba(255, 255, 255, 0.5); - backdrop-filter: blur(10px); - border-top: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 0 0 20px 20px; + /* Background and color controlled by theme */ font-size: 12px; - color: var(--gray-600); font-weight: 500; } /* Preview Styles - Modern Typography */ .preview-content h1, .preview-content h2, .preview-content h3 { - background: var(--primary-gradient); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + /* Color controlled by theme */ font-weight: 700; margin-top: 28px; margin-bottom: 16px; @@ -340,14 +326,13 @@ body { .preview-content h1 { font-size: 2.5em; - border-bottom: 3px solid; - border-image: var(--primary-gradient) 1; + border-bottom: 3px solid #e1e4e8; padding-bottom: 12px; } .preview-content h2 { font-size: 2em; - border-bottom: 2px solid rgba(102, 126, 234, 0.3); + border-bottom: 2px solid #e1e4e8; padding-bottom: 10px; } @@ -360,8 +345,8 @@ body { padding: 3px 8px; margin: 0 2px; font-size: 90%; - background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); - border: 1px solid rgba(102, 126, 234, 0.2); + background: rgba(175, 184, 193, 0.2); + border: 1px solid #e1e4e8; border-radius: 6px; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-weight: 500; @@ -372,9 +357,8 @@ body { overflow-x: auto; font-size: 14px; line-height: 1.6; - background: rgba(255, 255, 255, 0.6); - backdrop-filter: blur(10px); - border: 1px solid rgba(102, 126, 234, 0.2); + background: #f6f8fa; + border: 1px solid #e1e4e8; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); @@ -390,17 +374,15 @@ body { .preview-content blockquote { padding: 16px 20px; color: var(--gray-700); - border-left: 4px solid; - border-image: var(--primary-gradient) 1; + border-left: 4px solid #e1e4e8; margin-bottom: 20px; - background: rgba(255, 255, 255, 0.4); - backdrop-filter: blur(8px); + background: #f6f8fa; border-radius: 0 8px 8px 0; font-style: italic; } .preview-content a { - color: #667eea; + color: #0366d6; text-decoration: none; font-weight: 600; position: relative; @@ -414,12 +396,12 @@ body { bottom: -2px; width: 0; height: 2px; - background: var(--primary-gradient); + background: #0366d6; transition: width var(--transition-speed) var(--transition-ease); } .preview-content a:hover { - color: #764ba2; + color: #0366d6; } .preview-content a:hover::after { @@ -439,7 +421,7 @@ body { .preview-content table th, .preview-content table td { padding: 12px 16px; - border: 1px solid rgba(102, 126, 234, 0.2); + border: 1px solid #e1e4e8; } .preview-content table th { @@ -515,8 +497,8 @@ body { align-items: center; justify-content: space-between; padding: 20px 24px; - border-bottom: 1px solid rgba(102, 126, 234, 0.2); - background: var(--primary-gradient); + border-bottom: 1px solid #e1e4e8; + background: #24292e; border-radius: 20px 20px 0 0; } @@ -553,7 +535,7 @@ body { .batch-section input { width: 100%; padding: 10px 14px; - border: 2px solid rgba(102, 126, 234, 0.2); + border: 2px solid #e1e4e8; border-radius: 10px; font-size: 14px; background: rgba(255, 255, 255, 0.8); @@ -566,8 +548,8 @@ body { .batch-section select:focus, .batch-section input:focus { outline: none; - border-color: #667eea; - box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); + border-color: #0366d6; + box-shadow: 0 0 0 4px rgba(3, 102, 214, 0.1); background: white; } @@ -578,9 +560,8 @@ body { justify-content: flex-end; gap: 12px; padding: 20px 24px; - border-top: 1px solid rgba(102, 126, 234, 0.2); - background: rgba(255, 255, 255, 0.5); - backdrop-filter: blur(10px); + border-top: 1px solid #e1e4e8; + background: #f6f8fa; border-radius: 0 0 20px 20px; } @@ -600,30 +581,31 @@ body { .export-dialog-footer button.primary, .batch-dialog-footer button.primary { - background: var(--primary-gradient); + background: #2ea44f; color: white; - box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); + box-shadow: 0 4px 12px rgba(46, 164, 79, 0.3); } .export-dialog-footer button.primary:hover, .batch-dialog-footer button.primary:hover { transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5); + background: #22863a; + box-shadow: 0 6px 20px rgba(34, 134, 58, 0.4); } .export-dialog-footer button:not(.primary), .batch-dialog-footer button:not(.primary), .folder-input-group button { - background: rgba(255, 255, 255, 0.8); + background: #fafbfc; color: var(--gray-700); - border: 2px solid rgba(102, 126, 234, 0.2); + border: 1px solid #e1e4e8; } .export-dialog-footer button:not(.primary):hover, .batch-dialog-footer button:not(.primary):hover, .folder-input-group button:hover { - background: white; - border-color: #667eea; + background: #f3f4f6; + border-color: #d0d7de; transform: translateY(-2px); } @@ -662,7 +644,7 @@ body { .progress-bar { width: 100%; height: 8px; - background: rgba(102, 126, 234, 0.1); + background: #e1e4e8; border-radius: 10px; overflow: hidden; margin-bottom: 12px; @@ -670,7 +652,7 @@ body { .progress-fill { height: 100%; - background: var(--primary-gradient); + background: #2ea44f; transition: width 0.4s var(--transition-ease); width: 0%; position: relative; @@ -706,14 +688,14 @@ body { position: fixed; top: 24px; right: 24px; - background: var(--primary-gradient); + background: #2ea44f; color: white; padding: 10px 20px; border-radius: 12px; font-size: 13px; font-weight: 600; z-index: 10000; - box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4); + box-shadow: 0 4px 20px rgba(46, 164, 79, 0.3); animation: slideInRight 0.3s ease-out; display: flex; align-items: center; @@ -726,7 +708,7 @@ body { width: 18px; height: 18px; background: white; - color: #667eea; + color: #2ea44f; border-radius: 50%; font-size: 12px; display: flex; @@ -751,7 +733,7 @@ body { padding: 12px 8px; background: rgba(255, 255, 255, 0.3); backdrop-filter: blur(8px); - border-right: 1px solid rgba(102, 126, 234, 0.2); + border-right: 1px solid #e1e4e8; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.7; @@ -823,3 +805,1309 @@ body { .pane.full-width { border-right: none; } + +/* ======================================== + Dynamic Pane Resizer + Draggable splitter between editor/preview + ======================================== */ + +.pane-resizer { + width: 6px; + background: linear-gradient(to right, #e1e4e8, #d0d7de, #e1e4e8); + cursor: col-resize; + position: relative; + flex-shrink: 0; + transition: background 0.2s ease; + border-radius: 3px; +} + +.pane-resizer:hover { + background: linear-gradient(to right, #0366d6, #0366d6, #0366d6); +} + +.pane-resizer:active { + background: #0366d6; +} + +.pane-resizer::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 2px; + height: 30px; + background: rgba(0, 0, 0, 0.2); + border-radius: 1px; +} + +.pane-resizer:hover::before { + background: rgba(255, 255, 255, 0.8); +} + +/* Dark theme resizer */ +body.theme-dark .pane-resizer, +body.theme-one-dark .pane-resizer, +body.theme-dracula .pane-resizer, +body.theme-nord .pane-resizer, +body.theme-tokyo-night .pane-resizer, +body.theme-palenight .pane-resizer, +body.theme-ayu-dark .pane-resizer, +body.theme-ayu-mirage .pane-resizer, +body.theme-oceanic-next .pane-resizer, +body.theme-gruvbox-dark .pane-resizer, +body.theme-cobalt2 .pane-resizer { + background: linear-gradient(to right, #333, #444, #333); +} + +body.theme-dark .pane-resizer:hover, +body.theme-one-dark .pane-resizer:hover, +body.theme-dracula .pane-resizer:hover { + background: #61afef; +} + +/* ======================================== + PDF Viewer Styles + Full-featured PDF reader + ======================================== */ + +.pdf-viewer-container { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + background: #525659; +} + +.pdf-viewer-container.hidden { + display: none; +} + +.pdf-viewer-toolbar { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: #323639; + border-bottom: 1px solid #1a1a1a; + flex-shrink: 0; +} + +.pdf-viewer-toolbar button { + background: #474a4d; + border: 1px solid #5a5d60; + border-radius: 4px; + color: #fff; + padding: 6px 10px; + cursor: pointer; + font-size: 14px; + transition: all 0.15s ease; +} + +.pdf-viewer-toolbar button:hover { + background: #5a5d60; +} + +.pdf-viewer-toolbar button:active { + background: #0366d6; +} + +.pdf-toolbar-separator { + width: 1px; + height: 24px; + background: #5a5d60; + margin: 0 4px; +} + +#pdf-page-info { + color: #fff; + font-size: 13px; + display: flex; + align-items: center; + gap: 4px; +} + +#pdf-page-input { + width: 50px; + padding: 4px 6px; + border: 1px solid #5a5d60; + border-radius: 4px; + background: #474a4d; + color: #fff; + text-align: center; + font-size: 13px; +} + +#pdf-zoom-level { + color: #fff; + font-size: 13px; + min-width: 50px; + text-align: center; +} + +.pdf-filename { + color: #aaa; + font-size: 12px; + flex: 1; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 8px; +} + +#pdf-close { + background: #c0392b !important; + border-color: #e74c3c !important; +} + +#pdf-close:hover { + background: #e74c3c !important; +} + +/* PDF Editor Toolbar Buttons */ +.pdf-editor-btn { + display: flex; + align-items: center; + gap: 4px; + background: linear-gradient(135deg, #3498db, #2980b9) !important; + border: 1px solid #2980b9 !important; + color: #fff !important; + padding: 5px 8px !important; + font-size: 11px !important; + font-weight: 500; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; +} + +.pdf-editor-btn:hover { + background: linear-gradient(135deg, #2980b9, #1a5276) !important; + transform: translateY(-1px); +} + +.pdf-editor-btn:active { + transform: translateY(0); +} + +.pdf-editor-btn svg { + flex-shrink: 0; +} + +.pdf-editor-btn span { + white-space: nowrap; +} + +/* PDF toolbar wrapping for smaller screens */ +.pdf-toolbar-main { + flex-wrap: wrap; + min-height: auto; + padding: 6px 10px !important; +} + +/* Light theme PDF toolbar - refined styling */ +body.theme-light .pdf-viewer-toolbar, +body.theme-atomonelight .pdf-viewer-toolbar, +body.theme-github .pdf-viewer-toolbar, +body.theme-solarized .pdf-viewer-toolbar, +body.theme-gruvbox-light .pdf-viewer-toolbar, +body.theme-ayu-light .pdf-viewer-toolbar, +body.theme-sepia .pdf-viewer-toolbar, +body.theme-paper .pdf-viewer-toolbar, +body.theme-rosepine-dawn .pdf-viewer-toolbar { + background: linear-gradient(180deg, #fafafa 0%, #f0f0f0 100%) !important; + border-bottom: 1px solid #d0d0d0 !important; + box-shadow: 0 1px 3px rgba(0,0,0,0.08); +} + +body.theme-light .pdf-viewer-toolbar button, +body.theme-atomonelight .pdf-viewer-toolbar button, +body.theme-github .pdf-viewer-toolbar button, +body.theme-solarized .pdf-viewer-toolbar button, +body.theme-gruvbox-light .pdf-viewer-toolbar button, +body.theme-ayu-light .pdf-viewer-toolbar button, +body.theme-sepia .pdf-viewer-toolbar button, +body.theme-paper .pdf-viewer-toolbar button, +body.theme-rosepine-dawn .pdf-viewer-toolbar button { + background: #fff !important; + border: 1px solid #ccc !important; + color: #333 !important; + box-shadow: 0 1px 2px rgba(0,0,0,0.05); +} + +body.theme-light .pdf-viewer-toolbar button:hover, +body.theme-atomonelight .pdf-viewer-toolbar button:hover, +body.theme-github .pdf-viewer-toolbar button:hover, +body.theme-solarized .pdf-viewer-toolbar button:hover, +body.theme-gruvbox-light .pdf-viewer-toolbar button:hover, +body.theme-ayu-light .pdf-viewer-toolbar button:hover, +body.theme-sepia .pdf-viewer-toolbar button:hover, +body.theme-paper .pdf-viewer-toolbar button:hover, +body.theme-rosepine-dawn .pdf-viewer-toolbar button:hover { + background: #e8e8e8 !important; + border-color: #bbb !important; +} + +/* Light theme PDF editor buttons */ +body.theme-light .pdf-editor-btn, +body.theme-atomonelight .pdf-editor-btn, +body.theme-github .pdf-editor-btn, +body.theme-solarized .pdf-editor-btn, +body.theme-gruvbox-light .pdf-editor-btn, +body.theme-ayu-light .pdf-editor-btn, +body.theme-sepia .pdf-editor-btn, +body.theme-paper .pdf-editor-btn, +body.theme-rosepine-dawn .pdf-editor-btn { + background: linear-gradient(135deg, #4a90d9, #357abd) !important; + border: 1px solid #357abd !important; +} + +body.theme-light .pdf-editor-btn:hover, +body.theme-atomonelight .pdf-editor-btn:hover, +body.theme-github .pdf-editor-btn:hover, +body.theme-solarized .pdf-editor-btn:hover, +body.theme-gruvbox-light .pdf-editor-btn:hover, +body.theme-ayu-light .pdf-editor-btn:hover, +body.theme-sepia .pdf-editor-btn:hover, +body.theme-paper .pdf-editor-btn:hover, +body.theme-rosepine-dawn .pdf-editor-btn:hover { + background: linear-gradient(135deg, #357abd, #2868a0) !important; +} + +/* Light theme page info */ +body.theme-light #pdf-page-info, +body.theme-atomonelight #pdf-page-info, +body.theme-github #pdf-page-info, +body.theme-solarized #pdf-page-info, +body.theme-gruvbox-light #pdf-page-info, +body.theme-ayu-light #pdf-page-info, +body.theme-sepia #pdf-page-info, +body.theme-paper #pdf-page-info, +body.theme-rosepine-dawn #pdf-page-info, +body.theme-light #pdf-zoom-level, +body.theme-atomonelight #pdf-zoom-level, +body.theme-github #pdf-zoom-level, +body.theme-solarized #pdf-zoom-level, +body.theme-gruvbox-light #pdf-zoom-level, +body.theme-ayu-light #pdf-zoom-level, +body.theme-sepia #pdf-zoom-level, +body.theme-paper #pdf-zoom-level, +body.theme-rosepine-dawn #pdf-zoom-level { + color: #444 !important; + font-weight: 500; +} + +body.theme-light #pdf-page-input, +body.theme-atomonelight #pdf-page-input, +body.theme-github #pdf-page-input, +body.theme-solarized #pdf-page-input, +body.theme-gruvbox-light #pdf-page-input, +body.theme-ayu-light #pdf-page-input, +body.theme-sepia #pdf-page-input, +body.theme-paper #pdf-page-input, +body.theme-rosepine-dawn #pdf-page-input { + background: #fff !important; + border: 1px solid #ccc !important; + color: #333 !important; +} + +body.theme-light .pdf-toolbar-separator, +body.theme-atomonelight .pdf-toolbar-separator, +body.theme-github .pdf-toolbar-separator, +body.theme-solarized .pdf-toolbar-separator, +body.theme-gruvbox-light .pdf-toolbar-separator, +body.theme-ayu-light .pdf-toolbar-separator, +body.theme-sepia .pdf-toolbar-separator, +body.theme-paper .pdf-toolbar-separator, +body.theme-rosepine-dawn .pdf-toolbar-separator { + background: #d0d0d0 !important; +} + +body.theme-light .pdf-filename, +body.theme-atomonelight .pdf-filename, +body.theme-github .pdf-filename, +body.theme-solarized .pdf-filename, +body.theme-gruvbox-light .pdf-filename, +body.theme-ayu-light .pdf-filename, +body.theme-sepia .pdf-filename, +body.theme-paper .pdf-filename, +body.theme-rosepine-dawn .pdf-filename { + color: #555 !important; +} + +/* Sepia theme specific */ +body.theme-sepia .pdf-viewer-toolbar { + background: linear-gradient(180deg, #f4f0e8 0%, #e8e4dc 100%) !important; + border-bottom-color: #cdc8c0 !important; +} + +body.theme-sepia .pdf-viewer { + background: #d8d4cc !important; +} + +/* Rose Pine Dawn specific */ +body.theme-rosepine-dawn .pdf-viewer-toolbar { + background: linear-gradient(180deg, #faf4ed 0%, #f2e9e1 100%) !important; + border-bottom-color: #ddd8d1 !important; +} + +body.theme-rosepine-dawn .pdf-viewer { + background: #e6e0d8 !important; +} + +/* Dark theme PDF toolbar refinement */ +body.theme-dark .pdf-viewer-toolbar, +body.theme-onedark .pdf-viewer-toolbar, +body.theme-one-dark .pdf-viewer-toolbar, +body.theme-dracula .pdf-viewer-toolbar, +body.theme-nord .pdf-viewer-toolbar, +body.theme-tokyo-night .pdf-viewer-toolbar, +body.theme-tokyonight .pdf-viewer-toolbar, +body.theme-palenight .pdf-viewer-toolbar, +body.theme-monokai .pdf-viewer-toolbar, +body.theme-material .pdf-viewer-toolbar, +body.theme-gruvbox-dark .pdf-viewer-toolbar, +body.theme-ayu-dark .pdf-viewer-toolbar, +body.theme-ayu-mirage .pdf-viewer-toolbar, +body.theme-oceanic-next .pdf-viewer-toolbar, +body.theme-cobalt2 .pdf-viewer-toolbar, +body.theme-concrete-dark .pdf-viewer-toolbar, +body.theme-concrete-warm .pdf-viewer-toolbar { + background: linear-gradient(180deg, #2d2d2d 0%, #252525 100%) !important; + border-bottom: 1px solid #1a1a1a !important; +} + +body.theme-dark .pdf-viewer-toolbar button, +body.theme-onedark .pdf-viewer-toolbar button, +body.theme-one-dark .pdf-viewer-toolbar button, +body.theme-dracula .pdf-viewer-toolbar button, +body.theme-nord .pdf-viewer-toolbar button, +body.theme-tokyo-night .pdf-viewer-toolbar button, +body.theme-tokyonight .pdf-viewer-toolbar button, +body.theme-palenight .pdf-viewer-toolbar button, +body.theme-monokai .pdf-viewer-toolbar button, +body.theme-material .pdf-viewer-toolbar button, +body.theme-gruvbox-dark .pdf-viewer-toolbar button, +body.theme-ayu-dark .pdf-viewer-toolbar button, +body.theme-ayu-mirage .pdf-viewer-toolbar button, +body.theme-oceanic-next .pdf-viewer-toolbar button, +body.theme-cobalt2 .pdf-viewer-toolbar button, +body.theme-concrete-dark .pdf-viewer-toolbar button, +body.theme-concrete-warm .pdf-viewer-toolbar button { + background: #3d3d3d !important; + border: 1px solid #4d4d4d !important; + color: #e0e0e0 !important; +} + +body.theme-dark .pdf-viewer-toolbar button:hover, +body.theme-onedark .pdf-viewer-toolbar button:hover, +body.theme-one-dark .pdf-viewer-toolbar button:hover, +body.theme-dracula .pdf-viewer-toolbar button:hover, +body.theme-nord .pdf-viewer-toolbar button:hover, +body.theme-tokyo-night .pdf-viewer-toolbar button:hover, +body.theme-tokyonight .pdf-viewer-toolbar button:hover, +body.theme-palenight .pdf-viewer-toolbar button:hover, +body.theme-monokai .pdf-viewer-toolbar button:hover, +body.theme-material .pdf-viewer-toolbar button:hover, +body.theme-gruvbox-dark .pdf-viewer-toolbar button:hover, +body.theme-ayu-dark .pdf-viewer-toolbar button:hover, +body.theme-ayu-mirage .pdf-viewer-toolbar button:hover, +body.theme-oceanic-next .pdf-viewer-toolbar button:hover, +body.theme-cobalt2 .pdf-viewer-toolbar button:hover, +body.theme-concrete-dark .pdf-viewer-toolbar button:hover, +body.theme-concrete-warm .pdf-viewer-toolbar button:hover { + background: #4d4d4d !important; + border-color: #5d5d5d !important; +} + +body.theme-dark .pdf-viewer, +body.theme-onedark .pdf-viewer, +body.theme-one-dark .pdf-viewer, +body.theme-dracula .pdf-viewer, +body.theme-nord .pdf-viewer, +body.theme-tokyo-night .pdf-viewer, +body.theme-tokyonight .pdf-viewer, +body.theme-palenight .pdf-viewer, +body.theme-monokai .pdf-viewer, +body.theme-material .pdf-viewer, +body.theme-gruvbox-dark .pdf-viewer, +body.theme-ayu-dark .pdf-viewer, +body.theme-ayu-mirage .pdf-viewer, +body.theme-oceanic-next .pdf-viewer, +body.theme-cobalt2 .pdf-viewer, +body.theme-concrete-dark .pdf-viewer, +body.theme-concrete-warm .pdf-viewer { + background: #1a1a1a !important; +} + +body.theme-dark .pdf-viewer-container, +body.theme-onedark .pdf-viewer-container, +body.theme-one-dark .pdf-viewer-container, +body.theme-dracula .pdf-viewer-container, +body.theme-nord .pdf-viewer-container, +body.theme-tokyo-night .pdf-viewer-container, +body.theme-tokyonight .pdf-viewer-container, +body.theme-palenight .pdf-viewer-container, +body.theme-monokai .pdf-viewer-container, +body.theme-material .pdf-viewer-container, +body.theme-gruvbox-dark .pdf-viewer-container, +body.theme-ayu-dark .pdf-viewer-container, +body.theme-ayu-mirage .pdf-viewer-container, +body.theme-oceanic-next .pdf-viewer-container, +body.theme-cobalt2 .pdf-viewer-container, +body.theme-concrete-dark .pdf-viewer-container, +body.theme-concrete-warm .pdf-viewer-container { + background: #1a1a1a !important; +} + +body.theme-dark #pdf-page-input, +body.theme-onedark #pdf-page-input, +body.theme-one-dark #pdf-page-input, +body.theme-dracula #pdf-page-input, +body.theme-nord #pdf-page-input, +body.theme-tokyo-night #pdf-page-input, +body.theme-tokyonight #pdf-page-input, +body.theme-palenight #pdf-page-input, +body.theme-monokai #pdf-page-input, +body.theme-material #pdf-page-input, +body.theme-gruvbox-dark #pdf-page-input, +body.theme-ayu-dark #pdf-page-input, +body.theme-ayu-mirage #pdf-page-input, +body.theme-oceanic-next #pdf-page-input, +body.theme-cobalt2 #pdf-page-input, +body.theme-concrete-dark #pdf-page-input, +body.theme-concrete-warm #pdf-page-input { + background: #3d3d3d !important; + border: 1px solid #4d4d4d !important; + color: #e0e0e0 !important; +} + +body.theme-dark .pdf-toolbar-separator, +body.theme-onedark .pdf-toolbar-separator, +body.theme-one-dark .pdf-toolbar-separator, +body.theme-dracula .pdf-toolbar-separator, +body.theme-nord .pdf-toolbar-separator, +body.theme-tokyo-night .pdf-toolbar-separator, +body.theme-tokyonight .pdf-toolbar-separator, +body.theme-palenight .pdf-toolbar-separator, +body.theme-monokai .pdf-toolbar-separator, +body.theme-material .pdf-toolbar-separator, +body.theme-gruvbox-dark .pdf-toolbar-separator, +body.theme-ayu-dark .pdf-toolbar-separator, +body.theme-ayu-mirage .pdf-toolbar-separator, +body.theme-oceanic-next .pdf-toolbar-separator, +body.theme-cobalt2 .pdf-toolbar-separator, +body.theme-concrete-dark .pdf-toolbar-separator, +body.theme-concrete-warm .pdf-toolbar-separator { + background: #3d3d3d !important; +} + +.pdf-viewer { + flex: 1; + overflow: auto; + display: flex; + justify-content: center; + align-items: flex-start; + padding: 20px; + background: #525659; +} + +#pdf-canvas { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + background: white; +} + +/* Light theme PDF viewer */ +body.theme-light .pdf-viewer-container, +body.theme-atomonelight .pdf-viewer-container, +body.theme-github .pdf-viewer-container, +body.theme-solarized .pdf-viewer-container, +body.theme-gruvbox-light .pdf-viewer-container, +body.theme-ayu-light .pdf-viewer-container, +body.theme-sepia .pdf-viewer-container, +body.theme-paper .pdf-viewer-container, +body.theme-rosepine-dawn .pdf-viewer-container { + background: #e8e8e8; +} + +body.theme-light .pdf-viewer-toolbar, +body.theme-atomonelight .pdf-viewer-toolbar, +body.theme-github .pdf-viewer-toolbar, +body.theme-solarized .pdf-viewer-toolbar, +body.theme-gruvbox-light .pdf-viewer-toolbar, +body.theme-ayu-light .pdf-viewer-toolbar, +body.theme-sepia .pdf-viewer-toolbar, +body.theme-paper .pdf-viewer-toolbar, +body.theme-rosepine-dawn .pdf-viewer-toolbar { + background: #f5f5f5; + border-bottom-color: #ddd; +} + +body.theme-light .pdf-viewer-toolbar button, +body.theme-atomonelight .pdf-viewer-toolbar button, +body.theme-github .pdf-viewer-toolbar button { + background: #fff; + border-color: #ddd; + color: #333; +} + +body.theme-light .pdf-viewer-toolbar button:hover, +body.theme-atomonelight .pdf-viewer-toolbar button:hover, +body.theme-github .pdf-viewer-toolbar button:hover { + background: #e8e8e8; +} + +body.theme-light #pdf-page-info, +body.theme-atomonelight #pdf-page-info, +body.theme-github #pdf-page-info, +body.theme-light #pdf-zoom-level, +body.theme-atomonelight #pdf-zoom-level, +body.theme-github #pdf-zoom-level { + color: #333; +} + +body.theme-light #pdf-page-input, +body.theme-atomonelight #pdf-page-input, +body.theme-github #pdf-page-input { + background: #fff; + border-color: #ddd; + color: #333; +} + +body.theme-light .pdf-filename, +body.theme-atomonelight .pdf-filename, +body.theme-github .pdf-filename { + color: #666; +} + +body.theme-light .pdf-viewer, +body.theme-atomonelight .pdf-viewer, +body.theme-github .pdf-viewer, +body.theme-solarized .pdf-viewer, +body.theme-gruvbox-light .pdf-viewer, +body.theme-ayu-light .pdf-viewer, +body.theme-sepia .pdf-viewer, +body.theme-paper .pdf-viewer, +body.theme-rosepine-dawn .pdf-viewer { + background: #d0d0d0; +} + +/* ======================================== + Print Styles for styles-modern.css + Ensures proper print output + ======================================== */ + +@media print { + /* Reset container for print */ + .container { + padding: 0; + gap: 0; + display: block !important; + } + + /* Hide all UI elements */ + .tab-bar, + .toolbar, + .status-bar, + .pane-resizer, + .preview-header, + .find-dialog, + .export-dialog, + .batch-dialog, + .editor-pane, + [id^="editor-pane-"], + .new-tab-button, + .tab-close { + display: none !important; + } + + /* Show preview content */ + .preview-pane, + [id^="preview-pane-"], + .preview-content, + [id^="preview-"] { + display: block !important; + width: 100% !important; + padding: 20px !important; + position: static !important; + overflow: visible !important; + } + + /* Reset main content glass */ + .main-content-glass { + backdrop-filter: none !important; + border: none !important; + box-shadow: none !important; + } + + /* Reset editor container */ + .editor-container { + display: block !important; + } + + /* Reset tab content */ + .tab-content { + display: block !important; + } + + /* Reset pane */ + .pane { + display: block !important; + width: 100% !important; + } + + /* NO STYLES MODE - Black text on white (ink-saving) */ + body.printing-no-styles .container, + body.printing-no-styles .main-content-glass, + body.printing-no-styles .tab-content, + body.printing-no-styles .pane, + body.printing-no-styles .preview-pane, + body.printing-no-styles [id^="preview-pane-"], + body.printing-no-styles .preview-content, + body.printing-no-styles [id^="preview-"] { + background: white !important; + color: #000 !important; + } + + body.printing-no-styles .preview-content h1, + body.printing-no-styles .preview-content h2, + body.printing-no-styles .preview-content h3, + body.printing-no-styles .preview-content h4, + body.printing-no-styles .preview-content h5, + body.printing-no-styles .preview-content h6, + body.printing-no-styles .preview-content p, + body.printing-no-styles .preview-content li, + body.printing-no-styles .preview-content td, + body.printing-no-styles .preview-content th { + color: #000 !important; + } + + body.printing-no-styles .preview-content code, + body.printing-no-styles .preview-content pre { + background: #f5f5f5 !important; + color: #000 !important; + border-color: #ddd !important; + } + + body.printing-no-styles .preview-content a { + color: #000 !important; + } + + body.printing-no-styles .preview-content blockquote { + background: #f9f9f9 !important; + color: #333 !important; + border-left-color: #ccc !important; + } + + /* WITH STYLES MODE - Preserve theme colors */ + /* The theme colors are inherited from the body class */ +} + +/* ======================================== + Dark Theme Overrides for styles-modern.css + These ensure dark themes work properly + ======================================== */ + +/* Common dark theme styles for preview content */ +body.theme-dark .preview-content, +body.theme-dark [id^="preview-"], +body.theme-one-dark .preview-content, +body.theme-one-dark [id^="preview-"], +body.theme-dracula .preview-content, +body.theme-dracula [id^="preview-"], +body.theme-nord .preview-content, +body.theme-nord [id^="preview-"], +body.theme-tokyo-night .preview-content, +body.theme-tokyo-night [id^="preview-"], +body.theme-palenight .preview-content, +body.theme-palenight [id^="preview-"], +body.theme-ayu-dark .preview-content, +body.theme-ayu-dark [id^="preview-"], +body.theme-ayu-mirage .preview-content, +body.theme-ayu-mirage [id^="preview-"], +body.theme-oceanic-next .preview-content, +body.theme-oceanic-next [id^="preview-"], +body.theme-gruvbox-dark .preview-content, +body.theme-gruvbox-dark [id^="preview-"], +body.theme-cobalt2 .preview-content, +body.theme-cobalt2 [id^="preview-"] { + color: #e6e6e6; + background: transparent; +} + +/* Dark theme headings */ +body.theme-dark .preview-content h1, +body.theme-dark .preview-content h2, +body.theme-dark .preview-content h3, +body.theme-dark .preview-content h4, +body.theme-dark .preview-content h5, +body.theme-dark .preview-content h6, +body.theme-one-dark .preview-content h1, +body.theme-one-dark .preview-content h2, +body.theme-one-dark .preview-content h3, +body.theme-one-dark .preview-content h4, +body.theme-one-dark .preview-content h5, +body.theme-one-dark .preview-content h6, +body.theme-dracula .preview-content h1, +body.theme-dracula .preview-content h2, +body.theme-dracula .preview-content h3, +body.theme-dracula .preview-content h4, +body.theme-dracula .preview-content h5, +body.theme-dracula .preview-content h6, +body.theme-nord .preview-content h1, +body.theme-nord .preview-content h2, +body.theme-nord .preview-content h3, +body.theme-nord .preview-content h4, +body.theme-nord .preview-content h5, +body.theme-nord .preview-content h6, +body.theme-tokyo-night .preview-content h1, +body.theme-tokyo-night .preview-content h2, +body.theme-tokyo-night .preview-content h3, +body.theme-tokyo-night .preview-content h4, +body.theme-tokyo-night .preview-content h5, +body.theme-tokyo-night .preview-content h6, +body.theme-palenight .preview-content h1, +body.theme-palenight .preview-content h2, +body.theme-palenight .preview-content h3, +body.theme-palenight .preview-content h4, +body.theme-palenight .preview-content h5, +body.theme-palenight .preview-content h6, +body.theme-ayu-dark .preview-content h1, +body.theme-ayu-dark .preview-content h2, +body.theme-ayu-dark .preview-content h3, +body.theme-ayu-dark .preview-content h4, +body.theme-ayu-dark .preview-content h5, +body.theme-ayu-dark .preview-content h6, +body.theme-ayu-mirage .preview-content h1, +body.theme-ayu-mirage .preview-content h2, +body.theme-ayu-mirage .preview-content h3, +body.theme-ayu-mirage .preview-content h4, +body.theme-ayu-mirage .preview-content h5, +body.theme-ayu-mirage .preview-content h6, +body.theme-oceanic-next .preview-content h1, +body.theme-oceanic-next .preview-content h2, +body.theme-oceanic-next .preview-content h3, +body.theme-oceanic-next .preview-content h4, +body.theme-oceanic-next .preview-content h5, +body.theme-oceanic-next .preview-content h6, +body.theme-gruvbox-dark .preview-content h1, +body.theme-gruvbox-dark .preview-content h2, +body.theme-gruvbox-dark .preview-content h3, +body.theme-gruvbox-dark .preview-content h4, +body.theme-gruvbox-dark .preview-content h5, +body.theme-gruvbox-dark .preview-content h6, +body.theme-cobalt2 .preview-content h1, +body.theme-cobalt2 .preview-content h2, +body.theme-cobalt2 .preview-content h3, +body.theme-cobalt2 .preview-content h4, +body.theme-cobalt2 .preview-content h5, +body.theme-cobalt2 .preview-content h6 { + color: #f0f0f0; + border-bottom-color: #444; +} + +/* Dark theme paragraphs and text */ +body.theme-dark .preview-content p, +body.theme-dark .preview-content li, +body.theme-dark .preview-content td, +body.theme-one-dark .preview-content p, +body.theme-one-dark .preview-content li, +body.theme-one-dark .preview-content td, +body.theme-dracula .preview-content p, +body.theme-dracula .preview-content li, +body.theme-dracula .preview-content td, +body.theme-nord .preview-content p, +body.theme-nord .preview-content li, +body.theme-nord .preview-content td, +body.theme-tokyo-night .preview-content p, +body.theme-tokyo-night .preview-content li, +body.theme-tokyo-night .preview-content td, +body.theme-palenight .preview-content p, +body.theme-palenight .preview-content li, +body.theme-palenight .preview-content td, +body.theme-ayu-dark .preview-content p, +body.theme-ayu-dark .preview-content li, +body.theme-ayu-dark .preview-content td, +body.theme-ayu-mirage .preview-content p, +body.theme-ayu-mirage .preview-content li, +body.theme-ayu-mirage .preview-content td, +body.theme-oceanic-next .preview-content p, +body.theme-oceanic-next .preview-content li, +body.theme-oceanic-next .preview-content td, +body.theme-gruvbox-dark .preview-content p, +body.theme-gruvbox-dark .preview-content li, +body.theme-gruvbox-dark .preview-content td, +body.theme-cobalt2 .preview-content p, +body.theme-cobalt2 .preview-content li, +body.theme-cobalt2 .preview-content td { + color: #d4d4d4; +} + +/* Dark theme code blocks */ +body.theme-dark .preview-content pre, +body.theme-one-dark .preview-content pre, +body.theme-dracula .preview-content pre, +body.theme-nord .preview-content pre, +body.theme-tokyo-night .preview-content pre, +body.theme-palenight .preview-content pre, +body.theme-ayu-dark .preview-content pre, +body.theme-ayu-mirage .preview-content pre, +body.theme-oceanic-next .preview-content pre, +body.theme-gruvbox-dark .preview-content pre, +body.theme-cobalt2 .preview-content pre { + background: #1e1e1e; + border-color: #333; + color: #d4d4d4; +} + +/* Dark theme inline code */ +body.theme-dark .preview-content code, +body.theme-one-dark .preview-content code, +body.theme-dracula .preview-content code, +body.theme-nord .preview-content code, +body.theme-tokyo-night .preview-content code, +body.theme-palenight .preview-content code, +body.theme-ayu-dark .preview-content code, +body.theme-ayu-mirage .preview-content code, +body.theme-oceanic-next .preview-content code, +body.theme-gruvbox-dark .preview-content code, +body.theme-cobalt2 .preview-content code { + background: rgba(255, 255, 255, 0.1); + border-color: #444; + color: #e06c75; +} + +/* Dark theme blockquotes */ +body.theme-dark .preview-content blockquote, +body.theme-one-dark .preview-content blockquote, +body.theme-dracula .preview-content blockquote, +body.theme-nord .preview-content blockquote, +body.theme-tokyo-night .preview-content blockquote, +body.theme-palenight .preview-content blockquote, +body.theme-ayu-dark .preview-content blockquote, +body.theme-ayu-mirage .preview-content blockquote, +body.theme-oceanic-next .preview-content blockquote, +body.theme-gruvbox-dark .preview-content blockquote, +body.theme-cobalt2 .preview-content blockquote { + background: rgba(255, 255, 255, 0.05); + border-left-color: #555; + color: #aaa; +} + +/* Dark theme links */ +body.theme-dark .preview-content a, +body.theme-one-dark .preview-content a, +body.theme-dracula .preview-content a, +body.theme-nord .preview-content a, +body.theme-tokyo-night .preview-content a, +body.theme-palenight .preview-content a, +body.theme-ayu-dark .preview-content a, +body.theme-ayu-mirage .preview-content a, +body.theme-oceanic-next .preview-content a, +body.theme-gruvbox-dark .preview-content a, +body.theme-cobalt2 .preview-content a { + color: #61afef; +} + +/* Dark theme tables */ +body.theme-dark .preview-content table, +body.theme-one-dark .preview-content table, +body.theme-dracula .preview-content table, +body.theme-nord .preview-content table, +body.theme-tokyo-night .preview-content table, +body.theme-palenight .preview-content table, +body.theme-ayu-dark .preview-content table, +body.theme-ayu-mirage .preview-content table, +body.theme-oceanic-next .preview-content table, +body.theme-gruvbox-dark .preview-content table, +body.theme-cobalt2 .preview-content table { + border-color: #444; +} + +body.theme-dark .preview-content table th, +body.theme-one-dark .preview-content table th, +body.theme-dracula .preview-content table th, +body.theme-nord .preview-content table th, +body.theme-tokyo-night .preview-content table th, +body.theme-palenight .preview-content table th, +body.theme-ayu-dark .preview-content table th, +body.theme-ayu-mirage .preview-content table th, +body.theme-oceanic-next .preview-content table th, +body.theme-gruvbox-dark .preview-content table th, +body.theme-cobalt2 .preview-content table th { + background: #2d2d2d; + color: #e6e6e6; + border-color: #444; +} + +body.theme-dark .preview-content table td, +body.theme-one-dark .preview-content table td, +body.theme-dracula .preview-content table td, +body.theme-nord .preview-content table td, +body.theme-tokyo-night .preview-content table td, +body.theme-palenight .preview-content table td, +body.theme-ayu-dark .preview-content table td, +body.theme-ayu-mirage .preview-content table td, +body.theme-oceanic-next .preview-content table td, +body.theme-gruvbox-dark .preview-content table td, +body.theme-cobalt2 .preview-content table td { + border-color: #444; +} + +body.theme-dark .preview-content table tr:nth-child(even), +body.theme-one-dark .preview-content table tr:nth-child(even), +body.theme-dracula .preview-content table tr:nth-child(even), +body.theme-nord .preview-content table tr:nth-child(even), +body.theme-tokyo-night .preview-content table tr:nth-child(even), +body.theme-palenight .preview-content table tr:nth-child(even), +body.theme-ayu-dark .preview-content table tr:nth-child(even), +body.theme-ayu-mirage .preview-content table tr:nth-child(even), +body.theme-oceanic-next .preview-content table tr:nth-child(even), +body.theme-gruvbox-dark .preview-content table tr:nth-child(even), +body.theme-cobalt2 .preview-content table tr:nth-child(even) { + background: rgba(255, 255, 255, 0.03); +} + +/* Dark theme horizontal rules */ +body.theme-dark .preview-content hr, +body.theme-one-dark .preview-content hr, +body.theme-dracula .preview-content hr, +body.theme-nord .preview-content hr, +body.theme-tokyo-night .preview-content hr, +body.theme-palenight .preview-content hr, +body.theme-ayu-dark .preview-content hr, +body.theme-ayu-mirage .preview-content hr, +body.theme-oceanic-next .preview-content hr, +body.theme-gruvbox-dark .preview-content hr, +body.theme-cobalt2 .preview-content hr { + border-color: #444; + background: #444; +} + +/* ======================================== + Modal/Dialog Theming + Dialogs inherit app theme colors + ======================================== */ + +/* Dark theme modals */ +body.theme-dark .export-dialog-content, +body.theme-dark .batch-dialog-content, +body.theme-dark .pdf-editor-content, +body.theme-one-dark .export-dialog-content, +body.theme-one-dark .batch-dialog-content, +body.theme-one-dark .pdf-editor-content, +body.theme-onedark .export-dialog-content, +body.theme-onedark .batch-dialog-content, +body.theme-onedark .pdf-editor-content, +body.theme-dracula .export-dialog-content, +body.theme-dracula .batch-dialog-content, +body.theme-dracula .pdf-editor-content, +body.theme-nord .export-dialog-content, +body.theme-nord .batch-dialog-content, +body.theme-nord .pdf-editor-content, +body.theme-tokyo-night .export-dialog-content, +body.theme-tokyo-night .batch-dialog-content, +body.theme-tokyo-night .pdf-editor-content, +body.theme-tokyonight .export-dialog-content, +body.theme-tokyonight .batch-dialog-content, +body.theme-tokyonight .pdf-editor-content, +body.theme-palenight .export-dialog-content, +body.theme-palenight .batch-dialog-content, +body.theme-palenight .pdf-editor-content, +body.theme-ayu-dark .export-dialog-content, +body.theme-ayu-dark .batch-dialog-content, +body.theme-ayu-dark .pdf-editor-content, +body.theme-ayu-mirage .export-dialog-content, +body.theme-ayu-mirage .batch-dialog-content, +body.theme-ayu-mirage .pdf-editor-content, +body.theme-oceanic-next .export-dialog-content, +body.theme-oceanic-next .batch-dialog-content, +body.theme-oceanic-next .pdf-editor-content, +body.theme-gruvbox-dark .export-dialog-content, +body.theme-gruvbox-dark .batch-dialog-content, +body.theme-gruvbox-dark .pdf-editor-content, +body.theme-cobalt2 .export-dialog-content, +body.theme-cobalt2 .batch-dialog-content, +body.theme-cobalt2 .pdf-editor-content, +body.theme-monokai .export-dialog-content, +body.theme-monokai .batch-dialog-content, +body.theme-monokai .pdf-editor-content, +body.theme-material .export-dialog-content, +body.theme-material .batch-dialog-content, +body.theme-material .pdf-editor-content, +body.theme-concrete-dark .export-dialog-content, +body.theme-concrete-dark .batch-dialog-content, +body.theme-concrete-dark .pdf-editor-content, +body.theme-concrete-warm .export-dialog-content, +body.theme-concrete-warm .batch-dialog-content, +body.theme-concrete-warm .pdf-editor-content { + background: #2d2d2d !important; + border-color: #444 !important; + color: #e6e6e6 !important; +} + +/* Dark theme dialog headers */ +body.theme-dark .export-dialog-header, +body.theme-dark .batch-dialog-header, +body.theme-one-dark .export-dialog-header, +body.theme-one-dark .batch-dialog-header, +body.theme-onedark .export-dialog-header, +body.theme-onedark .batch-dialog-header, +body.theme-dracula .export-dialog-header, +body.theme-dracula .batch-dialog-header, +body.theme-nord .export-dialog-header, +body.theme-nord .batch-dialog-header, +body.theme-tokyo-night .export-dialog-header, +body.theme-tokyo-night .batch-dialog-header, +body.theme-tokyonight .export-dialog-header, +body.theme-tokyonight .batch-dialog-header, +body.theme-palenight .export-dialog-header, +body.theme-palenight .batch-dialog-header, +body.theme-ayu-dark .export-dialog-header, +body.theme-ayu-dark .batch-dialog-header, +body.theme-ayu-mirage .export-dialog-header, +body.theme-ayu-mirage .batch-dialog-header, +body.theme-oceanic-next .export-dialog-header, +body.theme-oceanic-next .batch-dialog-header, +body.theme-gruvbox-dark .export-dialog-header, +body.theme-gruvbox-dark .batch-dialog-header, +body.theme-cobalt2 .export-dialog-header, +body.theme-cobalt2 .batch-dialog-header, +body.theme-monokai .export-dialog-header, +body.theme-monokai .batch-dialog-header, +body.theme-material .export-dialog-header, +body.theme-material .batch-dialog-header, +body.theme-concrete-dark .export-dialog-header, +body.theme-concrete-dark .batch-dialog-header, +body.theme-concrete-warm .export-dialog-header, +body.theme-concrete-warm .batch-dialog-header { + background: #1a1a1a !important; + border-bottom-color: #333 !important; +} + +/* Dark theme dialog header text */ +body.theme-dark .export-dialog-header h3, +body.theme-dark .batch-dialog-header h3, +body.theme-onedark .export-dialog-header h3, +body.theme-onedark .batch-dialog-header h3, +body.theme-dracula .export-dialog-header h3, +body.theme-dracula .batch-dialog-header h3, +body.theme-nord .export-dialog-header h3, +body.theme-nord .batch-dialog-header h3, +body.theme-tokyonight .export-dialog-header h3, +body.theme-tokyonight .batch-dialog-header h3, +body.theme-monokai .export-dialog-header h3, +body.theme-monokai .batch-dialog-header h3, +body.theme-material .export-dialog-header h3, +body.theme-material .batch-dialog-header h3 { + color: #fff !important; +} + +/* Dark theme dialog body */ +body.theme-dark .export-dialog-body, +body.theme-dark .batch-dialog-body, +body.theme-one-dark .export-dialog-body, +body.theme-one-dark .batch-dialog-body, +body.theme-dracula .export-dialog-body, +body.theme-dracula .batch-dialog-body, +body.theme-nord .export-dialog-body, +body.theme-nord .batch-dialog-body, +body.theme-tokyo-night .export-dialog-body, +body.theme-tokyo-night .batch-dialog-body, +body.theme-palenight .export-dialog-body, +body.theme-palenight .batch-dialog-body, +body.theme-ayu-dark .export-dialog-body, +body.theme-ayu-dark .batch-dialog-body, +body.theme-ayu-mirage .export-dialog-body, +body.theme-ayu-mirage .batch-dialog-body, +body.theme-oceanic-next .export-dialog-body, +body.theme-oceanic-next .batch-dialog-body, +body.theme-gruvbox-dark .export-dialog-body, +body.theme-gruvbox-dark .batch-dialog-body, +body.theme-cobalt2 .export-dialog-body, +body.theme-cobalt2 .batch-dialog-body { + background: transparent; +} + +/* Dark theme dialog labels */ +body.theme-dark .export-section label, +body.theme-dark .batch-section label, +body.theme-one-dark .export-section label, +body.theme-one-dark .batch-section label, +body.theme-dracula .export-section label, +body.theme-dracula .batch-section label, +body.theme-nord .export-section label, +body.theme-nord .batch-section label, +body.theme-tokyo-night .export-section label, +body.theme-tokyo-night .batch-section label, +body.theme-palenight .export-section label, +body.theme-palenight .batch-section label, +body.theme-ayu-dark .export-section label, +body.theme-ayu-dark .batch-section label, +body.theme-ayu-mirage .export-section label, +body.theme-ayu-mirage .batch-section label, +body.theme-oceanic-next .export-section label, +body.theme-oceanic-next .batch-section label, +body.theme-gruvbox-dark .export-section label, +body.theme-gruvbox-dark .batch-section label, +body.theme-cobalt2 .export-section label, +body.theme-cobalt2 .batch-section label { + color: #ccc; +} + +/* Dark theme dialog inputs and selects */ +body.theme-dark .export-section select, +body.theme-dark .export-section input, +body.theme-dark .batch-section select, +body.theme-dark .batch-section input, +body.theme-one-dark .export-section select, +body.theme-one-dark .export-section input, +body.theme-one-dark .batch-section select, +body.theme-one-dark .batch-section input, +body.theme-dracula .export-section select, +body.theme-dracula .export-section input, +body.theme-dracula .batch-section select, +body.theme-dracula .batch-section input, +body.theme-nord .export-section select, +body.theme-nord .export-section input, +body.theme-nord .batch-section select, +body.theme-nord .batch-section input, +body.theme-tokyo-night .export-section select, +body.theme-tokyo-night .export-section input, +body.theme-tokyo-night .batch-section select, +body.theme-tokyo-night .batch-section input, +body.theme-palenight .export-section select, +body.theme-palenight .export-section input, +body.theme-palenight .batch-section select, +body.theme-palenight .batch-section input, +body.theme-ayu-dark .export-section select, +body.theme-ayu-dark .export-section input, +body.theme-ayu-dark .batch-section select, +body.theme-ayu-dark .batch-section input, +body.theme-ayu-mirage .export-section select, +body.theme-ayu-mirage .export-section input, +body.theme-ayu-mirage .batch-section select, +body.theme-ayu-mirage .batch-section input, +body.theme-oceanic-next .export-section select, +body.theme-oceanic-next .export-section input, +body.theme-oceanic-next .batch-section select, +body.theme-oceanic-next .batch-section input, +body.theme-gruvbox-dark .export-section select, +body.theme-gruvbox-dark .export-section input, +body.theme-gruvbox-dark .batch-section select, +body.theme-gruvbox-dark .batch-section input, +body.theme-cobalt2 .export-section select, +body.theme-cobalt2 .export-section input, +body.theme-cobalt2 .batch-section select, +body.theme-cobalt2 .batch-section input { + background: #2d2d2d; + border-color: #444; + color: #e6e6e6; +} + +/* Dark theme dialog footer */ +body.theme-dark .export-dialog-footer, +body.theme-dark .batch-dialog-footer, +body.theme-one-dark .export-dialog-footer, +body.theme-one-dark .batch-dialog-footer, +body.theme-dracula .export-dialog-footer, +body.theme-dracula .batch-dialog-footer, +body.theme-nord .export-dialog-footer, +body.theme-nord .batch-dialog-footer, +body.theme-tokyo-night .export-dialog-footer, +body.theme-tokyo-night .batch-dialog-footer, +body.theme-palenight .export-dialog-footer, +body.theme-palenight .batch-dialog-footer, +body.theme-ayu-dark .export-dialog-footer, +body.theme-ayu-dark .batch-dialog-footer, +body.theme-ayu-mirage .export-dialog-footer, +body.theme-ayu-mirage .batch-dialog-footer, +body.theme-oceanic-next .export-dialog-footer, +body.theme-oceanic-next .batch-dialog-footer, +body.theme-gruvbox-dark .export-dialog-footer, +body.theme-gruvbox-dark .batch-dialog-footer, +body.theme-cobalt2 .export-dialog-footer, +body.theme-cobalt2 .batch-dialog-footer { + background: #1a1a1a; + border-top-color: #333; +} + +/* Sepia theme modals */ +body.theme-sepia .export-dialog-content, +body.theme-sepia .batch-dialog-content, +body.theme-sepia .pdf-editor-content { + background: rgba(244, 236, 216, 0.98); + border-color: #d4c4a8; + color: #5b4636; +} + +body.theme-sepia .export-dialog-header, +body.theme-sepia .batch-dialog-header { + background: #e8dcc8; + border-bottom-color: #d4c4a8; +} + +body.theme-sepia .export-dialog-header h3, +body.theme-sepia .batch-dialog-header h3 { + color: #3d2914; +} + +body.theme-sepia .export-section label, +body.theme-sepia .batch-section label { + color: #5b4636; +} + +body.theme-sepia .export-section select, +body.theme-sepia .export-section input, +body.theme-sepia .batch-section select, +body.theme-sepia .batch-section input { + background: #f4ecd8; + border-color: #d4c4a8; + color: #5b4636; +} + +body.theme-sepia .export-dialog-footer, +body.theme-sepia .batch-dialog-footer { + background: #e8dcc8; + border-top-color: #d4c4a8; +} + +/* Rose Pine Dawn theme modals */ +body.theme-rosepine-dawn .export-dialog-content, +body.theme-rosepine-dawn .batch-dialog-content, +body.theme-rosepine-dawn .pdf-editor-content { + background: rgba(250, 244, 237, 0.98); + border-color: #dfdad9; + color: #575279; +} + +body.theme-rosepine-dawn .export-dialog-header, +body.theme-rosepine-dawn .batch-dialog-header { + background: #f2e9e1; + border-bottom-color: #dfdad9; +} + +body.theme-rosepine-dawn .export-dialog-header h3, +body.theme-rosepine-dawn .batch-dialog-header h3 { + color: #286983; +} + +body.theme-rosepine-dawn .export-section label, +body.theme-rosepine-dawn .batch-section label { + color: #575279; +} + +body.theme-rosepine-dawn .export-section select, +body.theme-rosepine-dawn .export-section input, +body.theme-rosepine-dawn .batch-section select, +body.theme-rosepine-dawn .batch-section input { + background: #faf4ed; + border-color: #dfdad9; + color: #575279; +} + +body.theme-rosepine-dawn .export-dialog-footer, +body.theme-rosepine-dawn .batch-dialog-footer { + background: #f2e9e1; + border-top-color: #dfdad9; +} diff --git a/src/styles.css b/src/styles.css index 41258b8..c377601 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1031,52 +1031,82 @@ body.theme-github .status-bar { .export-dialog-content { background: #fff; - border-radius: 8px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); - width: 90%; - max-width: 600px; - max-height: 90vh; + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + width: 85%; + max-width: 450px; + max-height: 80vh; overflow-y: auto; position: relative; + font-size: 12px; } .export-dialog-header { display: flex; align-items: center; justify-content: space-between; - padding: 16px 20px; + padding: 12px 16px; border-bottom: 1px solid #eee; } .export-dialog-header h3 { margin: 0; - font-size: 18px; + font-size: 14px; font-weight: 600; } .export-dialog-body { - padding: 20px; + padding: 14px 16px; } .export-section { - margin-bottom: 20px; + margin-bottom: 14px; } .export-section label { display: block; - font-weight: 600; - margin-bottom: 8px; + font-weight: 500; + margin-bottom: 4px; color: #333; + font-size: 12px; +} + +.export-section label.checkbox-inline { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + font-weight: 400; +} + +.export-section label.checkbox-inline input[type="checkbox"] { + width: auto; + margin: 0; +} + +/* PDF Editor buttons */ +#add-merge-file { + padding: 5px 10px; + font-size: 12px; + background: #4a90d9; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; +} + +#add-merge-file:hover { + background: #357abd; } .export-section select, .export-section input[type="text"], .export-section input[type="number"] { width: 100%; - padding: 8px 12px; + padding: 5px 8px; border: 1px solid #ddd; border-radius: 4px; - font-size: 14px; + font-size: 12px; } .export-section select:focus, @@ -1280,9 +1310,10 @@ body.theme-dark #add-metadata-field { /* PDF Editor Specific Styles */ .pdf-editor-content { - max-width: 600px !important; - max-height: 85vh; + max-width: 450px !important; + max-height: 75vh; overflow-y: auto; + font-size: 12px; } .pdf-editor-body { @@ -1418,12 +1449,13 @@ body.theme-dark #export-dialog-close:hover { .batch-dialog-content { background: #fff; - border-radius: 8px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); - width: 90%; - max-width: 500px; - max-height: 80vh; + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + width: 85%; + max-width: 450px; + max-height: 75vh; overflow-y: auto; + font-size: 12px; } .batch-dialog-header { @@ -3152,6 +3184,250 @@ body.theme-concrete-warm .status-bar { color: #e3e3e3; } +/* ======================================== + Sepia Theme - Warm reading theme + ======================================== */ +body.theme-sepia { + background: #f4ecd8; + color: #5b4636; +} + +body.theme-sepia .tab-bar { + background: #e8dcc8; + border-bottom-color: #d4c4a8; +} + +body.theme-sepia .tab { + background: #e8dcc8; + border-color: #d4c4a8; + color: #5b4636; +} + +body.theme-sepia .tab.active { + background: #f4ecd8; + border-bottom-color: #8b7355; +} + +body.theme-sepia .toolbar { + background: #e8dcc8; + border-bottom-color: #d4c4a8; +} + +body.theme-sepia .toolbar button { + color: #5b4636; +} + +body.theme-sepia .toolbar button:hover { + background: #d4c4a8; +} + +body.theme-sepia .editor-textarea { + background: #f4ecd8; + color: #5b4636; +} + +body.theme-sepia .pane:last-child { + background: #f4ecd8; +} + +body.theme-sepia .preview-content { + color: #5b4636; +} + +body.theme-sepia .preview-content h1, +body.theme-sepia .preview-content h2 { + color: #3d2914; + border-bottom-color: #d4c4a8; +} + +body.theme-sepia .preview-content code { + background: #e8dcc8; + color: #8b4513; +} + +body.theme-sepia .preview-content pre { + background: #e8dcc8; + border-color: #d4c4a8; +} + +body.theme-sepia .preview-content a { + color: #8b4513; +} + +body.theme-sepia .preview-content blockquote { + border-left-color: #8b7355; + background: #e8dcc8; +} + +body.theme-sepia .status-bar { + background: #e8dcc8; + border-top-color: #d4c4a8; + color: #5b4636; +} + +/* ======================================== + Paper Theme - Clean white paper + ======================================== */ +body.theme-paper { + background: #ffffff; + color: #1a1a1a; +} + +body.theme-paper .tab-bar { + background: #f8f8f8; + border-bottom-color: #e0e0e0; +} + +body.theme-paper .tab { + background: #f0f0f0; + border-color: #e0e0e0; + color: #333; +} + +body.theme-paper .tab.active { + background: #ffffff; + border-bottom-color: #1a1a1a; +} + +body.theme-paper .toolbar { + background: #f8f8f8; + border-bottom-color: #e0e0e0; +} + +body.theme-paper .toolbar button { + color: #333; +} + +body.theme-paper .toolbar button:hover { + background: #e8e8e8; +} + +body.theme-paper .editor-textarea { + background: #ffffff; + color: #1a1a1a; +} + +body.theme-paper .pane:last-child { + background: #ffffff; +} + +body.theme-paper .preview-content { + color: #1a1a1a; +} + +body.theme-paper .preview-content h1, +body.theme-paper .preview-content h2 { + color: #000; + border-bottom-color: #e0e0e0; +} + +body.theme-paper .preview-content code { + background: #f5f5f5; + color: #c41a16; +} + +body.theme-paper .preview-content pre { + background: #f5f5f5; + border-color: #e0e0e0; +} + +body.theme-paper .preview-content a { + color: #0366d6; +} + +body.theme-paper .preview-content blockquote { + border-left-color: #ddd; + background: #f9f9f9; +} + +body.theme-paper .status-bar { + background: #f8f8f8; + border-top-color: #e0e0e0; + color: #333; +} + +/* ======================================== + Rose Pine Dawn - Warm rose-tinted light + ======================================== */ +body.theme-rosepine-dawn { + background: #faf4ed; + color: #575279; +} + +body.theme-rosepine-dawn .tab-bar { + background: #f2e9e1; + border-bottom-color: #dfdad9; +} + +body.theme-rosepine-dawn .tab { + background: #f2e9e1; + border-color: #dfdad9; + color: #575279; +} + +body.theme-rosepine-dawn .tab.active { + background: #faf4ed; + border-bottom-color: #907aa9; +} + +body.theme-rosepine-dawn .toolbar { + background: #f2e9e1; + border-bottom-color: #dfdad9; +} + +body.theme-rosepine-dawn .toolbar button { + color: #575279; +} + +body.theme-rosepine-dawn .toolbar button:hover { + background: #dfdad9; +} + +body.theme-rosepine-dawn .editor-textarea { + background: #faf4ed; + color: #575279; +} + +body.theme-rosepine-dawn .pane:last-child { + background: #faf4ed; +} + +body.theme-rosepine-dawn .preview-content { + color: #575279; +} + +body.theme-rosepine-dawn .preview-content h1, +body.theme-rosepine-dawn .preview-content h2 { + color: #286983; + border-bottom-color: #dfdad9; +} + +body.theme-rosepine-dawn .preview-content code { + background: #f2e9e1; + color: #b4637a; +} + +body.theme-rosepine-dawn .preview-content pre { + background: #f2e9e1; + border-color: #dfdad9; +} + +body.theme-rosepine-dawn .preview-content a { + color: #56949f; +} + +body.theme-rosepine-dawn .preview-content blockquote { + border-left-color: #907aa9; + background: #f2e9e1; + color: #797593; +} + +body.theme-rosepine-dawn .status-bar { + background: #f2e9e1; + border-top-color: #dfdad9; + color: #575279; +} + /* Print Styles */ @media print { /* Hide UI elements for clean print output */ diff --git a/src/table-generator.html b/src/table-generator.html new file mode 100644 index 0000000..4ee64e6 --- /dev/null +++ b/src/table-generator.html @@ -0,0 +1,545 @@ + + + + + + Table Generator - MarkdownConverter + + + + +
+

Table Generator

+
+ +
+
+
Table Settings
+
+
+ Rows + +
+
+ Columns + +
+
+ Alignment + +
+
+ +
+
+
+ + + + + +
+
+ +
+
Edit Table Content
+
+ + +
+
+
+ +
+
Markdown Preview
+
+
+
+
+
+ + + + + + diff --git a/tests/preload.test.js b/tests/preload.test.js new file mode 100644 index 0000000..60d75d9 --- /dev/null +++ b/tests/preload.test.js @@ -0,0 +1,121 @@ +/** + * Tests for Preload Script Security + * Verifies that the IPC bridge is properly configured + */ + +describe('Preload Security', () => { + describe('Allowed Channels', () => { + const EXPECTED_SEND_CHANNELS = [ + 'save-file', + 'save-current-file', + 'set-current-file', + 'save-recent-files', + 'clear-recent-files', + 'renderer-ready', + 'get-theme', + 'do-print', + 'export-with-options', + 'export-spreadsheet', + 'batch-convert', + 'select-folder', + 'universal-convert', + 'universal-convert-batch', + 'get-header-footer-settings', + 'save-header-footer-settings', + 'browse-header-footer-logo', + 'save-header-footer-logo', + 'clear-header-footer-logo', + 'get-page-settings', + 'update-page-settings', + 'set-custom-start-page', + 'process-pdf-operation', + 'get-pdf-page-count', + 'select-pdf-folder' + ]; + + const EXPECTED_RECEIVE_CHANNELS = [ + 'file-new', + 'file-opened', + 'file-save', + 'get-content-for-save', + 'get-content-for-spreadsheet', + 'recent-files-cleared', + 'toggle-preview', + 'toggle-find', + 'theme-changed', + 'theme-data', + 'undo', + 'redo', + 'adjust-font-size', + 'print-preview', + 'print-preview-styled', + 'show-export-dialog', + 'show-batch-dialog', + 'show-universal-converter-dialog', + 'show-table-generator', + 'show-pdf-editor-dialog', + 'conversion-status', + 'conversion-complete', + 'batch-progress', + 'folder-selected', + 'pdf-folder-selected', + 'header-footer-settings-data', + 'header-footer-logo-selected', + 'header-footer-logo-saved', + 'page-settings-data', + 'pdf-page-count', + 'pdf-operation-complete', + 'pdf-operation-error' + ]; + + test('should define all expected send channels', () => { + // This test documents expected channels + expect(EXPECTED_SEND_CHANNELS.length).toBeGreaterThan(0); + }); + + test('should define all expected receive channels', () => { + // This test documents expected channels + expect(EXPECTED_RECEIVE_CHANNELS.length).toBeGreaterThan(0); + }); + }); + + describe('electronAPI Interface', () => { + test('should expose send method', () => { + expect(window.electronAPI.send).toBeDefined(); + expect(typeof window.electronAPI.send).toBe('function'); + }); + + test('should expose on method', () => { + expect(window.electronAPI.on).toBeDefined(); + expect(typeof window.electronAPI.on).toBe('function'); + }); + + test('should expose once method', () => { + expect(window.electronAPI.once).toBeDefined(); + expect(typeof window.electronAPI.once).toBe('function'); + }); + + test('should expose invoke method', () => { + expect(window.electronAPI.invoke).toBeDefined(); + expect(typeof window.electronAPI.invoke).toBe('function'); + }); + + test('should expose file convenience methods', () => { + expect(window.electronAPI.file).toBeDefined(); + expect(window.electronAPI.file.save).toBeDefined(); + expect(window.electronAPI.file.saveCurrent).toBeDefined(); + expect(window.electronAPI.file.setCurrent).toBeDefined(); + }); + + test('should expose theme convenience methods', () => { + expect(window.electronAPI.theme).toBeDefined(); + expect(window.electronAPI.theme.get).toBeDefined(); + }); + + test('should expose pdf convenience methods', () => { + expect(window.electronAPI.pdf).toBeDefined(); + expect(window.electronAPI.pdf.processOperation).toBeDefined(); + expect(window.electronAPI.pdf.getPageCount).toBeDefined(); + }); + }); +}); diff --git a/tests/setup.js b/tests/setup.js new file mode 100644 index 0000000..7c6bdaa --- /dev/null +++ b/tests/setup.js @@ -0,0 +1,100 @@ +/** + * Jest Test Setup + * Provides mocks and utilities for PanConverter tests + */ + +// Mock window.electronAPI for renderer tests +global.window = global.window || {}; +global.window.electronAPI = { + send: jest.fn(), + on: jest.fn(() => jest.fn()), // Returns cleanup function + once: jest.fn(), + invoke: jest.fn(() => Promise.resolve(null)), + removeAllListeners: jest.fn(), + file: { + save: jest.fn(), + saveCurrent: jest.fn(), + setCurrent: jest.fn(), + saveRecent: jest.fn(), + clearRecent: jest.fn(), + rendererReady: jest.fn() + }, + theme: { + get: jest.fn() + }, + print: { + doPrint: jest.fn() + }, + export: { + withOptions: jest.fn(), + spreadsheet: jest.fn() + }, + batch: { + convert: jest.fn(), + selectFolder: jest.fn() + }, + converter: { + convert: jest.fn(), + convertBatch: jest.fn() + }, + headerFooter: { + getSettings: jest.fn(), + saveSettings: jest.fn(), + browseLogo: jest.fn(), + saveLogo: jest.fn(), + clearLogo: jest.fn() + }, + page: { + getSettings: jest.fn(), + updateSettings: jest.fn(), + setCustomStartPage: jest.fn() + }, + pdf: { + processOperation: jest.fn(), + getPageCount: jest.fn(), + selectFolder: jest.fn() + } +}; + +// Mock marked library +global.window.marked = { + parse: jest.fn((text) => `

${text}

`), + setOptions: jest.fn() +}; + +// Mock DOMPurify +global.window.DOMPurify = { + sanitize: jest.fn((html) => html) +}; + +// Mock highlight.js +global.window.hljs = { + highlight: jest.fn((code, options) => ({ value: code })), + highlightAuto: jest.fn((code) => ({ value: code })), + getLanguage: jest.fn(() => true) +}; + +// Mock localStorage +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn(), + clear: jest.fn() +}; +global.localStorage = localStorageMock; + +// Console spy to catch unintended console logs in tests +const originalConsoleError = console.error; +console.error = (...args) => { + // Allow certain expected errors + const message = args[0]?.toString() || ''; + if (message.includes('Warning:') || message.includes('React')) { + return; + } + originalConsoleError.apply(console, args); +}; + +// Cleanup after each test +afterEach(() => { + jest.clearAllMocks(); +}); diff --git a/tests/utils.test.js b/tests/utils.test.js new file mode 100644 index 0000000..1bdc7df --- /dev/null +++ b/tests/utils.test.js @@ -0,0 +1,137 @@ +/** + * Tests for Utility Functions + * Tests helper functions that can be extracted and tested + */ + +describe('Utility Functions', () => { + describe('parseCommand', () => { + // This function parses command strings into command and args + function parseCommand(cmdString) { + const parts = []; + let current = ''; + let inQuotes = false; + let quoteChar = ''; + + for (let i = 0; i < cmdString.length; i++) { + const char = cmdString[i]; + if ((char === '"' || char === "'") && !inQuotes) { + inQuotes = true; + quoteChar = char; + } else if (char === quoteChar && inQuotes) { + inQuotes = false; + quoteChar = ''; + } else if (char === ' ' && !inQuotes) { + if (current) { + parts.push(current); + current = ''; + } + } else { + current += char; + } + } + if (current) { + parts.push(current); + } + + return { + command: parts[0], + args: parts.slice(1) + }; + } + + test('should parse simple command', () => { + const result = parseCommand('pandoc input.md -o output.pdf'); + expect(result.command).toBe('pandoc'); + expect(result.args).toEqual(['input.md', '-o', 'output.pdf']); + }); + + test('should handle double-quoted paths', () => { + const result = parseCommand('pandoc "C:/path with spaces/file.md" -o output.pdf'); + expect(result.command).toBe('pandoc'); + expect(result.args).toEqual(['C:/path with spaces/file.md', '-o', 'output.pdf']); + }); + + test('should handle single-quoted paths', () => { + const result = parseCommand("pandoc 'file name.md' -o output.pdf"); + expect(result.command).toBe('pandoc'); + expect(result.args).toEqual(['file name.md', '-o', 'output.pdf']); + }); + + test('should handle multiple options', () => { + const result = parseCommand('pandoc input.md --pdf-engine=xelatex -V geometry:margin=1in -o output.pdf'); + expect(result.command).toBe('pandoc'); + expect(result.args).toContain('--pdf-engine=xelatex'); + expect(result.args).toContain('-V'); + }); + + test('should handle empty command', () => { + const result = parseCommand(''); + expect(result.command).toBeUndefined(); + expect(result.args).toEqual([]); + }); + }); + + describe('hexToRgb', () => { + // This function converts hex colors to RGB + function hexToRgb(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16) / 255, + g: parseInt(result[2], 16) / 255, + b: parseInt(result[3], 16) / 255 + } : null; + } + + test('should convert black hex to RGB', () => { + const result = hexToRgb('#000000'); + expect(result).toEqual({ r: 0, g: 0, b: 0 }); + }); + + test('should convert white hex to RGB', () => { + const result = hexToRgb('#ffffff'); + expect(result).toEqual({ r: 1, g: 1, b: 1 }); + }); + + test('should convert red hex to RGB', () => { + const result = hexToRgb('#ff0000'); + expect(result.r).toBeCloseTo(1); + expect(result.g).toBeCloseTo(0); + expect(result.b).toBeCloseTo(0); + }); + + test('should handle hex without hash', () => { + const result = hexToRgb('00ff00'); + expect(result.r).toBeCloseTo(0); + expect(result.g).toBeCloseTo(1); + expect(result.b).toBeCloseTo(0); + }); + + test('should return null for invalid hex', () => { + expect(hexToRgb('invalid')).toBeNull(); + expect(hexToRgb('#xyz')).toBeNull(); + }); + }); + + describe('File Path Utilities', () => { + test('should extract file extension', () => { + const getExtension = (filepath) => { + const match = filepath.match(/\.([^/.]+)$/); + return match ? match[1].toLowerCase() : ''; + }; + + expect(getExtension('file.md')).toBe('md'); + expect(getExtension('document.PDF')).toBe('pdf'); + expect(getExtension('path/to/file.docx')).toBe('docx'); + expect(getExtension('noextension')).toBe(''); + }); + + test('should replace file extension', () => { + const replaceExtension = (filepath, newExt) => { + return filepath.replace(/\.[^/.]+$/, `.${newExt}`); + }; + + expect(replaceExtension('file.md', 'pdf')).toBe('file.pdf'); + expect(replaceExtension('path/to/doc.docx', 'html')).toBe('path/to/doc.html'); + }); + }); +});