mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e67667035 | ||
|
|
7783fbf622 | ||
|
|
458a387c15 | ||
|
|
72677698d2 | ||
|
|
59c6513b49 | ||
|
|
c305789428 | ||
|
|
fefb299e6d | ||
|
|
3f3bdf5637 | ||
|
|
2ab8deffeb | ||
|
|
cc471d4101 | ||
|
|
0ac92b7060 | ||
|
|
f74f5f4221 | ||
|
|
e1c0bc387f | ||
|
|
ef3ce1647e | ||
|
|
e1e0072eea | ||
|
|
1b2afb5f15 | ||
|
|
c811af9d48 | ||
|
|
a6e91b7403 | ||
|
|
a44b13d2fb | ||
|
|
015446bff3 | ||
|
|
9f15a02511 | ||
|
|
9799b95cea | ||
|
|
83150eea88 |
@@ -26,7 +26,14 @@
|
|||||||
"Bash(dir:*)",
|
"Bash(dir:*)",
|
||||||
"Bash(git merge:*)",
|
"Bash(git merge:*)",
|
||||||
"Bash(timeout:*)",
|
"Bash(timeout:*)",
|
||||||
"Bash(findstr:*)"
|
"Bash(findstr:*)",
|
||||||
|
"Bash(git stash:*)",
|
||||||
|
"Bash(npm start:*)",
|
||||||
|
"Bash(gh release delete-asset:*)",
|
||||||
|
"Bash(git rm:*)",
|
||||||
|
"Bash(gh release view:*)",
|
||||||
|
"Bash(npm run build:linux:*)",
|
||||||
|
"Bash(git reset:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"CurrentProjectSetting": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"ExpandedNodes": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"SelectedNode": "\\package.json",
|
||||||
|
"PreviewInSolutionExplorer": false
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"Version": 1,
|
||||||
|
"WorkspaceRootPath": "H:\\Development\\pan-converter\\",
|
||||||
|
"Documents": [
|
||||||
|
{
|
||||||
|
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\Development\\pan-converter\\package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
|
||||||
|
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DocumentGroupContainers": [
|
||||||
|
{
|
||||||
|
"Orientation": 0,
|
||||||
|
"VerticalTabListWidth": 256,
|
||||||
|
"DocumentGroups": [
|
||||||
|
{
|
||||||
|
"DockedWidth": 200,
|
||||||
|
"SelectedChildIndex": 0,
|
||||||
|
"Children": [
|
||||||
|
{
|
||||||
|
"$type": "Document",
|
||||||
|
"DocumentIndex": 0,
|
||||||
|
"Title": "package.json",
|
||||||
|
"DocumentMoniker": "H:\\Development\\pan-converter\\package.json",
|
||||||
|
"RelativeDocumentMoniker": "package.json",
|
||||||
|
"ToolTip": "H:\\Development\\pan-converter\\package.json",
|
||||||
|
"RelativeToolTip": "package.json",
|
||||||
|
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAqAAAAAAAAAA==",
|
||||||
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||||
|
"WhenOpened": "2025-10-11T13:44:30.357Z",
|
||||||
|
"EditorCaption": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"Version": 1,
|
||||||
|
"WorkspaceRootPath": "H:\\Development\\pan-converter\\",
|
||||||
|
"Documents": [
|
||||||
|
{
|
||||||
|
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\Development\\pan-converter\\package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
|
||||||
|
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DocumentGroupContainers": [
|
||||||
|
{
|
||||||
|
"Orientation": 0,
|
||||||
|
"VerticalTabListWidth": 256,
|
||||||
|
"DocumentGroups": [
|
||||||
|
{
|
||||||
|
"DockedWidth": 200,
|
||||||
|
"SelectedChildIndex": 0,
|
||||||
|
"Children": [
|
||||||
|
{
|
||||||
|
"$type": "Document",
|
||||||
|
"DocumentIndex": 0,
|
||||||
|
"Title": "package.json",
|
||||||
|
"DocumentMoniker": "H:\\Development\\pan-converter\\package.json",
|
||||||
|
"RelativeDocumentMoniker": "package.json",
|
||||||
|
"ToolTip": "H:\\Development\\pan-converter\\package.json",
|
||||||
|
"RelativeToolTip": "package.json",
|
||||||
|
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAqAAAAAAAAAA==",
|
||||||
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||||
|
"WhenOpened": "2025-10-11T13:44:30.357Z",
|
||||||
|
"EditorCaption": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
**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.
|
**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.
|
||||||
|
|
||||||
**Current Version**: v1.6.1
|
**Current Version**: v1.7.8
|
||||||
**Author**: Amit Haridas (amit.wh@gmail.com)
|
**Author**: Amit Haridas (amit.wh@gmail.com)
|
||||||
**License**: MIT
|
**License**: MIT
|
||||||
**Repository**: https://github.com/amitwh/pan-converter
|
**Repository**: https://github.com/amitwh/pan-converter
|
||||||
@@ -19,14 +19,17 @@
|
|||||||
- **highlight.js** - Syntax highlighting
|
- **highlight.js** - Syntax highlighting
|
||||||
- **KaTeX** - Mathematical expression rendering
|
- **KaTeX** - Mathematical expression rendering
|
||||||
- **DOMPurify** - HTML sanitization
|
- **DOMPurify** - HTML sanitization
|
||||||
|
- **PDFKit** - Native PDF generation without external dependencies (v1.7.7)
|
||||||
|
- **html2pdf** - HTML to PDF conversion library (v1.7.7)
|
||||||
|
|
||||||
### Application Structure
|
### Application Structure
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── main.js # Electron main process, menu system, IPC handlers
|
├── main.js # Electron main process, menu system, IPC handlers
|
||||||
├── renderer.js # TabManager class, multi-file editing, event handling
|
├── renderer.js # TabManager class, multi-file editing, event handling
|
||||||
├── index.html # Application layout with tabbed interface
|
├── index.html # Application layout with tabbed interface
|
||||||
└── styles.css # Comprehensive styling with multi-theme support
|
├── styles.css # Base styling with multi-theme support
|
||||||
|
└── styles-modern.css # Modern glassmorphism UI design system (v1.7.1)
|
||||||
|
|
||||||
assets/
|
assets/
|
||||||
└── icon.png # Application icon
|
└── icon.png # Application icon
|
||||||
@@ -110,7 +113,356 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
|
|||||||
|
|
||||||
## Feature Implementation Guide
|
## Feature Implementation Guide
|
||||||
|
|
||||||
### v1.7.0 PDF Editor & Universal Converter (Latest)
|
### v1.7.8 Critical Bug Fixes (Latest)
|
||||||
|
|
||||||
|
#### 🐛 File Association Fix for Packaged Apps
|
||||||
|
**Fixed Command-Line Argument Parsing** (`src/main.js:1598-1627`, `src/main.js:70-90`)
|
||||||
|
- **Root Cause**: `process.argv.slice(2)` works in development but fails in packaged apps
|
||||||
|
- **Solution**: Detect if app is packaged using `app.isPackaged` and use correct slice index
|
||||||
|
- Development: `process.argv.slice(2)` - ['electron', 'app.js', 'file.md']
|
||||||
|
- Packaged: `process.argv.slice(1)` - ['PanConverter.exe', 'file.md']
|
||||||
|
- **Path Resolution**: Added proper path resolution for both absolute and relative paths
|
||||||
|
- **Comprehensive Logging**: Added detailed console logs to debug file loading issues
|
||||||
|
- **Second Instance Handler**: Also fixed second-instance handler to use same logic
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
```javascript
|
||||||
|
// Detect packaged vs development mode
|
||||||
|
const startIndex = app.isPackaged ? 1 : 2;
|
||||||
|
const fileArgs = process.argv.slice(startIndex);
|
||||||
|
|
||||||
|
// Resolve paths properly
|
||||||
|
for (const arg of fileArgs) {
|
||||||
|
if ((arg.endsWith('.md') || arg.endsWith('.markdown'))) {
|
||||||
|
const resolvedPath = path.isAbsolute(arg) ? arg : path.resolve(process.cwd(), arg);
|
||||||
|
if (fs.existsSync(resolvedPath)) {
|
||||||
|
app.pendingFile = resolvedPath;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fixed Issues:**
|
||||||
|
- ✅ Files now open correctly on first double-click in packaged app
|
||||||
|
- ✅ Command-line arguments properly parsed in both dev and production
|
||||||
|
- ✅ Relative and absolute paths handled correctly
|
||||||
|
- ✅ Comprehensive logging for debugging file association issues
|
||||||
|
|
||||||
|
#### 🖨️ Print Preview Fix
|
||||||
|
**Fixed Print Output to Show Preview Content** (`src/renderer.js:1178-1204`, `src/styles.css:2828-3014`)
|
||||||
|
- **Root Cause**: Manual DOM manipulation conflicted with CSS `@media print` rules
|
||||||
|
- **Solution**: Simplified handler to rely on CSS `@media print` for hiding UI elements
|
||||||
|
- **CSS Enhancements**:
|
||||||
|
- Added selectors for dynamic preview panes `[id^="preview-pane-"]` and `[id^="editor-pane-"]`
|
||||||
|
- Added `.tab-content` to ensure full-width rendering
|
||||||
|
- Added body class-based styling for no-styles mode
|
||||||
|
- **Removed Manual Hiding**: Eliminated manual `display: none` manipulation in favor of CSS
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
```javascript
|
||||||
|
// Simplified print handler - let CSS do the work
|
||||||
|
function handlePrintPreview(withStyles) {
|
||||||
|
const activeTabId = tabManager ? tabManager.activeTabId : 1;
|
||||||
|
const previewContent = document.getElementById(`preview-${activeTabId}`);
|
||||||
|
|
||||||
|
if (!previewContent || !previewContent.innerHTML.trim()) {
|
||||||
|
alert('Nothing to print...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add body classes for CSS styling
|
||||||
|
if (!withStyles) {
|
||||||
|
document.body.classList.add('printing-no-styles');
|
||||||
|
}
|
||||||
|
document.body.classList.add('printing');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
ipcRenderer.send('do-print', { withStyles });
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.classList.remove('printing', 'printing-no-styles');
|
||||||
|
}, 1000);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSS Updates:**
|
||||||
|
```css
|
||||||
|
@media print {
|
||||||
|
/* Hide all UI elements */
|
||||||
|
#toolbar, #tab-bar, .editor-pane, [id^="editor-pane-"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show preview in full width */
|
||||||
|
[id^="preview-"], [id^="preview-pane-"], .tab-content {
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 20px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fixed Issues:**
|
||||||
|
- ✅ Print now correctly renders preview content, not toolbar
|
||||||
|
- ✅ Both print modes work (with styles and without styles)
|
||||||
|
- ✅ Clean, professional print output with proper formatting
|
||||||
|
- ✅ Proper page breaks, typography, and layout optimization
|
||||||
|
|
||||||
|
#### 🔧 Development Cleanup
|
||||||
|
**Removed Debugging Code** (`src/main.js:120-123`)
|
||||||
|
- Removed auto-opening of DevTools
|
||||||
|
- Clean production-ready build
|
||||||
|
- Debugging logs remain for troubleshooting but DevTools disabled by default
|
||||||
|
|
||||||
|
### v1.7.7 Print & Enhanced PDF Support
|
||||||
|
|
||||||
|
#### 🖨️ Native Print Functionality
|
||||||
|
**Added Print Submenu to File Menu** (`src/main.js:202-215`, `src/renderer.js:1152-1188`, `src/styles.css:2828-2994`)
|
||||||
|
- **Print Menu**: Submenu in File menu with two print options
|
||||||
|
- **Print Preview** (`Ctrl+P`) - Prints preview in black text, no background colors (ink-saving)
|
||||||
|
- **Print Preview (With Styles)** - Prints with full theme colors and styling
|
||||||
|
- **Preview-Only Printing**: Only renders the markdown preview, hides all editor UI
|
||||||
|
- **Native Print Dialog**: Uses Electron's webContents.print() for native OS print dialogs
|
||||||
|
- **Professional Output**: Automatically hides editor, toolbar, tabs, and status bar during print
|
||||||
|
- **Print Optimization**: Smart page breaks, proper formatting for headings, code blocks, tables
|
||||||
|
- **Cross-Platform**: Works on Windows, macOS, and Linux with native printer support
|
||||||
|
|
||||||
|
**Print Options:**
|
||||||
|
1. **Print Preview** (Ctrl+P)
|
||||||
|
- Black text on white background for professional printing
|
||||||
|
- No background colors to save ink
|
||||||
|
- Perfect for documents and reports
|
||||||
|
- Minimal ink consumption
|
||||||
|
|
||||||
|
2. **Print Preview (With Styles)**
|
||||||
|
- Full theme colors and styling
|
||||||
|
- Preserves markdown formatting with visual hierarchy
|
||||||
|
- Better for design-focused documents
|
||||||
|
- Shows code blocks with colored syntax highlighting
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
```javascript
|
||||||
|
// Main process - two print handlers
|
||||||
|
ipcMain.on('print-preview', (event) => {
|
||||||
|
mainWindow.webContents.send('prepare-print-preview', false);
|
||||||
|
setTimeout(() => {
|
||||||
|
mainWindow.webContents.print({
|
||||||
|
silent: false,
|
||||||
|
printBackground: false,
|
||||||
|
color: true,
|
||||||
|
margin: { marginType: 'default' }
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('print-preview-styled', (event) => {
|
||||||
|
mainWindow.webContents.send('prepare-print-preview', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
mainWindow.webContents.print({
|
||||||
|
silent: false,
|
||||||
|
printBackground: true,
|
||||||
|
color: true,
|
||||||
|
margin: { marginType: 'default' }
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Renderer process - prepares preview for printing
|
||||||
|
ipcRenderer.on('prepare-print-preview', (event, withStyles) => {
|
||||||
|
// Hide editor and UI elements
|
||||||
|
document.getElementById('editor-container').style.display = 'none';
|
||||||
|
document.getElementById('toolbar').style.display = 'none';
|
||||||
|
document.getElementById('tab-bar').style.display = 'none';
|
||||||
|
document.getElementById('status-bar').style.display = 'none';
|
||||||
|
|
||||||
|
// Add print mode classes
|
||||||
|
const preview = document.getElementById('preview');
|
||||||
|
preview.classList.add('print-mode');
|
||||||
|
if (!withStyles) {
|
||||||
|
preview.classList.add('print-no-styles');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore UI after print
|
||||||
|
setTimeout(() => {
|
||||||
|
document.getElementById('editor-container').style.display = '';
|
||||||
|
document.getElementById('toolbar').style.display = '';
|
||||||
|
document.getElementById('tab-bar').style.display = '';
|
||||||
|
document.getElementById('status-bar').style.display = '';
|
||||||
|
preview.classList.remove('print-mode', 'print-no-styles');
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSS Print Styles** (`src/styles.css:2828-2994`)
|
||||||
|
- Comprehensive @media print stylesheet
|
||||||
|
- Hides all UI elements in print view
|
||||||
|
- Optimizes preview for paper output
|
||||||
|
- Smart page breaks for headings and tables
|
||||||
|
- Proper formatting for code blocks, lists, tables
|
||||||
|
- Image optimization and link handling
|
||||||
|
- Professional typography with 12pt font size
|
||||||
|
|
||||||
|
#### 📦 Enhanced PDF Export Dependencies
|
||||||
|
**Added Native PDF Generation Libraries** (package.json)
|
||||||
|
- **PDFKit** (v0.14.0) - Native PDF generation without Pandoc dependency
|
||||||
|
- **html2pdf.js** (v0.10.1) - HTML to PDF conversion for rich formatted output
|
||||||
|
- **Benefits**: PDF exports now work even without system Pandoc installation
|
||||||
|
- **Future Enhancement**: Enables PDF generation with embedded fonts and graphics
|
||||||
|
- **Offline Support**: Complete PDF generation offline without external services
|
||||||
|
|
||||||
|
**Updated Dependencies:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pdfkit": "^0.14.0",
|
||||||
|
"html2pdf.js": "^0.10.1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features Enabled:**
|
||||||
|
- PDF export works independently of Pandoc installation
|
||||||
|
- Higher quality PDF output with better formatting control
|
||||||
|
- Support for custom fonts, images, and complex layouts
|
||||||
|
- Faster PDF generation times
|
||||||
|
- Reduced system dependencies for better portability
|
||||||
|
|
||||||
|
#### 🔧 Keyboard Shortcut Updates
|
||||||
|
**Remapped Shortcuts** (`src/main.js`)
|
||||||
|
- **Print**: `Ctrl+P` (new in v1.7.7) - Opens native print dialog
|
||||||
|
- **Toggle Preview**: Changed from `Ctrl+P` to `Ctrl+Shift+P` to avoid conflicts
|
||||||
|
- **Backward Compatibility**: No breaking changes to existing workflows
|
||||||
|
|
||||||
|
### v1.7.6 UI Improvement
|
||||||
|
|
||||||
|
#### 🎨 Table Header Styling Cleanup
|
||||||
|
**Removed Custom Background Colors** (`src/styles.css:327-329`, `src/styles.css:451-453`)
|
||||||
|
- Removed gray background color (#f6f8fa) from table headers in default theme
|
||||||
|
- Removed dark theme table header background styling block entirely
|
||||||
|
- Table headers now blend naturally with preview background for cleaner appearance
|
||||||
|
- Headers maintain font-weight: 600 for visual distinction
|
||||||
|
- All other table styling preserved (borders, alternating row colors)
|
||||||
|
|
||||||
|
**User Feedback Implementation:**
|
||||||
|
- Based on user feedback: "remove the theming of table headers, it looks bad"
|
||||||
|
- Result: Professional, clean table presentation matching overall preview styling
|
||||||
|
- Consistent with normal preview theming across all themes
|
||||||
|
|
||||||
|
### v1.7.5 Critical File Association Fix
|
||||||
|
|
||||||
|
#### 🐛 Single-Instance Lock for Windows File Association
|
||||||
|
**Fixed Installed App Double-Click Behavior** (`src/main.js:52-85`)
|
||||||
|
- **Root Cause**: Windows launches second instance when double-clicking files with app already running
|
||||||
|
- **Solution**: Implemented `app.requestSingleInstanceLock()` pattern
|
||||||
|
- **Second Instance Handler**: Captures file path from new instance attempts and opens in existing instance
|
||||||
|
- **Focus Management**: Automatically focuses and restores existing window
|
||||||
|
- **Renderer Readiness**: Proper handling of file queue with `rendererReady` state
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
```javascript
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
|
if (!gotTheLock) {
|
||||||
|
app.quit();
|
||||||
|
} else {
|
||||||
|
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||||
|
// Focus existing window
|
||||||
|
if (mainWindow) {
|
||||||
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
// Extract and open file from second instance
|
||||||
|
const fileArgs = commandLine.slice(2);
|
||||||
|
for (const arg of fileArgs) {
|
||||||
|
if ((arg.endsWith('.md') || arg.endsWith('.markdown')) && fs.existsSync(arg)) {
|
||||||
|
if (rendererReady) {
|
||||||
|
openFileFromPath(arg);
|
||||||
|
} else {
|
||||||
|
app.pendingFile = arg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fixed Issues:**
|
||||||
|
- ✅ Double-click `.md` files in Windows Explorer now opens correctly
|
||||||
|
- ✅ Right-click "Open with PanConverter" now works in installed app
|
||||||
|
- ✅ File association behavior now consistent between development and production
|
||||||
|
- ✅ Single instance maintained across all file opening methods
|
||||||
|
|
||||||
|
**Why Previous Attempts Failed:**
|
||||||
|
- v1.7.3-1.7.4: Only added `rendererReady` checks but missed second-instance handling
|
||||||
|
- Development testing passed but production failed (different execution paths)
|
||||||
|
- Second instance would start, receive file, then exit without passing to first instance
|
||||||
|
|
||||||
|
### v1.7.2 Enhanced Themes & Bug Fixes
|
||||||
|
|
||||||
|
#### 🎨 14 New Beautiful Themes
|
||||||
|
**Expanded Theme Collection** (`src/main.js:242-266`, `src/styles.css:1590-2555`)
|
||||||
|
- **Added 14 new professionally-designed themes** bringing the total to 19 themes
|
||||||
|
- **New Themes Include**:
|
||||||
|
- **Dracula** - Popular purple and pink dark theme
|
||||||
|
- **Nord** - Arctic-inspired blue theme
|
||||||
|
- **One Dark** - Atom's iconic dark theme
|
||||||
|
- **Atom One Light** - Clean, bright light theme
|
||||||
|
- **Material** - Google Material Design inspired
|
||||||
|
- **Gruvbox Dark** - Warm retro groove colors (dark)
|
||||||
|
- **Gruvbox Light** - Warm retro groove colors (light)
|
||||||
|
- **Tokyo Night** - Modern Japanese-inspired dark theme
|
||||||
|
- **Palenight** - Soft purple Material Design variant
|
||||||
|
- **Ayu Dark** - Simple, elegant dark theme
|
||||||
|
- **Ayu Light** - Minimalist light theme
|
||||||
|
- **Ayu Mirage** - Balanced dark-light hybrid
|
||||||
|
- **Oceanic Next** - Ocean-inspired teal and blue
|
||||||
|
- **Cobalt2** - Deep blue with yellow accents
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Complete CSS styling for all UI elements per theme
|
||||||
|
- Consistent theming across tabs, toolbar, editor, preview, and status bar
|
||||||
|
- Theme-aware color schemes for syntax highlighting
|
||||||
|
- All themes support glassmorphism effects from v1.7.1
|
||||||
|
|
||||||
|
#### 🐛 Undo/Redo Fix
|
||||||
|
**Fixed Menu Integration** (`src/renderer.js:1107-1118`)
|
||||||
|
- Fixed undo/redo menu items not connecting to custom undo/redo functionality
|
||||||
|
- Added IPC event listeners in renderer to receive 'undo' and 'redo' messages
|
||||||
|
- Connected Edit menu commands to TabManager's undo() and redo() methods
|
||||||
|
- Keyboard shortcuts (Ctrl+Z, Ctrl+Shift+Z) now work correctly with custom undo stack
|
||||||
|
|
||||||
|
### v1.7.1 Modern UI Design
|
||||||
|
|
||||||
|
#### 🎨 Glassmorphism & Gradient UI
|
||||||
|
**Modern Design System** (`src/styles-modern.css`, `src/index.html:7-11`)
|
||||||
|
- **Glassmorphism Effects** - Translucent backgrounds with backdrop-filter blur
|
||||||
|
- **Animated Gradient Background** - Purple-blue gradient that shifts colors dynamically
|
||||||
|
- **CSS Custom Properties** - Centralized theming with CSS variables
|
||||||
|
- **Modern Typography** - Inter font family for UI, JetBrains Mono for code
|
||||||
|
- **Smooth Animations** - CSS transitions with cubic-bezier easing
|
||||||
|
- **Enhanced Hover Effects** - Interactive elements with scale and transform effects
|
||||||
|
- **Custom Scrollbars** - Styled scrollbars matching the modern aesthetic
|
||||||
|
- **Progress Bars** - Shimmer animations and gradient fills
|
||||||
|
|
||||||
|
**Design Features:**
|
||||||
|
- Animated gradient background (15-second color shift loop)
|
||||||
|
- Glass-effect tabs with blur and semi-transparency
|
||||||
|
- Gradient-based buttons with hover/active states
|
||||||
|
- Modern card-based dialogs with rounded corners
|
||||||
|
- Enhanced typography with gradient text for headings
|
||||||
|
- Ripple-like hover effects on interactive elements
|
||||||
|
- Theme variables for consistent colors and spacing
|
||||||
|
- Shadow effects with proper depth perception
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Created comprehensive `styles-modern.css` file (780+ lines)
|
||||||
|
- Google Fonts integration (Inter, JetBrains Mono)
|
||||||
|
- CSS Grid and Flexbox for modern layouts
|
||||||
|
- backdrop-filter for glassmorphism (with fallbacks)
|
||||||
|
- CSS animations with @keyframes
|
||||||
|
- RGB/RGBA color system with opacity control
|
||||||
|
- CSS custom properties for easy theming
|
||||||
|
|
||||||
|
### v1.7.0 PDF Editor & Universal Converter
|
||||||
|
|
||||||
#### 📄 Comprehensive PDF Editor
|
#### 📄 Comprehensive PDF Editor
|
||||||
**Complete PDF Manipulation Suite** (`src/main.js:1566-2068`, `src/index.html:552-921`, `src/renderer.js:1951-2429`)
|
**Complete PDF Manipulation Suite** (`src/main.js:1566-2068`, `src/index.html:552-921`, `src/renderer.js:1951-2429`)
|
||||||
@@ -532,5 +884,10 @@ gh release create v1.2.1 --title "Title" --notes "Release notes" \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: September 15, 2025
|
**Last Updated**: October 26, 2025
|
||||||
**Claude Assistant**: Development completed for v1.5.0 with optional advanced export options, removed proprietary dependencies for open-source compatibility, enhanced auto-save functionality, comprehensive document statistics, recent files management, and mathematical expression support using KaTeX.
|
**Claude Assistant**: Development completed for v1.7.8 with critical bug fixes:
|
||||||
|
1. **File Association Fix**: Fixed command-line argument parsing to properly detect packaged vs development mode using `app.isPackaged`, enabling files to open correctly on first double-click in packaged app
|
||||||
|
2. **Print Preview Fix**: Completely rewrote print handler to rely on CSS `@media print` rules instead of manual DOM manipulation, ensuring preview content (not toolbar) is printed
|
||||||
|
3. **Code Cleanup**: Removed auto-opening DevTools for production-ready build
|
||||||
|
|
||||||
|
Previous releases: v1.7.7 (print menu with two options, PDFKit/html2pdf integration), v1.7.6 (table header styling cleanup), v1.7.5 (single-instance lock).
|
||||||
+9
-92
@@ -1,95 +1,12 @@
|
|||||||
# Push Instructions for Pan Converter
|
# Test Double-Click File Opening
|
||||||
|
|
||||||
## Prerequisites
|
This is a test file to verify double-click functionality.
|
||||||
1. Create a new repository on GitHub: https://github.com/new
|
|
||||||
- Repository name: `pan-converter`
|
|
||||||
- Keep it empty (no README, .gitignore, or license)
|
|
||||||
- Make it public or private as desired
|
|
||||||
|
|
||||||
2. Set up authentication:
|
## Features to Test
|
||||||
- **Option A - SSH Key** (Recommended):
|
- File should load automatically
|
||||||
```bash
|
- Content should display in editor
|
||||||
# Check if you have SSH key
|
- Tab should show filename
|
||||||
ls -la ~/.ssh/id_rsa.pub
|
- Preview should render markdown
|
||||||
|
|
||||||
# If not, generate one:
|
|
||||||
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
|
|
||||||
|
|
||||||
# Add to GitHub: Settings > SSH and GPG keys > New SSH key
|
|
||||||
cat ~/.ssh/id_rsa.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Option B - Personal Access Token**:
|
|
||||||
- Go to GitHub Settings > Developer settings > Personal access tokens
|
|
||||||
- Generate new token with 'repo' scope
|
|
||||||
- Save the token securely
|
|
||||||
|
|
||||||
## Push Commands
|
**This text should be bold**
|
||||||
|
*This text should be italic*
|
||||||
### Using SSH (Recommended)
|
|
||||||
```bash
|
|
||||||
# Set SSH remote
|
|
||||||
git remote set-url origin git@github.com:amitwh/pan-converter.git
|
|
||||||
|
|
||||||
# Push all branches
|
|
||||||
git push -u origin master
|
|
||||||
git push origin linux
|
|
||||||
git push origin macos
|
|
||||||
git push origin windows
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using HTTPS with Token
|
|
||||||
```bash
|
|
||||||
# Set HTTPS remote
|
|
||||||
git remote set-url origin https://github.com/amitwh/pan-converter.git
|
|
||||||
|
|
||||||
# Push all branches (you'll be prompted for username and token)
|
|
||||||
git push -u origin master
|
|
||||||
git push origin linux
|
|
||||||
git push origin macos
|
|
||||||
git push origin windows
|
|
||||||
|
|
||||||
# When prompted:
|
|
||||||
# Username: amitwh
|
|
||||||
# Password: [paste your Personal Access Token]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using GitHub CLI (if installed)
|
|
||||||
```bash
|
|
||||||
# Login to GitHub CLI
|
|
||||||
gh auth login
|
|
||||||
|
|
||||||
# Create repo and push
|
|
||||||
gh repo create pan-converter --public --source=. --remote=origin --push
|
|
||||||
```
|
|
||||||
|
|
||||||
## Automated Script
|
|
||||||
```bash
|
|
||||||
# Run the provided script
|
|
||||||
./push-to-github.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verify Success
|
|
||||||
After pushing, verify at: https://github.com/amitwh/pan-converter
|
|
||||||
|
|
||||||
You should see:
|
|
||||||
- 4 branches (master, linux, macos, windows)
|
|
||||||
- All project files
|
|
||||||
- Complete commit history
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Authentication Failed
|
|
||||||
- For SSH: Ensure your SSH key is added to GitHub
|
|
||||||
- For HTTPS: Use Personal Access Token, not password
|
|
||||||
- Check token has 'repo' scope
|
|
||||||
|
|
||||||
### Repository Not Found
|
|
||||||
- Ensure repository exists on GitHub
|
|
||||||
- Check spelling: `pan-converter`
|
|
||||||
- Verify you're logged into the correct GitHub account
|
|
||||||
|
|
||||||
### Permission Denied
|
|
||||||
- Check repository ownership
|
|
||||||
- Ensure you have write access
|
|
||||||
- Verify authentication method is correct
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pan-converter",
|
"name": "pan-converter",
|
||||||
"version": "1.7.0",
|
"version": "1.7.8",
|
||||||
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
"description": "Cross-platform Markdown editor and converter using Pandoc",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -33,9 +33,11 @@
|
|||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.2.6",
|
||||||
"electron-store": "^10.1.0",
|
"electron-store": "^10.1.0",
|
||||||
|
"html2pdf.js": "^0.10.1",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"marked": "^16.2.1",
|
"marked": "^16.2.1",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
|
"pdfkit": "^0.14.0",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -4,7 +4,11 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>PanConverter - Markdown Editor</title>
|
<title>PanConverter - Markdown Editor</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<link rel="stylesheet" href="styles-modern.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
+171
-34
@@ -49,6 +49,48 @@ let currentFile = null; // This will now represent the active tab's file
|
|||||||
let pandocAvailable = null; // Cache pandoc availability check
|
let pandocAvailable = null; // Cache pandoc availability check
|
||||||
let rendererReady = false; // Track if renderer is ready to receive file data
|
let rendererReady = false; // Track if renderer is ready to receive file data
|
||||||
|
|
||||||
|
// Handle single instance lock for Windows file association
|
||||||
|
// When a file is double-clicked and the app is already running,
|
||||||
|
// Windows tries to start a second instance. We prevent this and
|
||||||
|
// pass the file to the existing instance instead.
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
|
if (!gotTheLock) {
|
||||||
|
// Another instance is already running, quit this one
|
||||||
|
app.quit();
|
||||||
|
} else {
|
||||||
|
// This is the first instance, handle second-instance events
|
||||||
|
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||||
|
// Someone tried to run a second instance, focus our window instead
|
||||||
|
if (mainWindow) {
|
||||||
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file was passed to the second instance
|
||||||
|
// commandLine is an array like: ['PanConverter.exe', 'file.md']
|
||||||
|
console.log('[MAIN] Second instance commandLine:', JSON.stringify(commandLine));
|
||||||
|
const startIndex = app.isPackaged ? 1 : 2;
|
||||||
|
const fileArgs = commandLine.slice(startIndex);
|
||||||
|
console.log('[MAIN] Second instance file args:', fileArgs);
|
||||||
|
for (const arg of fileArgs) {
|
||||||
|
if ((arg.endsWith('.md') || arg.endsWith('.markdown'))) {
|
||||||
|
const resolvedPath = path.isAbsolute(arg) ? arg : path.resolve(workingDirectory, arg);
|
||||||
|
console.log('[MAIN] Second instance resolved path:', resolvedPath);
|
||||||
|
if (fs.existsSync(resolvedPath)) {
|
||||||
|
// Open the file in the existing instance
|
||||||
|
if (rendererReady) {
|
||||||
|
openFileFromPath(resolvedPath);
|
||||||
|
} else {
|
||||||
|
app.pendingFile = resolvedPath;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Check if pandoc is available
|
// Check if pandoc is available
|
||||||
function checkPandocAvailability() {
|
function checkPandocAvailability() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -83,7 +125,12 @@ function createWindow() {
|
|||||||
mainWindow = null;
|
mainWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle pending file from file association will be handled by renderer-ready event
|
// Wait for the page to fully load before sending file data
|
||||||
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
|
console.log('Window finished loading');
|
||||||
|
// Don't open file here - wait for renderer-ready signal
|
||||||
|
// The renderer will send renderer-ready when TabManager is initialized
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRecentFilesMenu() {
|
function buildRecentFilesMenu() {
|
||||||
@@ -159,6 +206,21 @@ function createMenu() {
|
|||||||
click: saveAsFile
|
click: saveAsFile
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Print',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Print Preview',
|
||||||
|
accelerator: 'CmdOrCtrl+P',
|
||||||
|
click: () => mainWindow.webContents.send('print-preview')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Print Preview (With Styles)',
|
||||||
|
click: () => mainWindow.webContents.send('print-preview-styled')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Recent Files',
|
label: 'Recent Files',
|
||||||
submenu: buildRecentFilesMenu()
|
submenu: buildRecentFilesMenu()
|
||||||
@@ -197,18 +259,26 @@ function createMenu() {
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
|
{
|
||||||
{ label: 'Redo', accelerator: 'CmdOrCtrl+Shift+Z', role: 'redo' },
|
label: 'Undo',
|
||||||
|
accelerator: 'CmdOrCtrl+Z',
|
||||||
|
click: () => mainWindow.webContents.send('undo')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Redo',
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||||
|
click: () => mainWindow.webContents.send('redo')
|
||||||
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
||||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
||||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
|
||||||
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
|
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Find & Replace',
|
label: 'Find & Replace',
|
||||||
accelerator: 'CmdOrCtrl+F',
|
accelerator: 'CmdOrCtrl+F',
|
||||||
click: () => mainWindow.webContents.send('toggle-find')
|
click: () => mainWindow.webContents.send('toggle-find')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -217,7 +287,7 @@ function createMenu() {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Toggle Preview',
|
label: 'Toggle Preview',
|
||||||
accelerator: 'CmdOrCtrl+P',
|
accelerator: 'CmdOrCtrl+Shift+P',
|
||||||
click: () => mainWindow.webContents.send('toggle-preview')
|
click: () => mainWindow.webContents.send('toggle-preview')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -227,7 +297,26 @@ function createMenu() {
|
|||||||
{ label: 'Dark', click: () => setTheme('dark') },
|
{ label: 'Dark', click: () => setTheme('dark') },
|
||||||
{ label: 'Solarized', click: () => setTheme('solarized') },
|
{ label: 'Solarized', click: () => setTheme('solarized') },
|
||||||
{ label: 'Monokai', click: () => setTheme('monokai') },
|
{ label: 'Monokai', click: () => setTheme('monokai') },
|
||||||
{ label: 'GitHub', click: () => setTheme('github') }
|
{ label: 'GitHub', click: () => setTheme('github') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Dracula', click: () => setTheme('dracula') },
|
||||||
|
{ label: 'Nord', click: () => setTheme('nord') },
|
||||||
|
{ label: 'One Dark', click: () => setTheme('onedark') },
|
||||||
|
{ label: 'Atom One Light', click: () => setTheme('atomonelight') },
|
||||||
|
{ 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') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -378,7 +467,7 @@ function createMenu() {
|
|||||||
type: 'info',
|
type: 'info',
|
||||||
title: 'About PanConverter',
|
title: 'About PanConverter',
|
||||||
message: 'PanConverter',
|
message: 'PanConverter',
|
||||||
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.7.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Comprehensive PDF Editor (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• 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• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• Multiple themes support\n• Undo/redo functionality\n• Live word count and statistics',
|
detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.7.8\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Modern glassmorphism UI with gradient backgrounds\n• Comprehensive PDF Editor (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• 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• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics',
|
||||||
buttons: ['OK']
|
buttons: ['OK']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -702,20 +791,17 @@ function performExportWithOptions(format, options) {
|
|||||||
|
|
||||||
function tryPdfFallback(inputFile, outputFile, engines, index, options, lastError) {
|
function tryPdfFallback(inputFile, outputFile, engines, index, options, lastError) {
|
||||||
if (index >= engines.length) {
|
if (index >= engines.length) {
|
||||||
dialog.showErrorBox('PDF Export Error',
|
// All Pandoc PDF engines failed, fallback to Electron's built-in PDF export
|
||||||
`Failed to export PDF with all available engines. Please ensure you have one of the following installed:\n` +
|
console.log('All Pandoc PDF engines failed, falling back to Electron PDF export');
|
||||||
`• XeLaTeX (recommended): sudo apt-get install texlive-xetex\n` +
|
exportToPDFElectron(outputFile);
|
||||||
`• PDFLaTeX: sudo apt-get install texlive-latex-base\n` +
|
|
||||||
`• LuaLaTeX: sudo apt-get install texlive-luatex\n\n` +
|
|
||||||
`Last error: ${lastError.message}`
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const engine = engines[index];
|
const engine = engines[index];
|
||||||
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -V geometry:${geometry} -o "${outputFile}"`;
|
let pandocCmd = `${getPandocPath()} "${inputFile}" --pdf-engine=${engine} -o "${outputFile}"`;
|
||||||
|
|
||||||
if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`;
|
// Add geometry if specified
|
||||||
|
if (options.geometry) pandocCmd = pandocCmd.replace(` -o `, ` -V geometry:"${options.geometry}" -o `);
|
||||||
|
|
||||||
// Add all other options
|
// Add all other options
|
||||||
if (options.template && options.template !== 'default') {
|
if (options.template && options.template !== 'default') {
|
||||||
@@ -1075,7 +1161,7 @@ ipcMain.on('save-current-file', (event, content) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('get-theme', (event) => {
|
ipcMain.on('get-theme', (event) => {
|
||||||
const theme = store.get('theme', 'light');
|
const theme = store.get('theme', 'github');
|
||||||
event.reply('theme-changed', theme);
|
event.reply('theme-changed', theme);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1084,10 +1170,36 @@ ipcMain.on('set-current-file', (event, filePath) => {
|
|||||||
currentFile = filePath;
|
currentFile = filePath;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle actual printing when renderer is ready
|
||||||
|
ipcMain.on('do-print', (event, { withStyles }) => {
|
||||||
|
if (mainWindow) {
|
||||||
|
// Renderer has already hidden UI, waited 300ms, and prepared the page
|
||||||
|
// Print immediately - DOM is fully rendered
|
||||||
|
mainWindow.webContents.print({
|
||||||
|
silent: false,
|
||||||
|
printBackground: withStyles,
|
||||||
|
color: true,
|
||||||
|
margin: { marginType: 'default' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Handle renderer ready for file association
|
// Handle renderer ready for file association
|
||||||
ipcMain.on('renderer-ready', (event) => {
|
ipcMain.on('renderer-ready', (event) => {
|
||||||
|
console.log('[MAIN] renderer-ready received, rendererReady was:', rendererReady);
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.executeJavaScript(`console.log('[MAIN->RENDERER] renderer-ready received, rendererReady was: ${rendererReady}')`);
|
||||||
|
}
|
||||||
rendererReady = true;
|
rendererReady = true;
|
||||||
|
console.log('[MAIN] app.pendingFile:', app.pendingFile);
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.executeJavaScript(`console.log('[MAIN->RENDERER] app.pendingFile: ${app.pendingFile}')`);
|
||||||
|
}
|
||||||
if (app.pendingFile) {
|
if (app.pendingFile) {
|
||||||
|
console.log('[MAIN] Opening pending file:', app.pendingFile);
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.executeJavaScript(`console.log('[MAIN->RENDERER] Opening pending file: ${app.pendingFile}')`);
|
||||||
|
}
|
||||||
openFileFromPath(app.pendingFile);
|
openFileFromPath(app.pendingFile);
|
||||||
app.pendingFile = null;
|
app.pendingFile = null;
|
||||||
}
|
}
|
||||||
@@ -1488,15 +1600,35 @@ app.whenReady().then(() => {
|
|||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
// Handle file association on app startup
|
// Handle file association on app startup
|
||||||
// Process all command line arguments except the first two (node and script path)
|
// In packaged apps, process.argv structure is different:
|
||||||
const fileArgs = process.argv.slice(2);
|
// Development: ['electron', 'app.js', 'file.md'] - need slice(2)
|
||||||
|
// Packaged: ['PanConverter.exe', 'file.md'] - need slice(1)
|
||||||
|
// We'll check all arguments after the executable
|
||||||
|
console.log('[MAIN] Full command line args:', JSON.stringify(process.argv));
|
||||||
|
console.log('[MAIN] App is packaged:', app.isPackaged);
|
||||||
|
|
||||||
|
// Start from index 1 (skip executable) and check each argument
|
||||||
|
const startIndex = app.isPackaged ? 1 : 2;
|
||||||
|
const fileArgs = process.argv.slice(startIndex);
|
||||||
|
console.log('[MAIN] File args to process (starting from index', startIndex + '):', fileArgs);
|
||||||
|
|
||||||
for (const arg of fileArgs) {
|
for (const arg of fileArgs) {
|
||||||
if ((arg.endsWith('.md') || arg.endsWith('.markdown')) && fs.existsSync(arg)) {
|
console.log('[MAIN] Checking arg:', arg);
|
||||||
// Store the file to open after window is ready
|
if ((arg.endsWith('.md') || arg.endsWith('.markdown'))) {
|
||||||
app.pendingFile = arg;
|
// Try to resolve the path (might be relative)
|
||||||
break;
|
const resolvedPath = path.isAbsolute(arg) ? arg : path.resolve(process.cwd(), arg);
|
||||||
|
console.log('[MAIN] Resolved path:', resolvedPath);
|
||||||
|
if (fs.existsSync(resolvedPath)) {
|
||||||
|
// Store the file to open after window is ready
|
||||||
|
console.log('[MAIN] Setting pendingFile to:', resolvedPath);
|
||||||
|
app.pendingFile = resolvedPath;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
console.log('[MAIN] File does not exist:', resolvedPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log('[MAIN] Final app.pendingFile:', app.pendingFile);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
@@ -1548,18 +1680,23 @@ app.on('open-file', (event, filePath) => {
|
|||||||
|
|
||||||
// Handle file opening from command line or file association
|
// Handle file opening from command line or file association
|
||||||
function openFileFromPath(filePath) {
|
function openFileFromPath(filePath) {
|
||||||
|
console.log('[MAIN] openFileFromPath called with:', filePath);
|
||||||
|
console.log('[MAIN] rendererReady:', rendererReady, 'mainWindow exists:', !!mainWindow);
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
currentFile = filePath;
|
currentFile = filePath;
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
if (mainWindow && mainWindow.webContents) {
|
console.log('[MAIN] File read successfully, content length:', content.length);
|
||||||
// Wait for the renderer to be ready (TabManager initialized) before sending file data
|
if (mainWindow && mainWindow.webContents && rendererReady) {
|
||||||
if (rendererReady) {
|
// Send file immediately - renderer-ready means UI is initialized
|
||||||
mainWindow.webContents.send('file-opened', { path: filePath, content });
|
console.log('[MAIN] Sending file-opened to renderer');
|
||||||
} else {
|
mainWindow.webContents.send('file-opened', { path: filePath, content });
|
||||||
// Store file to open after renderer is ready
|
} else {
|
||||||
app.pendingFile = filePath;
|
// Store file to open after renderer is ready
|
||||||
}
|
console.log('[MAIN] Storing as pending file');
|
||||||
|
app.pendingFile = filePath;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.error('[MAIN] File does not exist:', filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+97
-4
@@ -949,6 +949,7 @@ class TabManager {
|
|||||||
|
|
||||||
// File operations
|
// File operations
|
||||||
openFile(filePath, content) {
|
openFile(filePath, content) {
|
||||||
|
console.log('openFile called with:', filePath, 'content length:', content.length);
|
||||||
let tab = this.tabs.get(this.activeTabId);
|
let tab = this.tabs.get(this.activeTabId);
|
||||||
|
|
||||||
// Handle both forward and back slashes for cross-platform compatibility
|
// Handle both forward and back slashes for cross-platform compatibility
|
||||||
@@ -956,13 +957,24 @@ class TabManager {
|
|||||||
|
|
||||||
// If current tab is empty and untitled, reuse it
|
// If current tab is empty and untitled, reuse it
|
||||||
if (!tab.filePath && !tab.isDirty && tab.content === '') {
|
if (!tab.filePath && !tab.isDirty && tab.content === '') {
|
||||||
|
console.log('Reusing current tab');
|
||||||
tab.filePath = filePath;
|
tab.filePath = filePath;
|
||||||
tab.title = fileName;
|
tab.title = fileName;
|
||||||
tab.content = content;
|
tab.content = content;
|
||||||
tab.originalContent = content;
|
tab.originalContent = content;
|
||||||
tab.isDirty = false;
|
tab.isDirty = false;
|
||||||
|
|
||||||
|
// Update the editor and preview
|
||||||
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
|
if (editor) {
|
||||||
|
editor.value = content;
|
||||||
|
// Update preview after editor is updated
|
||||||
|
this.updatePreview(this.activeTabId);
|
||||||
|
this.updateWordCount();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create new tab for the file
|
// Create new tab for the file
|
||||||
|
console.log('Creating new tab for file');
|
||||||
this.createNewTab();
|
this.createNewTab();
|
||||||
tab = this.tabs.get(this.activeTabId);
|
tab = this.tabs.get(this.activeTabId);
|
||||||
tab.filePath = filePath;
|
tab.filePath = filePath;
|
||||||
@@ -970,12 +982,26 @@ class TabManager {
|
|||||||
tab.content = content;
|
tab.content = content;
|
||||||
tab.originalContent = content;
|
tab.originalContent = content;
|
||||||
tab.isDirty = false;
|
tab.isDirty = false;
|
||||||
}
|
|
||||||
|
|
||||||
this.restoreTabState(this.activeTabId);
|
// Wait a moment for the DOM to update, then set content
|
||||||
|
setTimeout(() => {
|
||||||
|
const editor = document.getElementById(`editor-${this.activeTabId}`);
|
||||||
|
if (editor) {
|
||||||
|
editor.value = content;
|
||||||
|
this.updatePreview(this.activeTabId);
|
||||||
|
this.updateWordCount();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
this.startAutoSave();
|
this.startAutoSave();
|
||||||
this.addToRecentFiles(filePath);
|
this.addToRecentFiles(filePath);
|
||||||
this.updateTabBar();
|
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentContent() {
|
getCurrentContent() {
|
||||||
@@ -1016,8 +1042,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Request current theme
|
// Request current theme
|
||||||
ipcRenderer.send('get-theme');
|
ipcRenderer.send('get-theme');
|
||||||
|
|
||||||
// Signal that renderer is ready for file operations
|
// Also send renderer-ready immediately as backup
|
||||||
ipcRenderer.send('renderer-ready');
|
// This ensures we don't get stuck waiting for theme-changed
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Backup renderer-ready timeout triggered');
|
||||||
|
ipcRenderer.send('renderer-ready');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// Set up auto-save interval
|
// Set up auto-save interval
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
@@ -1036,8 +1066,11 @@ ipcRenderer.on('file-new', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('file-opened', (event, data) => {
|
ipcRenderer.on('file-opened', (event, data) => {
|
||||||
|
console.log('[RENDERER] file-opened received:', data.path, 'content length:', data.content.length);
|
||||||
if (tabManager) {
|
if (tabManager) {
|
||||||
tabManager.openFile(data.path, data.content);
|
tabManager.openFile(data.path, data.content);
|
||||||
|
} else {
|
||||||
|
console.error('[RENDERER] tabManager not initialized!');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1075,7 +1108,28 @@ ipcRenderer.on('toggle-find', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('theme-changed', (event, theme) => {
|
ipcRenderer.on('theme-changed', (event, theme) => {
|
||||||
|
console.log('[RENDERER] Theme changed to:', theme);
|
||||||
document.body.className = `theme-${theme}`;
|
document.body.className = `theme-${theme}`;
|
||||||
|
|
||||||
|
// After theme is applied, wait for next frame then signal renderer is ready
|
||||||
|
// This ensures complete UI initialization before files are opened
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
console.log('[RENDERER] Sending renderer-ready from theme-changed');
|
||||||
|
ipcRenderer.send('renderer-ready');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Undo/Redo handlers
|
||||||
|
ipcRenderer.on('undo', () => {
|
||||||
|
if (tabManager) {
|
||||||
|
tabManager.undo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('redo', () => {
|
||||||
|
if (tabManager) {
|
||||||
|
tabManager.redo();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Font size adjustment
|
// Font size adjustment
|
||||||
@@ -1110,6 +1164,45 @@ ipcRenderer.on('adjust-font-size', (event, action) => {
|
|||||||
updateFontSizes(currentFontSize);
|
updateFontSizes(currentFontSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Print preview request handlers - handle printing directly
|
||||||
|
ipcRenderer.on('print-preview', () => {
|
||||||
|
console.log('[RENDERER] print-preview received');
|
||||||
|
handlePrintPreview(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('print-preview-styled', () => {
|
||||||
|
console.log('[RENDERER] print-preview-styled received');
|
||||||
|
handlePrintPreview(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
function handlePrintPreview(withStyles) {
|
||||||
|
const activeTabId = tabManager ? tabManager.activeTabId : 1;
|
||||||
|
const previewContent = document.getElementById(`preview-${activeTabId}`);
|
||||||
|
|
||||||
|
if (!previewContent || !previewContent.innerHTML.trim()) {
|
||||||
|
alert('Nothing to print. Please create or open a document and ensure the preview is visible.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add print style classes to body for styling control
|
||||||
|
// The @media print CSS will handle hiding toolbar/tabs automatically
|
||||||
|
if (!withStyles) {
|
||||||
|
document.body.classList.add('printing-no-styles');
|
||||||
|
}
|
||||||
|
document.body.classList.add('printing');
|
||||||
|
|
||||||
|
// Give browser time to apply classes before printing
|
||||||
|
setTimeout(() => {
|
||||||
|
// Tell main process to print
|
||||||
|
ipcRenderer.send('do-print', { withStyles });
|
||||||
|
|
||||||
|
// Restore classes after print dialog appears
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.classList.remove('printing', 'printing-no-styles');
|
||||||
|
}, 1000);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
// Export Dialog functionality
|
// Export Dialog functionality
|
||||||
let currentExportFormat = null;
|
let currentExportFormat = null;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,825 @@
|
|||||||
|
/* ========================================
|
||||||
|
PanConverter v1.7.1 - Modern UI
|
||||||
|
Glassmorphism, Gradients & Animations
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
/* Modern Color Palette */
|
||||||
|
:root {
|
||||||
|
/* Primary Brand Colors - Purple-Blue Gradient */
|
||||||
|
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
--primary-light: #8b9aff;
|
||||||
|
--primary-dark: #5661b3;
|
||||||
|
|
||||||
|
/* Accent Colors */
|
||||||
|
--accent-green: #10b981;
|
||||||
|
--accent-blue: #3b82f6;
|
||||||
|
--accent-purple: #8b5cf6;
|
||||||
|
--accent-pink: #ec4899;
|
||||||
|
|
||||||
|
/* Neutral Colors */
|
||||||
|
--gray-50: #f9fafb;
|
||||||
|
--gray-100: #f3f4f6;
|
||||||
|
--gray-200: #e5e7eb;
|
||||||
|
--gray-300: #d1d5db;
|
||||||
|
--gray-400: #9ca3af;
|
||||||
|
--gray-500: #6b7280;
|
||||||
|
--gray-600: #4b5563;
|
||||||
|
--gray-700: #374151;
|
||||||
|
--gray-800: #1f2937;
|
||||||
|
--gray-900: #111827;
|
||||||
|
|
||||||
|
/* Glassmorphism */
|
||||||
|
--glass-bg: rgba(255, 255, 255, 0.7);
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.18);
|
||||||
|
--glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
|
||||||
|
|
||||||
|
/* Effects */
|
||||||
|
--blur-amount: 10px;
|
||||||
|
--transition-speed: 0.3s;
|
||||||
|
--transition-ease: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset & Base */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradientShift {
|
||||||
|
0% { background-position: 0% 50%; }
|
||||||
|
50% { background-position: 100% 50%; }
|
||||||
|
100% { background-position: 0% 50%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 12px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glass Container for main content */
|
||||||
|
.main-content-glass {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(var(--blur-amount));
|
||||||
|
-webkit-backdrop-filter: blur(var(--blur-amount));
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: var(--glass-shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab Bar - Modern Glassmorphism */
|
||||||
|
.tab-bar {
|
||||||
|
display: flex;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 120px;
|
||||||
|
transition: all var(--transition-speed) var(--transition-ease);
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-title {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-close {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: currentColor;
|
||||||
|
transition: all var(--transition-speed) var(--transition-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-close:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-tab-button {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--gray-700);
|
||||||
|
transition: all var(--transition-speed) var(--transition-ease);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-tab-button:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar - Modern Gradient Buttons */
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-speed) var(--transition-ease);
|
||||||
|
color: var(--gray-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button:active {
|
||||||
|
transform: translateY(0) scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-separator {
|
||||||
|
width: 1px;
|
||||||
|
height: 24px;
|
||||||
|
background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.4), transparent);
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab Content */
|
||||||
|
.tab-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content:not(.active) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor Container */
|
||||||
|
.editor-container {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 0 0 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane:first-child {
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
.pane::-webkit-scrollbar,
|
||||||
|
.editor-textarea::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane::-webkit-scrollbar-track,
|
||||||
|
.editor-textarea::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane::-webkit-scrollbar-thumb,
|
||||||
|
.editor-textarea::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 24px;
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco, 'Courier New', monospace;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.7;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane:last-child {
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 24px 32px;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Bar - Glassmorphism */
|
||||||
|
.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;
|
||||||
|
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;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 28px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
border-bottom: 3px solid;
|
||||||
|
border-image: var(--primary-gradient) 1;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content h2 {
|
||||||
|
font-size: 2em;
|
||||||
|
border-bottom: 2px solid rgba(102, 126, 234, 0.3);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content p {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content code {
|
||||||
|
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);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content pre {
|
||||||
|
padding: 20px;
|
||||||
|
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);
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content pre code {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content blockquote {
|
||||||
|
padding: 16px 20px;
|
||||||
|
color: var(--gray-700);
|
||||||
|
border-left: 4px solid;
|
||||||
|
border-image: var(--primary-gradient) 1;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
position: relative;
|
||||||
|
transition: color var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content a::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -2px;
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
transition: width var(--transition-speed) var(--transition-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content a:hover {
|
||||||
|
color: #764ba2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content a:hover::after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content table {
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content table th,
|
||||||
|
.preview-content table td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid rgba(102, 126, 234, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content table th {
|
||||||
|
font-weight: 600;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content table tr:nth-child(even) {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modern Dialogs */
|
||||||
|
.export-dialog,
|
||||||
|
.batch-dialog,
|
||||||
|
.find-dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: fadeIn 0.3s var(--transition-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog.hidden,
|
||||||
|
.batch-dialog.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-content,
|
||||||
|
.batch-dialog-content {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 650px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
animation: slideUpFade 0.4s var(--transition-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUpFade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-header,
|
||||||
|
.batch-dialog-header {
|
||||||
|
display: flex;
|
||||||
|
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-radius: 20px 20px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-header h3,
|
||||||
|
.batch-dialog-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-body,
|
||||||
|
.batch-dialog-body {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-section,
|
||||||
|
.batch-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-section label,
|
||||||
|
.batch-section label {
|
||||||
|
display: block;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--gray-700);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-section select,
|
||||||
|
.export-section input,
|
||||||
|
.batch-section select,
|
||||||
|
.batch-section input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 2px solid rgba(102, 126, 234, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
transition: all var(--transition-speed) var(--transition-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-section select:focus,
|
||||||
|
.export-section input:focus,
|
||||||
|
.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);
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modern Buttons */
|
||||||
|
.export-dialog-footer,
|
||||||
|
.batch-dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
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-radius: 0 0 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-footer button,
|
||||||
|
.batch-dialog-footer button,
|
||||||
|
.folder-input-group button {
|
||||||
|
padding: 10px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all var(--transition-speed) var(--transition-ease);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-footer button.primary,
|
||||||
|
.batch-dialog-footer button.primary {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-footer button:not(.primary),
|
||||||
|
.batch-dialog-footer button:not(.primary),
|
||||||
|
.folder-input-group button {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
color: var(--gray-700);
|
||||||
|
border: 2px solid rgba(102, 126, 234, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-footer button:not(.primary):hover,
|
||||||
|
.batch-dialog-footer button:not(.primary):hover,
|
||||||
|
.folder-input-group button:hover {
|
||||||
|
background: white;
|
||||||
|
border-color: #667eea;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find Dialog - Floating Modern */
|
||||||
|
.find-dialog {
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
right: 24px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1000;
|
||||||
|
min-width: 420px;
|
||||||
|
padding: 16px;
|
||||||
|
animation: slideInRight 0.3s var(--transition-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-dialog.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Bars - Modern Gradient */
|
||||||
|
.progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: rgba(102, 126, 234, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
transition: width 0.4s var(--transition-ease);
|
||||||
|
width: 0%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
transparent,
|
||||||
|
rgba(255, 255, 255, 0.3),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Auto-save Indicator - Modern */
|
||||||
|
.auto-save-indicator {
|
||||||
|
position: fixed;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
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);
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-save-indicator::before {
|
||||||
|
content: '✓';
|
||||||
|
display: inline-block;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background: white;
|
||||||
|
color: #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced Statistics - Modern */
|
||||||
|
.enhanced-stats {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
padding: 2px 0;
|
||||||
|
margin-left: 16px;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Line Numbers - Modern */
|
||||||
|
.line-numbers {
|
||||||
|
min-width: 50px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border-right: 1px solid rgba(102, 126, 234, 0.2);
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--gray-500);
|
||||||
|
text-align: right;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations & Transitions */
|
||||||
|
.toolbar button,
|
||||||
|
.tab,
|
||||||
|
.new-tab-button,
|
||||||
|
.export-dialog-footer button,
|
||||||
|
.batch-dialog-footer button {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button::before,
|
||||||
|
.tab::before,
|
||||||
|
.new-tab-button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: width 0.6s, height 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button:hover::before,
|
||||||
|
.tab:hover::before,
|
||||||
|
.new-tab-button:hover::before {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dialog-content,
|
||||||
|
.batch-dialog-content {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-dialog {
|
||||||
|
right: 12px;
|
||||||
|
min-width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide default elements */
|
||||||
|
.pane.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane.full-width {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
+1438
-4
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
# Test Double-Click File Opening
|
||||||
|
|
||||||
|
This is a test file to verify double-click functionality.
|
||||||
|
|
||||||
|
## Features to Test
|
||||||
|
- File should load automatically
|
||||||
|
- Content should display in editor
|
||||||
|
- Tab should show filename
|
||||||
|
- Preview should render markdown
|
||||||
|
|
||||||
|
**This text should be bold**
|
||||||
|
*This text should be italic*
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Test File Opening
|
||||||
|
|
||||||
|
This is a test file to verify that double-click file opening works correctly.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Bold text**
|
||||||
|
- *Italic text*
|
||||||
|
- `Code`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
console.log("Hello World");
|
||||||
|
```
|
||||||
|
|
||||||
|
This should appear when you double-click to open the file!
|
||||||
Reference in New Issue
Block a user