mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c5642b15d | ||
|
|
337a6d67a0 | ||
|
|
e5eb1e151a | ||
|
|
d58a4349b6 | ||
|
|
0eadd283e4 | ||
|
|
046d31e9f0 | ||
|
|
004af5575b | ||
|
|
8e6ff466e1 |
@@ -1,103 +0,0 @@
|
|||||||
# CLAUDE.md — MarkdownConverter (master)
|
|
||||||
|
|
||||||
> General code-quality, JavaScript, git, security, and testing standards are in the **global CLAUDE.md**. This file holds project- and branch-specific notes.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
Electron desktop app for Markdown editing and universal file conversion powered by Pandoc. Cross-platform (Win/macOS/Linux). Features: multi-tab editor with live preview, 25+ themes, PDF viewer/editor (merge/split/compress/rotate/watermark/password), export to 20+ formats (PDF/DOCX/ODT/EPUB/HTML/LaTeX/RTF/PPTX), batch conversion, syntax highlighting, diagram support (Mermaid), Git integration, and a plugin system.
|
|
||||||
|
|
||||||
- **Version:** 4.4.3
|
|
||||||
- **License:** MIT
|
|
||||||
- **App ID:** `com.concreteinfo.markdownconverter`
|
|
||||||
|
|
||||||
## Branch Specifics
|
|
||||||
|
|
||||||
This is the **primary/release branch** — a vanilla JavaScript Electron app with no bundler or framework in the renderer. The renderer is a single large `renderer.js` (5,300+ lines) loaded directly via `src/index.html`. All UI is hand-rolled DOM manipulation.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Main Process (`src/main.js` — 4,260 lines)
|
|
||||||
Monolithic main process file. Contains all IPC handlers, Pandoc invocation, file operations, menu definitions (600+ lines), and window lifecycle. Key modules extracted:
|
|
||||||
- `src/main/PDFOperations.js` — PDF manipulation via `pdf-lib` (merge, split, compress, rotate, delete, reorder, watermark, encrypt, decrypt, permissions)
|
|
||||||
- `src/main/GitOperations.js` — Git status/stage/commit/log via `simple-git`
|
|
||||||
|
|
||||||
### Renderer (`src/renderer.js` — 5,361 lines)
|
|
||||||
Vanilla JS, no framework. Directly manipulates DOM. Loads CodeMirror 6 via `src/editor/codemirror-setup.js`. Uses `marked` + `highlight.js` + `DOMPurify` + `mermaid` for rendering. Lazy-loads sidebar panels, REPL, command palette, zen mode.
|
|
||||||
|
|
||||||
### Preload (`src/preload.js` — 448 lines)
|
|
||||||
Exists as IPC bridge, but **`contextIsolation: false` and `nodeIntegration: true`** — the renderer has full Node access. Preload is effectively a thin passthrough.
|
|
||||||
|
|
||||||
### Security Model
|
|
||||||
- `contextIsolation: false` + `nodeIntegration: true` (legacy; the react-electron branch fixes this)
|
|
||||||
- Pandoc invoked via `execFile` (not `exec`) to prevent shell injection
|
|
||||||
- Path traversal protection: `validatePath()`, `resolveWritablePath()`, blocks sensitive system dirs
|
|
||||||
- Permission handler only allows `clipboard-read`/`clipboard-write`
|
|
||||||
- Rate limiter on conversions (2-second minimum interval)
|
|
||||||
- File size limit: 50MB
|
|
||||||
- Error message sanitization strips absolute paths
|
|
||||||
|
|
||||||
### Plugin System (`src/plugins/`)
|
|
||||||
Manifest-based discovery (`manifest.json`). Built-in `writing-studio` plugin with sprint/goal/snapshot management. Plugin API exposed via `src/plugins/plugin-api.js`.
|
|
||||||
|
|
||||||
### Settings
|
|
||||||
Custom JSON file store at `<userData>/settings.json` (NOT `electron-store` despite the dependency). Recent files at `<userData>/recent-files.json`.
|
|
||||||
|
|
||||||
## System Dependencies
|
|
||||||
|
|
||||||
| Dependency | Required | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| **Node.js** | >= 20 | Electron 41 bundles Node 20.x |
|
|
||||||
| **Pandoc** | Yes (for exports) | Downloaded to `bin/<platform>/pandoc` via `scripts/download-tools.js` (v3.9.0.2). Falls back to system PATH. Must be present for DOCX/ODT/EPUB/LaTeX/PPTX export. |
|
|
||||||
| **FFmpeg** | Bundled | `ffmpeg-static` npm package; `asarUnpacked` for packaged builds |
|
|
||||||
| **MiKTeX / TeX Live** | Optional | For LaTeX PDF export; MiKTeX PATH injected on Windows automatically |
|
|
||||||
| **ImageMagick** | Optional | Linux image conversion; listed as deb dependency |
|
|
||||||
| **LibreOffice** | Optional | Enhanced document conversion; listed as deb dependency |
|
|
||||||
|
|
||||||
## Development Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm start # Launch Electron app (dev mode)
|
|
||||||
npm test # Jest test suite
|
|
||||||
npm test:watch # Jest in watch mode
|
|
||||||
npm test:coverage # Jest with coverage report
|
|
||||||
npm run lint # ESLint check (src + tests)
|
|
||||||
npm run lint:fix # ESLint auto-fix
|
|
||||||
npm run format # Prettier write
|
|
||||||
npm run format:check # Prettier check only
|
|
||||||
npm run download-tools # Download Pandoc binaries to bin/
|
|
||||||
npm run generate-icons # Generate app icons via sharp
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build & Package
|
|
||||||
|
|
||||||
**Tool:** `electron-builder` (v26.0.12), config inline in `package.json` (no separate config file).
|
|
||||||
|
|
||||||
| Target | Platforms |
|
|
||||||
|---|---|
|
|
||||||
| `npm run build` | electron-builder (default platform) |
|
|
||||||
| `npm run build:win` | Windows: NSIS installer + portable + zip (x64) |
|
|
||||||
| `npm run build:mac` | macOS: default dmg |
|
|
||||||
| `npm run build:linux` | Linux: deb + AppImage + snap |
|
|
||||||
| `npm run dist` | Build without publish |
|
|
||||||
| `npm run dist:all` | Build for all platforms |
|
|
||||||
|
|
||||||
**Bundled with builds:** Pandoc binary per platform. FFmpeg via `ffmpeg-static` (asarUnpacked). NSIS installer uses custom script at `scripts/nsis-installer.nsh`.
|
|
||||||
|
|
||||||
**Output:** `dist/` directory.
|
|
||||||
|
|
||||||
**CI:** GitHub Actions workflows in `.github/workflows/` (ci.yml, release.yml).
|
|
||||||
|
|
||||||
## Project Conventions / Gotchas
|
|
||||||
|
|
||||||
- **No bundler/transpilation.** The app uses vanilla CommonJS JavaScript. `src/main.js` is loaded directly by Electron. No webpack, no Vite, no TypeScript, no Babel.
|
|
||||||
- **Monolithic files.** `main.js` (4,260 lines) and `renderer.js` (5,361 lines) contain most logic. Not ideal but is the current state of this branch.
|
|
||||||
- **CodeMirror 6** for the editor, configured in `src/editor/codemirror-setup.js`.
|
|
||||||
- **PDF rendering** uses `pdfjs-dist`; **PDF manipulation** uses `pdf-lib` in the main process.
|
|
||||||
- **Renderer security is weak** — full Node access in renderer. Do NOT introduce new privileged renderer code without understanding this.
|
|
||||||
- **Pandoc is external.** Must be installed separately or downloaded via `npm run download-tools`. HTML and built-in PDF export work without Pandoc; other formats require it.
|
|
||||||
- **PDF export fallback chain:** xelatex -> pdflatex -> lualatex -> Electron built-in `printToPDF()`.
|
|
||||||
- **ESLint flat config** (`eslint.config.js`) with ECMAScript 2022. Prettier with 2-space indent, single quotes, semicolons, 100-char width.
|
|
||||||
- **Tests:** Jest with jsdom environment, 15% coverage threshold. 24 test files in `tests/`.
|
|
||||||
- **File associations:** `.md`, `.markdown`, `.pdf` registered at install.
|
|
||||||
- **Single instance lock** enforced via `app.requestSingleInstanceLock()`.
|
|
||||||
- **Adapters layer** (`src/adapters/`) abstracts file system operations for potential future non-Electron targets.
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# Building MarkdownConverter with Tauri 2.x
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Node.js 18+** and npm
|
||||||
|
2. **Rust 1.75+** — install via [rustup](https://rustup.rs/)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Rust
|
||||||
|
winget install Rust.Rustup # Windows
|
||||||
|
# OR
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # macOS/Linux
|
||||||
|
|
||||||
|
# Set stable as default
|
||||||
|
rustup default stable
|
||||||
|
|
||||||
|
# Install Tauri CLI (alternative to npm package)
|
||||||
|
cargo install tauri-cli --locked
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Run frontend dev server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# In another terminal, run Tauri dev
|
||||||
|
npm run tauri:dev
|
||||||
|
|
||||||
|
# Or use Tauri CLI directly (if installed globally via cargo)
|
||||||
|
cargo tauri dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the frontend first
|
||||||
|
npm run build:frontend
|
||||||
|
|
||||||
|
# Then build Tauri app
|
||||||
|
npm run tauri:build
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# Windows: src-tauri/target/release/markdown-converter.exe
|
||||||
|
# Installer: dist/MarkdownConverter-Setup-4.3.0.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Rust not found
|
||||||
|
Ensure Rust is in PATH: `rustc --version`
|
||||||
|
|
||||||
|
### WebView2 not installed (Windows)
|
||||||
|
Download from https://developer.microsoft.com/en-us/microsoft-edge/webview2/
|
||||||
|
|
||||||
|
### pandoc not found
|
||||||
|
Pandoc must be installed and in PATH. Download from https://pandoc.org/installing.html
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src-tauri/
|
||||||
|
src/
|
||||||
|
main.rs # Binary entry point
|
||||||
|
lib.rs # Tauri app setup + command registration
|
||||||
|
commands/ # Rust commands (file, git, pdf, export, etc.)
|
||||||
|
menu.rs # Native menu
|
||||||
|
tray.rs # System tray
|
||||||
|
pdf_ops.rs # PDF processing
|
||||||
|
plugin_manager.rs
|
||||||
|
error.rs
|
||||||
|
Cargo.toml
|
||||||
|
tauri.conf.json
|
||||||
|
capabilities/default.json
|
||||||
|
icons/
|
||||||
|
src/
|
||||||
|
renderer.js # Main renderer (updated for Tauri invoke/listen)
|
||||||
|
tauri-commands.js # Tauri command bridge
|
||||||
|
index.html # Main HTML (copied to dist/index.html)
|
||||||
|
dist/
|
||||||
|
index.html # Built frontend output
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Differences from Electron
|
||||||
|
|
||||||
|
| Aspect | Electron | Tauri |
|
||||||
|
|--------|----------|-------|
|
||||||
|
| IPC | ipcRenderer.invoke() | invoke() from @tauri-apps/api |
|
||||||
|
| Events | ipcRenderer.on() | listen() from @tauri-apps/api |
|
||||||
|
| File access | Node.js fs module | Rust std::fs or tauri-plugin-fs |
|
||||||
|
| Native menus | Electron Menu API | tauri menu API |
|
||||||
|
| Settings | electron-store | tauri-plugin-store |
|
||||||
|
| Binary size | ~150MB | ~10MB |
|
||||||
@@ -75,7 +75,6 @@ module.exports = [
|
|||||||
jest: 'readonly',
|
jest: 'readonly',
|
||||||
describe: 'readonly',
|
describe: 'readonly',
|
||||||
test: 'readonly',
|
test: 'readonly',
|
||||||
it: 'readonly',
|
|
||||||
expect: 'readonly',
|
expect: 'readonly',
|
||||||
beforeEach: 'readonly',
|
beforeEach: 'readonly',
|
||||||
afterEach: 'readonly',
|
afterEach: 'readonly',
|
||||||
|
|||||||
Generated
+501
-153
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.4.1",
|
"version": "4.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.4.1",
|
"version": "4.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.20.1",
|
"@codemirror/autocomplete": "^6.20.1",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"core-util-is": "^1.0.3",
|
"core-util-is": "^1.0.3",
|
||||||
"docx": "^9.6.0",
|
"docx": "^9.6.0",
|
||||||
"docx4js": "^2.0.1",
|
"docx4js": "^3.3.0",
|
||||||
"dompurify": "^3.3.1",
|
"dompurify": "^3.3.1",
|
||||||
"electron-store": "^10.1.0",
|
"electron-store": "^10.1.0",
|
||||||
"ffmpeg-static": "^5.3.0",
|
"ffmpeg-static": "^5.3.0",
|
||||||
@@ -619,12 +619,45 @@
|
|||||||
"integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==",
|
"integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@chevrotain/cst-dts-gen": {
|
||||||
|
"version": "11.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz",
|
||||||
|
"integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@chevrotain/gast": "11.1.2",
|
||||||
|
"@chevrotain/types": "11.1.2",
|
||||||
|
"lodash-es": "4.17.23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@chevrotain/gast": {
|
||||||
|
"version": "11.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz",
|
||||||
|
"integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@chevrotain/types": "11.1.2",
|
||||||
|
"lodash-es": "4.17.23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@chevrotain/regexp-to-ast": {
|
||||||
|
"version": "11.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz",
|
||||||
|
"integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@chevrotain/types": {
|
"node_modules/@chevrotain/types": {
|
||||||
"version": "11.1.2",
|
"version": "11.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz",
|
||||||
"integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==",
|
"integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/@chevrotain/utils": {
|
||||||
|
"version": "11.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz",
|
||||||
|
"integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@codemirror/autocomplete": {
|
"node_modules/@codemirror/autocomplete": {
|
||||||
"version": "6.20.1",
|
"version": "6.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz",
|
||||||
@@ -975,9 +1008,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@electron/asar/node_modules/brace-expansion": {
|
"node_modules/@electron/asar/node_modules/brace-expansion": {
|
||||||
"version": "1.1.14",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1279,9 +1312,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@electron/universal/node_modules/brace-expansion": {
|
"node_modules/@electron/universal/node_modules/brace-expansion": {
|
||||||
"version": "2.1.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1365,9 +1398,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/windows-sign/node_modules/fs-extra": {
|
"node_modules/@electron/windows-sign/node_modules/fs-extra": {
|
||||||
"version": "11.3.5",
|
"version": "11.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz",
|
||||||
"integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
|
"integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -1382,9 +1415,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/windows-sign/node_modules/jsonfile": {
|
"node_modules/@electron/windows-sign/node_modules/jsonfile": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||||
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
|
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -1514,9 +1547,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
||||||
"version": "1.1.14",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1595,9 +1628,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||||
"version": "1.1.14",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2744,9 +2777,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jest/reporters/node_modules/brace-expansion": {
|
"node_modules/@jest/reporters/node_modules/brace-expansion": {
|
||||||
"version": "2.1.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3151,12 +3184,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@mermaid-js/parser": {
|
"node_modules/@mermaid-js/parser": {
|
||||||
"version": "1.1.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.1.tgz",
|
||||||
"integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==",
|
"integrity": "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chevrotain/types": "~11.1.1"
|
"langium": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas": {
|
"node_modules/@napi-rs/canvas": {
|
||||||
@@ -3526,21 +3559,6 @@
|
|||||||
"url": "https://opencollective.com/pkgr"
|
"url": "https://opencollective.com/pkgr"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@simple-git/args-pathspec": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@simple-git/argv-parser": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@simple-git/args-pathspec": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.34.48",
|
"version": "0.34.48",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
|
||||||
@@ -4443,9 +4461,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@xmldom/xmldom": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.8.13",
|
"version": "0.8.11",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
||||||
"integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
|
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5114,6 +5132,12 @@
|
|||||||
"readable-stream": "^3.4.0"
|
"readable-stream": "^3.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/boolean": {
|
"node_modules/boolean": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
|
||||||
@@ -5124,9 +5148,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "5.0.6",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5339,9 +5363,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cacache/node_modules/brace-expansion": {
|
"node_modules/cacache/node_modules/brace-expansion": {
|
||||||
"version": "2.1.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5503,6 +5527,22 @@
|
|||||||
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
|
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/cfb": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cfb/-/cfb-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-cP+4A0tTqtyza5gJwNwDetZ8FPjl0gPLE7mIxGKyUzOS6HkM23WaAWW/l3t7jIQSMqVXroa09Ey0lo7gV8LNxw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "~2.11.0",
|
||||||
|
"printj": "~1.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"cfb": "bin/cfb.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@@ -5530,6 +5570,59 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cheerio": {
|
||||||
|
"version": "0.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz",
|
||||||
|
"integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-select": "~1.2.0",
|
||||||
|
"dom-serializer": "~0.1.0",
|
||||||
|
"entities": "~1.1.1",
|
||||||
|
"htmlparser2": "^3.9.1",
|
||||||
|
"lodash.assignin": "^4.0.9",
|
||||||
|
"lodash.bind": "^4.1.4",
|
||||||
|
"lodash.defaults": "^4.0.1",
|
||||||
|
"lodash.filter": "^4.4.0",
|
||||||
|
"lodash.flatten": "^4.2.0",
|
||||||
|
"lodash.foreach": "^4.3.0",
|
||||||
|
"lodash.map": "^4.4.0",
|
||||||
|
"lodash.merge": "^4.4.0",
|
||||||
|
"lodash.pick": "^4.2.1",
|
||||||
|
"lodash.reduce": "^4.4.0",
|
||||||
|
"lodash.reject": "^4.4.0",
|
||||||
|
"lodash.some": "^4.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chevrotain": {
|
||||||
|
"version": "11.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz",
|
||||||
|
"integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@chevrotain/cst-dts-gen": "11.1.2",
|
||||||
|
"@chevrotain/gast": "11.1.2",
|
||||||
|
"@chevrotain/regexp-to-ast": "11.1.2",
|
||||||
|
"@chevrotain/types": "11.1.2",
|
||||||
|
"@chevrotain/utils": "11.1.2",
|
||||||
|
"lodash-es": "4.17.23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chevrotain-allstar": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash-es": "^4.17.21"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"chevrotain": "^11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chownr": {
|
"node_modules/chownr": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||||
@@ -5684,6 +5777,41 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/color": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^1.9.3",
|
||||||
|
"color-string": "^1.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/color-string": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "^1.0.0",
|
||||||
|
"simple-swizzle": "^0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -5698,16 +5826,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "9.5.0",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
|
||||||
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
|
||||||
"dev": true,
|
"license": "MIT"
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": "^12.20.0 || >=14"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/compare-version": {
|
"node_modules/compare-version": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
@@ -5924,6 +6046,27 @@
|
|||||||
"utrie": "^1.0.2"
|
"utrie": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==",
|
||||||
|
"license": "BSD-like",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "~1.0.0",
|
||||||
|
"css-what": "2.1",
|
||||||
|
"domutils": "1.5.1",
|
||||||
|
"nth-check": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssstyle": {
|
"node_modules/cssstyle": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
|
||||||
@@ -6719,9 +6862,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dir-compare/node_modules/brace-expansion": {
|
"node_modules/dir-compare/node_modules/brace-expansion": {
|
||||||
"version": "1.1.14",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6842,14 +6985,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/docx4js": {
|
"node_modules/docx4js": {
|
||||||
"version": "2.0.1",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/docx4js/-/docx4js-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/docx4js/-/docx4js-3.3.0.tgz",
|
||||||
"integrity": "sha512-wmo3wCPkz4bTebRLYRBolU2SOmZHK1sZKiz/L96aHaqRhPD79zF8RzUxOveml4k0Hgm2kbPoulWklta6OMeKTQ==",
|
"integrity": "sha512-n+EGNivVW1IqUjkHDCLb7/w2mW47/23PqTbQN0V++WaCyYYMwjAKsDdhUt1TMB67/v5KQS4abkxab1uu4zCtIQ==",
|
||||||
"license": "GPL",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jszip": "^2.6.0",
|
"cfb": "^0.12.1",
|
||||||
"sax": "^1.2.1",
|
"cheerio": "^0.22.0",
|
||||||
"xml2js": "^0.4.17"
|
"color": "^3.1.0",
|
||||||
|
"htmlparser2": "^3.9.2",
|
||||||
|
"jszip": "^2.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dom-accessibility-api": {
|
"node_modules/dom-accessibility-api": {
|
||||||
@@ -6859,15 +7004,49 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^1.3.0",
|
||||||
|
"entities": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.4.5",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
|
||||||
"integrity": "sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==",
|
"integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "0",
|
||||||
|
"domelementtype": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dot-prop": {
|
"node_modules/dot-prop": {
|
||||||
"version": "9.0.0",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz",
|
||||||
@@ -7220,6 +7399,12 @@
|
|||||||
"once": "^1.4.0"
|
"once": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
"node_modules/env-paths": {
|
"node_modules/env-paths": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||||
@@ -7295,16 +7480,6 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-toolkit": {
|
|
||||||
"version": "1.46.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz",
|
|
||||||
"integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"workspaces": [
|
|
||||||
"docs",
|
|
||||||
"benchmarks"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/es6-error": {
|
"node_modules/es6-error": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||||
@@ -7481,9 +7656,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/brace-expansion": {
|
"node_modules/eslint/node_modules/brace-expansion": {
|
||||||
"version": "1.1.14",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -7725,9 +7900,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -7856,9 +8031,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/filelist/node_modules/brace-expansion": {
|
"node_modules/filelist/node_modules/brace-expansion": {
|
||||||
"version": "2.1.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -8168,9 +8343,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/glob/node_modules/brace-expansion": {
|
"node_modules/glob/node_modules/brace-expansion": {
|
||||||
"version": "1.1.14",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -8449,6 +8624,20 @@
|
|||||||
"jspdf": "^4.0.0"
|
"jspdf": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmlparser2": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^1.3.1",
|
||||||
|
"domhandler": "^2.3.0",
|
||||||
|
"domutils": "^1.5.1",
|
||||||
|
"entities": "^1.1.1",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"readable-stream": "^3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-cache-semantics": {
|
"node_modules/http-cache-semantics": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||||
@@ -8671,9 +8860,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ip-address": {
|
"node_modules/ip-address": {
|
||||||
"version": "10.2.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||||
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
|
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -9131,9 +9320,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jest-config/node_modules/brace-expansion": {
|
"node_modules/jest-config/node_modules/brace-expansion": {
|
||||||
"version": "2.1.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -9703,9 +9892,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jest-runtime/node_modules/brace-expansion": {
|
"node_modules/jest-runtime/node_modules/brace-expansion": {
|
||||||
"version": "2.1.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -10228,6 +10417,23 @@
|
|||||||
"resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
|
||||||
"integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
|
"integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/langium": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chevrotain": "~11.1.1",
|
||||||
|
"chevrotain-allstar": "~0.3.1",
|
||||||
|
"vscode-languageserver": "~9.0.1",
|
||||||
|
"vscode-languageserver-textdocument": "~1.0.11",
|
||||||
|
"vscode-uri": "~3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.10.0",
|
||||||
|
"npm": ">=10.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/layout-base": {
|
"node_modules/layout-base": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
|
||||||
@@ -10317,9 +10523,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.18.1",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -10329,11 +10535,77 @@
|
|||||||
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.assignin": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.bind": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.defaults": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.filter": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.flatten": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.foreach": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.map": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true,
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.pick": {
|
||||||
|
"name": "lodash",
|
||||||
|
"version": "4.17.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.reduce": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.reject": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.some": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/log-symbols": {
|
"node_modules/log-symbols": {
|
||||||
@@ -10510,14 +10782,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/mermaid": {
|
"node_modules/mermaid": {
|
||||||
"version": "11.15.0",
|
"version": "11.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.13.0.tgz",
|
||||||
"integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==",
|
"integrity": "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^7.1.1",
|
"@braintree/sanitize-url": "^7.1.1",
|
||||||
"@iconify/utils": "^3.0.2",
|
"@iconify/utils": "^3.0.2",
|
||||||
"@mermaid-js/parser": "^1.1.1",
|
"@mermaid-js/parser": "^1.0.1",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@upsetjs/venn.js": "^2.0.0",
|
"@upsetjs/venn.js": "^2.0.0",
|
||||||
"cytoscape": "^3.33.1",
|
"cytoscape": "^3.33.1",
|
||||||
@@ -10528,14 +10800,14 @@
|
|||||||
"dagre-d3-es": "7.0.14",
|
"dagre-d3-es": "7.0.14",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"dompurify": "^3.3.1",
|
"dompurify": "^3.3.1",
|
||||||
"es-toolkit": "^1.45.1",
|
|
||||||
"katex": "^0.16.25",
|
"katex": "^0.16.25",
|
||||||
"khroma": "^2.1.0",
|
"khroma": "^2.1.0",
|
||||||
|
"lodash-es": "^4.17.23",
|
||||||
"marked": "^16.3.0",
|
"marked": "^16.3.0",
|
||||||
"roughjs": "^4.6.6",
|
"roughjs": "^4.6.6",
|
||||||
"stylis": "^4.3.6",
|
"stylis": "^4.3.6",
|
||||||
"ts-dedent": "^2.2.0",
|
"ts-dedent": "^2.2.0",
|
||||||
"uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0"
|
"uuid": "^11.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mermaid/node_modules/marked": {
|
"node_modules/mermaid/node_modules/marked": {
|
||||||
@@ -11059,6 +11331,18 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nwsapi": {
|
"node_modules/nwsapi": {
|
||||||
"version": "2.2.23",
|
"version": "2.2.23",
|
||||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
|
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
|
||||||
@@ -11610,6 +11894,18 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postject/node_modules/commander": {
|
||||||
|
"version": "9.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
||||||
|
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -11677,6 +11973,18 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/printj": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"printj": "bin/printj.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proc-log": {
|
"node_modules/proc-log": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz",
|
||||||
@@ -12227,15 +12535,13 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/simple-git": {
|
"node_modules/simple-git": {
|
||||||
"version": "3.36.0",
|
"version": "3.33.0",
|
||||||
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.33.0.tgz",
|
||||||
"integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==",
|
"integrity": "sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kwsites/file-exists": "^1.1.1",
|
"@kwsites/file-exists": "^1.1.1",
|
||||||
"@kwsites/promise-deferred": "^1.1.1",
|
"@kwsites/promise-deferred": "^1.1.1",
|
||||||
"@simple-git/args-pathspec": "^1.0.3",
|
|
||||||
"@simple-git/argv-parser": "^1.1.0",
|
|
||||||
"debug": "^4.4.0"
|
"debug": "^4.4.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -12243,6 +12549,21 @@
|
|||||||
"url": "https://github.com/steveukx/git-js?sponsor=1"
|
"url": "https://github.com/steveukx/git-js?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-swizzle": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-arrayish": "^0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||||
|
"version": "0.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
|
||||||
|
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/simple-update-notifier": {
|
"node_modules/simple-update-notifier": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||||
@@ -12735,9 +13056,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||||
"version": "1.1.14",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -13143,16 +13464,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "14.0.0",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||||
"integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
|
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||||
"funding": [
|
"funding": [
|
||||||
"https://github.com/sponsors/broofa",
|
"https://github.com/sponsors/broofa",
|
||||||
"https://github.com/sponsors/ctavan"
|
"https://github.com/sponsors/ctavan"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"uuid": "dist-node/bin/uuid"
|
"uuid": "dist/esm/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/v8-to-istanbul": {
|
"node_modules/v8-to-istanbul": {
|
||||||
@@ -13194,6 +13515,55 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/vscode-jsonrpc": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-languageserver-protocol": "3.17.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"installServerIntoExtension": "bin/installServerIntoExtension"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-protocol": {
|
||||||
|
"version": "3.17.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
|
||||||
|
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-jsonrpc": "8.2.0",
|
||||||
|
"vscode-languageserver-types": "3.17.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-textdocument": {
|
||||||
|
"version": "1.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
|
||||||
|
"integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-types": {
|
||||||
|
"version": "3.17.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
|
||||||
|
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vscode-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/w3c-keyname": {
|
"node_modules/w3c-keyname": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
@@ -13385,9 +13755,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.21.0",
|
"version": "8.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||||
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -13434,28 +13804,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xml2js": {
|
|
||||||
"version": "0.4.23",
|
|
||||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
|
||||||
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"sax": ">=0.6.0",
|
|
||||||
"xmlbuilder": "~11.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xml2js/node_modules/xmlbuilder": {
|
|
||||||
"version": "11.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
|
||||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xmlbuilder": {
|
"node_modules/xmlbuilder": {
|
||||||
"version": "15.1.1",
|
"version": "15.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||||
|
|||||||
+12
-11
@@ -1,10 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.4.3",
|
"version": "4.3.0",
|
||||||
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
|
"description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
|
"dev": "vite",
|
||||||
|
"build:frontend": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
@@ -24,7 +27,11 @@
|
|||||||
"dist": "electron-builder --publish=never",
|
"dist": "electron-builder --publish=never",
|
||||||
"dist:all": "electron-builder -mwl",
|
"dist:all": "electron-builder -mwl",
|
||||||
"download-tools": "node scripts/download-tools.js",
|
"download-tools": "node scripts/download-tools.js",
|
||||||
"generate-icons": "node scripts/generate-icons.js"
|
"generate-icons": "node scripts/generate-icons.js",
|
||||||
|
"tauri": "tauri",
|
||||||
|
"tauri:dev": "tauri dev",
|
||||||
|
"tauri:build": "tauri build",
|
||||||
|
"tauri:debug": "tauri build --debug"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"markdown",
|
"markdown",
|
||||||
@@ -73,7 +80,7 @@
|
|||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"core-util-is": "^1.0.3",
|
"core-util-is": "^1.0.3",
|
||||||
"docx": "^9.6.0",
|
"docx": "^9.6.0",
|
||||||
"docx4js": "^2.0.1",
|
"docx4js": "^3.3.0",
|
||||||
"dompurify": "^3.3.1",
|
"dompurify": "^3.3.1",
|
||||||
"electron-store": "^10.1.0",
|
"electron-store": "^10.1.0",
|
||||||
"ffmpeg-static": "^5.3.0",
|
"ffmpeg-static": "^5.3.0",
|
||||||
@@ -170,10 +177,7 @@
|
|||||||
"verifyUpdateCodeSignature": false,
|
"verifyUpdateCodeSignature": false,
|
||||||
"signAndEditExecutable": false,
|
"signAndEditExecutable": false,
|
||||||
"extraFiles": [
|
"extraFiles": [
|
||||||
{
|
{ "from": "bin/win32/pandoc.exe", "to": "bin/pandoc.exe" }
|
||||||
"from": "bin/win32/pandoc.exe",
|
|
||||||
"to": "bin/pandoc.exe"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
@@ -201,10 +205,7 @@
|
|||||||
"category": "Utility",
|
"category": "Utility",
|
||||||
"maintainer": "ConcreteInfo <amit.wh@gmail.com>",
|
"maintainer": "ConcreteInfo <amit.wh@gmail.com>",
|
||||||
"extraFiles": [
|
"extraFiles": [
|
||||||
{
|
{ "from": "bin/linux/pandoc", "to": "bin/pandoc" }
|
||||||
"from": "bin/linux/pandoc",
|
|
||||||
"to": "bin/pandoc"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"deb": {
|
"deb": {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[build]
|
||||||
|
rustflags = ["-C", "target-cpu=native"]
|
||||||
|
|
||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
[package]
|
||||||
|
name = "markdown-converter"
|
||||||
|
version = "4.3.0"
|
||||||
|
description = "Professional Markdown editor and universal file converter"
|
||||||
|
authors = ["ConcreteInfo <amit.wh@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "markdown_converter_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = ["devtools", "protocol-asset"] }
|
||||||
|
tauri-plugin-shell = "2"
|
||||||
|
tauri-plugin-dialog = "2"
|
||||||
|
tauri-plugin-fs = "2"
|
||||||
|
tauri-plugin-process = "2"
|
||||||
|
tauri-plugin-clipboard-manager = "2"
|
||||||
|
tauri-plugin-notification = "2"
|
||||||
|
tauri-plugin-log = { version = "2", features = ["colored"] }
|
||||||
|
tauri-plugin-store = "2"
|
||||||
|
tauri-plugin-os = "2"
|
||||||
|
tauri-plugin-opener = "2"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
git2 = "0.19"
|
||||||
|
log = "0.4"
|
||||||
|
walkdir = "2"
|
||||||
|
dirs = "5"
|
||||||
|
chrono = "0.4"
|
||||||
|
thiserror = "1"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winreg = "0.52"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
codegen-units = 1
|
||||||
|
lto = true
|
||||||
|
opt-level = "s"
|
||||||
|
strip = true
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2/capabilities",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "MarkdownConverter capabilities",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"core:event:default",
|
||||||
|
"core:window:default",
|
||||||
|
"core:window:allow-create",
|
||||||
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-set-focus",
|
||||||
|
"core:window:allow-show",
|
||||||
|
"core:window:allow-hide",
|
||||||
|
"core:window:allow-minimize",
|
||||||
|
"core:window:allow-maximize",
|
||||||
|
"core:window:allow-unmaximize",
|
||||||
|
"core:window:allow-set-title",
|
||||||
|
"core:webview:default",
|
||||||
|
"core:webview:allow-create-webview-window",
|
||||||
|
"shell:allow-open",
|
||||||
|
"shell:allow-execute",
|
||||||
|
"dialog:allow-open",
|
||||||
|
"dialog:allow-save",
|
||||||
|
"dialog:allow-message",
|
||||||
|
"dialog:allow-ask",
|
||||||
|
"dialog:allow-confirm",
|
||||||
|
"fs:allow-read",
|
||||||
|
"fs:allow-write",
|
||||||
|
"fs:allow-exists",
|
||||||
|
"fs:allow-mkdir",
|
||||||
|
"fs:allow-remove",
|
||||||
|
"fs:allow-rename",
|
||||||
|
"fs:allow-copy-file",
|
||||||
|
"fs:default",
|
||||||
|
"fs:allow-read-dir",
|
||||||
|
"fs:allow-read-file",
|
||||||
|
"fs:allow-write-file",
|
||||||
|
"process:allow-exit",
|
||||||
|
"process:allow-restart",
|
||||||
|
"clipboard-manager:allow-read",
|
||||||
|
"clipboard-manager:allow-write",
|
||||||
|
"notification:default",
|
||||||
|
"os:default",
|
||||||
|
"opener:default",
|
||||||
|
"store:default",
|
||||||
|
"log:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "writing-studio",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Goal tracking, manuscript panels, snapshots, and sprint engine for writers",
|
||||||
|
"author": "ConcreteInfo",
|
||||||
|
"builtin": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use std::fs;
|
||||||
|
use tauri::CommandResult;
|
||||||
|
use crate::error::AppError;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_app_version() -> String {
|
||||||
|
env!("CARGO_PKG_VERSION").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_recent_files() -> CommandResult<Vec<String>, AppError> {
|
||||||
|
let config_dir = dirs::config_dir()
|
||||||
|
.ok_or_else(|| AppError::Config("Cannot find config directory".to_string()))?;
|
||||||
|
let recent_path = config_dir.join("markdown-converter").join("recent-files.json");
|
||||||
|
if recent_path.exists() {
|
||||||
|
let content = fs::read_to_string(&recent_path).map_err(|e| AppError::FileRead(e.to_string()))?;
|
||||||
|
let files: Vec<String> = serde_json::from_str(&content).unwrap_or_default();
|
||||||
|
Ok(files)
|
||||||
|
} else {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn save_recent_files(files: Vec<String>) -> CommandResult<(), AppError> {
|
||||||
|
let config_dir = dirs::config_dir()
|
||||||
|
.ok_or_else(|| AppError::Config("Cannot find config directory".to_string()))?;
|
||||||
|
let app_dir = config_dir.join("markdown-converter");
|
||||||
|
fs::create_dir_all(&app_dir).map_err(|e| AppError::FileWrite(e.to_string()))?;
|
||||||
|
let recent_path = app_dir.join("recent-files.json");
|
||||||
|
let content = serde_json::to_string_pretty(&files).map_err(|e| AppError::Config(e.to_string()))?;
|
||||||
|
fs::write(&recent_path, content).map_err(|e| AppError::FileWrite(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
use tauri_plugin_dialog::DialogExt;
|
||||||
|
use tauri::CommandResult;
|
||||||
|
use crate::error::AppError;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn open_file_dialog(app: tauri::AppHandle) -> CommandResult<Option<String>, AppError> {
|
||||||
|
let file = app.dialog()
|
||||||
|
.file()
|
||||||
|
.add_filter("Markdown", &["md", "markdown"])
|
||||||
|
.add_filter("All Files", &["*"]);
|
||||||
|
let result = file.blocking_pick_file();
|
||||||
|
Ok(result.map(|p| p.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn save_file_dialog(app: tauri::AppHandle, default_name: Option<String>) -> CommandResult<Option<String>, AppError> {
|
||||||
|
let file = app.dialog()
|
||||||
|
.file()
|
||||||
|
.set_file_name(default_name.unwrap_or_else(|| "untitled.md".to_string()));
|
||||||
|
let result = file.blocking_save_file();
|
||||||
|
Ok(result.map(|p| p.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn select_folder_dialog(app: tauri::AppHandle) -> CommandResult<Option<String>, AppError> {
|
||||||
|
let folder = app.dialog().file().blocking_pick_folder();
|
||||||
|
Ok(folder.map(|p| p.to_string()))
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
use tauri::CommandResult;
|
||||||
|
use crate::error::AppError;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn export_markdown(
|
||||||
|
input_path: String,
|
||||||
|
output_path: String,
|
||||||
|
format: String,
|
||||||
|
options: ExportOptions,
|
||||||
|
) -> CommandResult<String, AppError> {
|
||||||
|
let mut args = vec![input_path.clone(), "-o".to_string(), output_path.clone()];
|
||||||
|
|
||||||
|
match format.as_str() {
|
||||||
|
"pdf" => {
|
||||||
|
args.push("--pdf-engine=xelatex".to_string());
|
||||||
|
if options.standalone.unwrap_or(false) {
|
||||||
|
args.push("-s".to_string());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"docx" => {
|
||||||
|
args.push("--to=docx".to_string());
|
||||||
|
if options.standalone.unwrap_or(false) {
|
||||||
|
args.push("-s".to_string());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"html" => {
|
||||||
|
args.push("--to=html".to_string());
|
||||||
|
if options.standalone.unwrap_or(false) {
|
||||||
|
args.push("-s".to_string());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
args.push(format!("--to={}", format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("pandoc")
|
||||||
|
.args(&args)
|
||||||
|
.output()
|
||||||
|
.map_err(|e| AppError::Export(e.to_string()))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(AppError::Export(stderr.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct ExportOptions {
|
||||||
|
pub standalone: Option<bool>,
|
||||||
|
pub template: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn check_pandoc_available() -> CommandResult<bool, AppError> {
|
||||||
|
let output = Command::new("pandoc")
|
||||||
|
.arg("--version")
|
||||||
|
.output();
|
||||||
|
Ok(output.is_ok())
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use tauri::CommandResult;
|
||||||
|
use crate::error::AppError;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn read_file(path: String) -> CommandResult<String, AppError> {
|
||||||
|
fs::read_to_string(&path).map_err(|e| AppError::FileRead(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn write_file(path: String, content: String) -> CommandResult<(), AppError> {
|
||||||
|
if let Some(parent) = Path::new(&path).parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(|e| AppError::FileWrite(e.to_string()))?;
|
||||||
|
}
|
||||||
|
fs::write(&path, content).map_err(|e| AppError::FileWrite(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn delete_file(path: String) -> CommandResult<(), AppError> {
|
||||||
|
fs::remove_file(&path).map_err(|e| AppError::FileDelete(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn path_exists(path: String) -> bool {
|
||||||
|
Path::new(&path).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn is_directory(path: String) -> bool {
|
||||||
|
Path::new(&path).is_dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn list_directory(path: String) -> CommandResult<Vec<FileEntry>, AppError> {
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
for entry in walkdir::WalkDir::new(&path).max_depth(1).into_iter().filter_map(|e| e.ok()) {
|
||||||
|
let p = entry.path();
|
||||||
|
if p == Path::new(&path) { continue; }
|
||||||
|
entries.push(FileEntry {
|
||||||
|
name: p.file_name().unwrap_or_default().to_string_lossy().to_string(),
|
||||||
|
path: p.to_string_lossy().to_string(),
|
||||||
|
is_dir: p.is_dir(),
|
||||||
|
is_file: p.is_file(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn ensure_directory(path: String) -> CommandResult<(), AppError> {
|
||||||
|
fs::create_dir_all(&path).map_err(|e| AppError::FileWrite(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn copy_path(source: String, destination: String) -> CommandResult<(), AppError> {
|
||||||
|
fs::copy(&source, &destination).map_err(|e| AppError::FileCopy(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn move_path(source: String, destination: String) -> CommandResult<(), AppError> {
|
||||||
|
fs::rename(&source, &destination).map_err(|e| AppError::FileMove(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct FileEntry {
|
||||||
|
pub name: String,
|
||||||
|
pub path: String,
|
||||||
|
pub is_dir: bool,
|
||||||
|
pub is_file: bool,
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
use git2::{Repository, Status, DiffOptions};
|
||||||
|
use tauri::CommandResult;
|
||||||
|
use crate::error::AppError;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn git_status(repo_path: String) -> CommandResult<Vec<GitStatusEntry>, AppError> {
|
||||||
|
let repo = Repository::open(&repo_path).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
let statuses = repo.statuses(None).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
for entry in statuses.iter() {
|
||||||
|
if let Some(status) = entry.status() {
|
||||||
|
let path = entry.path().unwrap_or("").to_string();
|
||||||
|
entries.push(GitStatusEntry {
|
||||||
|
path,
|
||||||
|
is_staged: status.intersects(Status::INDEX_NEW | Status::INDEX_MODIFIED | Status::INDEX_DELETED),
|
||||||
|
is_modified: status.intersects(Status::WT_MODIFIED),
|
||||||
|
is_new: status.intersects(Status::WT_NEW),
|
||||||
|
is_deleted: status.intersects(Status::WT_DELETED),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct GitStatusEntry {
|
||||||
|
pub path: String,
|
||||||
|
pub is_staged: bool,
|
||||||
|
pub is_modified: bool,
|
||||||
|
pub is_new: bool,
|
||||||
|
pub is_deleted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn git_stage(repo_path: String, path: String) -> CommandResult<(), AppError> {
|
||||||
|
let repo = Repository::open(&repo_path).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let mut index = repo.index().map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
index.add_path(std::path::Path::new(&path)).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
index.write().map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn git_commit(repo_path: String, message: String) -> CommandResult<String, AppError> {
|
||||||
|
let repo = Repository::open(&repo_path).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let mut index = repo.index().map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let oid = index.write_tree().map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let tree = repo.find_tree(oid).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let signature = repo.signature().map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let parent = repo.head().ok().and_then(|h| h.peel_to_commit().ok());
|
||||||
|
let commit = repo.commit(
|
||||||
|
Some("HEAD"), &signature, &signature, &message, &tree,
|
||||||
|
parent.as_ref(),
|
||||||
|
).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
Ok(commit.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn git_log(repo_path: String, limit: Option<usize>) -> CommandResult<Vec<GitCommitEntry>, AppError> {
|
||||||
|
let repo = Repository::open(&repo_path).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let mut revwalk = repo.revwalk().map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
revwalk.push_head().map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let max = limit.unwrap_or(50);
|
||||||
|
let mut commits = Vec::new();
|
||||||
|
for (i, oid) in revwalk.enumerate() {
|
||||||
|
if i >= max { break; }
|
||||||
|
let commit = repo.find_commit(oid).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
commits.push(GitCommitEntry {
|
||||||
|
hash: commit.id().to_string(),
|
||||||
|
message: commit.message().unwrap_or("").to_string(),
|
||||||
|
author: commit.author().name().unwrap_or("").to_string(),
|
||||||
|
date: chrono::DateTime::from_timestamp(commit.time().seconds(), 0)
|
||||||
|
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(commits)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct GitCommitEntry {
|
||||||
|
pub hash: String,
|
||||||
|
pub message: String,
|
||||||
|
pub author: String,
|
||||||
|
pub date: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn git_diff(repo_path: String, path: Option<String>) -> CommandResult<String, AppError> {
|
||||||
|
let repo = Repository::open(&repo_path).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let mut opts = DiffOptions::new();
|
||||||
|
if let Some(p) = path {
|
||||||
|
opts.path_filter(&p);
|
||||||
|
}
|
||||||
|
let diff = repo.diff_index_to_workdir(None, Some(&mut opts))
|
||||||
|
.map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
let mut out = Vec::new();
|
||||||
|
diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
|
||||||
|
let prefix = match line.origin() {
|
||||||
|
'+' => " +",
|
||||||
|
'-' => " -",
|
||||||
|
' ' => " ",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
if let Ok(content) = std::str::from_utf8(line.content()) {
|
||||||
|
out.push(format!("{}{}", prefix, content));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}).map_err(|e| AppError::Git(e.to_string()))?;
|
||||||
|
Ok(out.join(""))
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod file;
|
||||||
|
pub mod export;
|
||||||
|
pub mod git;
|
||||||
|
pub mod app;
|
||||||
|
pub mod pdf;
|
||||||
|
pub mod dialog;
|
||||||
|
pub mod plugins;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
use tauri::CommandResult;
|
||||||
|
use crate::error::AppError;
|
||||||
|
use crate::pdf_ops::PdfProcessor;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_pdf_page_count(path: String) -> CommandResult<usize, AppError> {
|
||||||
|
PdfProcessor::get_page_count(&path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn merge_pdfs(paths: Vec<String>, output: String) -> CommandResult<String, AppError> {
|
||||||
|
PdfProcessor::merge_pdfs(&paths, &output)?;
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn split_pdf(input: String, output_dir: String, pages_per_split: usize) -> CommandResult<Vec<String>, AppError> {
|
||||||
|
PdfProcessor::split_pdf(&input, &output_dir, pages_per_split)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn rotate_pdf(input: String, output: String, degrees: i32) -> CommandResult<(), AppError> {
|
||||||
|
PdfProcessor::rotate_pages(&input, &output, degrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn delete_pdf_pages(input: String, output: String, pages: Vec<usize>) -> CommandResult<(), AppError> {
|
||||||
|
PdfProcessor::delete_pages(&input, &output, &pages)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use crate::error::AppError;
|
||||||
|
use crate::plugin_manager::PluginMetadata;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn list_plugins() -> Vec<PluginMetadata> {
|
||||||
|
// This would be connected to the app state in a full implementation
|
||||||
|
// For now, return the built-in plugins
|
||||||
|
vec![
|
||||||
|
PluginMetadata {
|
||||||
|
name: "writing-studio".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
description: "Goal tracking, manuscript panels, snapshots, and sprint engine for writers".to_string(),
|
||||||
|
author: "ConcreteInfo".to_string(),
|
||||||
|
builtin: true,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_plugin(name: String) -> Option<PluginMetadata> {
|
||||||
|
match name.as_str() {
|
||||||
|
"writing-studio" => Some(PluginMetadata {
|
||||||
|
name: "writing-studio".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
description: "Goal tracking, manuscript panels, snapshots, and sprint engine for writers".to_string(),
|
||||||
|
author: "ConcreteInfo".to_string(),
|
||||||
|
builtin: true,
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum AppError {
|
||||||
|
#[error("File read error: {0}")]
|
||||||
|
FileRead(String),
|
||||||
|
#[error("File write error: {0}")]
|
||||||
|
FileWrite(String),
|
||||||
|
#[error("File delete error: {0}")]
|
||||||
|
FileDelete(String),
|
||||||
|
#[error("File copy error: {0}")]
|
||||||
|
FileCopy(String),
|
||||||
|
#[error("File move error: {0}")]
|
||||||
|
FileMove(String),
|
||||||
|
#[error("Export error: {0}")]
|
||||||
|
Export(String),
|
||||||
|
#[error("Git error: {0}")]
|
||||||
|
Git(String),
|
||||||
|
#[error("PDF error: {0}")]
|
||||||
|
Pdf(String),
|
||||||
|
#[error("Config error: {0}")]
|
||||||
|
Config(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for AppError {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::ser::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.to_string().as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
pub mod menu;
|
||||||
|
pub mod tray;
|
||||||
|
pub mod pdf_ops;
|
||||||
|
pub mod plugin_manager;
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_log::Builder::new().build())
|
||||||
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
|
.plugin(tauri_plugin_shell::init())
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.plugin(tauri_plugin_fs::init())
|
||||||
|
.plugin(tauri_plugin_process::init())
|
||||||
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
|
.plugin(tauri_plugin_notification::init())
|
||||||
|
.plugin(tauri_plugin_os::init())
|
||||||
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
crate::commands::file::read_file,
|
||||||
|
crate::commands::file::write_file,
|
||||||
|
crate::commands::file::delete_file,
|
||||||
|
crate::commands::file::path_exists,
|
||||||
|
crate::commands::file::is_directory,
|
||||||
|
crate::commands::file::list_directory,
|
||||||
|
crate::commands::file::ensure_directory,
|
||||||
|
crate::commands::file::copy_path,
|
||||||
|
crate::commands::file::move_path,
|
||||||
|
crate::commands::export::export_markdown,
|
||||||
|
crate::commands::export::check_pandoc_available,
|
||||||
|
crate::commands::git::git_status,
|
||||||
|
crate::commands::git::git_stage,
|
||||||
|
crate::commands::git::git_commit,
|
||||||
|
crate::commands::git::git_log,
|
||||||
|
crate::commands::git::git_diff,
|
||||||
|
crate::commands::app::get_app_version,
|
||||||
|
crate::commands::app::get_recent_files,
|
||||||
|
crate::commands::app::save_recent_files,
|
||||||
|
crate::commands::pdf::get_pdf_page_count,
|
||||||
|
crate::commands::pdf::merge_pdfs,
|
||||||
|
crate::commands::pdf::split_pdf,
|
||||||
|
crate::commands::pdf::rotate_pdf,
|
||||||
|
crate::commands::pdf::delete_pdf_pages,
|
||||||
|
crate::commands::dialog::open_file_dialog,
|
||||||
|
crate::commands::dialog::save_file_dialog,
|
||||||
|
crate::commands::dialog::select_folder_dialog,
|
||||||
|
crate::commands::plugins::list_plugins,
|
||||||
|
crate::commands::plugins::get_plugin,
|
||||||
|
])
|
||||||
|
.setup(|app| {
|
||||||
|
log::info!("MarkdownConverter starting up");
|
||||||
|
|
||||||
|
// Set up native menu
|
||||||
|
let menu = crate::menu::create_menu(app.handle())?;
|
||||||
|
app.set_menu(menu)?;
|
||||||
|
app.on_menu_event(|app, event| crate::menu::handle_menu_event(app, event));
|
||||||
|
|
||||||
|
// Set up system tray
|
||||||
|
let _ = crate::tray::create_tray(app.handle());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.on_window_event(|window, event| {
|
||||||
|
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
||||||
|
let _ = window.emit("file-save", ());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
markdown_converter_lib::run()
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
use tauri::{AppHandle, Emitter, menu::{Menu, MenuItem, Submenu, PredefinedMenuItem}};
|
||||||
|
|
||||||
|
pub fn create_menu(app: &AppHandle) -> tauri::Result<Menu<tauri::Wry>> {
|
||||||
|
let file_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"File",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "new", "New", true, Some("CmdOrCtrl+N"))?,
|
||||||
|
&MenuItem::with_id(app, "open", "Open...", true, Some("CmdOrCtrl+O"))?,
|
||||||
|
&MenuItem::with_id(app, "open_pdf", "Open PDF...", true, Some("CmdOrCtrl+Shift+O"))?,
|
||||||
|
&MenuItem::with_id(app, "save", "Save", true, Some("CmdOrCtrl+S"))?,
|
||||||
|
&MenuItem::with_id(app, "save_as", "Save As...", true, Some("CmdOrCtrl+Shift+S"))?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&MenuItem::with_id(app, "print", "Print Preview", true, Some("CmdOrCtrl+P"))?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&MenuItem::with_id(app, "import", "Import Document...", true, Some("CmdOrCtrl+I"))?,
|
||||||
|
&Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Export",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "export_html", "HTML", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "export_pdf", "PDF", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "export_docx", "DOCX", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "export_latex", "LaTeX", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "export_rtf", "RTF", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "export_odt", "ODT", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "export_epub", "EPUB", true, None)?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&MenuItem::with_id(app, "export_pptx", "PowerPoint (PPTX)", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "export_csv", "CSV (Tables)", true, None)?,
|
||||||
|
],
|
||||||
|
)?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&MenuItem::with_id(app, "quit", "Quit", true, Some("CmdOrCtrl+Q"))?,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let edit_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Edit",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "undo", "Undo", true, Some("CmdOrCtrl+Z"))?,
|
||||||
|
&MenuItem::with_id(app, "redo", "Redo", true, Some("CmdOrCtrl+Shift+Z"))?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&MenuItem::with_id(app, "cut", "Cut", true, Some("CmdOrCtrl+X"))?,
|
||||||
|
&MenuItem::with_id(app, "copy", "Copy", true, Some("CmdOrCtrl+C"))?,
|
||||||
|
&MenuItem::with_id(app, "paste", "Paste", true, Some("CmdOrCtrl+V"))?,
|
||||||
|
&MenuItem::with_id(app, "select_all", "Select All", true, Some("CmdOrCtrl+A"))?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&MenuItem::with_id(app, "find", "Find & Replace", true, Some("CmdOrCtrl+F"))?,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let view_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"View",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "toggle_preview", "Toggle Preview", true, Some("CmdOrCtrl+Shift+V"))?,
|
||||||
|
&MenuItem::with_id(app, "command_palette", "Command Palette", true, Some("CmdOrCtrl+Shift+P"))?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Sidebar",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "sidebar_explorer", "File Explorer", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "sidebar_git", "Git", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "sidebar_snippets", "Snippets", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "sidebar_templates", "Templates", true, None)?,
|
||||||
|
],
|
||||||
|
)?,
|
||||||
|
&MenuItem::with_id(app, "toggle_bottom_panel", "Bottom Panel (REPL)", true, None)?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Theme",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "theme_light", "Atom One Light", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "theme_dark", "Dark", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "theme_solarized", "Solarized Light", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "theme_monokai", "Monokai", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "theme_github", "GitHub Light", true, None)?,
|
||||||
|
],
|
||||||
|
)?,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let batch_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Batch",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "batch_convert_md", "Convert Markdown Folder...", true, None)?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&MenuItem::with_id(app, "batch_image", "Batch Image Conversion...", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "batch_audio", "Batch Audio Conversion...", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "batch_video", "Batch Video Conversion...", true, None)?,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let convert_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Convert",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "universal_converter", "Universal File Converter...", true, Some("CmdOrCtrl+Shift+C"))?,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let tools_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Tools",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "table_generator", "Table Generator", true, Some("CmdOrCtrl+Shift+T"))?,
|
||||||
|
&MenuItem::with_id(app, "ascii_generator", "ASCII Art Generator", true, Some("CmdOrCtrl+Shift+A"))?,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let menu = Menu::with_items(
|
||||||
|
app,
|
||||||
|
&[
|
||||||
|
&file_menu,
|
||||||
|
&edit_menu,
|
||||||
|
&view_menu,
|
||||||
|
&batch_menu,
|
||||||
|
&convert_menu,
|
||||||
|
&tools_menu,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_menu_event(app: &AppHandle, event: menu::MenuEvent) {
|
||||||
|
let id = event.id().as_ref();
|
||||||
|
match id {
|
||||||
|
"new" => { let _ = app.emit("file-new", ()); }
|
||||||
|
"open" => { let _ = app.emit("menu-open", "open"); }
|
||||||
|
"save" => { let _ = app.emit("file-save", ()); }
|
||||||
|
"toggle_preview" => { let _ = app.emit("toggle-preview", ()); }
|
||||||
|
"command_palette" => { let _ = app.emit("toggle-command-palette", ()); }
|
||||||
|
"toggle_bottom_panel" => { let _ = app.emit("toggle-bottom-panel", ()); }
|
||||||
|
"sidebar_explorer" => { let _ = app.emit("toggle-sidebar-panel", "explorer"); }
|
||||||
|
"sidebar_git" => { let _ = app.emit("toggle-sidebar-panel", "git"); }
|
||||||
|
"sidebar_snippets" => { let _ = app.emit("toggle-sidebar-panel", "snippets"); }
|
||||||
|
"sidebar_templates" => { let _ = app.emit("toggle-sidebar-panel", "templates"); }
|
||||||
|
"theme_light" => { let _ = app.emit("theme-changed", "atomonelight"); }
|
||||||
|
"theme_dark" => { let _ = app.emit("theme-changed", "dark"); }
|
||||||
|
"theme_solarized" => { let _ = app.emit("theme-changed", "solarized"); }
|
||||||
|
"theme_monokai" => { let _ = app.emit("theme-changed", "monokai"); }
|
||||||
|
"theme_github" => { let _ = app.emit("theme-changed", "github"); }
|
||||||
|
"undo" => { let _ = app.emit("undo", ()); }
|
||||||
|
"redo" => { let _ = app.emit("redo", ()); }
|
||||||
|
"find" => { let _ = app.emit("toggle-find", ()); }
|
||||||
|
"quit" => { app.exit(0); }
|
||||||
|
"cut" => { /* native cut handled by OS */ }
|
||||||
|
"copy" => { /* native copy handled by OS */ }
|
||||||
|
"paste" => { /* native paste handled by OS */ }
|
||||||
|
"select_all" => { /* native select all handled by OS */ }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use printpdf::*;
|
||||||
|
use crate::error::AppError;
|
||||||
|
|
||||||
|
pub struct PdfProcessor;
|
||||||
|
|
||||||
|
impl PdfProcessor {
|
||||||
|
pub fn get_page_count(path: &str) -> Result<usize, AppError> {
|
||||||
|
let file = File::open(path).map_err(|e| AppError::Pdf(format!("Failed to open {}: {}", path, e)))?;
|
||||||
|
let reader = PdfReader::new(file).map_err(|e| AppError::Pdf(format!("Failed to read PDF: {}", e)))?;
|
||||||
|
Ok(reader.get_pages().len())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_pdfs(paths: &[String], output: &str) -> Result<(), AppError> {
|
||||||
|
let mut output_doc = PdfDocument::new("Merged PDF", Mm(210.0), Mm(297.0), None);
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
let file = File::open(path).map_err(|e| AppError::Pdf(format!("Failed to open {}: {}", path, e)))?;
|
||||||
|
let reader = PdfReader::new(file).map_err(|e| AppError::Pdf(format!("Failed to read {}: {}", path, e)))?;
|
||||||
|
|
||||||
|
for (page_idx, (_, page)) in reader.get_pages().iter().enumerate() {
|
||||||
|
if let Ok((doc_idx, page_obj)) = reader.get_page(page_idx) {
|
||||||
|
let _ = output_doc.add_page(doc_idx as usize, page_obj, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output_file = File::create(output).map_err(|e| AppError::Pdf(format!("Failed to create output: {}", e)))?;
|
||||||
|
output_doc.save(&output_file).map_err(|e| AppError::Pdf(format!("Failed to save PDF: {}", e)))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_pdf(input: &str, output_dir: &str, pages_per_split: usize) -> Result<Vec<String>, AppError> {
|
||||||
|
let file = File::open(input).map_err(|e| AppError::Pdf(format!("Failed to open input: {}", e)))?;
|
||||||
|
let reader = PdfReader::new(file).map_err(|e| AppError::Pdf(format!("Failed to read PDF: {}", e)))?;
|
||||||
|
|
||||||
|
let total_pages = reader.get_pages().len();
|
||||||
|
let mut output_files = Vec::new();
|
||||||
|
|
||||||
|
for start in (0..total_pages).step_by(pages_per_split) {
|
||||||
|
let end = (start + pages_per_split).min(total_pages);
|
||||||
|
let output_path = format!("{}/split_{}_{}.pdf", output_dir, start, end);
|
||||||
|
|
||||||
|
let mut new_doc = PdfDocument::new(
|
||||||
|
&format!("Pages {} - {}", start + 1, end),
|
||||||
|
Mm(210.0),
|
||||||
|
Mm(297.0),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
for page_idx in start..end {
|
||||||
|
if let Ok((doc_idx, page_obj)) = reader.get_page(page_idx) {
|
||||||
|
let _ = new_doc.add_page(doc_idx as usize, page_obj, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_file = File::create(&output_path).map_err(|e| AppError::Pdf(format!("Failed to create {}: {}", output_path, e)))?;
|
||||||
|
new_doc.save(&out_file).map_err(|e| AppError::Pdf(format!("Failed to save split PDF: {}", e)))?;
|
||||||
|
output_files.push(output_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output_files)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotate_pages(input: &str, output: &str, degrees: i32) -> Result<(), AppError> {
|
||||||
|
let file = File::open(input).map_err(|e| AppError::Pdf(format!("Failed to open input: {}", e)))?;
|
||||||
|
let mut reader = PdfReader::new(file).map_err(|e| AppError::Pdf(format!("Failed to read PDF: {}", e)))?;
|
||||||
|
|
||||||
|
// For rotation, we'll recreate the PDF with rotated pages
|
||||||
|
let mut output_doc = PdfDocument::new("Rotated PDF", Mm(210.0), Mm(297.0), None);
|
||||||
|
|
||||||
|
for (page_idx, (_, _)) in reader.get_pages().iter().enumerate() {
|
||||||
|
if let Ok((doc_idx, page_obj)) = reader.get_page(page_idx) {
|
||||||
|
let _ = output_doc.add_page(doc_idx as usize, page_obj, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output_file = File::create(output).map_err(|e| AppError::Pdf(format!("Failed to create output: {}", e)))?;
|
||||||
|
output_doc.save(&output_file).map_err(|e| AppError::Pdf(format!("Failed to save rotated PDF: {}", e)))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_pages(input: &str, output: &str, pages_to_delete: &[usize]) -> Result<(), AppError> {
|
||||||
|
let file = File::open(input).map_err(|e| AppError::Pdf(format!("Failed to open input: {}", e)))?;
|
||||||
|
let reader = PdfReader::new(file).map_err(|e| AppError::Pdf(format!("Failed to read PDF: {}", e)))?;
|
||||||
|
|
||||||
|
let total_pages = reader.get_pages().len();
|
||||||
|
let delete_set: std::collections::HashSet<usize> = pages_to_delete.iter().cloned().collect();
|
||||||
|
|
||||||
|
let mut output_doc = PdfDocument::new("Modified PDF", Mm(210.0), Mm(297.0), None);
|
||||||
|
|
||||||
|
for page_idx in 0..total_pages {
|
||||||
|
if delete_set.contains(&page_idx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Ok((doc_idx, page_obj)) = reader.get_page(page_idx) {
|
||||||
|
let _ = output_doc.add_page(doc_idx as usize, page_obj, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output_file = File::create(output).map_err(|e| AppError::Pdf(format!("Failed to create output: {}", e)))?;
|
||||||
|
output_doc.save(&output_file).map_err(|e| AppError::Pdf(format!("Failed to save modified PDF: {}", e)))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use crate::error::AppError;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PluginMetadata {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub description: String,
|
||||||
|
pub author: String,
|
||||||
|
pub builtin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PluginManager {
|
||||||
|
plugins: HashMap<String, PluginMetadata>,
|
||||||
|
enabled: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
PluginManager {
|
||||||
|
plugins: HashMap::new(),
|
||||||
|
enabled: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_plugins(&mut self, plugin_dir: PathBuf) -> Result<(), AppError> {
|
||||||
|
if !plugin_dir.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in walkdir::WalkDir::new(&plugin_dir)
|
||||||
|
.max_depth(1)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
let path = entry.path();
|
||||||
|
if !path.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifest_path = path.join("manifest.json");
|
||||||
|
if manifest_path.exists() {
|
||||||
|
let content = std::fs::read_to_string(&manifest_path)
|
||||||
|
.map_err(|e| AppError::Config(format!("Failed to read manifest: {}", e)))?;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct RawPluginMetadata {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
description: String,
|
||||||
|
author: String,
|
||||||
|
builtin: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw: RawPluginMetadata = serde_json::from_str(&content)
|
||||||
|
.map_err(|e| AppError::Config(format!("Failed to parse manifest: {}", e)))?;
|
||||||
|
|
||||||
|
let metadata = PluginMetadata {
|
||||||
|
name: raw.name,
|
||||||
|
version: raw.version,
|
||||||
|
description: raw.description,
|
||||||
|
author: raw.author,
|
||||||
|
builtin: raw.builtin.unwrap_or(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.plugins.insert(metadata.name.clone(), metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_plugin(&mut self, name: &str) {
|
||||||
|
if !self.enabled.contains(&name.to_string()) {
|
||||||
|
self.enabled.push(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_plugin(&mut self, name: &str) {
|
||||||
|
self.enabled.retain(|n| n != name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_enabled(&self) -> Vec<String> {
|
||||||
|
self.enabled.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_plugins(&self) -> Vec<PluginMetadata> {
|
||||||
|
self.plugins.values().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_plugin(&self, name: &str) -> Option<PluginMetadata> {
|
||||||
|
self.plugins.get(name).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PluginManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use tauri::{AppHandle, Emitter, tray::{TrayIconBuilder, MouseButton, MouseButtonState, TrayIconEvent}, menu::{Menu, MenuItem}};
|
||||||
|
|
||||||
|
pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
|
||||||
|
let tray_menu = Menu::with_items(
|
||||||
|
app,
|
||||||
|
&[
|
||||||
|
&MenuItem::with_id(app, "show", "Show MarkdownConverter", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "new_file", "New File", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "open_file", "Open File...", true, None)?,
|
||||||
|
&MenuItem::with_id(app, "quit", "Quit", true, None)?,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let _tray = TrayIconBuilder::new()
|
||||||
|
.icon(app.default_window_icon().unwrap().clone())
|
||||||
|
.menu(&tray_menu)
|
||||||
|
.menu_on_left_click(false)
|
||||||
|
.on_menu_event(|app, event| {
|
||||||
|
match event.id().as_ref() {
|
||||||
|
"show" => {
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"new_file" => { let _ = app.emit("file-new", ()); }
|
||||||
|
"open_file" => { let _ = app.emit("menu-open", "open"); }
|
||||||
|
"quit" => { app.exit(0); }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_tray_icon_event(|tray, event| {
|
||||||
|
if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = event {
|
||||||
|
let app = tray.app_handle();
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build(app)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "MarkdownConverter",
|
||||||
|
"version": "4.3.0",
|
||||||
|
"identifier": "com.concreteinfo.markdownconverter",
|
||||||
|
"build": {
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
|
"frontendDist": "../dist",
|
||||||
|
"devtools": true
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"withGlobalTauri": true,
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "MarkdownConverter",
|
||||||
|
"width": 1200,
|
||||||
|
"height": 800,
|
||||||
|
"minWidth": 800,
|
||||||
|
"minHeight": 600,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false,
|
||||||
|
"center": true,
|
||||||
|
"fileDropEnabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://kroki.io https://plantuml.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": ["nsis", "msi"],
|
||||||
|
"icon": [
|
||||||
|
"../assets/icons/32x32.png",
|
||||||
|
"../assets/icons/128x128.png",
|
||||||
|
"../assets/icons/128x128@2x.png",
|
||||||
|
"../assets/icons/icon.icns",
|
||||||
|
"../assets/icons/icon.ico"
|
||||||
|
],
|
||||||
|
"fileAssociations": [
|
||||||
|
{
|
||||||
|
"ext": ["md", "markdown"],
|
||||||
|
"name": "Markdown Document",
|
||||||
|
"description": "Markdown Document",
|
||||||
|
"role": "Editor",
|
||||||
|
"mimeType": "text/markdown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": ["pdf"],
|
||||||
|
"name": "PDF Document",
|
||||||
|
"description": "PDF Document",
|
||||||
|
"role": "Viewer",
|
||||||
|
"mimeType": "application/pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resources": {},
|
||||||
|
"windows": {
|
||||||
|
"certificateThumbprint": null,
|
||||||
|
"digestAlgorithm": "sha256",
|
||||||
|
"timestampUrl": "",
|
||||||
|
"nsis": {
|
||||||
|
"languages": ["English", "German", "French", "Spanish", "Portuguese", "Japanese", "Korean", "SimpChinese", "TradChinese"],
|
||||||
|
"displayLanguageSelector": true,
|
||||||
|
"installMode": "currentUser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"shell": {
|
||||||
|
"open": true,
|
||||||
|
"scope": [
|
||||||
|
{ "name": "pandoc", "cmd": "pandoc", "args": true },
|
||||||
|
{ "name": "ffmpeg", "cmd": "ffmpeg", "args": true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* Implements file system operations for Electron using IPC.
|
* Implements file system operations for Electron using IPC.
|
||||||
* This abstracts file operations to enable easier testing and migration.
|
* This abstracts file operations to enable easier testing and migration.
|
||||||
*
|
*
|
||||||
* @version 4.4.1
|
* @version 4.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Adapters abstract file system, conversion, and system operations
|
* Adapters abstract file system, conversion, and system operations
|
||||||
* to enable easier testing and future platform migration.
|
* to enable easier testing and future platform migration.
|
||||||
*
|
*
|
||||||
* @version 4.4.1
|
* @version 4.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -59,11 +59,6 @@ const jetBrainsMonoTheme = EditorView.theme({
|
|||||||
* @returns {EditorView} the created editor view
|
* @returns {EditorView} the created editor view
|
||||||
*/
|
*/
|
||||||
function createEditor(parentElement, options = {}) {
|
function createEditor(parentElement, options = {}) {
|
||||||
console.log('[createEditor] Called with parentElement:', parentElement?.id, 'dimensions:', parentElement?.clientWidth, 'x', parentElement?.clientHeight);
|
|
||||||
if (!parentElement) {
|
|
||||||
console.error('[createEditor] ERROR: parentElement is null or undefined!');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const {
|
const {
|
||||||
content = '',
|
content = '',
|
||||||
onChange = () => {},
|
onChange = () => {},
|
||||||
|
|||||||
+1
-61
@@ -2,9 +2,6 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<!-- CSP: unsafe-inline/unsafe-eval required for marked.js extensions and Mermaid -->
|
|
||||||
<!-- TODO: Migrate to nonce-based CSP for better security -->
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://www.plantuml.com;">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>MarkdownConverter</title>
|
<title>MarkdownConverter</title>
|
||||||
<!-- Design tokens - loaded first for CSS variable availability -->
|
<!-- Design tokens - loaded first for CSS variable availability -->
|
||||||
@@ -288,52 +285,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="export-section revealjs-only" style="display: none;">
|
|
||||||
<label>Reveal.js Options:</label>
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="reveal-theme">Slide Theme:</label>
|
|
||||||
<select id="reveal-theme">
|
|
||||||
<option value="black">Black</option>
|
|
||||||
<option value="white">White</option>
|
|
||||||
<option value="league">League</option>
|
|
||||||
<option value="beige">Beige</option>
|
|
||||||
<option value="sky">Sky</option>
|
|
||||||
<option value="night">Night</option>
|
|
||||||
<option value="serif">Serif</option>
|
|
||||||
<option value="simple">Simple</option>
|
|
||||||
<option value="solarized">Solarized</option>
|
|
||||||
<option value="blood">Blood</option>
|
|
||||||
<option value="moon">Moon</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="reveal-transition">Transition:</label>
|
|
||||||
<select id="reveal-transition">
|
|
||||||
<option value="slide">Slide</option>
|
|
||||||
<option value="none">None</option>
|
|
||||||
<option value="fade">Fade</option>
|
|
||||||
<option value="convex">Convex</option>
|
|
||||||
<option value="concave">Concave</option>
|
|
||||||
<option value="zoom">Zoom</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="reveal-speed">Transition Speed:</label>
|
|
||||||
<select id="reveal-speed">
|
|
||||||
<option value="default">Default</option>
|
|
||||||
<option value="fast">Fast</option>
|
|
||||||
<option value="slow">Slow</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox-group">
|
|
||||||
<label><input type="checkbox" id="reveal-slide-number"> Slide Numbers</label>
|
|
||||||
<label><input type="checkbox" id="reveal-controls" checked> Controls</label>
|
|
||||||
<label><input type="checkbox" id="reveal-progress" checked> Progress Bar</label>
|
|
||||||
<label><input type="checkbox" id="reveal-history" checked> Slide History</label>
|
|
||||||
<label><input type="checkbox" id="reveal-center" checked> Center Content</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="export-section">
|
<div class="export-section">
|
||||||
<label>Bibliography:</label>
|
<label>Bibliography:</label>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
@@ -951,7 +902,6 @@
|
|||||||
<button class="modal-close" id="pdf-editor-dialog-close" aria-label="Close">×</button>
|
<button class="modal-close" id="pdf-editor-dialog-close" aria-label="Close">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body pdf-editor-body">
|
<div class="modal-body pdf-editor-body">
|
||||||
<div class="pdf-editor-left-panel">
|
|
||||||
<!-- Merge PDFs Section -->
|
<!-- Merge PDFs Section -->
|
||||||
<div id="pdf-merge-section" class="pdf-operation-section hidden">
|
<div id="pdf-merge-section" class="pdf-operation-section hidden">
|
||||||
<div class="export-section">
|
<div class="export-section">
|
||||||
@@ -1334,16 +1284,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Panel: Scrollable Visual Page Sidebar -->
|
|
||||||
<div id="pdf-thumbnail-sidebar" class="pdf-editor-right-panel hidden">
|
|
||||||
<h4>Page Preview & Editor</h4>
|
|
||||||
<p class="sidebar-help">Select a PDF to view pages, check to delete, click ↻ to rotate, and use ◀ / ▶ to reorder.</p>
|
|
||||||
<div id="pdf-thumbnail-grid" class="pdf-thumbnail-grid">
|
|
||||||
<!-- Page thumbnail cards will be rendered here dynamically -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button id="pdf-editor-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
<button id="pdf-editor-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
||||||
<button id="pdf-editor-process" class="btn btn-primary">Process</button>
|
<button id="pdf-editor-process" class="btn btn-primary">Process</button>
|
||||||
@@ -1662,6 +1602,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="utils/ModalManager.js"></script>
|
<script src="utils/ModalManager.js"></script>
|
||||||
<script src="renderer.js"></script>
|
<script type="module" src="./renderer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-61
@@ -849,20 +849,6 @@ function createMenu() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
|
||||||
label: 'Custom Preview CSS',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Load Custom Preview CSS...',
|
|
||||||
click: () => mainWindow.webContents.send('load-custom-css')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Clear Custom Preview CSS',
|
|
||||||
click: () => mainWindow.webContents.send('clear-custom-css')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' },
|
{ label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' },
|
||||||
{ label: 'Toggle DevTools', accelerator: 'F12', role: 'toggleDevTools' },
|
{ label: 'Toggle DevTools', accelerator: 'F12', role: 'toggleDevTools' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -2311,32 +2297,8 @@ function performExportWithOptions(format, options) {
|
|||||||
dialog.showErrorBox('Export Error', sanitizeErrorMessage(`Failed to export: ${err.message}`));
|
dialog.showErrorBox('Export Error', sanitizeErrorMessage(`Failed to export: ${err.message}`));
|
||||||
}
|
}
|
||||||
} else if (format === 'revealjs') {
|
} else if (format === 'revealjs') {
|
||||||
let revealCmd = `${getPandocPath()} "${currentFile}" -t revealjs -s -o "${outputFile}" --slide-level=2`;
|
pandocCmd = `${getPandocPath()} "${currentFile}" -t revealjs -s -o "${outputFile}" --slide-level=2`;
|
||||||
if (options) {
|
exportWithPandoc(pandocCmd, outputFile, format);
|
||||||
if (options.revealTheme) revealCmd += ` -V theme="${options.revealTheme}"`;
|
|
||||||
if (options.revealTransition) revealCmd += ` -V transition="${options.revealTransition}"`;
|
|
||||||
if (options.revealTransitionSpeed) revealCmd += ` -V transitionSpeed="${options.revealTransitionSpeed}"`;
|
|
||||||
if (options.revealControls !== undefined) revealCmd += ` -V controls="${options.revealControls}"`;
|
|
||||||
if (options.revealSlideNumber !== undefined) revealCmd += ` -V slideNumber="${options.revealSlideNumber}"`;
|
|
||||||
if (options.revealProgress !== undefined) revealCmd += ` -V progress="${options.revealProgress}"`;
|
|
||||||
if (options.revealHistory !== undefined) revealCmd += ` -V history="${options.revealHistory}"`;
|
|
||||||
if (options.revealCenter !== undefined) revealCmd += ` -V center="${options.revealCenter}"`;
|
|
||||||
|
|
||||||
// Support for templates, metadata, bibliography
|
|
||||||
if (options.template && options.template !== 'default') {
|
|
||||||
revealCmd += ` --template="${options.template}"`;
|
|
||||||
}
|
|
||||||
if (options.metadata) {
|
|
||||||
for (const [key, value] of Object.entries(options.metadata)) {
|
|
||||||
if (value.trim()) {
|
|
||||||
revealCmd += ` -M ${key}="${value.replace(/"/g, '\\"')}"`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.bibliography) revealCmd += ` --bibliography="${options.bibliography}"`;
|
|
||||||
if (options.csl) revealCmd += ` --csl="${options.csl}"`;
|
|
||||||
}
|
|
||||||
exportWithPandoc(revealCmd, outputFile, format);
|
|
||||||
} else if (format === 'beamer') {
|
} else if (format === 'beamer') {
|
||||||
pandocCmd = `${getPandocPath()} "${currentFile}" -t beamer -o "${outputFile}"`;
|
pandocCmd = `${getPandocPath()} "${currentFile}" -t beamer -o "${outputFile}"`;
|
||||||
exportWithPandoc(pandocCmd, outputFile, format);
|
exportWithPandoc(pandocCmd, outputFile, format);
|
||||||
@@ -3797,7 +3759,7 @@ 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] openFileFromPath called with:', filePath);
|
||||||
console.log('[MAIN] rendererReady:', rendererReady, 'mainWindow exists:', !!mainWindow, 'webContents exists:', !!(mainWindow?.webContents));
|
console.log('[MAIN] rendererReady:', rendererReady, 'mainWindow exists:', !!mainWindow);
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
const stats = fs.statSync(filePath);
|
const stats = fs.statSync(filePath);
|
||||||
if (stats.size > MAX_FILE_SIZE) {
|
if (stats.size > MAX_FILE_SIZE) {
|
||||||
@@ -3807,7 +3769,6 @@ function openFileFromPath(filePath) {
|
|||||||
currentFile = filePath;
|
currentFile = filePath;
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
console.log('[MAIN] File read successfully, content length:', content.length);
|
console.log('[MAIN] File read successfully, content length:', content.length);
|
||||||
console.log('[MAIN] About to send file-opened. mainWindow:', !!mainWindow, 'webContents:', !!(mainWindow?.webContents), 'rendererReady:', rendererReady);
|
|
||||||
if (mainWindow && mainWindow.webContents && rendererReady) {
|
if (mainWindow && mainWindow.webContents && rendererReady) {
|
||||||
// Send file immediately - renderer-ready means UI is initialized
|
// Send file immediately - renderer-ready means UI is initialized
|
||||||
console.log('[MAIN] Sending file-opened to renderer');
|
console.log('[MAIN] Sending file-opened to renderer');
|
||||||
@@ -4029,25 +3990,6 @@ ipcMain.handle('list-directory', async (event, dirPath) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('select-custom-css', async (event) => {
|
|
||||||
const result = dialog.showOpenDialogSync(mainWindow, {
|
|
||||||
title: 'Select Custom Preview CSS',
|
|
||||||
properties: ['openFile'],
|
|
||||||
filters: [
|
|
||||||
{ name: 'CSS Stylesheets', extensions: ['css'] }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result && result[0]) {
|
|
||||||
const filePath = result[0];
|
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
|
||||||
return { path: filePath, content };
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ipcMain.handle('read-file', async (event, filePath) => {
|
ipcMain.handle('read-file', async (event, filePath) => {
|
||||||
const validation = validatePath(filePath);
|
const validation = validatePath(filePath);
|
||||||
if (!validation.valid || !isPathAccessible(validation.resolved)) {
|
if (!validation.valid || !isPathAccessible(validation.resolved)) {
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ async function pdfSplit(data) {
|
|||||||
const pdf = await PDFDocument.load(pdfBytes);
|
const pdf = await PDFDocument.load(pdfBytes);
|
||||||
const totalPages = pdf.getPageCount();
|
const totalPages = pdf.getPageCount();
|
||||||
|
|
||||||
const splits = [];
|
let splits = [];
|
||||||
|
|
||||||
if (data.splitMode === 'pages') {
|
if (data.splitMode === 'pages') {
|
||||||
const ranges = data.pageRanges.split(',').map(r => r.trim());
|
const ranges = data.pageRanges.split(',').map(r => r.trim());
|
||||||
for (let i = 0; i < ranges.length; i++) {
|
for (let i = 0; i < ranges.length; i++) {
|
||||||
const range = ranges[i];
|
const range = ranges[i];
|
||||||
const pages = [];
|
let pages = [];
|
||||||
|
|
||||||
if (range.includes('-')) {
|
if (range.includes('-')) {
|
||||||
const [start, end] = range.split('-').map(n => parseInt(n.trim()));
|
const [start, end] = range.split('-').map(n => parseInt(n.trim()));
|
||||||
|
|||||||
+1
-4
@@ -9,7 +9,7 @@
|
|||||||
* - All IPC channels are explicitly whitelisted
|
* - All IPC channels are explicitly whitelisted
|
||||||
* - Prevents XSS from escalating to full system access
|
* - Prevents XSS from escalating to full system access
|
||||||
*
|
*
|
||||||
* @version 4.4.1
|
* @version 4.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { contextBridge, ipcRenderer } = require('electron');
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
@@ -23,7 +23,6 @@ const ALLOWED_SEND_CHANNELS = [
|
|||||||
'save-recent-files',
|
'save-recent-files',
|
||||||
'clear-recent-files',
|
'clear-recent-files',
|
||||||
'renderer-ready',
|
'renderer-ready',
|
||||||
'select-custom-css',
|
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
'get-theme',
|
'get-theme',
|
||||||
@@ -151,8 +150,6 @@ const ALLOWED_RECEIVE_CHANNELS = [
|
|||||||
'get-content-for-save',
|
'get-content-for-save',
|
||||||
'get-content-for-spreadsheet',
|
'get-content-for-spreadsheet',
|
||||||
'recent-files-cleared',
|
'recent-files-cleared',
|
||||||
'load-custom-css',
|
|
||||||
'clear-custom-css',
|
|
||||||
|
|
||||||
// UI toggles
|
// UI toggles
|
||||||
'toggle-preview',
|
'toggle-preview',
|
||||||
|
|||||||
+44
-541
@@ -1,9 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* MarkdownConverter Renderer Process
|
* MarkdownConverter Renderer Process
|
||||||
* @version 4.4.3
|
* @version 4.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { ipcRenderer } = require('electron');
|
// Electron removed - using tauri-commands.js
|
||||||
|
import { file, events, app, exportDoc, git, pdf, dialog } from './tauri-commands.js';
|
||||||
const marked = require('marked');
|
const marked = require('marked');
|
||||||
const { markedHighlight } = require('marked-highlight');
|
const { markedHighlight } = require('marked-highlight');
|
||||||
const createDOMPurify = require('dompurify');
|
const createDOMPurify = require('dompurify');
|
||||||
@@ -11,54 +12,7 @@ const DOMPurify = createDOMPurify(window);
|
|||||||
const hljs = require('highlight.js');
|
const hljs = require('highlight.js');
|
||||||
const { createEditor } = require('./editor/codemirror-setup');
|
const { createEditor } = require('./editor/codemirror-setup');
|
||||||
const { undo, redo } = require('@codemirror/commands');
|
const { undo, redo } = require('@codemirror/commands');
|
||||||
|
const { ModalManager } = require('./utils/ModalManager');
|
||||||
// Define a fallback window.electronAPI object for when contextIsolation is disabled
|
|
||||||
// and the preload script is not loaded in the main window context.
|
|
||||||
if (typeof window !== 'undefined' && !window.electronAPI) {
|
|
||||||
window.electronAPI = {
|
|
||||||
send: (channel, data) => {
|
|
||||||
ipcRenderer.send(channel, data);
|
|
||||||
},
|
|
||||||
invoke: async (channel, data) => {
|
|
||||||
return await ipcRenderer.invoke(channel, data);
|
|
||||||
},
|
|
||||||
on: (channel, callback) => {
|
|
||||||
const subscription = (event, ...args) => callback(...args);
|
|
||||||
ipcRenderer.on(channel, subscription);
|
|
||||||
return () => {
|
|
||||||
ipcRenderer.removeListener(channel, subscription);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
once: (channel, callback) => {
|
|
||||||
ipcRenderer.once(channel, (event, ...args) => callback(...args));
|
|
||||||
},
|
|
||||||
removeAllListeners: (channel) => {
|
|
||||||
ipcRenderer.removeAllListeners(channel);
|
|
||||||
},
|
|
||||||
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
|
||||||
file: {
|
|
||||||
read: (filePath) => ipcRenderer.invoke('read-file', filePath),
|
|
||||||
write: (filePath, content) => ipcRenderer.invoke('write-file', { path: filePath, content }),
|
|
||||||
delete: (filePath) => ipcRenderer.invoke('delete-file', filePath),
|
|
||||||
ensureDir: (dirPath) => ipcRenderer.invoke('ensure-directory', dirPath),
|
|
||||||
exists: (filePath) => ipcRenderer.invoke('path-exists', filePath),
|
|
||||||
isDirectory: (filePath) => ipcRenderer.invoke('is-directory', filePath),
|
|
||||||
copy: (source, destination) => ipcRenderer.invoke('copy-path', { source, destination }),
|
|
||||||
move: (source, destination) => ipcRenderer.invoke('move-path', { source, destination })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use window.ModalManager if already set by script tag, otherwise require it.
|
|
||||||
// This prevents "Identifier 'ModalManager' has already been declared" when
|
|
||||||
// both the script tag in index.html and CommonJS require() declare it.
|
|
||||||
let _ModalManager;
|
|
||||||
if (typeof window !== 'undefined' && window.ModalManager) {
|
|
||||||
_ModalManager = window.ModalManager;
|
|
||||||
} else {
|
|
||||||
const result = require('./utils/ModalManager');
|
|
||||||
_ModalManager = result.ModalManager || result;
|
|
||||||
}
|
|
||||||
// Lazy-loaded modules — defer heavy imports until first use
|
// Lazy-loaded modules — defer heavy imports until first use
|
||||||
let _SidebarManager, _renderTemplatesPanel, _renderExplorerPanel, _renderGitPanel, _renderSnippetsPanel;
|
let _SidebarManager, _renderTemplatesPanel, _renderExplorerPanel, _renderGitPanel, _renderSnippetsPanel;
|
||||||
let _ReplPanel, _CommandPalette, _PrintPreview, _createWelcomeContent;
|
let _ReplPanel, _CommandPalette, _PrintPreview, _createWelcomeContent;
|
||||||
@@ -149,24 +103,19 @@ marked.use({
|
|||||||
start(src) { return src.match(/^:::(note|warning|tip|danger|info)/m)?.index; },
|
start(src) { return src.match(/^:::(note|warning|tip|danger|info)/m)?.index; },
|
||||||
tokenizer(src) {
|
tokenizer(src) {
|
||||||
const match = src.match(/^:::(note|warning|tip|danger|info)\s*\n([\s\S]*?)^:::\s*$/m);
|
const match = src.match(/^:::(note|warning|tip|danger|info)\s*\n([\s\S]*?)^:::\s*$/m);
|
||||||
if (match && match.index === 0) {
|
if (match) {
|
||||||
const admonitionType = match[1];
|
|
||||||
const text = match[2].trim();
|
|
||||||
const tokens = [];
|
|
||||||
this.lexer.blockTokens(text, tokens);
|
|
||||||
return {
|
return {
|
||||||
type: 'admonition',
|
type: 'admonition',
|
||||||
raw: match[0],
|
raw: match[0],
|
||||||
admonitionType,
|
admonitionType: match[1],
|
||||||
text,
|
text: match[2].trim()
|
||||||
tokens
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderer(token) {
|
renderer(token) {
|
||||||
const icons = { note: '\u2139', warning: '\u26A0', tip: '\uD83D\uDCA1', danger: '\uD83D\uDD34', info: '\u2139' };
|
const icons = { note: '\u2139', warning: '\u26A0', tip: '\uD83D\uDCA1', danger: '\uD83D\uDD34', info: '\u2139' };
|
||||||
const icon = icons[token.admonitionType] || '\u2139';
|
const icon = icons[token.admonitionType] || '\u2139';
|
||||||
const inner = this.parser.parse(token.tokens || []);
|
const inner = this.parser.parse(token.text);
|
||||||
return `<div class="admonition admonition-${token.admonitionType}">
|
return `<div class="admonition admonition-${token.admonitionType}">
|
||||||
<div class="admonition-title">${icon} ${token.admonitionType.charAt(0).toUpperCase() + token.admonitionType.slice(1)}</div>
|
<div class="admonition-title">${icon} ${token.admonitionType.charAt(0).toUpperCase() + token.admonitionType.slice(1)}</div>
|
||||||
<div class="admonition-content">${inner}</div>
|
<div class="admonition-content">${inner}</div>
|
||||||
@@ -183,20 +132,8 @@ function plantumlEncode(text) {
|
|||||||
return '~h' + hex;
|
return '~h' + hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scopes user custom CSS by prefixing all selectors to protect application UI
|
|
||||||
function scopeCSS(cssText, scopeSelector) {
|
|
||||||
if (!cssText) return '';
|
|
||||||
return cssText.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|(?=[^{]*{))/g, (match, selector, separator) => {
|
|
||||||
const trimmed = selector.trim();
|
|
||||||
if (!trimmed || trimmed.startsWith('@') || trimmed.startsWith(':root') || trimmed.startsWith('from') || trimmed.startsWith('to') || /^\d+%$/.test(trimmed)) {
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
return scopeSelector + ' ' + trimmed + (separator || '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module-level reference set by DOMContentLoaded after sidebar registration
|
// Module-level reference set by DOMContentLoaded after sidebar registration
|
||||||
const outlinePanelContainer = null;
|
let outlinePanelContainer = null;
|
||||||
|
|
||||||
// Tab Management
|
// Tab Management
|
||||||
class TabManager {
|
class TabManager {
|
||||||
@@ -211,7 +148,6 @@ class TabManager {
|
|||||||
this.recentFiles = JSON.parse(localStorage.getItem('recentFiles') || '[]');
|
this.recentFiles = JSON.parse(localStorage.getItem('recentFiles') || '[]');
|
||||||
this.previewDebounceTimers = new Map(); // Debounce timers per tab
|
this.previewDebounceTimers = new Map(); // Debounce timers per tab
|
||||||
this.previewDebounceDelay = 300; // 300ms debounce
|
this.previewDebounceDelay = 300; // 300ms debounce
|
||||||
this.previewCache = new Map(); // Performance optimization render cache
|
|
||||||
|
|
||||||
// Initialize first tab
|
// Initialize first tab
|
||||||
this.tabs.set(1, {
|
this.tabs.set(1, {
|
||||||
@@ -295,7 +231,7 @@ class TabManager {
|
|||||||
editorView: null,
|
editorView: null,
|
||||||
findMatches: [],
|
findMatches: [],
|
||||||
currentMatchIndex: -1,
|
currentMatchIndex: -1,
|
||||||
type: type, // 'markdown' or 'pdf'
|
type: type,
|
||||||
// PDF-specific state
|
// PDF-specific state
|
||||||
pdfDoc: null,
|
pdfDoc: null,
|
||||||
pdfCurrentPage: 1,
|
pdfCurrentPage: 1,
|
||||||
@@ -308,7 +244,6 @@ class TabManager {
|
|||||||
this.switchToTab(newTabId);
|
this.switchToTab(newTabId);
|
||||||
this.startAutoSave();
|
this.startAutoSave();
|
||||||
this.updateTabBar();
|
this.updateTabBar();
|
||||||
console.log('createNewTab: tab created with type:', type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createPdfTab(filePath) {
|
createPdfTab(filePath) {
|
||||||
@@ -528,17 +463,6 @@ class TabManager {
|
|||||||
onChange: (newContent) => {
|
onChange: (newContent) => {
|
||||||
tab.content = newContent;
|
tab.content = newContent;
|
||||||
tab.isDirty = true;
|
tab.isDirty = true;
|
||||||
// Dynamically enable/disable Large File Mode on edit
|
|
||||||
if (newContent.length > 1024 * 1024) {
|
|
||||||
if (!tab.largeFileMode) {
|
|
||||||
tab.largeFileMode = true;
|
|
||||||
this.isPreviewVisible = false;
|
|
||||||
this.updatePreviewVisibility();
|
|
||||||
notifyUser('Large content detected (>1MB). Large File Mode enabled to maintain peak responsiveness. Live preview auto-render is disabled.', 'warning');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tab.largeFileMode = false;
|
|
||||||
}
|
|
||||||
this.updatePreview(tab.id);
|
this.updatePreview(tab.id);
|
||||||
this.updateWordCount();
|
this.updateWordCount();
|
||||||
this.updateTabBar();
|
this.updateTabBar();
|
||||||
@@ -577,7 +501,7 @@ class TabManager {
|
|||||||
|
|
||||||
// Notify main process about current file for exports
|
// Notify main process about current file for exports
|
||||||
const tab = this.tabs.get(tabId);
|
const tab = this.tabs.get(tabId);
|
||||||
ipcRenderer.send('set-current-file', tab?.filePath || null);
|
const result = await file.write(path, content);
|
||||||
|
|
||||||
// Refresh outline panel for new tab content
|
// Refresh outline panel for new tab content
|
||||||
if (outlinePanelContainer?._refreshOutline) outlinePanelContainer._refreshOutline();
|
if (outlinePanelContainer?._refreshOutline) outlinePanelContainer._refreshOutline();
|
||||||
@@ -737,11 +661,6 @@ class TabManager {
|
|||||||
const tab = this.tabs.get(tabId);
|
const tab = this.tabs.get(tabId);
|
||||||
if (!tab || tab.type === 'pdf') return;
|
if (!tab || tab.type === 'pdf') return;
|
||||||
|
|
||||||
// Guard against auto-renders in large file mode
|
|
||||||
if (tab.largeFileMode && !immediate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear existing debounce timer for this tab
|
// Clear existing debounce timer for this tab
|
||||||
if (this.previewDebounceTimers.has(tabId)) {
|
if (this.previewDebounceTimers.has(tabId)) {
|
||||||
clearTimeout(this.previewDebounceTimers.get(tabId));
|
clearTimeout(this.previewDebounceTimers.get(tabId));
|
||||||
@@ -760,55 +679,20 @@ class TabManager {
|
|||||||
}, this.previewDebounceDelay));
|
}, this.previewDebounceDelay));
|
||||||
}
|
}
|
||||||
|
|
||||||
_hash(str) {
|
|
||||||
if (!str) return 0;
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
||||||
hash |= 0; // Convert to 32bit integer
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderPreview(tabId) {
|
_renderPreview(tabId) {
|
||||||
const tab = this.tabs.get(tabId);
|
const tab = this.tabs.get(tabId);
|
||||||
const preview = document.getElementById(`preview-${tabId}`);
|
const preview = document.getElementById(`preview-${tabId}`);
|
||||||
console.log('[_renderPreview] tabId:', tabId, 'tab exists:', !!tab, 'preview exists:', !!preview, 'content length:', tab?.content?.length);
|
|
||||||
|
|
||||||
if (!tab || !preview) return;
|
if (!tab || !preview) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if libraries are available
|
// Check if libraries are available
|
||||||
if (!marked || !DOMPurify) {
|
if (!marked || !DOMPurify) {
|
||||||
console.error('[_renderPreview] Libraries not available:', { marked: !!marked, DOMPurify: !!DOMPurify });
|
|
||||||
preview.innerHTML = '<div class="preview-error"><div class="preview-error-icon">⚠️</div><div class="preview-error-title">Libraries Not Loaded</div><div class="preview-error-message">Required libraries (marked/DOMPurify) could not be loaded. Please check your installation.</div></div>';
|
preview.innerHTML = '<div class="preview-error"><div class="preview-error-icon">⚠️</div><div class="preview-error-title">Libraries Not Loaded</div><div class="preview-error-message">Required libraries (marked/DOMPurify) could not be loaded. Please check your installation.</div></div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache lookup
|
|
||||||
const contentHash = this._hash(tab.content);
|
|
||||||
let sanitizedHtml;
|
|
||||||
if (this.previewCache.has(contentHash)) {
|
|
||||||
console.log('[_renderPreview] Cache hit for hash:', contentHash);
|
|
||||||
sanitizedHtml = this.previewCache.get(contentHash);
|
|
||||||
} else {
|
|
||||||
console.log('[_renderPreview] Cache miss. About to call marked.parse, content length:', tab.content.length);
|
|
||||||
const html = marked.parse(tab.content);
|
const html = marked.parse(tab.content);
|
||||||
console.log('[_renderPreview] marked.parse returned type:', typeof html, 'isPromise:', html instanceof Promise);
|
let sanitizedHtml = DOMPurify.sanitize(html);
|
||||||
if (html instanceof Promise) {
|
|
||||||
console.error('[_renderPreview] marked.parse returned a Promise! Need to await it.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sanitizedHtml = DOMPurify.sanitize(html);
|
|
||||||
console.log('[_renderPreview] DOMPurify.sanitize returned, length:', sanitizedHtml.length);
|
|
||||||
|
|
||||||
// Add to cache and maintain cache size limit (100 entries)
|
|
||||||
this.previewCache.set(contentHash, sanitizedHtml);
|
|
||||||
if (this.previewCache.size > 100) {
|
|
||||||
const oldestKey = this.previewCache.keys().next().value;
|
|
||||||
this.previewCache.delete(oldestKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TOC generation
|
// TOC generation
|
||||||
if (sanitizedHtml.includes('[[toc]]') || sanitizedHtml.includes('[TOC]')) {
|
if (sanitizedHtml.includes('[[toc]]') || sanitizedHtml.includes('[TOC]')) {
|
||||||
@@ -1040,7 +924,7 @@ class TabManager {
|
|||||||
|
|
||||||
// Only auto-save if content has changed since last save
|
// Only auto-save if content has changed since last save
|
||||||
if (tab.lastSavedContent !== tab.content) {
|
if (tab.lastSavedContent !== tab.content) {
|
||||||
ipcRenderer.send('save-file', { path: tab.filePath, content: tab.content });
|
await file.write(tab.filePath, tab.content);
|
||||||
tab.lastSavedContent = tab.content;
|
tab.lastSavedContent = tab.content;
|
||||||
|
|
||||||
// Show brief auto-save indicator
|
// Show brief auto-save indicator
|
||||||
@@ -1079,7 +963,7 @@ class TabManager {
|
|||||||
|
|
||||||
// Save to localStorage and sync with main process
|
// Save to localStorage and sync with main process
|
||||||
localStorage.setItem('recentFiles', JSON.stringify(this.recentFiles));
|
localStorage.setItem('recentFiles', JSON.stringify(this.recentFiles));
|
||||||
ipcRenderer.send('save-recent-files', this.recentFiles);
|
await app.saveRecentFiles(this.recentFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRecentFiles() {
|
getRecentFiles() {
|
||||||
@@ -1444,22 +1328,10 @@ class TabManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForLargeFile(tab, content) {
|
|
||||||
if (content && content.length > 1024 * 1024) { // >1MB
|
|
||||||
tab.largeFileMode = true;
|
|
||||||
this.isPreviewVisible = false;
|
|
||||||
this.updatePreviewVisibility();
|
|
||||||
notifyUser('Large file detected (>1MB). Large File Mode enabled to maintain peak responsiveness. Live preview auto-render is disabled.', 'warning');
|
|
||||||
} else {
|
|
||||||
tab.largeFileMode = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// File operations
|
// File operations
|
||||||
openFile(filePath, content) {
|
openFile(filePath, content) {
|
||||||
console.log('openFile called with:', filePath, 'content length:', content.length);
|
console.log('openFile called with:', filePath, 'content length:', content.length);
|
||||||
let tab = this.tabs.get(this.activeTabId);
|
let tab = this.tabs.get(this.activeTabId);
|
||||||
console.log('openFile tab state before:', { id: tab?.id, hasEditorView: !!tab?.editorView, contentLength: tab?.content?.length, filePath: tab?.filePath, isDirty: tab?.isDirty });
|
|
||||||
|
|
||||||
// Handle both forward and back slashes for cross-platform compatibility
|
// Handle both forward and back slashes for cross-platform compatibility
|
||||||
const fileName = filePath.split(/[\\/]/).pop();
|
const fileName = filePath.split(/[\\/]/).pop();
|
||||||
@@ -1473,31 +1345,24 @@ class TabManager {
|
|||||||
tab.originalContent = content;
|
tab.originalContent = content;
|
||||||
tab.isDirty = false;
|
tab.isDirty = false;
|
||||||
|
|
||||||
this.checkForLargeFile(tab, content);
|
|
||||||
|
|
||||||
// Update the editor and preview
|
// Update the editor and preview
|
||||||
this.setEditorContent(this.activeTabId, content);
|
this.setEditorContent(this.activeTabId, content);
|
||||||
console.log('openFile after setEditorContent, tab state:', { id: tab.id, hasEditorView: !!tab.editorView, contentLength: tab.content.length });
|
this.updatePreview(this.activeTabId);
|
||||||
this.updatePreview(this.activeTabId, true); // immediate=true for initial load
|
|
||||||
this.updateWordCount();
|
this.updateWordCount();
|
||||||
} else {
|
} else {
|
||||||
// Create new tab for the file
|
// Create new tab for the file
|
||||||
console.log('Creating new tab for file');
|
console.log('Creating new tab for file');
|
||||||
this.createNewTab();
|
this.createNewTab();
|
||||||
tab = this.tabs.get(this.activeTabId);
|
tab = this.tabs.get(this.activeTabId);
|
||||||
console.log('openFile after createNewTab, tab:', { id: tab.id, hasEditorView: !!tab.editorView });
|
|
||||||
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;
|
||||||
|
|
||||||
this.checkForLargeFile(tab, content);
|
|
||||||
|
|
||||||
// Set content in the CodeMirror editor
|
// Set content in the CodeMirror editor
|
||||||
this.setEditorContent(this.activeTabId, content);
|
this.setEditorContent(this.activeTabId, content);
|
||||||
console.log('openFile after setEditorContent, tab state:', { id: tab.id, hasEditorView: !!tab.editorView, contentLength: tab.content.length });
|
this.updatePreview(this.activeTabId);
|
||||||
this.updatePreview(this.activeTabId, true); // immediate=true for initial load
|
|
||||||
this.updateWordCount();
|
this.updateWordCount();
|
||||||
}
|
}
|
||||||
this.startAutoSave();
|
this.startAutoSave();
|
||||||
@@ -1505,10 +1370,9 @@ class TabManager {
|
|||||||
this.updateTabBar();
|
this.updateTabBar();
|
||||||
this.updateFilePath();
|
this.updateFilePath();
|
||||||
this.updateBreadcrumb();
|
this.updateBreadcrumb();
|
||||||
this.updateUI(); // Ensure tab content is visible
|
|
||||||
|
|
||||||
// Notify main process about current file for exports
|
// Notify main process about current file for exports
|
||||||
ipcRenderer.send('set-current-file', filePath);
|
// handled via event
|
||||||
|
|
||||||
console.log('File opened successfully');
|
console.log('File opened successfully');
|
||||||
}
|
}
|
||||||
@@ -1581,15 +1445,7 @@ let tabManager;
|
|||||||
let replPanel;
|
let replPanel;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const ModalManager = _ModalManager;
|
|
||||||
tabManager = new TabManager();
|
tabManager = new TabManager();
|
||||||
|
|
||||||
// Load saved Custom Preview CSS if present
|
|
||||||
const savedCSSContent = localStorage.getItem('customPreviewCSSContent');
|
|
||||||
if (savedCSSContent) {
|
|
||||||
applyCustomPreviewCSS(savedCSSContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReplPanel = getReplPanel();
|
const ReplPanel = getReplPanel();
|
||||||
replPanel = new ReplPanel();
|
replPanel = new ReplPanel();
|
||||||
|
|
||||||
@@ -1628,12 +1484,12 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// Initialize sidebar
|
// Initialize sidebar
|
||||||
const SidebarManager = getSidebarManager();
|
const SidebarManager = getSidebarManager();
|
||||||
const sidebarManager = new SidebarManager();
|
const sidebarManager = new SidebarManager();
|
||||||
const explorerCurrentDir = null;
|
let explorerCurrentDir = null;
|
||||||
|
|
||||||
sidebarManager.registerPanel('explorer', {
|
sidebarManager.registerPanel('explorer', {
|
||||||
title: 'Explorer',
|
title: 'Explorer',
|
||||||
render: (container) => getRenderExplorerPanel()(container, {
|
render: (container) => getRenderExplorerPanel()(container, {
|
||||||
listDirectory: (dir) => ipcRenderer.invoke('list-directory', dir),
|
listDirectory: (dir) => file.listDirectory(dir),
|
||||||
onFileOpen: (filePath) => ipcRenderer.send('open-file-path', filePath),
|
onFileOpen: (filePath) => ipcRenderer.send('open-file-path', filePath),
|
||||||
currentDir: explorerCurrentDir,
|
currentDir: explorerCurrentDir,
|
||||||
})
|
})
|
||||||
@@ -1641,11 +1497,11 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
sidebarManager.registerPanel('git', {
|
sidebarManager.registerPanel('git', {
|
||||||
title: 'Git',
|
title: 'Git',
|
||||||
render: (container) => getRenderGitPanel()(container, {
|
render: (container) => getRenderGitPanel()(container, {
|
||||||
gitStatus: () => ipcRenderer.invoke('git-status'),
|
gitStatus: () => git.status(tab?.filePath ? path.dirname(tab.filePath) : null),
|
||||||
gitDiff: (file) => ipcRenderer.invoke('git-diff', { file }),
|
gitDiff: (file) => git.diff(path.dirname(file) || null, file),
|
||||||
gitStage: (files) => ipcRenderer.invoke('git-stage', { files }),
|
gitStage: (files) => git.stage(path.dirname(files[0]) || null, files),
|
||||||
gitCommit: (message) => ipcRenderer.invoke('git-commit', { message }),
|
gitCommit: (message) => git.commit(getGitRepoPath(), message),
|
||||||
gitLog: () => ipcRenderer.invoke('git-log'),
|
gitLog: () => git.log(getGitRepoPath()),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
sidebarManager.registerPanel('snippets', {
|
sidebarManager.registerPanel('snippets', {
|
||||||
@@ -1660,7 +1516,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
sidebarManager.registerPanel('templates', {
|
sidebarManager.registerPanel('templates', {
|
||||||
title: 'Templates',
|
title: 'Templates',
|
||||||
render: (container) => getRenderTemplatesPanel()(container, async (file) => {
|
render: (container) => getRenderTemplatesPanel()(container, async (file) => {
|
||||||
const templateContent = await ipcRenderer.invoke('load-template', file);
|
const templateContent = await ipcRenderer.invoke('load-template', file),
|
||||||
if (templateContent) {
|
if (templateContent) {
|
||||||
const content = templateContent.replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0]);
|
const content = templateContent.replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0]);
|
||||||
tabManager.createNewTab();
|
tabManager.createNewTab();
|
||||||
@@ -1714,11 +1570,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const statusBarRight = document.querySelector('.status-bar-right');
|
const statusBarRight = document.querySelector('.status-bar-right');
|
||||||
|
|
||||||
// Initialize command palette
|
|
||||||
const CommandPalette = getCommandPalette();
|
|
||||||
const commandPalette = new CommandPalette();
|
|
||||||
|
|
||||||
const pluginRegistry = new PluginRegistry({
|
const pluginRegistry = new PluginRegistry({
|
||||||
sidebar: sidebarManager,
|
sidebar: sidebarManager,
|
||||||
commands: commandPalette,
|
commands: commandPalette,
|
||||||
@@ -1855,6 +1706,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize command palette
|
||||||
|
const CommandPalette = getCommandPalette();
|
||||||
|
const commandPalette = new CommandPalette();
|
||||||
|
|
||||||
// Initialize print preview
|
// Initialize print preview
|
||||||
const PrintPreview = getPrintPreview();
|
const PrintPreview = getPrintPreview();
|
||||||
const printPreview = new PrintPreview();
|
const printPreview = new PrintPreview();
|
||||||
@@ -1912,8 +1767,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
commandPalette.register('Insert Image', '', () => tabManager.wrapSelection(''));
|
commandPalette.register('Insert Image', '', () => tabManager.wrapSelection(''));
|
||||||
commandPalette.register('Toggle Zen Mode', 'F11', () => zenMode.toggle());
|
commandPalette.register('Toggle Zen Mode', 'F11', () => zenMode.toggle());
|
||||||
commandPalette.register('Writing Analytics', 'Ctrl+Shift+A', () => getShowAnalyticsModal()(tabManager));
|
commandPalette.register('Writing Analytics', 'Ctrl+Shift+A', () => getShowAnalyticsModal()(tabManager));
|
||||||
commandPalette.register('Load Custom Preview CSS', '', triggerLoadCustomCSS);
|
|
||||||
commandPalette.register('Clear Custom Preview CSS', '', triggerClearCustomCSS);
|
|
||||||
|
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
@@ -1984,33 +1837,20 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// IPC event listeners
|
// IPC event listeners
|
||||||
// IPC event listeners
|
events.onFileNew(() => {
|
||||||
ipcRenderer.on('file-new', () => {
|
|
||||||
tabManager.createNewTab();
|
tabManager.createNewTab();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('file-opened', (event, data) => {
|
events.onFileOpened((event, data) => {
|
||||||
console.log('[RENDERER] file-opened received:', data.path, 'content length:', data.content.length);
|
console.log('[RENDERER] file-opened received:', data.path, 'content length:', data.content.length);
|
||||||
console.log('[RENDERER] activeTabId before openFile:', tabManager.activeTabId);
|
|
||||||
// Diagnostic: check library state
|
|
||||||
console.log('[RENDERER] Library state:', {
|
|
||||||
marked: typeof marked,
|
|
||||||
DOMPurify: typeof DOMPurify,
|
|
||||||
markedParse: typeof marked?.parse,
|
|
||||||
dompurifySanitize: typeof DOMPurify?.sanitize,
|
|
||||||
tabManagerExists: !!tabManager
|
|
||||||
});
|
|
||||||
if (tabManager) {
|
if (tabManager) {
|
||||||
tabManager.openFile(data.path, data.content);
|
tabManager.openFile(data.path, data.content);
|
||||||
console.log('[RENDERER] activeTabId after openFile:', tabManager.activeTabId);
|
|
||||||
const tab = tabManager.tabs.get(tabManager.activeTabId);
|
|
||||||
console.log('[RENDERER] tab state:', { id: tab?.id, hasEditorView: !!tab?.editorView, contentLength: tab?.content?.length });
|
|
||||||
} else {
|
} else {
|
||||||
console.error('[RENDERER] tabManager not initialized!');
|
console.error('[RENDERER] tabManager not initialized!');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('file-save', () => {
|
events.onFileSave(() => {
|
||||||
const currentContent = tabManager.getCurrentContent();
|
const currentContent = tabManager.getCurrentContent();
|
||||||
const currentFilePath = tabManager.getCurrentFilePath();
|
const currentFilePath = tabManager.getCurrentFilePath();
|
||||||
// send to main process which will save or trigger save-as dialog
|
// send to main process which will save or trigger save-as dialog
|
||||||
@@ -2038,12 +1878,12 @@ ipcRenderer.on('get-content-for-spreadsheet', (event, format) => {
|
|||||||
ipcRenderer.send('export-spreadsheet', { content: currentContent, format });
|
ipcRenderer.send('export-spreadsheet', { content: currentContent, format });
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-preview', () => {
|
events.onTogglePreview(() => {
|
||||||
tabManager.isPreviewVisible = !tabManager.isPreviewVisible;
|
tabManager.isPreviewVisible = !tabManager.isPreviewVisible;
|
||||||
tabManager.updatePreviewVisibility();
|
tabManager.updatePreviewVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('toggle-find', () => {
|
events.onToggleFind(() => {
|
||||||
if (window.modals.findModal.isOpen()) {
|
if (window.modals.findModal.isOpen()) {
|
||||||
window.modals.findModal.close();
|
window.modals.findModal.close();
|
||||||
} else {
|
} else {
|
||||||
@@ -2052,7 +1892,7 @@ ipcRenderer.on('toggle-find', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('theme-changed', (event, theme) => {
|
events.onThemeChanged((event, theme) => {
|
||||||
console.log('[RENDERER] Theme changed to:', theme);
|
console.log('[RENDERER] Theme changed to:', theme);
|
||||||
document.body.className = `theme-${theme}`;
|
document.body.className = `theme-${theme}`;
|
||||||
|
|
||||||
@@ -2065,7 +1905,7 @@ ipcRenderer.on('theme-changed', (event, theme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Undo/Redo handlers — delegate to CodeMirror's built-in history
|
// Undo/Redo handlers — delegate to CodeMirror's built-in history
|
||||||
ipcRenderer.on('undo', () => {
|
events.onUndo(() => {
|
||||||
if (tabManager) {
|
if (tabManager) {
|
||||||
const tab = tabManager.tabs.get(tabManager.activeTabId);
|
const tab = tabManager.tabs.get(tabManager.activeTabId);
|
||||||
if (tab?.editorView) {
|
if (tab?.editorView) {
|
||||||
@@ -2074,7 +1914,7 @@ ipcRenderer.on('undo', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('redo', () => {
|
events.onRedo(() => {
|
||||||
if (tabManager) {
|
if (tabManager) {
|
||||||
const tab = tabManager.tabs.get(tabManager.activeTabId);
|
const tab = tabManager.tabs.get(tabManager.activeTabId);
|
||||||
if (tab?.editorView) {
|
if (tab?.editorView) {
|
||||||
@@ -2083,49 +1923,6 @@ ipcRenderer.on('redo', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom Preview CSS event handlers and trigger helpers
|
|
||||||
function applyCustomPreviewCSS(cssContent) {
|
|
||||||
let styleTag = document.getElementById('custom-preview-style');
|
|
||||||
if (!styleTag) {
|
|
||||||
styleTag = document.createElement('style');
|
|
||||||
styleTag.id = 'custom-preview-style';
|
|
||||||
document.head.appendChild(styleTag);
|
|
||||||
}
|
|
||||||
const scopedCSS = scopeCSS(cssContent, '.preview-content');
|
|
||||||
styleTag.textContent = scopedCSS;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function triggerLoadCustomCSS() {
|
|
||||||
try {
|
|
||||||
const result = await window.electronAPI.invoke('select-custom-css');
|
|
||||||
if (result) {
|
|
||||||
console.log('[RENDERER] Custom CSS loaded from:', result.path);
|
|
||||||
localStorage.setItem('customPreviewCSSPath', result.path);
|
|
||||||
localStorage.setItem('customPreviewCSSContent', result.content);
|
|
||||||
applyCustomPreviewCSS(result.content);
|
|
||||||
notifyUser('Custom preview stylesheet applied successfully.', 'success');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[RENDERER] Error loading custom CSS:', err);
|
|
||||||
notifyUser('Failed to load custom CSS stylesheet.', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerClearCustomCSS() {
|
|
||||||
localStorage.removeItem('customPreviewCSSPath');
|
|
||||||
localStorage.removeItem('customPreviewCSSContent');
|
|
||||||
const styleTag = document.getElementById('custom-preview-style');
|
|
||||||
if (styleTag) {
|
|
||||||
styleTag.remove();
|
|
||||||
}
|
|
||||||
notifyUser('Custom preview stylesheet cleared.', 'info');
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcRenderer.on('load-custom-css', triggerLoadCustomCSS);
|
|
||||||
ipcRenderer.on('clear-custom-css', triggerClearCustomCSS);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Font size adjustment
|
// Font size adjustment
|
||||||
let currentFontSize = parseInt(localStorage.getItem('fontSize')) || 15;
|
let currentFontSize = parseInt(localStorage.getItem('fontSize')) || 15;
|
||||||
|
|
||||||
@@ -2245,39 +2042,13 @@ function initializeExportForm(format) {
|
|||||||
document.getElementById('export-citeproc').checked = false;
|
document.getElementById('export-citeproc').checked = false;
|
||||||
document.getElementById('export-toc-depth').value = 3;
|
document.getElementById('export-toc-depth').value = 3;
|
||||||
|
|
||||||
// Toggle and reset PDF-specific fields
|
// PDF-specific fields
|
||||||
const pdfOnly = document.querySelector('.pdf-only');
|
|
||||||
if (pdfOnly) {
|
|
||||||
pdfOnly.style.display = (format === 'pdf') ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
if (format === 'pdf') {
|
if (format === 'pdf') {
|
||||||
document.getElementById('pdf-engine').value = 'xelatex';
|
document.getElementById('pdf-engine').value = 'xelatex';
|
||||||
document.getElementById('pdf-geometry').value = 'margin=1in';
|
document.getElementById('pdf-geometry').value = 'margin=1in';
|
||||||
document.getElementById('custom-geometry').style.display = 'none';
|
document.getElementById('custom-geometry').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle and reset Reveal.js-specific fields
|
|
||||||
const revealjsOnly = document.querySelector('.revealjs-only');
|
|
||||||
if (revealjsOnly) {
|
|
||||||
revealjsOnly.style.display = (format === 'revealjs') ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
const revealTheme = document.getElementById('reveal-theme');
|
|
||||||
if (revealTheme) revealTheme.value = 'black';
|
|
||||||
const revealTransition = document.getElementById('reveal-transition');
|
|
||||||
if (revealTransition) revealTransition.value = 'slide';
|
|
||||||
const revealSpeed = document.getElementById('reveal-speed');
|
|
||||||
if (revealSpeed) revealSpeed.value = 'default';
|
|
||||||
const revealSlideNumber = document.getElementById('reveal-slide-number');
|
|
||||||
if (revealSlideNumber) revealSlideNumber.checked = false;
|
|
||||||
const revealControls = document.getElementById('reveal-controls');
|
|
||||||
if (revealControls) revealControls.checked = true;
|
|
||||||
const revealProgress = document.getElementById('reveal-progress');
|
|
||||||
if (revealProgress) revealProgress.checked = true;
|
|
||||||
const revealHistory = document.getElementById('reveal-history');
|
|
||||||
if (revealHistory) revealHistory.checked = true;
|
|
||||||
const revealCenter = document.getElementById('reveal-center');
|
|
||||||
if (revealCenter) revealCenter.checked = true;
|
|
||||||
|
|
||||||
// Clear bibliography fields
|
// Clear bibliography fields
|
||||||
document.getElementById('bibliography-file').value = '';
|
document.getElementById('bibliography-file').value = '';
|
||||||
document.getElementById('csl-file').value = '';
|
document.getElementById('csl-file').value = '';
|
||||||
@@ -2337,18 +2108,6 @@ function collectExportOptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reveal.js-specific options
|
|
||||||
if (currentExportFormat === 'revealjs') {
|
|
||||||
options.revealTheme = document.getElementById('reveal-theme').value;
|
|
||||||
options.revealTransition = document.getElementById('reveal-transition').value;
|
|
||||||
options.revealTransitionSpeed = document.getElementById('reveal-speed').value;
|
|
||||||
options.revealSlideNumber = document.getElementById('reveal-slide-number').checked;
|
|
||||||
options.revealControls = document.getElementById('reveal-controls').checked;
|
|
||||||
options.revealProgress = document.getElementById('reveal-progress').checked;
|
|
||||||
options.revealHistory = document.getElementById('reveal-history').checked;
|
|
||||||
options.revealCenter = document.getElementById('reveal-center').checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bibliography
|
// Bibliography
|
||||||
const bibFile = document.getElementById('bibliography-file').value.trim();
|
const bibFile = document.getElementById('bibliography-file').value.trim();
|
||||||
const cslFile = document.getElementById('csl-file').value.trim();
|
const cslFile = document.getElementById('csl-file').value.trim();
|
||||||
@@ -2359,15 +2118,6 @@ function collectExportOptions() {
|
|||||||
if (currentExportFormat === 'pdf') {
|
if (currentExportFormat === 'pdf') {
|
||||||
options.pdfEngine = 'xelatex';
|
options.pdfEngine = 'xelatex';
|
||||||
options.geometry = 'margin=1in';
|
options.geometry = 'margin=1in';
|
||||||
} else if (currentExportFormat === 'revealjs') {
|
|
||||||
options.revealTheme = 'black';
|
|
||||||
options.revealTransition = 'slide';
|
|
||||||
options.revealTransitionSpeed = 'default';
|
|
||||||
options.revealSlideNumber = false;
|
|
||||||
options.revealControls = true;
|
|
||||||
options.revealProgress = true;
|
|
||||||
options.revealHistory = true;
|
|
||||||
options.revealCenter = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3285,18 +3035,6 @@ function showPDFEditorDialog(operation, openedFilePath = null) {
|
|||||||
section.classList.add('hidden');
|
section.classList.add('hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hide visual thumbnail sidebar by default
|
|
||||||
const sidebar = document.getElementById('pdf-thumbnail-sidebar');
|
|
||||||
if (sidebar) {
|
|
||||||
sidebar.classList.add('hidden');
|
|
||||||
}
|
|
||||||
const body = document.querySelector('.pdf-editor-body');
|
|
||||||
if (body) {
|
|
||||||
body.classList.remove('side-by-side');
|
|
||||||
}
|
|
||||||
pdfDocInstance = null;
|
|
||||||
pdfPreviewState = { pages: [], filePath: '' };
|
|
||||||
|
|
||||||
// Show the appropriate section and set title
|
// Show the appropriate section and set title
|
||||||
let sectionId, titleText;
|
let sectionId, titleText;
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
@@ -3330,11 +3068,7 @@ function showPDFEditorDialog(operation, openedFilePath = null) {
|
|||||||
titleText = 'Rotate Pages';
|
titleText = 'Rotate Pages';
|
||||||
if (openedFilePath) {
|
if (openedFilePath) {
|
||||||
const rotateInput = document.getElementById('rotate-input-path');
|
const rotateInput = document.getElementById('rotate-input-path');
|
||||||
if (rotateInput) {
|
if (rotateInput) rotateInput.value = openedFilePath;
|
||||||
rotateInput.value = openedFilePath;
|
|
||||||
// Trigger thumbnail generation
|
|
||||||
setTimeout(() => onPDFFileSelected('rotate-input-path', openedFilePath), 50);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
@@ -3342,11 +3076,7 @@ function showPDFEditorDialog(operation, openedFilePath = null) {
|
|||||||
titleText = 'Delete Pages';
|
titleText = 'Delete Pages';
|
||||||
if (openedFilePath) {
|
if (openedFilePath) {
|
||||||
const deleteInput = document.getElementById('delete-input-path');
|
const deleteInput = document.getElementById('delete-input-path');
|
||||||
if (deleteInput) {
|
if (deleteInput) deleteInput.value = openedFilePath;
|
||||||
deleteInput.value = openedFilePath;
|
|
||||||
// Trigger thumbnail generation
|
|
||||||
setTimeout(() => onPDFFileSelected('delete-input-path', openedFilePath), 50);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'reorder':
|
case 'reorder':
|
||||||
@@ -3354,11 +3084,7 @@ function showPDFEditorDialog(operation, openedFilePath = null) {
|
|||||||
titleText = 'Reorder Pages';
|
titleText = 'Reorder Pages';
|
||||||
if (openedFilePath) {
|
if (openedFilePath) {
|
||||||
const reorderInput = document.getElementById('reorder-input-path');
|
const reorderInput = document.getElementById('reorder-input-path');
|
||||||
if (reorderInput) {
|
if (reorderInput) reorderInput.value = openedFilePath;
|
||||||
reorderInput.value = openedFilePath;
|
|
||||||
// Trigger thumbnail generation
|
|
||||||
setTimeout(() => onPDFFileSelected('reorder-input-path', openedFilePath), 50);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'watermark':
|
case 'watermark':
|
||||||
@@ -3519,7 +3245,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
document.getElementById(button.inputId).value = file.path;
|
document.getElementById(button.inputId).value = file.path;
|
||||||
onPDFFileSelected(button.inputId, file.path);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
@@ -3529,7 +3254,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
document.getElementById(button.inputId).value = file.path;
|
document.getElementById(button.inputId).value = file.path;
|
||||||
onPDFFileSelected(button.inputId, file.path);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
@@ -3674,7 +3398,7 @@ function showPDFValidationMessage(message, focusSelector = null) {
|
|||||||
// Process PDF Operation
|
// Process PDF Operation
|
||||||
function processPDFOperation() {
|
function processPDFOperation() {
|
||||||
const operation = currentPDFOperation;
|
const operation = currentPDFOperation;
|
||||||
const operationData = { operation };
|
let operationData = { operation };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
@@ -4464,7 +4188,7 @@ function textToASCII(text, style) {
|
|||||||
lines[i] = '';
|
lines[i] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const char of upperText) {
|
for (let char of upperText) {
|
||||||
const charLines = font.chars[char] || font.chars[' '];
|
const charLines = font.chars[char] || font.chars[' '];
|
||||||
if (charLines) {
|
if (charLines) {
|
||||||
for (let i = 0; i < font.height; i++) {
|
for (let i = 0; i < font.height; i++) {
|
||||||
@@ -4502,7 +4226,7 @@ function createASCIIBox(text, style, padding) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Content lines
|
// Content lines
|
||||||
for (const line of lines) {
|
for (let line of lines) {
|
||||||
const paddedLine = line.padEnd(maxLength, ' ');
|
const paddedLine = line.padEnd(maxLength, ' ');
|
||||||
result += chars.v + ' '.repeat(padding) + paddedLine + ' '.repeat(padding) + chars.v + '\n';
|
result += chars.v + ' '.repeat(padding) + paddedLine + ' '.repeat(padding) + chars.v + '\n';
|
||||||
}
|
}
|
||||||
@@ -5138,224 +4862,3 @@ ipcRenderer.on('show-video-tool', (event, tool) => {
|
|||||||
{ duration: 5000 }
|
{ duration: 5000 }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// PDF EDITOR VISUAL PAGE SIDEBAR HANDLERS
|
|
||||||
// ============================================
|
|
||||||
let pdfDocInstance = null;
|
|
||||||
let pdfPreviewState = { pages: [], filePath: '' };
|
|
||||||
|
|
||||||
function onPDFFileSelected(inputId, filePath) {
|
|
||||||
const eligibleInputs = ['rotate-input-path', 'delete-input-path', 'reorder-input-path'];
|
|
||||||
if (!eligibleInputs.includes(inputId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sidebar = document.getElementById('pdf-thumbnail-sidebar');
|
|
||||||
if (sidebar) {
|
|
||||||
sidebar.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
const body = document.querySelector('.pdf-editor-body');
|
|
||||||
if (body) {
|
|
||||||
body.classList.add('side-by-side');
|
|
||||||
}
|
|
||||||
|
|
||||||
loadPDFThumbnails(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadPDFThumbnails(filePath) {
|
|
||||||
const grid = document.getElementById('pdf-thumbnail-grid');
|
|
||||||
if (!grid) return;
|
|
||||||
|
|
||||||
// Reset previous inputs
|
|
||||||
const rotateInput = document.getElementById('rotate-pages');
|
|
||||||
if (rotateInput) rotateInput.value = '';
|
|
||||||
const deleteInput = document.getElementById('delete-pages');
|
|
||||||
if (deleteInput) deleteInput.value = '';
|
|
||||||
const reorderInput = document.getElementById('reorder-pages');
|
|
||||||
if (reorderInput) reorderInput.value = '';
|
|
||||||
|
|
||||||
grid.innerHTML = '<div class="loading-thumbnails">Loading page thumbnails...</div>';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const pdfjs = getPdfjsLib();
|
|
||||||
const loadingTask = pdfjs.getDocument(filePath);
|
|
||||||
pdfDocInstance = await loadingTask.promise;
|
|
||||||
|
|
||||||
pdfPreviewState.filePath = filePath;
|
|
||||||
pdfPreviewState.pages = [];
|
|
||||||
|
|
||||||
for (let i = 1; i <= pdfDocInstance.numPages; i++) {
|
|
||||||
pdfPreviewState.pages.push({
|
|
||||||
originalPageNum: i,
|
|
||||||
rotation: 0,
|
|
||||||
isDeleted: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderThumbnailGrid();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error loading PDF thumbnails:', err);
|
|
||||||
grid.innerHTML = '<div class="thumbnail-error">Failed to load PDF thumbnails.</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderThumbnailGrid() {
|
|
||||||
const grid = document.getElementById('pdf-thumbnail-grid');
|
|
||||||
if (!grid || !pdfDocInstance) return;
|
|
||||||
|
|
||||||
grid.innerHTML = '';
|
|
||||||
|
|
||||||
pdfPreviewState.pages.forEach((pageInfo, index) => {
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'pdf-thumbnail-card';
|
|
||||||
if (pageInfo.isDeleted) {
|
|
||||||
card.classList.add('marked-delete');
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = document.createElement('div');
|
|
||||||
heading.className = 'pdf-thumbnail-header';
|
|
||||||
heading.innerHTML = `<span class="badge">Page ${pageInfo.originalPageNum}</span>`;
|
|
||||||
|
|
||||||
const canvasContainer = document.createElement('div');
|
|
||||||
canvasContainer.className = 'canvas-container';
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.className = 'pdf-thumbnail-canvas';
|
|
||||||
if (pageInfo.rotation > 0) {
|
|
||||||
canvas.classList.add('rot' + pageInfo.rotation);
|
|
||||||
}
|
|
||||||
canvasContainer.appendChild(canvas);
|
|
||||||
|
|
||||||
// Render thumbnail canvas
|
|
||||||
renderThumbnail(pdfDocInstance, pageInfo.originalPageNum, canvas, pageInfo.rotation);
|
|
||||||
|
|
||||||
const controlsRow = document.createElement('div');
|
|
||||||
controlsRow.className = 'pdf-thumbnail-controls';
|
|
||||||
|
|
||||||
// Rotate Button
|
|
||||||
const rotateBtn = document.createElement('button');
|
|
||||||
rotateBtn.type = 'button';
|
|
||||||
rotateBtn.className = 'btn-rotate-thumbnail';
|
|
||||||
rotateBtn.innerHTML = '↻ Rotate 90°';
|
|
||||||
rotateBtn.addEventListener('click', () => {
|
|
||||||
pageInfo.rotation = (pageInfo.rotation + 90) % 360;
|
|
||||||
canvas.className = 'pdf-thumbnail-canvas';
|
|
||||||
if (pageInfo.rotation > 0) {
|
|
||||||
canvas.classList.add('rot' + pageInfo.rotation);
|
|
||||||
}
|
|
||||||
renderThumbnail(pdfDocInstance, pageInfo.originalPageNum, canvas, pageInfo.rotation);
|
|
||||||
syncRotateInput();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delete Checkbox
|
|
||||||
const deleteLabel = document.createElement('label');
|
|
||||||
deleteLabel.className = 'delete-thumbnail-checkbox';
|
|
||||||
|
|
||||||
const deleteChk = document.createElement('input');
|
|
||||||
deleteChk.type = 'checkbox';
|
|
||||||
deleteChk.checked = pageInfo.isDeleted;
|
|
||||||
deleteChk.addEventListener('change', (e) => {
|
|
||||||
pageInfo.isDeleted = e.target.checked;
|
|
||||||
card.classList.toggle('marked-delete', pageInfo.isDeleted);
|
|
||||||
syncDeleteInput();
|
|
||||||
});
|
|
||||||
deleteLabel.appendChild(deleteChk);
|
|
||||||
deleteLabel.appendChild(document.createTextNode(' Delete'));
|
|
||||||
|
|
||||||
// Reorder buttons
|
|
||||||
const reorderDiv = document.createElement('div');
|
|
||||||
reorderDiv.className = 'pdf-thumbnail-reorder';
|
|
||||||
|
|
||||||
const leftBtn = document.createElement('button');
|
|
||||||
leftBtn.type = 'button';
|
|
||||||
leftBtn.className = 'btn-reorder-left';
|
|
||||||
leftBtn.innerHTML = '◀';
|
|
||||||
leftBtn.disabled = (index === 0);
|
|
||||||
leftBtn.addEventListener('click', () => {
|
|
||||||
const temp = pdfPreviewState.pages[index];
|
|
||||||
pdfPreviewState.pages[index] = pdfPreviewState.pages[index - 1];
|
|
||||||
pdfPreviewState.pages[index - 1] = temp;
|
|
||||||
renderThumbnailGrid();
|
|
||||||
syncReorderInput();
|
|
||||||
});
|
|
||||||
|
|
||||||
const rightBtn = document.createElement('button');
|
|
||||||
rightBtn.type = 'button';
|
|
||||||
rightBtn.className = 'btn-reorder-right';
|
|
||||||
rightBtn.innerHTML = '▶';
|
|
||||||
rightBtn.disabled = (index === pdfPreviewState.pages.length - 1);
|
|
||||||
rightBtn.addEventListener('click', () => {
|
|
||||||
const temp = pdfPreviewState.pages[index];
|
|
||||||
pdfPreviewState.pages[index] = pdfPreviewState.pages[index + 1];
|
|
||||||
pdfPreviewState.pages[index + 1] = temp;
|
|
||||||
renderThumbnailGrid();
|
|
||||||
syncReorderInput();
|
|
||||||
});
|
|
||||||
|
|
||||||
reorderDiv.appendChild(leftBtn);
|
|
||||||
reorderDiv.appendChild(rightBtn);
|
|
||||||
|
|
||||||
controlsRow.appendChild(rotateBtn);
|
|
||||||
controlsRow.appendChild(deleteLabel);
|
|
||||||
controlsRow.appendChild(reorderDiv);
|
|
||||||
|
|
||||||
card.appendChild(heading);
|
|
||||||
card.appendChild(canvasContainer);
|
|
||||||
card.appendChild(controlsRow);
|
|
||||||
|
|
||||||
grid.appendChild(card);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function renderThumbnail(pdfDoc, originalPageNum, canvasElement, rotation) {
|
|
||||||
try {
|
|
||||||
const page = await pdfDoc.getPage(originalPageNum);
|
|
||||||
const ctx = canvasElement.getContext('2d');
|
|
||||||
|
|
||||||
// Render at a fixed width of 100px
|
|
||||||
const unscaledViewport = page.getViewport({ scale: 1, rotation: 0 });
|
|
||||||
const scale = 100 / unscaledViewport.width;
|
|
||||||
const viewport = page.getViewport({ scale: scale, rotation: 0 });
|
|
||||||
|
|
||||||
canvasElement.width = viewport.width;
|
|
||||||
canvasElement.height = viewport.height;
|
|
||||||
|
|
||||||
await page.render({
|
|
||||||
canvasContext: ctx,
|
|
||||||
viewport: viewport
|
|
||||||
}).promise;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error rendering page canvas:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncRotateInput() {
|
|
||||||
const rotatedPages = pdfPreviewState.pages
|
|
||||||
.filter(p => p.rotation > 0)
|
|
||||||
.map(p => p.originalPageNum);
|
|
||||||
|
|
||||||
const rotateInput = document.getElementById('rotate-pages');
|
|
||||||
if (rotateInput) {
|
|
||||||
rotateInput.value = rotatedPages.join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncDeleteInput() {
|
|
||||||
const deletedPages = pdfPreviewState.pages
|
|
||||||
.filter(p => p.isDeleted)
|
|
||||||
.map(p => p.originalPageNum);
|
|
||||||
|
|
||||||
const deleteInput = document.getElementById('delete-pages');
|
|
||||||
if (deleteInput) {
|
|
||||||
deleteInput.value = deletedPages.join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncReorderInput() {
|
|
||||||
const order = pdfPreviewState.pages.map(p => p.originalPageNum);
|
|
||||||
const reorderInput = document.getElementById('reorder-pages');
|
|
||||||
if (reorderInput) {
|
|
||||||
reorderInput.value = order.join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2372,254 +2372,3 @@ body[class*="dark"] .breadcrumb-bar {
|
|||||||
@keyframes analyticsSlideUp { from{transform:translateY(16px);opacity:0} to{transform:translateY(0);opacity:1} }
|
@keyframes analyticsSlideUp { from{transform:translateY(16px);opacity:0} to{transform:translateY(0);opacity:1} }
|
||||||
body[class*="dark"] .analytics-modal { background:var(--gray-800,#1f2937); }
|
body[class*="dark"] .analytics-modal { background:var(--gray-800,#1f2937); }
|
||||||
body[class*="dark"] .word-tag { background:var(--gray-700,#374151); }
|
body[class*="dark"] .word-tag { background:var(--gray-700,#374151); }
|
||||||
|
|
||||||
/* ========================================
|
|
||||||
Interactive PDF Thumbnail Sidebar Styles
|
|
||||||
======================================== */
|
|
||||||
.pdf-editor-body.side-by-side {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 16px;
|
|
||||||
height: 550px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-editor-left-panel {
|
|
||||||
flex: 1.2;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-right: 8px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-editor-right-panel {
|
|
||||||
flex: 0.8;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-left: 1px solid var(--border-color, #e5e7eb);
|
|
||||||
padding-left: 16px;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-editor-right-panel h4 {
|
|
||||||
margin: 0 0 4px 0;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-editor-right-panel .sidebar-help {
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--text-secondary, #6b7280);
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-grid {
|
|
||||||
flex: 1;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border: 1px solid var(--border-color, #e5e7eb);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--bg-primary, #ffffff);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
padding: 8px;
|
|
||||||
transition: all var(--transition-speed, 0.3s) var(--transition-ease);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
border-color: var(--primary-light, #8b9aff);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-card.marked-delete {
|
|
||||||
border-color: var(--error, #ef4444);
|
|
||||||
background: rgba(239, 68, 68, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-card.marked-delete::after {
|
|
||||||
content: "DELETE";
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%) rotate(-15deg);
|
|
||||||
background: rgba(239, 68, 68, 0.95);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
pointer-events: none;
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-header .badge {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
background: var(--bg-tertiary, #f3f4f6);
|
|
||||||
color: var(--text-primary);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: var(--font-mono, monospace);
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--bg-secondary, #f9fafb);
|
|
||||||
border: 1px solid var(--border-color, #e5e7eb);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px;
|
|
||||||
height: 120px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-canvas {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CSS Rotations */
|
|
||||||
.rot90 {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
.rot180 {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
.rot270 {
|
|
||||||
transform: rotate(270deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-controls {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-controls button,
|
|
||||||
.pdf-thumbnail-controls label {
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-rotate-thumbnail {
|
|
||||||
background: var(--bg-tertiary, #f3f4f6);
|
|
||||||
border: 1px solid var(--border-color, #e5e7eb);
|
|
||||||
color: var(--text-primary);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 3px 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
transition: all 0.2s;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-rotate-thumbnail:hover {
|
|
||||||
background: var(--primary-light, #8b9aff);
|
|
||||||
color: #fff;
|
|
||||||
border-color: var(--primary-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-thumbnail-checkbox {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-thumbnail-checkbox input {
|
|
||||||
margin: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-thumbnail-reorder {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-reorder-left,
|
|
||||||
.btn-reorder-right {
|
|
||||||
flex: 1;
|
|
||||||
background: var(--bg-tertiary, #f3f4f6);
|
|
||||||
border: 1px solid var(--border-color, #e5e7eb);
|
|
||||||
color: var(--text-primary);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-reorder-left:hover:not(:disabled),
|
|
||||||
.btn-reorder-right:hover:not(:disabled) {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-reorder-left:disabled,
|
|
||||||
.btn-reorder-right:disabled {
|
|
||||||
opacity: 0.3;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-thumbnails,
|
|
||||||
.thumbnail-error {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 20px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail-error {
|
|
||||||
color: var(--error, #ef4444);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode adjustments */
|
|
||||||
body[class*="dark"] .pdf-thumbnail-card {
|
|
||||||
background: var(--gray-800, #1f2937);
|
|
||||||
border-color: var(--gray-700, #374151);
|
|
||||||
}
|
|
||||||
body[class*="dark"] .pdf-thumbnail-header .badge {
|
|
||||||
background: var(--gray-700, #374151);
|
|
||||||
color: var(--gray-200);
|
|
||||||
}
|
|
||||||
body[class*="dark"] .canvas-container {
|
|
||||||
background: var(--gray-900, #111827);
|
|
||||||
border-color: var(--gray-700);
|
|
||||||
}
|
|
||||||
body[class*="dark"] .btn-rotate-thumbnail,
|
|
||||||
body[class*="dark"] .btn-reorder-left,
|
|
||||||
body[class*="dark"] .btn-reorder-right {
|
|
||||||
background: var(--gray-700, #374151);
|
|
||||||
border-color: var(--gray-600);
|
|
||||||
color: var(--gray-200);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Tauri Commands Bridge for MarkdownConverter
|
||||||
|
// Replaces preload.js — uses Tauri's invoke() instead of ipcRenderer
|
||||||
|
|
||||||
|
const { invoke } = window.__TAURI__.core;
|
||||||
|
const { listen } = window.__TAURI__.event;
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// FILE OPERATIONS
|
||||||
|
// ============================================
|
||||||
|
export const file = {
|
||||||
|
read: (path) => invoke('read_file', { path }),
|
||||||
|
write: (path, content) => invoke('write_file', { path, content }),
|
||||||
|
delete: (path) => invoke('delete_file', { path }),
|
||||||
|
exists: (path) => invoke('path_exists', { path }),
|
||||||
|
isDirectory: (path) => invoke('is_directory', { path }),
|
||||||
|
listDirectory: (path) => invoke('list_directory', { path }),
|
||||||
|
ensureDir: (path) => invoke('ensure_directory', { path }),
|
||||||
|
copy: (source, destination) => invoke('copy_path', { source, destination }),
|
||||||
|
move: (source, destination) => invoke('move_path', { source, destination }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// EXPORT OPERATIONS
|
||||||
|
// ============================================
|
||||||
|
export const exportDoc = {
|
||||||
|
markdown: (inputPath, outputPath, format, options) =>
|
||||||
|
invoke('export_markdown', { inputPath, outputPath, format, options }),
|
||||||
|
checkPandoc: () => invoke('check_pandoc_available', {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// GIT OPERATIONS
|
||||||
|
// ============================================
|
||||||
|
export const git = {
|
||||||
|
status: (repoPath) => invoke('git_status', { repoPath }),
|
||||||
|
stage: (repoPath, path) => invoke('git_stage', { repoPath, path }),
|
||||||
|
commit: (repoPath, message) => invoke('git_commit', { repoPath, message }),
|
||||||
|
log: (repoPath, limit) => invoke('git_log', { repoPath, limit }),
|
||||||
|
diff: (repoPath, path) => invoke('git_diff', { repoPath, path }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// APP OPERATIONS
|
||||||
|
// ============================================
|
||||||
|
export const app = {
|
||||||
|
getVersion: () => invoke('get_app_version'),
|
||||||
|
getRecentFiles: () => invoke('get_recent_files'),
|
||||||
|
saveRecentFiles: (files) => invoke('save_recent_files', { files }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// PDF OPERATIONS
|
||||||
|
// ============================================
|
||||||
|
export const pdf = {
|
||||||
|
getPageCount: (path) => invoke('get_pdf_page_count', { path }),
|
||||||
|
merge: (paths, output) => invoke('merge_pdfs', { paths, output }),
|
||||||
|
split: (input, outputDir, pagesPerSplit) => invoke('split_pdf', { input, outputDir, pagesPerSplit }),
|
||||||
|
rotate: (input, output, degrees) => invoke('rotate_pdf', { input, output, degrees }),
|
||||||
|
deletePages: (input, output, pages) => invoke('delete_pdf_pages', { input, output, pages }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// DIALOG OPERATIONS
|
||||||
|
// ============================================
|
||||||
|
export const dialog = {
|
||||||
|
openFile: () => invoke('open_file_dialog', {}),
|
||||||
|
saveFile: (defaultName) => invoke('save_file_dialog', { defaultName }),
|
||||||
|
selectFolder: () => invoke('select_folder_dialog', {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// EVENT LISTENERS
|
||||||
|
// ============================================
|
||||||
|
export const events = {
|
||||||
|
onFileNew: (callback) => listen('file-new', callback),
|
||||||
|
onFileOpened: (callback) => listen('file-opened', callback),
|
||||||
|
onFileSave: (callback) => listen('file-save', callback),
|
||||||
|
onTogglePreview: (callback) => listen('toggle-preview', callback),
|
||||||
|
onToggleFind: (callback) => listen('toggle-find', callback),
|
||||||
|
onUndo: (callback) => listen('undo', callback),
|
||||||
|
onRedo: (callback) => listen('redo', callback),
|
||||||
|
onThemeChanged: (callback) => listen('theme-changed', callback),
|
||||||
|
onToggleCommandPalette: (callback) => listen('toggle-command-palette', callback),
|
||||||
|
onToggleSidebarPanel: (callback) => listen('toggle-sidebar-panel', callback),
|
||||||
|
onToggleBottomPanel: (callback) => listen('toggle-bottom-panel', callback),
|
||||||
|
onConversionComplete: (callback) => listen('conversion-complete', callback),
|
||||||
|
onConversionStatus: (callback) => listen('conversion-status', callback),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// PLUGIN OPERATIONS
|
||||||
|
// ============================================
|
||||||
|
export const plugins = {
|
||||||
|
list: () => invoke('list_plugins', {}),
|
||||||
|
get: (name) => invoke('get_plugin', { name }),
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* ModalManager - Unified modal system with accessibility support
|
* ModalManager - Unified modal system with accessibility support
|
||||||
* @version 4.4.3
|
* @version 4.3.0
|
||||||
*/
|
*/
|
||||||
class ModalManager {
|
class ModalManager {
|
||||||
#modal;
|
#modal;
|
||||||
@@ -237,8 +237,8 @@ class ModalManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export for use in renderer - avoid duplicate declaration
|
// Export for use in renderer
|
||||||
if (typeof window !== 'undefined' && !window.ModalManager) {
|
if (typeof window !== 'undefined') {
|
||||||
window.ModalManager = ModalManager;
|
window.ModalManager = ModalManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: '.',
|
||||||
|
base: './',
|
||||||
|
build: {
|
||||||
|
outDir: '../dist',
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
},
|
||||||
|
clearScreen: false,
|
||||||
|
});
|
||||||
@@ -655,7 +655,7 @@ class WordTemplateExporter {
|
|||||||
*/
|
*/
|
||||||
parseInlineFormatting(text) {
|
parseInlineFormatting(text) {
|
||||||
let xml = '';
|
let xml = '';
|
||||||
const pos = 0;
|
let pos = 0;
|
||||||
|
|
||||||
// Patterns for inline formatting
|
// Patterns for inline formatting
|
||||||
const patterns = [
|
const patterns = [
|
||||||
|
|||||||
+1
-1
@@ -117,7 +117,7 @@ class ZenMode {
|
|||||||
_applyTypewriterBehavior(view) {
|
_applyTypewriterBehavior(view) {
|
||||||
// We use a custom update listener that scrolls the cursor to center.
|
// We use a custom update listener that scrolls the cursor to center.
|
||||||
// This is stored on the instance so we can clean it up.
|
// This is stored on the instance so we can clean it up.
|
||||||
const scrollTimeout = null;
|
let scrollTimeout = null;
|
||||||
|
|
||||||
const scrollFn = () => {
|
const scrollFn = () => {
|
||||||
if (!this.active) return;
|
if (!this.active) return;
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
/**
|
|
||||||
* Git Operations Utilities Test Suite
|
|
||||||
* Tests for async patterns and error handling
|
|
||||||
* @version 4.3.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('GitOperations Utilities', () => {
|
|
||||||
describe('error handling patterns', () => {
|
|
||||||
it('should handle git errors gracefully', () => {
|
|
||||||
const errorResponse = { error: 'Not a git repository' };
|
|
||||||
|
|
||||||
expect(errorResponse).toHaveProperty('error');
|
|
||||||
expect(errorResponse.error).toContain('repository');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error object on failure', () => {
|
|
||||||
const failureResult = { error: 'Failed to commit' };
|
|
||||||
|
|
||||||
expect(failureResult).toBeDefined();
|
|
||||||
expect(failureResult.error).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('async patterns', () => {
|
|
||||||
it('should handle async operations', async () => {
|
|
||||||
const asyncFn = async () => {
|
|
||||||
return { status: 'success' };
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await asyncFn();
|
|
||||||
expect(result).toHaveProperty('status');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle async errors', async () => {
|
|
||||||
const asyncFnWithError = async () => {
|
|
||||||
throw new Error('Git operation failed');
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await asyncFnWithError();
|
|
||||||
expect(true).toBe(false); // Should not reach here
|
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).toContain('Git');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('git operations', () => {
|
|
||||||
it('should document expected operations', () => {
|
|
||||||
const gitOps = [
|
|
||||||
'getStatus',
|
|
||||||
'stage',
|
|
||||||
'commit',
|
|
||||||
'log',
|
|
||||||
'diff',
|
|
||||||
'branch',
|
|
||||||
'checkout',
|
|
||||||
'push',
|
|
||||||
'pull'
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(gitOps.length).toBeGreaterThan(0);
|
|
||||||
gitOps.forEach(op => {
|
|
||||||
expect(typeof op).toBe('string');
|
|
||||||
expect(op.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle directory paths', () => {
|
|
||||||
const paths = [
|
|
||||||
'/home/user/project',
|
|
||||||
'./current/dir',
|
|
||||||
'../parent/dir'
|
|
||||||
];
|
|
||||||
|
|
||||||
paths.forEach(pathStr => {
|
|
||||||
expect(typeof pathStr).toBe('string');
|
|
||||||
expect(pathStr.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle commit messages', () => {
|
|
||||||
const messages = [
|
|
||||||
'fix: bug in git panel',
|
|
||||||
'feat: add new feature',
|
|
||||||
'refactor: clean up code'
|
|
||||||
];
|
|
||||||
|
|
||||||
messages.forEach(msg => {
|
|
||||||
expect(typeof msg).toBe('string');
|
|
||||||
expect(msg.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('response structures', () => {
|
|
||||||
it('should return status info', () => {
|
|
||||||
const statusResponse = {
|
|
||||||
conflicted: [],
|
|
||||||
created: [],
|
|
||||||
deleted: [],
|
|
||||||
modified: [],
|
|
||||||
renamed: [],
|
|
||||||
staged: ['file.md']
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(statusResponse).toHaveProperty('staged');
|
|
||||||
expect(Array.isArray(statusResponse.staged)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return log entries', () => {
|
|
||||||
const logResponse = {
|
|
||||||
all: [
|
|
||||||
{ hash: 'abc123', message: 'fix: something' }
|
|
||||||
],
|
|
||||||
latest: { hash: 'abc123', message: 'fix: something' }
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(logResponse).toHaveProperty('all');
|
|
||||||
expect(Array.isArray(logResponse.all)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -76,75 +76,6 @@ describe('Markdown Extensions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Admonitions full parsing integration (mocked environment)', () => {
|
|
||||||
const extension = {
|
|
||||||
name: 'admonition',
|
|
||||||
level: 'block',
|
|
||||||
start(src) { return src.match(/^:::(note|warning|tip|danger|info)/m)?.index; },
|
|
||||||
tokenizer(src) {
|
|
||||||
const match = src.match(/^:::(note|warning|tip|danger|info)\s*\n([\s\S]*?)^:::\s*$/m);
|
|
||||||
if (match && match.index === 0) {
|
|
||||||
const admonitionType = match[1];
|
|
||||||
const text = match[2].trim();
|
|
||||||
const tokens = [];
|
|
||||||
this.lexer.blockTokens(text, tokens);
|
|
||||||
return {
|
|
||||||
type: 'admonition',
|
|
||||||
raw: match[0],
|
|
||||||
admonitionType,
|
|
||||||
text,
|
|
||||||
tokens
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer(token) {
|
|
||||||
const icons = { note: 'ℹ', warning: '⚠', tip: '💡', danger: '🔴', info: 'ℹ' };
|
|
||||||
const icon = icons[token.admonitionType] || 'ℹ';
|
|
||||||
const inner = this.parser.parse(token.tokens || []);
|
|
||||||
return `<div class="admonition admonition-${token.admonitionType}">
|
|
||||||
<div class="admonition-title">${icon} ${token.admonitionType.charAt(0).toUpperCase() + token.admonitionType.slice(1)}</div>
|
|
||||||
<div class="admonition-content">${inner}</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test('tokenizer correctly extracts tokens and calls blockTokens', () => {
|
|
||||||
const src = ':::note\nThis is a note.\n:::';
|
|
||||||
const mockLexer = {
|
|
||||||
blockTokens: jest.fn((text, tokens) => {
|
|
||||||
tokens.push({ type: 'text', text });
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const context = { lexer: mockLexer };
|
|
||||||
const result = extension.tokenizer.call(context, src);
|
|
||||||
|
|
||||||
expect(result).toBeDefined();
|
|
||||||
expect(result.type).toBe('admonition');
|
|
||||||
expect(result.admonitionType).toBe('note');
|
|
||||||
expect(result.text).toBe('This is a note.');
|
|
||||||
expect(mockLexer.blockTokens).toHaveBeenCalledWith('This is a note.', expect.any(Array));
|
|
||||||
expect(result.tokens).toEqual([{ type: 'text', text: 'This is a note.' }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('renderer correctly translates tokens to HTML', () => {
|
|
||||||
const token = {
|
|
||||||
type: 'admonition',
|
|
||||||
admonitionType: 'warning',
|
|
||||||
tokens: [{ type: 'text', text: 'Be careful!' }]
|
|
||||||
};
|
|
||||||
const mockParser = {
|
|
||||||
parse: jest.fn((tokens) => '<p>Be careful!</p>')
|
|
||||||
};
|
|
||||||
const context = { parser: mockParser };
|
|
||||||
const html = extension.renderer.call(context, token);
|
|
||||||
|
|
||||||
expect(html).toContain('admonition admonition-warning');
|
|
||||||
expect(html).toContain('⚠ Warning');
|
|
||||||
expect(html).toContain('<p>Be careful!</p>');
|
|
||||||
expect(mockParser.parse).toHaveBeenCalledWith(token.tokens);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PlantUML hex encoding', () => {
|
describe('PlantUML hex encoding', () => {
|
||||||
const plantumlEncode = (text) => {
|
const plantumlEncode = (text) => {
|
||||||
const hex = Array.from(Buffer.from(text, 'utf-8'))
|
const hex = Array.from(Buffer.from(text, 'utf-8'))
|
||||||
@@ -196,53 +127,4 @@ describe('Markdown Extensions', () => {
|
|||||||
expect(slugify('simple')).toBe('simple');
|
expect(slugify('simple')).toBe('simple');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('scopeCSS utility', () => {
|
|
||||||
const scopeCSS = (cssText, scopeSelector) => {
|
|
||||||
if (!cssText) return '';
|
|
||||||
return cssText.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|(?=[^{]*{))/g, (match, selector, separator) => {
|
|
||||||
const trimmed = selector.trim();
|
|
||||||
if (!trimmed || trimmed.startsWith('@') || trimmed.startsWith(':root') || trimmed.startsWith('from') || trimmed.startsWith('to') || /^\d+%$/.test(trimmed)) {
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
return scopeSelector + ' ' + trimmed + (separator || '');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
test('scopes standard tag selector', () => {
|
|
||||||
const css = 'h1 { color: red; }';
|
|
||||||
const scoped = scopeCSS(css, '.preview-content');
|
|
||||||
expect(scoped).toBe('.preview-content h1{ color: red; }');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('scopes multiple class selectors', () => {
|
|
||||||
const css = '.title, .content { font-family: sans-serif; }';
|
|
||||||
const scoped = scopeCSS(css, '.preview-content');
|
|
||||||
expect(scoped).toBe('.preview-content .title,.preview-content .content{ font-family: sans-serif; }');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ignores @rules like @media', () => {
|
|
||||||
const css = '@media (max-width: 600px) { h1 { color: blue; } }';
|
|
||||||
const scoped = scopeCSS(css, '.preview-content');
|
|
||||||
expect(scoped).toContain('@media (max-width: 600px)');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ignores :root selector', () => {
|
|
||||||
const css = ':root { --color: red; }';
|
|
||||||
const scoped = scopeCSS(css, '.preview-content');
|
|
||||||
expect(scoped).toBe(':root { --color: red; }');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ignores keyframe percentages', () => {
|
|
||||||
const css = '0% { opacity: 0; } 100% { opacity: 1; }';
|
|
||||||
const scoped = scopeCSS(css, '.preview-content');
|
|
||||||
expect(scoped).toContain('0%');
|
|
||||||
expect(scoped).toContain('100%');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles empty input', () => {
|
|
||||||
expect(scopeCSS('', '.preview-content')).toBe('');
|
|
||||||
expect(scopeCSS(null, '.preview-content')).toBe('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
/**
|
|
||||||
* PDF Operations Utilities Test Suite
|
|
||||||
* Tests for helper functions and logic patterns
|
|
||||||
* @version 4.3.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('PDFOperations Utilities', () => {
|
|
||||||
describe('page range parsing', () => {
|
|
||||||
it('should parse single page numbers', () => {
|
|
||||||
// Test logic: parsing "1" should extract page index 0
|
|
||||||
const input = '1';
|
|
||||||
const pages = [0]; // Parsed result
|
|
||||||
|
|
||||||
expect(pages.length).toBe(1);
|
|
||||||
expect(pages[0]).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse page ranges', () => {
|
|
||||||
// Test logic: parsing "1-3" should extract pages 0, 1, 2
|
|
||||||
const input = '1-3';
|
|
||||||
const pages = [0, 1, 2]; // Expected result
|
|
||||||
|
|
||||||
expect(pages.length).toBe(3);
|
|
||||||
expect(pages).toEqual([0, 1, 2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle multiple ranges', () => {
|
|
||||||
// Test logic: parsing "1-2,4-5" should extract pages 0,1,3,4
|
|
||||||
const input = '1-2,4-5';
|
|
||||||
const pages = [0, 1, 3, 4]; // Expected result
|
|
||||||
|
|
||||||
expect(pages.length).toBe(4);
|
|
||||||
expect(pages).toEqual([0, 1, 3, 4]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sort pages in ascending order', () => {
|
|
||||||
const unsorted = [2, 0, 3, 1];
|
|
||||||
const sorted = unsorted.sort((a, b) => a - b);
|
|
||||||
|
|
||||||
expect(sorted).toEqual([0, 1, 2, 3]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('hex color conversion', () => {
|
|
||||||
it('should validate hex color format', () => {
|
|
||||||
const validHex = '#FF5733';
|
|
||||||
const isValid = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.test(validHex);
|
|
||||||
|
|
||||||
expect(isValid).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect invalid hex colors', () => {
|
|
||||||
const invalidColors = ['#GG5733', '#12345', 'notahex'];
|
|
||||||
|
|
||||||
invalidColors.forEach(color => {
|
|
||||||
const isValid = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.test(color);
|
|
||||||
expect(isValid).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('error handling', () => {
|
|
||||||
it('should handle empty input', () => {
|
|
||||||
const input = '';
|
|
||||||
expect(input.length).toBe(0);
|
|
||||||
// Should not process empty strings
|
|
||||||
expect(input === '').toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle invalid page numbers', () => {
|
|
||||||
const testPages = [
|
|
||||||
{ input: '0', isValid: false },
|
|
||||||
{ input: '-1', isValid: false },
|
|
||||||
{ input: 'abc', isValid: false },
|
|
||||||
{ input: '1', isValid: true },
|
|
||||||
{ input: '5', isValid: true }
|
|
||||||
];
|
|
||||||
|
|
||||||
testPages.forEach(({ input, isValid }) => {
|
|
||||||
const num = parseInt(input);
|
|
||||||
|
|
||||||
if (isNaN(num)) {
|
|
||||||
// Non-numeric string
|
|
||||||
expect(isValid).toBe(false);
|
|
||||||
} else if (num < 1) {
|
|
||||||
// Zero or negative
|
|
||||||
expect(isValid).toBe(false);
|
|
||||||
} else {
|
|
||||||
// Positive number
|
|
||||||
expect(isValid).toBe(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
/**
|
|
||||||
* Security: Path Traversal Prevention Test Suite
|
|
||||||
* @version 4.3.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
describe('Security: Path Handling', () => {
|
|
||||||
describe('path traversal prevention', () => {
|
|
||||||
it('should detect path traversal patterns', () => {
|
|
||||||
const maliciousPaths = [
|
|
||||||
'../etc/passwd',
|
|
||||||
'../../sensitive',
|
|
||||||
'./../outside'
|
|
||||||
];
|
|
||||||
|
|
||||||
maliciousPaths.forEach(pathStr => {
|
|
||||||
// Path traversal attempts contain .. patterns
|
|
||||||
expect(pathStr).toMatch(/\.\./);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should normalize relative paths safely', () => {
|
|
||||||
const safePaths = [
|
|
||||||
'./documents/file.md',
|
|
||||||
'relative/path/file.txt'
|
|
||||||
];
|
|
||||||
|
|
||||||
safePaths.forEach(pathStr => {
|
|
||||||
const normalized = path.normalize(pathStr);
|
|
||||||
// Safe relative paths should normalize cleanly
|
|
||||||
expect(normalized).toBeDefined();
|
|
||||||
expect(typeof normalized).toBe('string');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect absolute paths', () => {
|
|
||||||
const absolutePath = '/etc/passwd';
|
|
||||||
const isAbsolute = path.isAbsolute(absolutePath);
|
|
||||||
|
|
||||||
// Linux/Mac: /path is absolute
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
expect(isAbsolute).toBe(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should safely join paths with base directory', () => {
|
|
||||||
const baseDir = '/safe/base/directory';
|
|
||||||
const userInput = 'documents/file.md';
|
|
||||||
|
|
||||||
const joined = path.join(baseDir, userInput);
|
|
||||||
|
|
||||||
// Result should contain the safe base
|
|
||||||
expect(joined).toContain('base');
|
|
||||||
expect(joined).toContain('documents');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('filename safety', () => {
|
|
||||||
it('should identify safe filenames', () => {
|
|
||||||
const safeNames = [
|
|
||||||
'document.md',
|
|
||||||
'my-file.txt',
|
|
||||||
'file_name.pdf',
|
|
||||||
'report_2026_04_24.xlsx'
|
|
||||||
];
|
|
||||||
|
|
||||||
safeNames.forEach(name => {
|
|
||||||
// Safe names should not contain path separators or null bytes
|
|
||||||
const isSafe = !/[\\/\0]/.test(name) && name.length > 0;
|
|
||||||
expect(isSafe).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should flag filenames with path separators', () => {
|
|
||||||
const problematicNames = [
|
|
||||||
'file/with/slashes.txt',
|
|
||||||
'file\\with\\backslashes.txt'
|
|
||||||
];
|
|
||||||
|
|
||||||
problematicNames.forEach(name => {
|
|
||||||
// These contain path separators and should be flagged
|
|
||||||
const hasPathSeparators = /[\\/]/.test(name);
|
|
||||||
expect(hasPathSeparators).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should enforce minimum filename length', () => {
|
|
||||||
const emptyName = '';
|
|
||||||
|
|
||||||
expect(emptyName.length).toBe(0);
|
|
||||||
expect(emptyName.length > 0).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('validation patterns', () => {
|
|
||||||
it('should validate path existence check pattern', () => {
|
|
||||||
const validationPattern = /^[a-zA-Z0-9._\-/]+$/;
|
|
||||||
|
|
||||||
const validPaths = ['documents/file.md', 'folder_2026/data.csv'];
|
|
||||||
validPaths.forEach(pathStr => {
|
|
||||||
// These should match a reasonable filename pattern
|
|
||||||
expect(typeof pathStr).toBe('string');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should prevent null byte injection', () => {
|
|
||||||
const pathWithNullByte = 'file.txt\0.exe';
|
|
||||||
|
|
||||||
const isSafe = !pathWithNullByte.includes('\0');
|
|
||||||
expect(isSafe).toBe(false); // Has null byte, not safe
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user