mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
Compare 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.5
|
|
||||||
- **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.
|
|
||||||
@@ -162,4 +162,4 @@ Amit Haridas (amit.wh@gmail.com)
|
|||||||
|
|
||||||
## Version
|
## Version
|
||||||
|
|
||||||
v4.4.5
|
v4.1.0
|
||||||
|
|||||||
@@ -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.4",
|
"version": "4.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.4.4",
|
"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.5",
|
"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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +65,7 @@ const electronFsAdapter = {
|
|||||||
isDir: entry.isDirectory,
|
isDir: entry.isDirectory,
|
||||||
size: entry.size ?? 0,
|
size: entry.size ?? 0,
|
||||||
modified: entry.modified ?? 0,
|
modified: entry.modified ?? 0,
|
||||||
path: entry.path,
|
path: entry.path
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ const electronFsAdapter = {
|
|||||||
*/
|
*/
|
||||||
async move(source, dest) {
|
async move(source, dest) {
|
||||||
return await window.electronAPI.file.move(source, dest);
|
return await window.electronAPI.file.move(source, dest);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { electronFsAdapter };
|
module.exports = { electronFsAdapter };
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -61,15 +61,11 @@ function showAnalyticsModal(tabManager) {
|
|||||||
<span class="analytics-label">Avg Sentence</span>
|
<span class="analytics-label">Avg Sentence</span>
|
||||||
<span class="analytics-value">${metrics.avgSentenceLength} words</span>
|
<span class="analytics-value">${metrics.avgSentenceLength} words</span>
|
||||||
</div>
|
</div>
|
||||||
${
|
${metrics.longestSentenceLength > 0 ? `
|
||||||
metrics.longestSentenceLength > 0
|
|
||||||
? `
|
|
||||||
<div class="analytics-row analytics-longest">
|
<div class="analytics-row analytics-longest">
|
||||||
<span class="analytics-label">Longest (${metrics.longestSentenceLength} words)</span>
|
<span class="analytics-label">Longest (${metrics.longestSentenceLength} words)</span>
|
||||||
<span class="analytics-value analytics-sentence-preview">${escapeHtml(metrics.longestSentence)}</span>
|
<span class="analytics-value analytics-sentence-preview">${escapeHtml(metrics.longestSentence)}</span>
|
||||||
</div>`
|
</div>` : ''}
|
||||||
: ''
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="analytics-section">
|
<div class="analytics-section">
|
||||||
@@ -78,19 +74,13 @@ function showAnalyticsModal(tabManager) {
|
|||||||
<span class="analytics-label">Unique</span>
|
<span class="analytics-label">Unique</span>
|
||||||
<span class="analytics-value">${metrics.uniqueWordCount} / ${metrics.wordCount}<small>${metrics.lexicalDiversity}%</small></span>
|
<span class="analytics-value">${metrics.uniqueWordCount} / ${metrics.wordCount}<small>${metrics.lexicalDiversity}%</small></span>
|
||||||
</div>
|
</div>
|
||||||
${
|
${metrics.topWords.length > 0 ? `
|
||||||
metrics.topWords.length > 0
|
|
||||||
? `
|
|
||||||
<div class="word-cloud">
|
<div class="word-cloud">
|
||||||
${metrics.topWords
|
${metrics.topWords.map(w => {
|
||||||
.map((w) => {
|
|
||||||
const scale = 13 + Math.round((w.count / maxCount) * 3);
|
const scale = 13 + Math.round((w.count / maxCount) * 3);
|
||||||
return `<span class="word-tag" style="font-size:${scale}px">${escapeHtml(w.word)}<small>${w.count}</small></span>`;
|
return `<span class="word-tag" style="font-size:${scale}px">${escapeHtml(w.word)}<small>${w.count}</small></span>`;
|
||||||
})
|
}).join('')}
|
||||||
.join('')}
|
</div>` : ''}
|
||||||
</div>`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,74 +4,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const STOP_WORDS = new Set([
|
const STOP_WORDS = new Set([
|
||||||
'the',
|
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been',
|
||||||
'a',
|
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
||||||
'an',
|
'could', 'should', 'to', 'of', 'in', 'for', 'on', 'with',
|
||||||
'is',
|
'at', 'by', 'from', 'as', 'and', 'or', 'but', 'if', 'it',
|
||||||
'are',
|
'its', 'this', 'that', 'these', 'those', 'i', 'me', 'my',
|
||||||
'was',
|
'we', 'our', 'you', 'your', 'he', 'him', 'his', 'she', 'her',
|
||||||
'were',
|
'they', 'them', 'their', 'not', 'no', 'so', 'than', 'too',
|
||||||
'be',
|
'very', 'also', 'just', 'about', 'up', 'out', 'what', 'which', 'who'
|
||||||
'been',
|
|
||||||
'have',
|
|
||||||
'has',
|
|
||||||
'had',
|
|
||||||
'do',
|
|
||||||
'does',
|
|
||||||
'did',
|
|
||||||
'will',
|
|
||||||
'would',
|
|
||||||
'could',
|
|
||||||
'should',
|
|
||||||
'to',
|
|
||||||
'of',
|
|
||||||
'in',
|
|
||||||
'for',
|
|
||||||
'on',
|
|
||||||
'with',
|
|
||||||
'at',
|
|
||||||
'by',
|
|
||||||
'from',
|
|
||||||
'as',
|
|
||||||
'and',
|
|
||||||
'or',
|
|
||||||
'but',
|
|
||||||
'if',
|
|
||||||
'it',
|
|
||||||
'its',
|
|
||||||
'this',
|
|
||||||
'that',
|
|
||||||
'these',
|
|
||||||
'those',
|
|
||||||
'i',
|
|
||||||
'me',
|
|
||||||
'my',
|
|
||||||
'we',
|
|
||||||
'our',
|
|
||||||
'you',
|
|
||||||
'your',
|
|
||||||
'he',
|
|
||||||
'him',
|
|
||||||
'his',
|
|
||||||
'she',
|
|
||||||
'her',
|
|
||||||
'they',
|
|
||||||
'them',
|
|
||||||
'their',
|
|
||||||
'not',
|
|
||||||
'no',
|
|
||||||
'so',
|
|
||||||
'than',
|
|
||||||
'too',
|
|
||||||
'very',
|
|
||||||
'also',
|
|
||||||
'just',
|
|
||||||
'about',
|
|
||||||
'up',
|
|
||||||
'out',
|
|
||||||
'what',
|
|
||||||
'which',
|
|
||||||
'who',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function countSyllables(word) {
|
function countSyllables(word) {
|
||||||
@@ -108,23 +48,17 @@ function analyze(text) {
|
|||||||
avgSentenceLength: 0,
|
avgSentenceLength: 0,
|
||||||
longestSentence: '',
|
longestSentence: '',
|
||||||
longestSentenceLength: 0,
|
longestSentenceLength: 0,
|
||||||
topWords: [],
|
topWords: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const words = extractWords(text);
|
const words = extractWords(text);
|
||||||
const wordCount = words.length;
|
const wordCount = words.length;
|
||||||
|
|
||||||
const sentences = text
|
const sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(Boolean);
|
||||||
.split(/[.!?]+/)
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
const sentenceCount = Math.max(sentences.length, 1);
|
const sentenceCount = Math.max(sentences.length, 1);
|
||||||
|
|
||||||
const paragraphs = text
|
const paragraphs = text.split(/\n\s*\n/).map(p => p.trim()).filter(Boolean);
|
||||||
.split(/\n\s*\n/)
|
|
||||||
.map((p) => p.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
const paragraphCount = Math.max(paragraphs.length, 1);
|
const paragraphCount = Math.max(paragraphs.length, 1);
|
||||||
|
|
||||||
let totalSyllables = 0;
|
let totalSyllables = 0;
|
||||||
@@ -132,23 +66,16 @@ function analyze(text) {
|
|||||||
totalSyllables += countSyllables(w);
|
totalSyllables += countSyllables(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fleschEase =
|
const fleschEase = Math.round((206.835 - 1.015 * (wordCount / sentenceCount) - 84.6 * (totalSyllables / wordCount)) * 10) / 10;
|
||||||
Math.round(
|
const fleschGrade = Math.round((0.39 * (wordCount / sentenceCount) + 11.8 * (totalSyllables / wordCount) - 15.59) * 10) / 10;
|
||||||
(206.835 - 1.015 * (wordCount / sentenceCount) - 84.6 * (totalSyllables / wordCount)) * 10
|
|
||||||
) / 10;
|
|
||||||
const fleschGrade =
|
|
||||||
Math.round(
|
|
||||||
(0.39 * (wordCount / sentenceCount) + 11.8 * (totalSyllables / wordCount) - 15.59) * 10
|
|
||||||
) / 10;
|
|
||||||
const readabilityLabel = getReadabilityLabel(fleschEase);
|
const readabilityLabel = getReadabilityLabel(fleschEase);
|
||||||
|
|
||||||
const readingTime = Math.ceil(wordCount / 200);
|
const readingTime = Math.ceil(wordCount / 200);
|
||||||
const speakingTime = Math.ceil(wordCount / 130);
|
const speakingTime = Math.ceil(wordCount / 130);
|
||||||
|
|
||||||
const uniqueWords = new Set(words.map((w) => w.toLowerCase()));
|
const uniqueWords = new Set(words.map(w => w.toLowerCase()));
|
||||||
const uniqueWordCount = uniqueWords.size;
|
const uniqueWordCount = uniqueWords.size;
|
||||||
const lexicalDiversity =
|
const lexicalDiversity = wordCount > 0 ? Math.round((uniqueWordCount / wordCount) * 1000) / 10 : 0;
|
||||||
wordCount > 0 ? Math.round((uniqueWordCount / wordCount) * 1000) / 10 : 0;
|
|
||||||
|
|
||||||
const avgSentenceLength = Math.round((wordCount / sentenceCount) * 10) / 10;
|
const avgSentenceLength = Math.round((wordCount / sentenceCount) * 10) / 10;
|
||||||
|
|
||||||
@@ -193,7 +120,7 @@ function analyze(text) {
|
|||||||
avgSentenceLength,
|
avgSentenceLength,
|
||||||
longestSentence,
|
longestSentence,
|
||||||
longestSentenceLength,
|
longestSentenceLength,
|
||||||
topWords,
|
topWords
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+139
-288
@@ -1,13 +1,10 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>ASCII Art Generator - MarkdownConverter</title>
|
<title>ASCII Art Generator - MarkdownConverter</title>
|
||||||
<link
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--ci-dark-gray: #464646;
|
--ci-dark-gray: #464646;
|
||||||
@@ -106,9 +103,7 @@
|
|||||||
color: var(--ci-dark-gray);
|
color: var(--ci-dark-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-input,
|
.form-input, .form-select, .form-textarea {
|
||||||
.form-select,
|
|
||||||
.form-textarea {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
border: 2px solid var(--ci-light-gray);
|
border: 2px solid var(--ci-light-gray);
|
||||||
@@ -118,9 +113,7 @@
|
|||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-input:focus,
|
.form-input:focus, .form-select:focus, .form-textarea:focus {
|
||||||
.form-select:focus,
|
|
||||||
.form-textarea:focus {
|
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--ci-accent);
|
border-color: var(--ci-accent);
|
||||||
}
|
}
|
||||||
@@ -234,8 +227,8 @@
|
|||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>ASCII Art Generator</h1>
|
<h1>ASCII Art Generator</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -252,13 +245,7 @@
|
|||||||
<div id="text-mode" class="mode-section active">
|
<div id="text-mode" class="mode-section active">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Text to Convert</label>
|
<label class="form-label">Text to Convert</label>
|
||||||
<input
|
<input type="text" id="text-input" class="form-input" placeholder="Enter your text..." maxlength="30">
|
||||||
type="text"
|
|
||||||
id="text-input"
|
|
||||||
class="form-input"
|
|
||||||
placeholder="Enter your text..."
|
|
||||||
maxlength="30"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Style</label>
|
<label class="form-label">Style</label>
|
||||||
@@ -276,11 +263,7 @@
|
|||||||
<div id="box-mode" class="mode-section">
|
<div id="box-mode" class="mode-section">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Text Content</label>
|
<label class="form-label">Text Content</label>
|
||||||
<textarea
|
<textarea id="box-text" class="form-textarea" placeholder="Enter text for the box..."></textarea>
|
||||||
id="box-text"
|
|
||||||
class="form-textarea"
|
|
||||||
placeholder="Enter text for the box..."
|
|
||||||
></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Box Style</label>
|
<label class="form-label">Box Style</label>
|
||||||
@@ -294,15 +277,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Padding</label>
|
<label class="form-label">Padding</label>
|
||||||
<input
|
<input type="number" id="box-padding" class="form-input" min="0" max="10" value="2" style="width: 100px;">
|
||||||
type="number"
|
|
||||||
id="box-padding"
|
|
||||||
class="form-input"
|
|
||||||
min="0"
|
|
||||||
max="10"
|
|
||||||
value="2"
|
|
||||||
style="width: 100px"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -366,262 +341,138 @@
|
|||||||
standard: {
|
standard: {
|
||||||
height: 5,
|
height: 5,
|
||||||
chars: {
|
chars: {
|
||||||
A: [' /\\ ', ' / \\ ', '/----\\', '| |', '| |'],
|
'A': [' /\\ ', ' / \\ ', '/----\\', '| |', '| |'],
|
||||||
B: ['|----\\', '| |', '|----/', '| \\', '|----/'],
|
'B': ['|----\\', '| |', '|----/', '| \\', '|----/'],
|
||||||
C: ['/----\\', '| ', '| ', '| ', '\\----/'],
|
'C': ['/----\\', '| ', '| ', '| ', '\\----/'],
|
||||||
D: ['|----\\', '| |', '| |', '| |', '|----/'],
|
'D': ['|----\\', '| |', '| |', '| |', '|----/'],
|
||||||
E: ['|----', '| ', '|--- ', '| ', '|----'],
|
'E': ['|----', '| ', '|--- ', '| ', '|----'],
|
||||||
F: ['|----', '| ', '|--- ', '| ', '| '],
|
'F': ['|----', '| ', '|--- ', '| ', '| '],
|
||||||
G: ['/----\\', '| ', '| |--\\', '| |', '\\----/'],
|
'G': ['/----\\', '| ', '| |--\\', '| |', '\\----/'],
|
||||||
H: ['| |', '| |', '|----/', '| |', '| |'],
|
'H': ['| |', '| |', '|----/', '| |', '| |'],
|
||||||
I: ['|---|', ' | ', ' | ', ' | ', '|---|'],
|
'I': ['|---|', ' | ', ' | ', ' | ', '|---|'],
|
||||||
J: [' |', ' |', ' |', '| |', '\\---/'],
|
'J': [' |', ' |', ' |', '| |', '\\---/'],
|
||||||
K: ['| /', '| / ', '|-- ', '| \\ ', '| \\'],
|
'K': ['| /', '| / ', '|-- ', '| \\ ', '| \\'],
|
||||||
L: ['| ', '| ', '| ', '| ', '|----'],
|
'L': ['| ', '| ', '| ', '| ', '|----'],
|
||||||
M: ['|\\ /|', '| \\/ |', '| |', '| |', '| |'],
|
'M': ['|\\ /|', '| \\/ |', '| |', '| |', '| |'],
|
||||||
N: ['|\\ |', '| \\ |', '| \\ |', '| \\|', '| |'],
|
'N': ['|\\ |', '| \\ |', '| \\ |', '| \\|', '| |'],
|
||||||
O: ['/----\\', '| |', '| |', '| |', '\\----/'],
|
'O': ['/----\\', '| |', '| |', '| |', '\\----/'],
|
||||||
P: ['|----\\', '| |', '|----/', '| ', '| '],
|
'P': ['|----\\', '| |', '|----/', '| ', '| '],
|
||||||
Q: ['/----\\', '| |', '| \\ |', '| \\|', '\\----\\'],
|
'Q': ['/----\\', '| |', '| \\ |', '| \\|', '\\----\\'],
|
||||||
R: ['|----\\', '| |', '|----/', '| \\ ', '| \\ '],
|
'R': ['|----\\', '| |', '|----/', '| \\ ', '| \\ '],
|
||||||
S: ['/----\\', '| ', '\\----\\', ' |', '\\----/'],
|
'S': ['/----\\', '| ', '\\----\\', ' |', '\\----/'],
|
||||||
T: ['-----', ' | ', ' | ', ' | ', ' | '],
|
'T': ['-----', ' | ', ' | ', ' | ', ' | '],
|
||||||
U: ['| |', '| |', '| |', '| |', '\\----/'],
|
'U': ['| |', '| |', '| |', '| |', '\\----/'],
|
||||||
V: ['| |', '| |', ' \\ / ', ' \\/ ', ' '],
|
'V': ['| |', '| |', ' \\ / ', ' \\/ ', ' '],
|
||||||
W: ['| |', '| |', '| |', '| /\\ |', '|/ \\|'],
|
'W': ['| |', '| |', '| |', '| /\\ |', '|/ \\|'],
|
||||||
X: ['\\ /', ' \\ / ', ' \\/ ', ' /\\ ', ' / \\ '],
|
'X': ['\\ /', ' \\ / ', ' \\/ ', ' /\\ ', ' / \\ '],
|
||||||
Y: ['\\ /', ' \\ / ', ' | ', ' | ', ' | '],
|
'Y': ['\\ /', ' \\ / ', ' | ', ' | ', ' | '],
|
||||||
Z: ['-----', ' / ', ' / ', ' / ', '-----'],
|
'Z': ['-----', ' / ', ' / ', ' / ', '-----'],
|
||||||
' ': [' ', ' ', ' ', ' ', ' '],
|
' ': [' ', ' ', ' ', ' ', ' '],
|
||||||
0: ['/---\\', '| |', '| / |', '|/ |', '\\---/'],
|
'0': ['/---\\', '| |', '| / |', '|/ |', '\\---/'],
|
||||||
1: [' /| ', ' / | ', ' | ', ' | ', ' ----'],
|
'1': [' /| ', ' / | ', ' | ', ' | ', ' ----'],
|
||||||
2: ['/---\\', ' |', ' ---/', '/ ', '-----'],
|
'2': ['/---\\', ' |', ' ---/', '/ ', '-----'],
|
||||||
3: ['----\\', ' |', ' ---/', ' |', '----/'],
|
'3': ['----\\', ' |', ' ---/', ' |', '----/'],
|
||||||
4: ['| |', '| |', '-----', ' |', ' |'],
|
'4': ['| |', '| |', '-----', ' |', ' |'],
|
||||||
5: ['-----', '| ', '----\\', ' |', '----/'],
|
'5': ['-----', '| ', '----\\', ' |', '----/'],
|
||||||
6: ['/----', '| ', '|---\\', '| |', '\\---/'],
|
'6': ['/----', '| ', '|---\\', '| |', '\\---/'],
|
||||||
7: ['-----', ' / ', ' / ', ' / ', '/ '],
|
'7': ['-----', ' / ', ' / ', ' / ', '/ '],
|
||||||
8: ['/---\\', '| |', ' --- ', '| |', '\\---/'],
|
'8': ['/---\\', '| |', ' --- ', '| |', '\\---/'],
|
||||||
9: ['/---\\', '| |', '\\----', ' |', '----/'],
|
'9': ['/---\\', '| |', '\\----', ' |', '----/']
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
banner: {
|
banner: {
|
||||||
height: 7,
|
height: 7,
|
||||||
chars: {
|
chars: {
|
||||||
A: [
|
'A': [' ##### ', ' ## ##', '## ##', '#########', '## ##', '## ##', '## ##'],
|
||||||
' ##### ',
|
'B': ['######## ', '## ##', '## ##', '######## ', '## ##', '## ##', '######## '],
|
||||||
' ## ##',
|
'C': [' ###### ', '## ##', '## ', '## ', '## ', '## ##', ' ###### '],
|
||||||
'## ##',
|
'D': ['######## ', '## ##', '## ##', '## ##', '## ##', '## ##', '######## '],
|
||||||
'#########',
|
'E': ['########', '## ', '## ', '###### ', '## ', '## ', '########'],
|
||||||
'## ##',
|
'F': ['########', '## ', '## ', '###### ', '## ', '## ', '## '],
|
||||||
'## ##',
|
'G': [' ###### ', '## ##', '## ', '## ####', '## ##', '## ##', ' ###### '],
|
||||||
'## ##',
|
'H': ['## ##', '## ##', '## ##', '#########', '## ##', '## ##', '## ##'],
|
||||||
],
|
'I': ['####', ' ## ', ' ## ', ' ## ', ' ## ', ' ## ', '####'],
|
||||||
B: [
|
'J': [' ##', ' ##', ' ##', ' ##', '## ##', '## ##', ' ###### '],
|
||||||
'######## ',
|
'K': ['## ##', '## ## ', '## ## ', '##### ', '## ## ', '## ## ', '## ##'],
|
||||||
'## ##',
|
'L': ['## ', '## ', '## ', '## ', '## ', '## ', '########'],
|
||||||
'## ##',
|
'M': ['## ##', '### ###', '#### ####', '## ### ##', '## ##', '## ##', '## ##'],
|
||||||
'######## ',
|
'N': ['## ##', '### ##', '#### ##', '## ## ##', '## ####', '## ###', '## ##'],
|
||||||
'## ##',
|
'O': [' ####### ', '## ##', '## ##', '## ##', '## ##', '## ##', ' ####### '],
|
||||||
'## ##',
|
'P': ['######## ', '## ##', '## ##', '######## ', '## ', '## ', '## '],
|
||||||
'######## ',
|
'Q': [' ####### ', '## ##', '## ##', '## ##', '## ## ##', '## ## ', ' ##### ##'],
|
||||||
],
|
'R': ['######## ', '## ##', '## ##', '######## ', '## ## ', '## ## ', '## ##'],
|
||||||
C: [' ###### ', '## ##', '## ', '## ', '## ', '## ##', ' ###### '],
|
'S': [' ###### ', '## ##', '## ', ' ###### ', ' ##', '## ##', ' ###### '],
|
||||||
D: [
|
'T': ['########', ' ## ', ' ## ', ' ## ', ' ## ', ' ## ', ' ## '],
|
||||||
'######## ',
|
'U': ['## ##', '## ##', '## ##', '## ##', '## ##', '## ##', ' ####### '],
|
||||||
'## ##',
|
'V': ['## ##', '## ##', '## ##', '## ##', ' ## ## ', ' ## ## ', ' ### '],
|
||||||
'## ##',
|
'W': ['## ##', '## ## ##', '## ## ##', '## ## ##', '## ## ##', '## ## ##', ' ### ### '],
|
||||||
'## ##',
|
'X': ['## ##', ' ## ## ', ' ## ## ', ' ### ', ' ## ## ', ' ## ## ', '## ##'],
|
||||||
'## ##',
|
'Y': ['## ##', ' ## ## ', ' #### ', ' ## ', ' ## ', ' ## ', ' ## '],
|
||||||
'## ##',
|
'Z': ['########', ' ## ', ' ## ', ' ## ', ' ## ', ' ## ', '########'],
|
||||||
'######## ',
|
|
||||||
],
|
|
||||||
E: ['########', '## ', '## ', '###### ', '## ', '## ', '########'],
|
|
||||||
F: ['########', '## ', '## ', '###### ', '## ', '## ', '## '],
|
|
||||||
G: [' ###### ', '## ##', '## ', '## ####', '## ##', '## ##', ' ###### '],
|
|
||||||
H: [
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'#########',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
],
|
|
||||||
I: ['####', ' ## ', ' ## ', ' ## ', ' ## ', ' ## ', '####'],
|
|
||||||
J: [' ##', ' ##', ' ##', ' ##', '## ##', '## ##', ' ###### '],
|
|
||||||
K: ['## ##', '## ## ', '## ## ', '##### ', '## ## ', '## ## ', '## ##'],
|
|
||||||
L: ['## ', '## ', '## ', '## ', '## ', '## ', '########'],
|
|
||||||
M: [
|
|
||||||
'## ##',
|
|
||||||
'### ###',
|
|
||||||
'#### ####',
|
|
||||||
'## ### ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
],
|
|
||||||
N: ['## ##', '### ##', '#### ##', '## ## ##', '## ####', '## ###', '## ##'],
|
|
||||||
O: [
|
|
||||||
' ####### ',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
' ####### ',
|
|
||||||
],
|
|
||||||
P: [
|
|
||||||
'######## ',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'######## ',
|
|
||||||
'## ',
|
|
||||||
'## ',
|
|
||||||
'## ',
|
|
||||||
],
|
|
||||||
Q: [
|
|
||||||
' ####### ',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ## ##',
|
|
||||||
'## ## ',
|
|
||||||
' ##### ##',
|
|
||||||
],
|
|
||||||
R: [
|
|
||||||
'######## ',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'######## ',
|
|
||||||
'## ## ',
|
|
||||||
'## ## ',
|
|
||||||
'## ##',
|
|
||||||
],
|
|
||||||
S: [' ###### ', '## ##', '## ', ' ###### ', ' ##', '## ##', ' ###### '],
|
|
||||||
T: ['########', ' ## ', ' ## ', ' ## ', ' ## ', ' ## ', ' ## '],
|
|
||||||
U: [
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
' ####### ',
|
|
||||||
],
|
|
||||||
V: [
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
'## ##',
|
|
||||||
' ## ## ',
|
|
||||||
' ## ## ',
|
|
||||||
' ### ',
|
|
||||||
],
|
|
||||||
W: [
|
|
||||||
'## ##',
|
|
||||||
'## ## ##',
|
|
||||||
'## ## ##',
|
|
||||||
'## ## ##',
|
|
||||||
'## ## ##',
|
|
||||||
'## ## ##',
|
|
||||||
' ### ### ',
|
|
||||||
],
|
|
||||||
X: [
|
|
||||||
'## ##',
|
|
||||||
' ## ## ',
|
|
||||||
' ## ## ',
|
|
||||||
' ### ',
|
|
||||||
' ## ## ',
|
|
||||||
' ## ## ',
|
|
||||||
'## ##',
|
|
||||||
],
|
|
||||||
Y: ['## ##', ' ## ## ', ' #### ', ' ## ', ' ## ', ' ## ', ' ## '],
|
|
||||||
Z: ['########', ' ## ', ' ## ', ' ## ', ' ## ', ' ## ', '########'],
|
|
||||||
' ': [' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
' ': [' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
||||||
0: [' ###### ', '## ##', '## ##', '## ##', '## ##', '## ##', ' ###### '],
|
'0': [' ###### ', '## ##', '## ##', '## ##', '## ##', '## ##', ' ###### '],
|
||||||
1: [' ## ', ' #### ', ' ## ', ' ## ', ' ## ', ' ## ', ' ###### '],
|
'1': [' ## ', ' #### ', ' ## ', ' ## ', ' ## ', ' ## ', ' ###### '],
|
||||||
2: [' ###### ', '## ##', ' ## ', ' ## ', ' ## ', ' ## ', '########'],
|
'2': [' ###### ', '## ##', ' ## ', ' ## ', ' ## ', ' ## ', '########'],
|
||||||
3: [' ###### ', '## ##', ' ## ', ' #### ', ' ## ', '## ##', ' ###### '],
|
'3': [' ###### ', '## ##', ' ## ', ' #### ', ' ## ', '## ##', ' ###### '],
|
||||||
4: [' ## ', ' ### ', ' # ## ', ' # ## ', '########', ' ## ', ' ## '],
|
'4': [' ## ', ' ### ', ' # ## ', ' # ## ', '########', ' ## ', ' ## '],
|
||||||
5: ['########', '## ', '####### ', ' ##', ' ##', '## ##', ' ###### '],
|
'5': ['########', '## ', '####### ', ' ##', ' ##', '## ##', ' ###### '],
|
||||||
6: [' ###### ', '## ', '####### ', '## ##', '## ##', '## ##', ' ###### '],
|
'6': [' ###### ', '## ', '####### ', '## ##', '## ##', '## ##', ' ###### '],
|
||||||
7: ['########', '## ## ', ' ## ', ' ## ', ' ## ', ' ## ', ' ## '],
|
'7': ['########', '## ## ', ' ## ', ' ## ', ' ## ', ' ## ', ' ## '],
|
||||||
8: [' ###### ', '## ##', '## ##', ' ###### ', '## ##', '## ##', ' ###### '],
|
'8': [' ###### ', '## ##', '## ##', ' ###### ', '## ##', '## ##', ' ###### '],
|
||||||
9: [' ###### ', '## ##', '## ##', ' #######', ' ##', '## ##', ' ###### '],
|
'9': [' ###### ', '## ##', '## ##', ' #######', ' ##', '## ##', ' ###### ']
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
block: {
|
block: {
|
||||||
height: 6,
|
height: 6,
|
||||||
chars: {
|
chars: {
|
||||||
A: ['█████╗ ', '██╔══██╗', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
|
'A': ['█████╗ ', '██╔══██╗', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
|
||||||
B: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██████╔╝', '╚═════╝ '],
|
'B': ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██████╔╝', '╚═════╝ '],
|
||||||
C: ['█████╗ ', '██╔══██╗', '██║ ', '██║ ', '╚█████╔╝', ' ╚════╝ '],
|
'C': ['█████╗ ', '██╔══██╗', '██║ ', '██║ ', '╚█████╔╝', ' ╚════╝ '],
|
||||||
D: ['██████╗ ', '██╔══██╗', '██║ ██║', '██║ ██║', '██████╔╝', '╚═════╝ '],
|
'D': ['██████╗ ', '██╔══██╗', '██║ ██║', '██║ ██║', '██████╔╝', '╚═════╝ '],
|
||||||
E: ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
|
'E': ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
|
||||||
F: ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '██║ ', '╚═╝ '],
|
'F': ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '██║ ', '╚═╝ '],
|
||||||
G: ['█████╗ ', '██╔══██╗', '██║ ███', '██║ ██', '╚█████╔╝', ' ╚════╝ '],
|
'G': ['█████╗ ', '██╔══██╗', '██║ ███', '██║ ██', '╚█████╔╝', ' ╚════╝ '],
|
||||||
H: ['██╗ ██╗', '██║ ██║', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
|
'H': ['██╗ ██╗', '██║ ██║', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
|
||||||
I: ['██╗', '██║', '██║', '██║', '██║', '╚═╝'],
|
'I': ['██╗', '██║', '██║', '██║', '██║', '╚═╝'],
|
||||||
J: [' ██╗', ' ██║', ' ██║', '██ ██║', '╚████╔╝', ' ╚═══╝ '],
|
'J': [' ██╗', ' ██║', ' ██║', '██ ██║', '╚████╔╝', ' ╚═══╝ '],
|
||||||
K: ['██╗ ██╗', '██║ ██╔╝', '█████╔╝ ', '██╔═██╗ ', '██║ ██╗', '╚═╝ ╚═╝'],
|
'K': ['██╗ ██╗', '██║ ██╔╝', '█████╔╝ ', '██╔═██╗ ', '██║ ██╗', '╚═╝ ╚═╝'],
|
||||||
L: ['██╗ ', '██║ ', '██║ ', '██║ ', '███████╗', '╚══════╝'],
|
'L': ['██╗ ', '██║ ', '██║ ', '██║ ', '███████╗', '╚══════╝'],
|
||||||
M: [
|
'M': ['███╗ ███╗', '████╗ ████║', '██╔████╔██║', '██║╚██╔╝██║', '██║ ╚═╝ ██║', '╚═╝ ╚═╝'],
|
||||||
'███╗ ███╗',
|
'N': ['███╗ ██╗', '████╗ ██║', '██╔██╗ ██║', '██║╚██╗██║', '██║ ╚████║', '╚═╝ ╚═══╝'],
|
||||||
'████╗ ████║',
|
'O': ['█████╗ ', '██╔══██╗', '██║ ██║', '██║ ██║', '╚█████╔╝', ' ╚════╝ '],
|
||||||
'██╔████╔██║',
|
'P': ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔═══╝ ', '██║ ', '╚═╝ '],
|
||||||
'██║╚██╔╝██║',
|
'Q': ['█████╗ ', '██╔══██╗', '██║ ██║', '██║ ██║', '╚██████╗', ' ╚═══██╝'],
|
||||||
'██║ ╚═╝ ██║',
|
'R': ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██║ ██║', '╚═╝ ╚═╝'],
|
||||||
'╚═╝ ╚═╝',
|
'S': ['█████╗ ', '██╔══╝ ', '█████╗ ', '╚══██║ ', '█████║ ', '╚════╝ '],
|
||||||
],
|
'T': ['████████╗', '╚══██╔══╝', ' ██║ ', ' ██║ ', ' ██║ ', ' ╚═╝ '],
|
||||||
N: ['███╗ ██╗', '████╗ ██║', '██╔██╗ ██║', '██║╚██╗██║', '██║ ╚████║', '╚═╝ ╚═══╝'],
|
'U': ['██╗ ██╗', '██║ ██║', '██║ ██║', '██║ ██║', '╚█████╔╝', ' ╚════╝ '],
|
||||||
O: ['█████╗ ', '██╔══██╗', '██║ ██║', '██║ ██║', '╚█████╔╝', ' ╚════╝ '],
|
'V': ['██╗ ██╗', '██║ ██║', '██║ ██║', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚═══╝ '],
|
||||||
P: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔═══╝ ', '██║ ', '╚═╝ '],
|
'W': ['██╗ ██╗', '██║ ██║', '██║ █╗ ██║', '██║███╗██║', '╚███╔███╔╝', ' ╚══╝╚══╝ '],
|
||||||
Q: ['█████╗ ', '██╔══██╗', '██║ ██║', '██║ ██║', '╚██████╗', ' ╚═══██╝'],
|
'X': ['██╗ ██╗', '╚██╗██╔╝', ' ╚███╔╝ ', ' ██╔██╗ ', '██╔╝ ██╗', '╚═╝ ╚═╝'],
|
||||||
R: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██║ ██║', '╚═╝ ╚═╝'],
|
'Y': ['██╗ ██╗', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚██╔╝ ', ' ██║ ', ' ╚═╝ '],
|
||||||
S: ['█████╗ ', '██╔══╝ ', '█████╗ ', '╚══██║ ', '█████║ ', '╚════╝ '],
|
'Z': ['███████╗', '╚════██║', ' ███╔═╝', ' ██╔══╝ ', '███████╗', '╚══════╝'],
|
||||||
T: ['████████╗', '╚══██╔══╝', ' ██║ ', ' ██║ ', ' ██║ ', ' ╚═╝ '],
|
' ': [' ', ' ', ' ', ' ', ' ', ' ']
|
||||||
U: ['██╗ ██╗', '██║ ██║', '██║ ██║', '██║ ██║', '╚█████╔╝', ' ╚════╝ '],
|
}
|
||||||
V: ['██╗ ██╗', '██║ ██║', '██║ ██║', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚═══╝ '],
|
}
|
||||||
W: ['██╗ ██╗', '██║ ██║', '██║ █╗ ██║', '██║███╗██║', '╚███╔███╔╝', ' ╚══╝╚══╝ '],
|
|
||||||
X: ['██╗ ██╗', '╚██╗██╔╝', ' ╚███╔╝ ', ' ██╔██╗ ', '██╔╝ ██╗', '╚═╝ ╚═╝'],
|
|
||||||
Y: ['██╗ ██╗', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚██╔╝ ', ' ██║ ', ' ╚═╝ '],
|
|
||||||
Z: ['███████╗', '╚════██║', ' ███╔═╝', ' ██╔══╝ ', '███████╗', '╚══════╝'],
|
|
||||||
' ': [' ', ' ', ' ', ' ', ' ', ' '],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TEMPLATES = {
|
const TEMPLATES = {
|
||||||
'arrow-right':
|
'arrow-right': ' ┌─────────────────────┐\n──▶│ Process or Action │──▶\n └─────────────────────┘',
|
||||||
' ┌─────────────────────┐\n──▶│ Process or Action │──▶\n └─────────────────────┘',
|
'arrow-down': ' │\n ▼\n┌───────────────┐\n│ Process │\n└───────────────┘\n │\n ▼',
|
||||||
'arrow-down':
|
'decision': ' ╱╲\n ╱ ╲\n ╱ ? ╲\n ╱ ╲\n ╱────────╲\n ╱ ╲\n YES NO\n │ │\n ▼ ▼',
|
||||||
' │\n ▼\n┌───────────────┐\n│ Process │\n└───────────────┘\n │\n ▼',
|
'process': '┌─────┐ ┌─────┐ ┌─────┐\n│ 1 │──▶│ 2 │──▶│ 3 │\n└─────┘ └─────┘ └─────┘',
|
||||||
decision:
|
'flowchart': '┌─────────────┐\n│ START │\n└──────┬──────┘\n │\n ▼\n┌─────────────┐\n│ Process A │\n└──────┬──────┘\n │\n ▼\n ╱────────╲\n ╱ Decision ╲\n ╲ ? ╱\n ╲────────╱\n │ │\n YES NO\n │ │\n ▼ ▼\n┌──────┐ ┌──────┐\n│ B │ │ C │\n└──────┘ └──────┘',
|
||||||
' ╱╲\n ╱ ╲\n ╱ ? ╲\n ╱ ╲\n ╱────────╲\n ╱ ╲\n YES NO\n │ │\n ▼ ▼',
|
'sequence': ' User System Database\n │ │ │\n │ Request │ │\n ├──────────►│ │\n │ │ Query │\n │ ├──────────►│\n │ │ │\n │ │ Result │\n │ │◄──────────┤\n │ Response │ │\n │◄──────────┤ │\n │ │ │',
|
||||||
process:
|
'network': ' ┌─────────┐\n │ Server │\n └────┬────┘\n │\n ┌─────────┼─────────┐\n │ │ │\n┌────┴────┐ ┌──┴──┐ ┌────┴────┐\n│ Client1 │ │ DB │ │ Client2 │\n└─────────┘ └─────┘ └─────────┘',
|
||||||
'┌─────┐ ┌─────┐ ┌─────┐\n│ 1 │──▶│ 2 │──▶│ 3 │\n└─────┘ └─────┘ └─────┘',
|
'hierarchy': ' ┌─────────┐\n │ CEO │\n └────┬────┘\n ┌─────────┼─────────┐\n │ │ │\n ┌───┴───┐ ┌───┴───┐ ┌───┴───┐\n │ VP1 │ │ VP2 │ │ VP3 │\n └───┬───┘ └───┬───┘ └───┬───┘\n │ │ │\n ┌───┴───┐ ┌───┴───┐ ┌───┴───┐\n │ Team1 │ │ Team2 │ │ Team3 │\n └───────┘ └───────┘ └───────┘',
|
||||||
flowchart:
|
'header': '╔════════════════════════════════════╗\n║ SECTION TITLE ║\n╚════════════════════════════════════╝',
|
||||||
'┌─────────────┐\n│ START │\n└──────┬──────┘\n │\n ▼\n┌─────────────┐\n│ Process A │\n└──────┬──────┘\n │\n ▼\n ╱────────╲\n ╱ Decision ╲\n ╲ ? ╱\n ╲────────╱\n │ │\n YES NO\n │ │\n ▼ ▼\n┌──────┐ ┌──────┐\n│ B │ │ C │\n└──────┘ └──────┘',
|
'note': '┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃ NOTE: ┃\n┃ This is an important note ┃\n┃ that requires attention! ┃\n┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
|
||||||
sequence:
|
'warning': '╔════════════════════════════════════╗\n║ ⚠️ WARNING ║\n║ ║\n║ Critical information here! ║\n╚════════════════════════════════════╝',
|
||||||
' User System Database\n │ │ │\n │ Request │ │\n ├──────────►│ │\n │ │ Query │\n │ ├──────────►│\n │ │ │\n │ │ Result │\n │ │◄──────────┤\n │ Response │ │\n │◄──────────┤ │\n │ │ │',
|
'info': '╭────────────────────────────────────╮\n│ ℹ️ INFO │\n│ │\n│ Helpful information here. │\n╰────────────────────────────────────╯',
|
||||||
network:
|
'divider': '════════════════════════════════════════',
|
||||||
' ┌─────────┐\n │ Server │\n └────┬────┘\n │\n ┌─────────┼─────────┐\n │ │ │\n┌────┴────┐ ┌──┴──┐ ┌────┴────┐\n│ Client1 │ │ DB │ │ Client2 │\n└─────────┘ └─────┘ └─────────┘',
|
'separator': '╭──────────────────────────────────────╮\n│ │\n╰──────────────────────────────────────╯',
|
||||||
hierarchy:
|
'banner': '★══════════════════════════════════════★\n║ YOUR TITLE HERE ║\n★══════════════════════════════════════★',
|
||||||
' ┌─────────┐\n │ CEO │\n └────┬────┘\n ┌─────────┼─────────┐\n │ │ │\n ┌───┴───┐ ┌───┴───┐ ┌───┴───┐\n │ VP1 │ │ VP2 │ │ VP3 │\n └───┬───┘ └───┬───┘ └───┬───┘\n │ │ │\n ┌───┴───┐ ┌───┴───┐ ┌───┴───┐\n │ Team1 │ │ Team2 │ │ Team3 │\n └───────┘ └───────┘ └───────┘',
|
'checklist': '☐ Task 1 - Not completed\n☑ Task 2 - Completed \n☐ Task 3 - Not completed\n☐ Task 4 - Not completed'
|
||||||
header:
|
|
||||||
'╔════════════════════════════════════╗\n║ SECTION TITLE ║\n╚════════════════════════════════════╝',
|
|
||||||
note: '┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃ NOTE: ┃\n┃ This is an important note ┃\n┃ that requires attention! ┃\n┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
|
|
||||||
warning:
|
|
||||||
'╔════════════════════════════════════╗\n║ ⚠️ WARNING ║\n║ ║\n║ Critical information here! ║\n╚════════════════════════════════════╝',
|
|
||||||
info: '╭────────────────────────────────────╮\n│ ℹ️ INFO │\n│ │\n│ Helpful information here. │\n╰────────────────────────────────────╯',
|
|
||||||
divider: '════════════════════════════════════════',
|
|
||||||
separator:
|
|
||||||
'╭──────────────────────────────────────╮\n│ │\n╰──────────────────────────────────────╯',
|
|
||||||
banner:
|
|
||||||
'★══════════════════════════════════════★\n║ YOUR TITLE HERE ║\n★══════════════════════════════════════★',
|
|
||||||
checklist:
|
|
||||||
'☐ Task 1 - Not completed\n☑ Task 2 - Completed \n☐ Task 3 - Not completed\n☐ Task 4 - Not completed',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const BOX_STYLES = {
|
const BOX_STYLES = {
|
||||||
@@ -629,20 +480,20 @@
|
|||||||
double: { tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' },
|
double: { tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' },
|
||||||
rounded: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' },
|
rounded: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' },
|
||||||
bold: { tl: '┏', tr: '┓', bl: '┗', br: '┛', h: '━', v: '┃' },
|
bold: { tl: '┏', tr: '┓', bl: '┗', br: '┛', h: '━', v: '┃' },
|
||||||
ascii: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' },
|
ascii: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' }
|
||||||
};
|
};
|
||||||
|
|
||||||
let currentMode = 'text';
|
let currentMode = 'text';
|
||||||
let currentTemplate = null;
|
let currentTemplate = null;
|
||||||
|
|
||||||
// Mode switching
|
// Mode switching
|
||||||
document.querySelectorAll('.mode-tab').forEach((tab) => {
|
document.querySelectorAll('.mode-tab').forEach(tab => {
|
||||||
tab.addEventListener('click', () => {
|
tab.addEventListener('click', () => {
|
||||||
document.querySelectorAll('.mode-tab').forEach((t) => t.classList.remove('active'));
|
document.querySelectorAll('.mode-tab').forEach(t => t.classList.remove('active'));
|
||||||
tab.classList.add('active');
|
tab.classList.add('active');
|
||||||
currentMode = tab.dataset.mode;
|
currentMode = tab.dataset.mode;
|
||||||
|
|
||||||
document.querySelectorAll('.mode-section').forEach((s) => s.classList.remove('active'));
|
document.querySelectorAll('.mode-section').forEach(s => s.classList.remove('active'));
|
||||||
document.getElementById(currentMode + '-mode').classList.add('active');
|
document.getElementById(currentMode + '-mode').classList.add('active');
|
||||||
|
|
||||||
generatePreview();
|
generatePreview();
|
||||||
@@ -650,9 +501,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Template selection
|
// Template selection
|
||||||
document.querySelectorAll('.template-btn').forEach((btn) => {
|
document.querySelectorAll('.template-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
document.querySelectorAll('.template-btn').forEach((b) => b.classList.remove('active'));
|
document.querySelectorAll('.template-btn').forEach(b => b.classList.remove('active'));
|
||||||
btn.classList.add('active');
|
btn.classList.add('active');
|
||||||
currentTemplate = btn.dataset.template;
|
currentTemplate = btn.dataset.template;
|
||||||
generatePreview();
|
generatePreview();
|
||||||
@@ -680,7 +531,7 @@
|
|||||||
function generateBox(text, style, padding) {
|
function generateBox(text, style, padding) {
|
||||||
const box = BOX_STYLES[style] || BOX_STYLES.single;
|
const box = BOX_STYLES[style] || BOX_STYLES.single;
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
const maxLen = Math.max(...lines.map((l) => l.length)) + padding * 2;
|
const maxLen = Math.max(...lines.map(l => l.length)) + padding * 2;
|
||||||
|
|
||||||
let result = box.tl + box.h.repeat(maxLen + 2) + box.tr + '\n';
|
let result = box.tl + box.h.repeat(maxLen + 2) + box.tr + '\n';
|
||||||
|
|
||||||
@@ -746,5 +597,5 @@
|
|||||||
// Initial preview
|
// Initial preview
|
||||||
generatePreview();
|
generatePreview();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -60,19 +60,15 @@ class CommandPalette {
|
|||||||
|
|
||||||
renderResults(query) {
|
renderResults(query) {
|
||||||
this.filteredCommands = query
|
this.filteredCommands = query
|
||||||
? this.commands.filter((cmd) => cmd.label.toLowerCase().includes(query.toLowerCase()))
|
? this.commands.filter(cmd => cmd.label.toLowerCase().includes(query.toLowerCase()))
|
||||||
: [...this.commands];
|
: [...this.commands];
|
||||||
|
|
||||||
this.results.innerHTML = this.filteredCommands
|
this.results.innerHTML = this.filteredCommands.map((cmd, i) => `
|
||||||
.map(
|
|
||||||
(cmd, i) => `
|
|
||||||
<div class="command-item ${i === this.selectedIndex ? 'selected' : ''}" data-index="${i}">
|
<div class="command-item ${i === this.selectedIndex ? 'selected' : ''}" data-index="${i}">
|
||||||
<span class="command-label">${this.highlightMatch(cmd.label, query)}</span>
|
<span class="command-label">${this.highlightMatch(cmd.label, query)}</span>
|
||||||
${cmd.shortcut ? `<span class="command-shortcut">${cmd.shortcut}</span>` : ''}
|
${cmd.shortcut ? `<span class="command-shortcut">${cmd.shortcut}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
`
|
`).join('');
|
||||||
)
|
|
||||||
.join('');
|
|
||||||
|
|
||||||
this.results.querySelectorAll('.command-item').forEach((el) => {
|
this.results.querySelectorAll('.command-item').forEach((el) => {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
|
|||||||
@@ -12,23 +12,38 @@ const { EditorState } = require('@codemirror/state');
|
|||||||
const { markdown, markdownLanguage } = require('@codemirror/lang-markdown');
|
const { markdown, markdownLanguage } = require('@codemirror/lang-markdown');
|
||||||
// Language extensions loaded lazily on first use
|
// Language extensions loaded lazily on first use
|
||||||
let _javascript, _html, _css, _json, _python;
|
let _javascript, _html, _css, _json, _python;
|
||||||
const { defaultKeymap, history, historyKeymap, indentWithTab } = require('@codemirror/commands');
|
const {
|
||||||
const { searchKeymap, highlightSelectionMatches } = require('@codemirror/search');
|
defaultKeymap,
|
||||||
const { autocompletion, completionKeymap } = require('@codemirror/autocomplete');
|
history,
|
||||||
const { bracketMatching, foldGutter, indentOnInput } = require('@codemirror/language');
|
historyKeymap,
|
||||||
|
indentWithTab,
|
||||||
|
} = require('@codemirror/commands');
|
||||||
|
const {
|
||||||
|
searchKeymap,
|
||||||
|
highlightSelectionMatches,
|
||||||
|
} = require('@codemirror/search');
|
||||||
|
const {
|
||||||
|
autocompletion,
|
||||||
|
completionKeymap,
|
||||||
|
} = require('@codemirror/autocomplete');
|
||||||
|
const {
|
||||||
|
bracketMatching,
|
||||||
|
foldGutter,
|
||||||
|
indentOnInput,
|
||||||
|
} = require('@codemirror/language');
|
||||||
const { oneDark } = require('@codemirror/theme-one-dark');
|
const { oneDark } = require('@codemirror/theme-one-dark');
|
||||||
|
|
||||||
// Custom theme for JetBrains Mono font
|
// Custom theme for JetBrains Mono font
|
||||||
const jetBrainsMonoTheme = EditorView.theme({
|
const jetBrainsMonoTheme = EditorView.theme({
|
||||||
'&': {
|
'&': {
|
||||||
fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco, 'Courier New', monospace",
|
fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco, 'Courier New', monospace"
|
||||||
},
|
},
|
||||||
'.cm-content': {
|
'.cm-content': {
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit'
|
||||||
},
|
},
|
||||||
'.cm-scroller': {
|
'.cm-scroller': {
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit'
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,18 +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 = () => {},
|
||||||
@@ -117,26 +120,11 @@ function createEditor(parentElement, options = {}) {
|
|||||||
*/
|
*/
|
||||||
function getLanguageExtension(lang) {
|
function getLanguageExtension(lang) {
|
||||||
const loaders = {
|
const loaders = {
|
||||||
javascript: () => {
|
javascript: () => { if (!_javascript) _javascript = require('@codemirror/lang-javascript').javascript; return _javascript(); },
|
||||||
if (!_javascript) _javascript = require('@codemirror/lang-javascript').javascript;
|
html: () => { if (!_html) _html = require('@codemirror/lang-html').html; return _html(); },
|
||||||
return _javascript();
|
css: () => { if (!_css) _css = require('@codemirror/lang-css').css; return _css(); },
|
||||||
},
|
json: () => { if (!_json) _json = require('@codemirror/lang-json').json; return _json(); },
|
||||||
html: () => {
|
python: () => { if (!_python) _python = require('@codemirror/lang-python').python; return _python(); },
|
||||||
if (!_html) _html = require('@codemirror/lang-html').html;
|
|
||||||
return _html();
|
|
||||||
},
|
|
||||||
css: () => {
|
|
||||||
if (!_css) _css = require('@codemirror/lang-css').css;
|
|
||||||
return _css();
|
|
||||||
},
|
|
||||||
json: () => {
|
|
||||||
if (!_json) _json = require('@codemirror/lang-json').json;
|
|
||||||
return _json();
|
|
||||||
},
|
|
||||||
python: () => {
|
|
||||||
if (!_python) _python = require('@codemirror/lang-python').python;
|
|
||||||
return _python();
|
|
||||||
},
|
|
||||||
markdown: () => markdown({ base: markdownLanguage }),
|
markdown: () => markdown({ base: markdownLanguage }),
|
||||||
};
|
};
|
||||||
loaders.js = loaders.javascript;
|
loaders.js = loaders.javascript;
|
||||||
|
|||||||
+255
-1102
File diff suppressed because it is too large
Load Diff
+867
-1428
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ async function getStatus(dir) {
|
|||||||
try {
|
try {
|
||||||
const git = getGitInstance(dir);
|
const git = getGitInstance(dir);
|
||||||
return await git.status();
|
return await git.status();
|
||||||
} catch {
|
} catch (err) {
|
||||||
return { error: 'Not a git repository' };
|
return { error: 'Not a git repository' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-60
@@ -4,11 +4,11 @@ const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib');
|
|||||||
|
|
||||||
function parsePageRanges(rangeString, totalPages) {
|
function parsePageRanges(rangeString, totalPages) {
|
||||||
const pages = [];
|
const pages = [];
|
||||||
const ranges = rangeString.split(',').map((r) => r.trim());
|
const ranges = rangeString.split(',').map(r => r.trim());
|
||||||
|
|
||||||
for (const range of ranges) {
|
for (const range of ranges) {
|
||||||
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()));
|
||||||
for (let i = start; i <= end && i <= totalPages; i++) {
|
for (let i = start; i <= end && i <= totalPages; i++) {
|
||||||
if (i > 0 && !pages.includes(i - 1)) {
|
if (i > 0 && !pages.includes(i - 1)) {
|
||||||
pages.push(i - 1);
|
pages.push(i - 1);
|
||||||
@@ -27,13 +27,11 @@ function parsePageRanges(rangeString, totalPages) {
|
|||||||
|
|
||||||
function hexToRgb(hex) {
|
function hexToRgb(hex) {
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
return result
|
return result ? {
|
||||||
? {
|
|
||||||
r: parseInt(result[1], 16) / 255,
|
r: parseInt(result[1], 16) / 255,
|
||||||
g: parseInt(result[2], 16) / 255,
|
g: parseInt(result[2], 16) / 255,
|
||||||
b: parseInt(result[3], 16) / 255,
|
b: parseInt(result[3], 16) / 255
|
||||||
}
|
} : { r: 0, g: 0, b: 0 };
|
||||||
: { r: 0, g: 0, b: 0 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pdfMerge(data) {
|
async function pdfMerge(data) {
|
||||||
@@ -44,7 +42,7 @@ async function pdfMerge(data) {
|
|||||||
const pdfBytes = fs.readFileSync(filePath);
|
const pdfBytes = fs.readFileSync(filePath);
|
||||||
const pdf = await PDFDocument.load(pdfBytes);
|
const pdf = await PDFDocument.load(pdfBytes);
|
||||||
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
||||||
copiedPages.forEach((page) => mergedPdf.addPage(page));
|
copiedPages.forEach(page => mergedPdf.addPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
const pdfBytes = await mergedPdf.save();
|
const pdfBytes = await mergedPdf.save();
|
||||||
@@ -62,16 +60,16 @@ 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()));
|
||||||
for (let p = start; p <= end && p <= totalPages; p++) {
|
for (let p = start; p <= end && p <= totalPages; p++) {
|
||||||
pages.push(p - 1);
|
pages.push(p - 1);
|
||||||
}
|
}
|
||||||
@@ -110,7 +108,7 @@ async function pdfSplit(data) {
|
|||||||
for (const split of splits) {
|
for (const split of splits) {
|
||||||
const newPdf = await PDFDocument.create();
|
const newPdf = await PDFDocument.create();
|
||||||
const copiedPages = await newPdf.copyPages(pdf, split.pages);
|
const copiedPages = await newPdf.copyPages(pdf, split.pages);
|
||||||
copiedPages.forEach((page) => newPdf.addPage(page));
|
copiedPages.forEach(page => newPdf.addPage(page));
|
||||||
|
|
||||||
const outputPath = path.join(data.outputFolder, `${baseName}_${split.name}.pdf`);
|
const outputPath = path.join(data.outputFolder, `${baseName}_${split.name}.pdf`);
|
||||||
const newPdfBytes = await newPdf.save();
|
const newPdfBytes = await newPdf.save();
|
||||||
@@ -131,18 +129,18 @@ async function pdfCompress(data) {
|
|||||||
const compressedPdfBytes = await pdf.save({
|
const compressedPdfBytes = await pdf.save({
|
||||||
useObjectStreams: true,
|
useObjectStreams: true,
|
||||||
addDefaultPage: false,
|
addDefaultPage: false,
|
||||||
objectsPerTick: 50,
|
objectsPerTick: 50
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(data.outputPath, compressedPdfBytes);
|
fs.writeFileSync(data.outputPath, compressedPdfBytes);
|
||||||
|
|
||||||
const originalSize = fs.statSync(data.inputPath).size;
|
const originalSize = fs.statSync(data.inputPath).size;
|
||||||
const compressedSize = fs.statSync(data.outputPath).size;
|
const compressedSize = fs.statSync(data.outputPath).size;
|
||||||
const savings = (((originalSize - compressedSize) / originalSize) * 100).toFixed(1);
|
const savings = ((originalSize - compressedSize) / originalSize * 100).toFixed(1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `PDF compressed. Size reduced by ${savings}% (${(originalSize / 1024).toFixed(1)}KB → ${(compressedSize / 1024).toFixed(1)}KB)`,
|
message: `PDF compressed. Size reduced by ${savings}% (${(originalSize / 1024).toFixed(1)}KB → ${(compressedSize / 1024).toFixed(1)}KB)`
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@@ -162,7 +160,7 @@ async function pdfRotate(data) {
|
|||||||
pagesToRotate = Array.from({ length: totalPages }, (_, i) => i);
|
pagesToRotate = Array.from({ length: totalPages }, (_, i) => i);
|
||||||
}
|
}
|
||||||
|
|
||||||
pagesToRotate.forEach((pageIndex) => {
|
pagesToRotate.forEach(pageIndex => {
|
||||||
const page = pdf.getPage(pageIndex);
|
const page = pdf.getPage(pageIndex);
|
||||||
page.setRotation(degrees(data.angle));
|
page.setRotation(degrees(data.angle));
|
||||||
});
|
});
|
||||||
@@ -172,7 +170,7 @@ async function pdfRotate(data) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `Successfully rotated ${pagesToRotate.length} page(s) by ${data.angle}\u00B0`,
|
message: `Successfully rotated ${pagesToRotate.length} page(s) by ${data.angle}\u00B0`
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@@ -187,9 +185,7 @@ async function pdfDeletePages(data) {
|
|||||||
|
|
||||||
const pagesToDelete = parsePageRanges(data.pages, totalPages);
|
const pagesToDelete = parsePageRanges(data.pages, totalPages);
|
||||||
|
|
||||||
pagesToDelete
|
pagesToDelete.sort((a, b) => b - a).forEach(pageIndex => {
|
||||||
.sort((a, b) => b - a)
|
|
||||||
.forEach((pageIndex) => {
|
|
||||||
pdf.removePage(pageIndex);
|
pdf.removePage(pageIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,7 +194,7 @@ async function pdfDeletePages(data) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `Successfully deleted ${pagesToDelete.length} page(s). New PDF has ${totalPages - pagesToDelete.length} pages`,
|
message: `Successfully deleted ${pagesToDelete.length} page(s). New PDF has ${totalPages - pagesToDelete.length} pages`
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@@ -211,7 +207,7 @@ async function pdfReorder(data) {
|
|||||||
const pdf = await PDFDocument.load(pdfBytes);
|
const pdf = await PDFDocument.load(pdfBytes);
|
||||||
const totalPages = pdf.getPageCount();
|
const totalPages = pdf.getPageCount();
|
||||||
|
|
||||||
const newOrder = data.newOrder.split(',').map((n) => parseInt(n.trim()) - 1);
|
const newOrder = data.newOrder.split(',').map(n => parseInt(n.trim()) - 1);
|
||||||
|
|
||||||
if (newOrder.length !== totalPages) {
|
if (newOrder.length !== totalPages) {
|
||||||
return { success: false, error: `New order must include all ${totalPages} pages` };
|
return { success: false, error: `New order must include all ${totalPages} pages` };
|
||||||
@@ -219,7 +215,7 @@ async function pdfReorder(data) {
|
|||||||
|
|
||||||
const newPdf = await PDFDocument.create();
|
const newPdf = await PDFDocument.create();
|
||||||
const copiedPages = await newPdf.copyPages(pdf, newOrder);
|
const copiedPages = await newPdf.copyPages(pdf, newOrder);
|
||||||
copiedPages.forEach((page) => newPdf.addPage(page));
|
copiedPages.forEach(page => newPdf.addPage(page));
|
||||||
|
|
||||||
const reorderedPdfBytes = await newPdf.save();
|
const reorderedPdfBytes = await newPdf.save();
|
||||||
fs.writeFileSync(data.outputPath, reorderedPdfBytes);
|
fs.writeFileSync(data.outputPath, reorderedPdfBytes);
|
||||||
@@ -250,9 +246,7 @@ async function pdfWatermark(data) {
|
|||||||
const page = pdf.getPage(pageIndex);
|
const page = pdf.getPage(pageIndex);
|
||||||
const { width, height } = page.getSize();
|
const { width, height } = page.getSize();
|
||||||
|
|
||||||
let x,
|
let x, y, rotation = 0;
|
||||||
y,
|
|
||||||
rotation = 0;
|
|
||||||
|
|
||||||
switch (data.position) {
|
switch (data.position) {
|
||||||
case 'center':
|
case 'center':
|
||||||
@@ -300,7 +294,7 @@ async function pdfWatermark(data) {
|
|||||||
font,
|
font,
|
||||||
color: rgb(color.r, color.g, color.b),
|
color: rgb(color.r, color.g, color.b),
|
||||||
opacity: data.opacity,
|
opacity: data.opacity,
|
||||||
rotate: degrees(rotation),
|
rotate: degrees(rotation)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +303,7 @@ async function pdfWatermark(data) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `Successfully added watermark to ${pagesToWatermark.length} page(s)`,
|
message: `Successfully added watermark to ${pagesToWatermark.length} page(s)`
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@@ -331,8 +325,8 @@ async function pdfEncrypt(data) {
|
|||||||
annotating: data.permissions.annotating,
|
annotating: data.permissions.annotating,
|
||||||
fillingForms: data.permissions.fillingForms,
|
fillingForms: data.permissions.fillingForms,
|
||||||
contentAccessibility: data.permissions.contentAccessibility,
|
contentAccessibility: data.permissions.contentAccessibility,
|
||||||
documentAssembly: data.permissions.documentAssembly,
|
documentAssembly: data.permissions.documentAssembly
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(data.outputPath, encryptedPdfBytes);
|
fs.writeFileSync(data.outputPath, encryptedPdfBytes);
|
||||||
@@ -342,8 +336,7 @@ async function pdfEncrypt(data) {
|
|||||||
if (error.message.includes('encrypt') || error.message.includes('password')) {
|
if (error.message.includes('encrypt') || error.message.includes('password')) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error: 'PDF encryption requires pdf-lib with encryption support. This feature may not be available in the current version.'
|
||||||
'PDF encryption requires pdf-lib with encryption support. This feature may not be available in the current version.',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@@ -382,8 +375,8 @@ async function pdfSetPermissions(data) {
|
|||||||
annotating: data.permissions.annotating,
|
annotating: data.permissions.annotating,
|
||||||
fillingForms: data.permissions.fillingForms,
|
fillingForms: data.permissions.fillingForms,
|
||||||
contentAccessibility: data.permissions.contentAccessibility,
|
contentAccessibility: data.permissions.contentAccessibility,
|
||||||
documentAssembly: data.permissions.documentAssembly,
|
documentAssembly: data.permissions.documentAssembly
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(data.outputPath, newPdfBytes);
|
fs.writeFileSync(data.outputPath, newPdfBytes);
|
||||||
@@ -393,8 +386,7 @@ async function pdfSetPermissions(data) {
|
|||||||
if (error.message.includes('encrypt') || error.message.includes('permission')) {
|
if (error.message.includes('encrypt') || error.message.includes('permission')) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error:
|
error: 'PDF permissions require pdf-lib with encryption support. This feature may not be available in the current version.'
|
||||||
'PDF permissions require pdf-lib with encryption support. This feature may not be available in the current version.',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@@ -403,28 +395,17 @@ async function pdfSetPermissions(data) {
|
|||||||
|
|
||||||
function executeOperation(operation, data) {
|
function executeOperation(operation, data) {
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case 'merge':
|
case 'merge': return pdfMerge(data);
|
||||||
return pdfMerge(data);
|
case 'split': return pdfSplit(data);
|
||||||
case 'split':
|
case 'compress': return pdfCompress(data);
|
||||||
return pdfSplit(data);
|
case 'rotate': return pdfRotate(data);
|
||||||
case 'compress':
|
case 'delete': return pdfDeletePages(data);
|
||||||
return pdfCompress(data);
|
case 'reorder': return pdfReorder(data);
|
||||||
case 'rotate':
|
case 'watermark': return pdfWatermark(data);
|
||||||
return pdfRotate(data);
|
case 'encrypt': return pdfEncrypt(data);
|
||||||
case 'delete':
|
case 'decrypt': return pdfDecrypt(data);
|
||||||
return pdfDeletePages(data);
|
case 'permissions': return pdfSetPermissions(data);
|
||||||
case 'reorder':
|
default: return Promise.resolve({ success: false, error: `Unknown operation: ${operation}` });
|
||||||
return pdfReorder(data);
|
|
||||||
case 'watermark':
|
|
||||||
return pdfWatermark(data);
|
|
||||||
case 'encrypt':
|
|
||||||
return pdfEncrypt(data);
|
|
||||||
case 'decrypt':
|
|
||||||
return pdfDecrypt(data);
|
|
||||||
case 'permissions':
|
|
||||||
return pdfSetPermissions(data);
|
|
||||||
default:
|
|
||||||
return Promise.resolve({ success: false, error: `Unknown operation: ${operation}` });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,5 +429,5 @@ module.exports = {
|
|||||||
pdfDecrypt,
|
pdfDecrypt,
|
||||||
pdfSetPermissions,
|
pdfSetPermissions,
|
||||||
executeOperation,
|
executeOperation,
|
||||||
getPageCount,
|
getPageCount
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
"description": "Demonstrates the plugin system. Safe to delete.",
|
"description": "Demonstrates the plugin system. Safe to delete.",
|
||||||
"icon": "puzzle",
|
"icon": "puzzle",
|
||||||
"extensionPoints": {
|
"extensionPoints": {
|
||||||
"commands": [{ "id": "hello", "label": "Sample: Hello World", "shortcut": "" }]
|
"commands": [
|
||||||
|
{ "id": "hello", "label": "Sample: Hello World", "shortcut": "" }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"settings": []
|
"settings": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class WritingStudioPlugin extends PluginAPI {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
this.sprintEngine = new SprintEngine({
|
this.sprintEngine = new SprintEngine({
|
||||||
onEvent: (name, data) => context.events.emit(name, data),
|
onEvent: (name, data) => context.events.emit(name, data)
|
||||||
});
|
});
|
||||||
this.goalTracker = new GoalTracker(context.settings);
|
this.goalTracker = new GoalTracker(context.settings);
|
||||||
this.snapshotManager = new SnapshotManager(context.settings);
|
this.snapshotManager = new SnapshotManager(context.settings);
|
||||||
@@ -17,14 +17,14 @@ class WritingStudioPlugin extends PluginAPI {
|
|||||||
readFile: (p) => context.ipc.invoke('read-file', p),
|
readFile: (p) => context.ipc.invoke('read-file', p),
|
||||||
writeFile: (p, c) => context.ipc.invoke('write-file', p, c),
|
writeFile: (p, c) => context.ipc.invoke('write-file', p, c),
|
||||||
fileExists: (p) => context.ipc.invoke('path-exists', p),
|
fileExists: (p) => context.ipc.invoke('path-exists', p),
|
||||||
listDir: (p) => context.ipc.invoke('list-directory', p),
|
listDir: (p) => context.ipc.invoke('list-directory', p)
|
||||||
});
|
});
|
||||||
|
|
||||||
this._engines = {
|
this._engines = {
|
||||||
sprint: this.sprintEngine,
|
sprint: this.sprintEngine,
|
||||||
goals: this.goalTracker,
|
goals: this.goalTracker,
|
||||||
snapshots: this.snapshotManager,
|
snapshots: this.snapshotManager,
|
||||||
projects: this.projectManager,
|
projects: this.projectManager
|
||||||
};
|
};
|
||||||
|
|
||||||
this._registerCommands(context);
|
this._registerCommands(context);
|
||||||
@@ -34,89 +34,59 @@ class WritingStudioPlugin extends PluginAPI {
|
|||||||
_registerCommands(context) {
|
_registerCommands(context) {
|
||||||
const { sprintEngine, snapshotManager, goalTracker } = this;
|
const { sprintEngine, snapshotManager, goalTracker } = this;
|
||||||
|
|
||||||
context.commands.register(
|
context.commands.register('start-sprint', 'Studio: Start Sprint', () => {
|
||||||
'start-sprint',
|
|
||||||
'Studio: Start Sprint',
|
|
||||||
() => {
|
|
||||||
const duration = context.settings.get('sprintDuration') || 25;
|
const duration = context.settings.get('sprintDuration') || 25;
|
||||||
const content = context.editor.getContent() || '';
|
const content = context.editor.getContent() || '';
|
||||||
const words = content.split(/\s+/).filter(Boolean).length;
|
const words = content.split(/\s+/).filter(Boolean).length;
|
||||||
sprintEngine.start(duration, words);
|
sprintEngine.start(duration, words);
|
||||||
},
|
}, 'Ctrl+Alt+S');
|
||||||
'Ctrl+Alt+S'
|
|
||||||
);
|
|
||||||
|
|
||||||
context.commands.register(
|
context.commands.register('stop-sprint', 'Studio: Stop Sprint', () => {
|
||||||
'stop-sprint',
|
|
||||||
'Studio: Stop Sprint',
|
|
||||||
() => {
|
|
||||||
if (!sprintEngine.isActive()) return;
|
if (!sprintEngine.isActive()) return;
|
||||||
const content = context.editor.getContent() || '';
|
const content = context.editor.getContent() || '';
|
||||||
const words = content.split(/\s+/).filter(Boolean).length;
|
const words = content.split(/\s+/).filter(Boolean).length;
|
||||||
const result = sprintEngine.stop(words);
|
const result = sprintEngine.stop(words);
|
||||||
goalTracker.addWords(result.wordDelta);
|
goalTracker.addWords(result.wordDelta);
|
||||||
context.events.emit('sprint:stopped', result);
|
context.events.emit('sprint:stopped', result);
|
||||||
},
|
}, 'Ctrl+Alt+Shift+S');
|
||||||
'Ctrl+Alt+Shift+S'
|
|
||||||
);
|
|
||||||
|
|
||||||
context.commands.register(
|
context.commands.register('take-snapshot', 'Studio: Take Snapshot', () => {
|
||||||
'take-snapshot',
|
|
||||||
'Studio: Take Snapshot',
|
|
||||||
() => {
|
|
||||||
const content = context.editor.getContent() || '';
|
const content = context.editor.getContent() || '';
|
||||||
snapshotManager.create(content, 'manual');
|
snapshotManager.create(content, 'manual');
|
||||||
context.events.emit('snapshot:created', {});
|
context.events.emit('snapshot:created', {});
|
||||||
},
|
}, 'Ctrl+Alt+N');
|
||||||
'Ctrl+Alt+N'
|
|
||||||
);
|
|
||||||
|
|
||||||
context.commands.register(
|
context.commands.register('restore-last-snapshot', 'Studio: Restore Last Snapshot', () => {
|
||||||
'restore-last-snapshot',
|
|
||||||
'Studio: Restore Last Snapshot',
|
|
||||||
() => {
|
|
||||||
const snaps = snapshotManager.list();
|
const snaps = snapshotManager.list();
|
||||||
if (snaps.length === 0) return;
|
if (snaps.length === 0) return;
|
||||||
const content = snapshotManager.restore(snaps[0].id);
|
const content = snapshotManager.restore(snaps[0].id);
|
||||||
context.editor.insertAtCursor(content);
|
context.editor.insertAtCursor(content);
|
||||||
},
|
}, 'Ctrl+Alt+Z');
|
||||||
'Ctrl+Alt+Z'
|
|
||||||
);
|
|
||||||
|
|
||||||
context.commands.register('new-project', 'Studio: New Project', () => {
|
context.commands.register('new-project', 'Studio: New Project', () => {
|
||||||
context.events.emit('studio:new-project', {});
|
context.events.emit('studio:new-project', {});
|
||||||
});
|
});
|
||||||
|
|
||||||
context.commands.register(
|
context.commands.register('compile-manuscript', 'Studio: Compile Manuscript', () => {
|
||||||
'compile-manuscript',
|
|
||||||
'Studio: Compile Manuscript',
|
|
||||||
() => {
|
|
||||||
context.events.emit('studio:compile', {});
|
context.events.emit('studio:compile', {});
|
||||||
},
|
}, 'Ctrl+Alt+E');
|
||||||
'Ctrl+Alt+E'
|
|
||||||
);
|
|
||||||
|
|
||||||
context.commands.register(
|
context.commands.register('proofread-document', 'Studio: Proofread Document', () => {
|
||||||
'proofread-document',
|
|
||||||
'Studio: Proofread Document',
|
|
||||||
() => {
|
|
||||||
if (context.events.hasHandler('ai:analyze')) {
|
if (context.events.hasHandler('ai:analyze')) {
|
||||||
const content = context.editor.getContent() || '';
|
const content = context.editor.getContent() || '';
|
||||||
context.events.emit('ai:analyze', { text: content, type: 'grammar' });
|
context.events.emit('ai:analyze', { text: content, type: 'grammar' });
|
||||||
}
|
}
|
||||||
},
|
}, 'Ctrl+Alt+G');
|
||||||
'Ctrl+Alt+G'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerStatusBar(context) {
|
_registerStatusBar(context) {
|
||||||
context.statusBar.registerIndicator('word-goal', {
|
context.statusBar.registerIndicator('word-goal', {
|
||||||
text: '0/1000',
|
text: '0/1000',
|
||||||
tooltip: 'Daily word goal progress',
|
tooltip: 'Daily word goal progress'
|
||||||
});
|
});
|
||||||
context.statusBar.registerIndicator('sprint-timer', {
|
context.statusBar.registerIndicator('sprint-timer', {
|
||||||
text: '',
|
text: '',
|
||||||
tooltip: 'Writing sprint timer',
|
tooltip: 'Writing sprint timer'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,34 +15,17 @@
|
|||||||
{ "id": "start-sprint", "label": "Studio: Start Sprint", "shortcut": "Ctrl+Alt+S" },
|
{ "id": "start-sprint", "label": "Studio: Start Sprint", "shortcut": "Ctrl+Alt+S" },
|
||||||
{ "id": "stop-sprint", "label": "Studio: Stop Sprint", "shortcut": "Ctrl+Alt+Shift+S" },
|
{ "id": "stop-sprint", "label": "Studio: Stop Sprint", "shortcut": "Ctrl+Alt+Shift+S" },
|
||||||
{ "id": "take-snapshot", "label": "Studio: Take Snapshot", "shortcut": "Ctrl+Alt+N" },
|
{ "id": "take-snapshot", "label": "Studio: Take Snapshot", "shortcut": "Ctrl+Alt+N" },
|
||||||
{
|
{ "id": "restore-last-snapshot", "label": "Studio: Restore Last Snapshot", "shortcut": "Ctrl+Alt+Z" },
|
||||||
"id": "restore-last-snapshot",
|
|
||||||
"label": "Studio: Restore Last Snapshot",
|
|
||||||
"shortcut": "Ctrl+Alt+Z"
|
|
||||||
},
|
|
||||||
{ "id": "new-project", "label": "Studio: New Project", "shortcut": "" },
|
{ "id": "new-project", "label": "Studio: New Project", "shortcut": "" },
|
||||||
{
|
{ "id": "compile-manuscript", "label": "Studio: Compile Manuscript", "shortcut": "Ctrl+Alt+E" },
|
||||||
"id": "compile-manuscript",
|
{ "id": "proofread-document", "label": "Studio: Proofread Document", "shortcut": "Ctrl+Alt+G" }
|
||||||
"label": "Studio: Compile Manuscript",
|
|
||||||
"shortcut": "Ctrl+Alt+E"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "proofread-document",
|
|
||||||
"label": "Studio: Proofread Document",
|
|
||||||
"shortcut": "Ctrl+Alt+G"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"statusBar": { "indicators": ["sprint-timer", "word-goal"] }
|
"statusBar": { "indicators": ["sprint-timer", "word-goal"] }
|
||||||
},
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{ "key": "dailyGoal", "type": "number", "default": 1000, "label": "Daily word goal" },
|
{ "key": "dailyGoal", "type": "number", "default": 1000, "label": "Daily word goal" },
|
||||||
{ "key": "sprintDuration", "type": "number", "default": 25, "label": "Sprint duration (min)" },
|
{ "key": "sprintDuration", "type": "number", "default": 25, "label": "Sprint duration (min)" },
|
||||||
{
|
{ "key": "autoSnapshotInterval", "type": "number", "default": 0, "label": "Auto-snapshot interval (min, 0=off)" },
|
||||||
"key": "autoSnapshotInterval",
|
|
||||||
"type": "number",
|
|
||||||
"default": 0,
|
|
||||||
"label": "Auto-snapshot interval (min, 0=off)"
|
|
||||||
},
|
|
||||||
{ "key": "maxSnapshots", "type": "number", "default": 50, "label": "Max snapshots to keep" }
|
{ "key": "maxSnapshots", "type": "number", "default": 50, "label": "Max snapshots to keep" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ function renderGoalsPanel(container, { engines, settings }) {
|
|||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'ws-stat-row';
|
row.className = 'ws-stat-row';
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.textContent =
|
label.textContent = progress.written.toLocaleString() + ' / ' + dailyGoal.toLocaleString() + ' words';
|
||||||
progress.written.toLocaleString() + ' / ' + dailyGoal.toLocaleString() + ' words';
|
|
||||||
const pct = document.createElement('span');
|
const pct = document.createElement('span');
|
||||||
pct.className = 'ws-pct';
|
pct.className = 'ws-pct';
|
||||||
pct.textContent = progress.pct + '%';
|
pct.textContent = progress.pct + '%';
|
||||||
@@ -82,7 +81,7 @@ function renderGoalsPanel(container, { engines, settings }) {
|
|||||||
|
|
||||||
const chart = document.createElement('div');
|
const chart = document.createElement('div');
|
||||||
chart.className = 'ws-chart';
|
chart.className = 'ws-chart';
|
||||||
const maxWords = Math.max(...last30.map((d) => d.words), 1);
|
const maxWords = Math.max(...last30.map(d => d.words), 1);
|
||||||
for (const day of last30) {
|
for (const day of last30) {
|
||||||
const barEl = document.createElement('div');
|
const barEl = document.createElement('div');
|
||||||
const height = Math.max(2, (day.words / maxWords) * 60);
|
const height = Math.max(2, (day.words / maxWords) * 60);
|
||||||
|
|||||||
@@ -69,8 +69,7 @@ function renderManuscriptPanel(container, { engines, editor, settings }) {
|
|||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'ws-stat-row';
|
row.className = 'ws-stat-row';
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.textContent =
|
label.textContent = stats.totalWords.toLocaleString() + ' / ' + stats.targetWords.toLocaleString() + ' words';
|
||||||
stats.totalWords.toLocaleString() + ' / ' + stats.targetWords.toLocaleString() + ' words';
|
|
||||||
const pct = document.createElement('span');
|
const pct = document.createElement('span');
|
||||||
pct.className = 'ws-pct';
|
pct.className = 'ws-pct';
|
||||||
pct.textContent = stats.pctComplete + '%';
|
pct.textContent = stats.pctComplete + '%';
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function renderProofreadPanel(container, { events, editor }) {
|
|||||||
if (result && result.issues) {
|
if (result && result.issues) {
|
||||||
renderIssues(issuesList, result.issues);
|
renderIssues(issuesList, result.issues);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -80,10 +80,7 @@ function renderIssues(container, issues) {
|
|||||||
|
|
||||||
const actions = document.createElement('div');
|
const actions = document.createElement('div');
|
||||||
actions.className = 'ws-issue-actions';
|
actions.className = 'ws-issue-actions';
|
||||||
for (const [, label] of [
|
for (const [action, label] of [['accept', 'Accept'], ['dismiss', 'Dismiss']]) {
|
||||||
['accept', 'Accept'],
|
|
||||||
['dismiss', 'Dismiss'],
|
|
||||||
]) {
|
|
||||||
const actionBtn = document.createElement('button');
|
const actionBtn = document.createElement('button');
|
||||||
actionBtn.className = 'ws-btn ws-btn-sm';
|
actionBtn.className = 'ws-btn ws-btn-sm';
|
||||||
actionBtn.textContent = label;
|
actionBtn.textContent = label;
|
||||||
|
|||||||
@@ -45,11 +45,7 @@ function renderSnapshotsPanel(container, { engines, editor }) {
|
|||||||
|
|
||||||
const actions = document.createElement('div');
|
const actions = document.createElement('div');
|
||||||
actions.className = 'ws-snapshot-actions';
|
actions.className = 'ws-snapshot-actions';
|
||||||
for (const [action, text, cls] of [
|
for (const [action, text, cls] of [['restore', 'Restore', ''], ['diff', 'Diff', ''], ['delete', 'Delete', 'ws-btn-danger']]) {
|
||||||
['restore', 'Restore', ''],
|
|
||||||
['diff', 'Diff', ''],
|
|
||||||
['delete', 'Delete', 'ws-btn-danger'],
|
|
||||||
]) {
|
|
||||||
const actionBtn = document.createElement('button');
|
const actionBtn = document.createElement('button');
|
||||||
actionBtn.className = 'ws-btn ws-btn-sm' + (cls ? ' ' + cls : '');
|
actionBtn.className = 'ws-btn ws-btn-sm' + (cls ? ' ' + cls : '');
|
||||||
actionBtn.textContent = text;
|
actionBtn.textContent = text;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class ProjectManager {
|
|||||||
type: opts.type || 'manuscript',
|
type: opts.type || 'manuscript',
|
||||||
target: { words: opts.targetWords || 0, deadline: opts.deadline || null },
|
target: { words: opts.targetWords || 0, deadline: opts.deadline || null },
|
||||||
chapters: [],
|
chapters: [],
|
||||||
metadata: opts.metadata || {},
|
metadata: opts.metadata || {}
|
||||||
};
|
};
|
||||||
this.fs.writeFile(dir + '/.project.json', JSON.stringify(project, null, 2));
|
this.fs.writeFile(dir + '/.project.json', JSON.stringify(project, null, 2));
|
||||||
return project;
|
return project;
|
||||||
@@ -66,7 +66,7 @@ class ProjectManager {
|
|||||||
totalWords,
|
totalWords,
|
||||||
chapterCount: project.chapters.length,
|
chapterCount: project.chapters.length,
|
||||||
targetWords: target,
|
targetWords: target,
|
||||||
pctComplete: target > 0 ? Math.min(100, Math.round((totalWords / target) * 100)) : 0,
|
pctComplete: target > 0 ? Math.min(100, Math.round((totalWords / target) * 100)) : 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class SnapshotManager {
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
content,
|
content,
|
||||||
wordCount: content.split(/\s+/).filter(Boolean).length,
|
wordCount: content.split(/\s+/).filter(Boolean).length,
|
||||||
label,
|
label
|
||||||
};
|
};
|
||||||
snaps.unshift(snap);
|
snaps.unshift(snap);
|
||||||
this._saveAll(snaps);
|
this._saveAll(snaps);
|
||||||
@@ -36,7 +36,7 @@ class SnapshotManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getById(id) {
|
getById(id) {
|
||||||
return this._getAll().find((s) => s.id === id) || null;
|
return this._getAll().find(s => s.id === id) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
restore(id) {
|
restore(id) {
|
||||||
@@ -46,7 +46,7 @@ class SnapshotManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(id) {
|
delete(id) {
|
||||||
const snaps = this._getAll().filter((s) => s.id !== id);
|
const snaps = this._getAll().filter(s => s.id !== id);
|
||||||
this._saveAll(snaps);
|
this._saveAll(snaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,12 +59,8 @@ class SnapshotManager {
|
|||||||
const newSet = new Set(newLines);
|
const newSet = new Set(newLines);
|
||||||
let added = 0;
|
let added = 0;
|
||||||
let removed = 0;
|
let removed = 0;
|
||||||
for (const line of newLines) {
|
for (const line of newLines) { if (!oldSet.has(line)) added++; }
|
||||||
if (!oldSet.has(line)) added++;
|
for (const line of oldLines) { if (!newSet.has(line)) removed++; }
|
||||||
}
|
|
||||||
for (const line of oldLines) {
|
|
||||||
if (!newSet.has(line)) removed++;
|
|
||||||
}
|
|
||||||
return { added, removed };
|
return { added, removed };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ class SprintEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive() {
|
isActive() { return this._active; }
|
||||||
return this._active;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRemaining() {
|
getRemaining() {
|
||||||
if (!this._active) return 0;
|
if (!this._active) return 0;
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ class PluginContext {
|
|||||||
* @param {object} deps.exportHooks - { preHooks: [], postHooks: [] }
|
* @param {object} deps.exportHooks - { preHooks: [], postHooks: [] }
|
||||||
*/
|
*/
|
||||||
constructor(deps) {
|
constructor(deps) {
|
||||||
const { pluginId, sidebar, commands, statusBar, eventBus, settings, editor, ipc, exportHooks } =
|
const { pluginId, sidebar, commands, statusBar, eventBus, settings, editor, ipc, exportHooks } = deps;
|
||||||
deps;
|
|
||||||
|
|
||||||
this.sidebar = {
|
this.sidebar = {
|
||||||
registerPanel: (id, opts) => sidebar.registerPanel(`${pluginId}:${id}`, opts),
|
registerPanel: (id, opts) => sidebar.registerPanel(`${pluginId}:${id}`, opts)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.commands = {
|
this.commands = {
|
||||||
@@ -29,45 +28,41 @@ class PluginContext {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
commands.register(`${pluginId}:${id}`, label, safeHandler, shortcut);
|
commands.register(`${pluginId}:${id}`, label, safeHandler, shortcut);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.statusBar = {
|
this.statusBar = {
|
||||||
registerIndicator: (id, opts) => statusBar.registerIndicator(`${pluginId}:${id}`, opts),
|
registerIndicator: (id, opts) => statusBar.registerIndicator(`${pluginId}:${id}`, opts)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.settings = {
|
this.settings = {
|
||||||
get: (key) => settings.get(`plugins.${pluginId}.${key}`),
|
get: (key) => settings.get(`plugins.${pluginId}.${key}`),
|
||||||
set: (key, value) => settings.set(`plugins.${pluginId}.${key}`, value),
|
set: (key, value) => settings.set(`plugins.${pluginId}.${key}`, value),
|
||||||
onChanged: (key, cb) => settings.onChanged(`plugins.${pluginId}.${key}`, cb),
|
onChanged: (key, cb) => settings.onChanged(`plugins.${pluginId}.${key}`, cb)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.editor = {
|
this.editor = {
|
||||||
getContent: () => editor.getContent(),
|
getContent: () => editor.getContent(),
|
||||||
getSelection: () => editor.getSelection(),
|
getSelection: () => editor.getSelection(),
|
||||||
insertAtCursor: (text) => editor.insertAtCursor(text),
|
insertAtCursor: (text) => editor.insertAtCursor(text),
|
||||||
onContentChanged: (cb) => editor.onContentChanged(cb),
|
onContentChanged: (cb) => editor.onContentChanged(cb)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.events = {
|
this.events = {
|
||||||
on: (event, handler) => eventBus.on(event, handler),
|
on: (event, handler) => eventBus.on(event, handler),
|
||||||
off: (event, handler) => eventBus.off(event, handler),
|
off: (event, handler) => eventBus.off(event, handler),
|
||||||
emit: (event, payload) => eventBus.emit(event, payload),
|
emit: (event, payload) => eventBus.emit(event, payload),
|
||||||
hasHandler: (event) => eventBus.hasHandler(event),
|
hasHandler: (event) => eventBus.hasHandler(event)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.ipc = {
|
this.ipc = {
|
||||||
invoke: (channel, ...args) => ipc.invoke(channel, ...args),
|
invoke: (channel, ...args) => ipc.invoke(channel, ...args),
|
||||||
on: (channel, handler) => ipc.on(channel, handler),
|
on: (channel, handler) => ipc.on(channel, handler)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.exports = {
|
this.exports = {
|
||||||
registerPreHook: (handler) => {
|
registerPreHook: (handler) => { if (exportHooks) exportHooks.preHooks.push(handler); },
|
||||||
if (exportHooks) exportHooks.preHooks.push(handler);
|
registerPostHook: (handler) => { if (exportHooks) exportHooks.postHooks.push(handler); }
|
||||||
},
|
|
||||||
registerPostHook: (handler) => {
|
|
||||||
if (exportHooks) exportHooks.postHooks.push(handler);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ class PluginLoader {
|
|||||||
const loaded = require(indexPath);
|
const loaded = require(indexPath);
|
||||||
PluginClass = loaded.Plugin || loaded.default || null;
|
PluginClass = loaded.Plugin || loaded.default || null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(`[PluginLoader] Failed to load index.js for "${manifest.id}":`, err.message);
|
||||||
`[PluginLoader] Failed to load index.js for "${manifest.id}":`,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +46,7 @@ class PluginLoader {
|
|||||||
description: manifest.description,
|
description: manifest.description,
|
||||||
manifest,
|
manifest,
|
||||||
PluginClass,
|
PluginClass,
|
||||||
dir: pluginDir,
|
dir: pluginDir
|
||||||
});
|
});
|
||||||
this.loadedIds.add(manifest.id);
|
this.loadedIds.add(manifest.id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class PluginRegistry {
|
|||||||
settings: this.deps.settings,
|
settings: this.deps.settings,
|
||||||
editor: this.deps.editor,
|
editor: this.deps.editor,
|
||||||
ipc: this.deps.ipc,
|
ipc: this.deps.ipc,
|
||||||
exportHooks: this.exportHooks,
|
exportHooks: this.exportHooks
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class SettingsStore {
|
|||||||
this.backend.set(key, value);
|
this.backend.set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChanged(_key, _callback) {
|
onChanged(key, callback) {
|
||||||
// Deferred: plugins read settings on init/activate for MVP.
|
// Deferred: plugins read settings on init/activate for MVP.
|
||||||
// Full change notification requires IPC watcher in main process.
|
// Full change notification requires IPC watcher in main process.
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-29
@@ -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',
|
||||||
@@ -140,7 +139,7 @@ const ALLOWED_SEND_CHANNELS = [
|
|||||||
|
|
||||||
// Plugin settings
|
// Plugin settings
|
||||||
'plugin-settings:get',
|
'plugin-settings:get',
|
||||||
'plugin-settings:set',
|
'plugin-settings:set'
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALLOWED_RECEIVE_CHANNELS = [
|
const ALLOWED_RECEIVE_CHANNELS = [
|
||||||
@@ -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',
|
||||||
@@ -237,7 +234,7 @@ const ALLOWED_RECEIVE_CHANNELS = [
|
|||||||
'load-template-menu',
|
'load-template-menu',
|
||||||
'toggle-command-palette',
|
'toggle-command-palette',
|
||||||
'toggle-sidebar-panel',
|
'toggle-sidebar-panel',
|
||||||
'toggle-bottom-panel',
|
'toggle-bottom-panel'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -344,23 +341,23 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
exists: (filePath) => ipcRenderer.invoke('path-exists', filePath),
|
exists: (filePath) => ipcRenderer.invoke('path-exists', filePath),
|
||||||
isDirectory: (filePath) => ipcRenderer.invoke('is-directory', filePath),
|
isDirectory: (filePath) => ipcRenderer.invoke('is-directory', filePath),
|
||||||
copy: (source, destination) => ipcRenderer.invoke('copy-path', { source, destination }),
|
copy: (source, destination) => ipcRenderer.invoke('copy-path', { source, destination }),
|
||||||
move: (source, destination) => ipcRenderer.invoke('move-path', { source, destination }),
|
move: (source, destination) => ipcRenderer.invoke('move-path', { source, destination })
|
||||||
},
|
},
|
||||||
|
|
||||||
// Theme Operations
|
// Theme Operations
|
||||||
theme: {
|
theme: {
|
||||||
get: () => ipcRenderer.send('get-theme'),
|
get: () => ipcRenderer.send('get-theme')
|
||||||
},
|
},
|
||||||
|
|
||||||
// Print Operations
|
// Print Operations
|
||||||
print: {
|
print: {
|
||||||
doPrint: (options) => ipcRenderer.send('do-print', options),
|
doPrint: (options) => ipcRenderer.send('do-print', options)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Export Operations
|
// Export Operations
|
||||||
export: {
|
export: {
|
||||||
withOptions: (format, options) => ipcRenderer.send('export-with-options', { format, options }),
|
withOptions: (format, options) => ipcRenderer.send('export-with-options', { format, options }),
|
||||||
spreadsheet: (content, format) => ipcRenderer.send('export-spreadsheet', { content, format }),
|
spreadsheet: (content, format) => ipcRenderer.send('export-spreadsheet', { content, format })
|
||||||
},
|
},
|
||||||
|
|
||||||
// Batch Conversion
|
// Batch Conversion
|
||||||
@@ -368,7 +365,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
convert: (inputFolder, outputFolder, format, options) => {
|
convert: (inputFolder, outputFolder, format, options) => {
|
||||||
ipcRenderer.send('batch-convert', { inputFolder, outputFolder, format, options });
|
ipcRenderer.send('batch-convert', { inputFolder, outputFolder, format, options });
|
||||||
},
|
},
|
||||||
selectFolder: (type) => ipcRenderer.send('select-folder', type),
|
selectFolder: (type) => ipcRenderer.send('select-folder', type)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Universal Converter
|
// Universal Converter
|
||||||
@@ -377,14 +374,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
ipcRenderer.send('universal-convert', { tool, fromFormat, toFormat, filePath });
|
ipcRenderer.send('universal-convert', { tool, fromFormat, toFormat, filePath });
|
||||||
},
|
},
|
||||||
convertBatch: (tool, fromFormat, toFormat, inputFolder, outputFolder) => {
|
convertBatch: (tool, fromFormat, toFormat, inputFolder, outputFolder) => {
|
||||||
ipcRenderer.send('universal-convert-batch', {
|
ipcRenderer.send('universal-convert-batch', { tool, fromFormat, toFormat, inputFolder, outputFolder });
|
||||||
tool,
|
}
|
||||||
fromFormat,
|
|
||||||
toFormat,
|
|
||||||
inputFolder,
|
|
||||||
outputFolder,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Header/Footer Operations
|
// Header/Footer Operations
|
||||||
@@ -392,23 +383,22 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
getSettings: () => ipcRenderer.send('get-header-footer-settings'),
|
getSettings: () => ipcRenderer.send('get-header-footer-settings'),
|
||||||
saveSettings: (settings) => ipcRenderer.send('save-header-footer-settings', settings),
|
saveSettings: (settings) => ipcRenderer.send('save-header-footer-settings', settings),
|
||||||
browseLogo: (position) => ipcRenderer.send('browse-header-footer-logo', position),
|
browseLogo: (position) => ipcRenderer.send('browse-header-footer-logo', position),
|
||||||
saveLogo: (position, filePath) =>
|
saveLogo: (position, filePath) => ipcRenderer.send('save-header-footer-logo', { position, filePath }),
|
||||||
ipcRenderer.send('save-header-footer-logo', { position, filePath }),
|
clearLogo: (position) => ipcRenderer.send('clear-header-footer-logo', position)
|
||||||
clearLogo: (position) => ipcRenderer.send('clear-header-footer-logo', position),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Page Settings
|
// Page Settings
|
||||||
page: {
|
page: {
|
||||||
getSettings: () => ipcRenderer.send('get-page-settings'),
|
getSettings: () => ipcRenderer.send('get-page-settings'),
|
||||||
updateSettings: (settings) => ipcRenderer.send('update-page-settings', settings),
|
updateSettings: (settings) => ipcRenderer.send('update-page-settings', settings),
|
||||||
setCustomStartPage: (pageNumber) => ipcRenderer.send('set-custom-start-page', pageNumber),
|
setCustomStartPage: (pageNumber) => ipcRenderer.send('set-custom-start-page', pageNumber)
|
||||||
},
|
},
|
||||||
|
|
||||||
// PDF Operations
|
// PDF Operations
|
||||||
pdf: {
|
pdf: {
|
||||||
processOperation: (data) => ipcRenderer.send('process-pdf-operation', data),
|
processOperation: (data) => ipcRenderer.send('process-pdf-operation', data),
|
||||||
getPageCount: (filePath) => ipcRenderer.send('get-pdf-page-count', filePath),
|
getPageCount: (filePath) => ipcRenderer.send('get-pdf-page-count', filePath),
|
||||||
selectFolder: (inputId) => ipcRenderer.send('select-pdf-folder', inputId),
|
selectFolder: (inputId) => ipcRenderer.send('select-pdf-folder', inputId)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Image Converter Operations
|
// Image Converter Operations
|
||||||
@@ -417,7 +407,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
batchConvert: (data) => ipcRenderer.send('image-batch-convert', data),
|
batchConvert: (data) => ipcRenderer.send('image-batch-convert', data),
|
||||||
resize: (data) => ipcRenderer.send('image-resize', data),
|
resize: (data) => ipcRenderer.send('image-resize', data),
|
||||||
compress: (data) => ipcRenderer.send('image-compress', data),
|
compress: (data) => ipcRenderer.send('image-compress', data),
|
||||||
rotate: (data) => ipcRenderer.send('image-rotate', data),
|
rotate: (data) => ipcRenderer.send('image-rotate', data)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Audio Converter Operations
|
// Audio Converter Operations
|
||||||
@@ -426,7 +416,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
batchConvert: (data) => ipcRenderer.send('audio-batch-convert', data),
|
batchConvert: (data) => ipcRenderer.send('audio-batch-convert', data),
|
||||||
extract: (data) => ipcRenderer.send('audio-extract', data),
|
extract: (data) => ipcRenderer.send('audio-extract', data),
|
||||||
trim: (data) => ipcRenderer.send('audio-trim', data),
|
trim: (data) => ipcRenderer.send('audio-trim', data),
|
||||||
merge: (data) => ipcRenderer.send('audio-merge', data),
|
merge: (data) => ipcRenderer.send('audio-merge', data)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Video Converter Operations
|
// Video Converter Operations
|
||||||
@@ -436,16 +426,16 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
compress: (data) => ipcRenderer.send('video-compress', data),
|
compress: (data) => ipcRenderer.send('video-compress', data),
|
||||||
trim: (data) => ipcRenderer.send('video-trim', data),
|
trim: (data) => ipcRenderer.send('video-trim', data),
|
||||||
extractFrames: (data) => ipcRenderer.send('video-frames', data),
|
extractFrames: (data) => ipcRenderer.send('video-frames', data),
|
||||||
toGif: (data) => ipcRenderer.send('video-gif', data),
|
toGif: (data) => ipcRenderer.send('video-gif', data)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Generator Windows
|
// Generator Windows
|
||||||
generators: {
|
generators: {
|
||||||
openAscii: () => ipcRenderer.send('open-ascii-generator'),
|
openAscii: () => ipcRenderer.send('open-ascii-generator'),
|
||||||
openTable: () => ipcRenderer.send('open-table-generator'),
|
openTable: () => ipcRenderer.send('open-table-generator')
|
||||||
},
|
},
|
||||||
|
|
||||||
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
getAppVersion: () => ipcRenderer.invoke('get-app-version')
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log successful preload initialization
|
// Log successful preload initialization
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class PrintPreview {
|
|||||||
document.getElementById('print-execute')?.addEventListener('click', () => this.executePrint());
|
document.getElementById('print-execute')?.addEventListener('click', () => this.executePrint());
|
||||||
|
|
||||||
// Update preview on option changes
|
// Update preview on option changes
|
||||||
['print-paper-size', 'print-orientation', 'print-margins'].forEach((id) => {
|
['print-paper-size', 'print-orientation', 'print-margins'].forEach(id => {
|
||||||
document.getElementById(id)?.addEventListener('change', () => this.refreshPreview());
|
document.getElementById(id)?.addEventListener('change', () => this.refreshPreview());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,12 +67,12 @@ class PrintPreview {
|
|||||||
|
|
||||||
// Get dimensions for paper size
|
// Get dimensions for paper size
|
||||||
const sizes = {
|
const sizes = {
|
||||||
A3: { width: '297mm', height: '420mm' },
|
'A3': { width: '297mm', height: '420mm' },
|
||||||
A4: { width: '210mm', height: '297mm' },
|
'A4': { width: '210mm', height: '297mm' },
|
||||||
A5: { width: '148mm', height: '210mm' },
|
'A5': { width: '148mm', height: '210mm' },
|
||||||
Letter: { width: '8.5in', height: '11in' },
|
'Letter': { width: '8.5in', height: '11in' },
|
||||||
Legal: { width: '8.5in', height: '14in' },
|
'Legal': { width: '8.5in', height: '14in' },
|
||||||
Tabloid: { width: '11in', height: '17in' },
|
'Tabloid': { width: '11in', height: '17in' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const size = sizes[paperSize] || sizes['A4'];
|
const size = sizes[paperSize] || sizes['A4'];
|
||||||
|
|||||||
+842
-2087
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
function renderExplorerPanel(container, { listDirectory, onFileOpen, currentDir }) {
|
function renderExplorerPanel(container, { listDirectory, onFileOpen, currentDir }) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="explorer-panel">
|
<div class="explorer-panel">
|
||||||
@@ -13,33 +15,19 @@ function renderExplorerPanel(container, { listDirectory, onFileOpen, currentDir
|
|||||||
const dir = await listDirectory(null); // null means open folder dialog
|
const dir = await listDirectory(null); // null means open folder dialog
|
||||||
if (dir) {
|
if (dir) {
|
||||||
document.getElementById('explorer-path').value = dir.path;
|
document.getElementById('explorer-path').value = dir.path;
|
||||||
renderTree(
|
renderTree(document.getElementById('explorer-tree'), dir.entries, listDirectory, onFileOpen, dir.path);
|
||||||
document.getElementById('explorer-tree'),
|
|
||||||
dir.entries,
|
|
||||||
listDirectory,
|
|
||||||
onFileOpen,
|
|
||||||
dir.path
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentDir) {
|
if (currentDir) {
|
||||||
listDirectory(currentDir).then((dir) => {
|
listDirectory(currentDir).then(dir => {
|
||||||
if (dir)
|
if (dir) renderTree(document.getElementById('explorer-tree'), dir.entries, listDirectory, onFileOpen, currentDir);
|
||||||
renderTree(
|
|
||||||
document.getElementById('explorer-tree'),
|
|
||||||
dir.entries,
|
|
||||||
listDirectory,
|
|
||||||
onFileOpen,
|
|
||||||
currentDir
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTree(container, entries, listDirectory, onFileOpen, _basePath) {
|
function renderTree(container, entries, listDirectory, onFileOpen, basePath) {
|
||||||
container.innerHTML = entries
|
container.innerHTML = entries.map(entry => {
|
||||||
.map((entry) => {
|
|
||||||
if (entry.isDirectory) {
|
if (entry.isDirectory) {
|
||||||
return `<div class="tree-item tree-folder collapsed" data-path="${entry.path}">
|
return `<div class="tree-item tree-folder collapsed" data-path="${entry.path}">
|
||||||
<span class="tree-icon">▶</span>
|
<span class="tree-icon">▶</span>
|
||||||
@@ -51,10 +39,9 @@ function renderTree(container, entries, listDirectory, onFileOpen, _basePath) {
|
|||||||
<span class="tree-icon">${getFileIcon(entry.name)}</span>
|
<span class="tree-icon">${getFileIcon(entry.name)}</span>
|
||||||
<span class="tree-name">${entry.name}</span>
|
<span class="tree-name">${entry.name}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
})
|
}).join('');
|
||||||
.join('');
|
|
||||||
|
|
||||||
container.querySelectorAll('.tree-folder').forEach((el) => {
|
container.querySelectorAll('.tree-folder').forEach(el => {
|
||||||
el.querySelector('.tree-name').addEventListener('click', async () => {
|
el.querySelector('.tree-name').addEventListener('click', async () => {
|
||||||
const isCollapsed = el.classList.contains('collapsed');
|
const isCollapsed = el.classList.contains('collapsed');
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
@@ -65,29 +52,18 @@ function renderTree(container, entries, listDirectory, onFileOpen, _basePath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
el.classList.toggle('collapsed');
|
el.classList.toggle('collapsed');
|
||||||
el.querySelector('.tree-icon').textContent = el.classList.contains('collapsed')
|
el.querySelector('.tree-icon').textContent = el.classList.contains('collapsed') ? '\u25B6' : '\u25BC';
|
||||||
? '\u25B6'
|
|
||||||
: '\u25BC';
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
container.querySelectorAll('.tree-file').forEach((el) => {
|
container.querySelectorAll('.tree-file').forEach(el => {
|
||||||
el.addEventListener('click', () => onFileOpen(el.dataset.path));
|
el.addEventListener('click', () => onFileOpen(el.dataset.path));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileIcon(filename) {
|
function getFileIcon(filename) {
|
||||||
const ext = filename.split('.').pop().toLowerCase();
|
const ext = filename.split('.').pop().toLowerCase();
|
||||||
const icons = {
|
const icons = { md: '\u{1F4DD}', js: '\u{1F4DC}', json: '{}', html: '\u{1F310}', css: '\u{1F3A8}', py: '\u{1F40D}', pdf: '\u{1F4D5}', txt: '\u{1F4C4}' };
|
||||||
md: '\u{1F4DD}',
|
|
||||||
js: '\u{1F4DC}',
|
|
||||||
json: '{}',
|
|
||||||
html: '\u{1F310}',
|
|
||||||
css: '\u{1F3A8}',
|
|
||||||
py: '\u{1F40D}',
|
|
||||||
pdf: '\u{1F4D5}',
|
|
||||||
txt: '\u{1F4C4}',
|
|
||||||
};
|
|
||||||
return icons[ext] || '\u{1F4C4}';
|
return icons[ext] || '\u{1F4C4}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-21
@@ -1,4 +1,4 @@
|
|||||||
function renderGitPanel(container, { gitStatus, _gitDiff, gitStage, gitCommit, gitLog }) {
|
function renderGitPanel(container, { gitStatus, gitDiff, gitStage, gitCommit, gitLog }) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="git-panel">
|
<div class="git-panel">
|
||||||
<div class="git-section">
|
<div class="git-section">
|
||||||
@@ -32,29 +32,25 @@ function renderGitPanel(container, { gitStatus, _gitDiff, gitStage, gitCommit, g
|
|||||||
}
|
}
|
||||||
|
|
||||||
const files = [
|
const files = [
|
||||||
...status.modified.map((f) => ({ file: f, status: 'M', color: '#f59e0b' })),
|
...status.modified.map(f => ({ file: f, status: 'M', color: '#f59e0b' })),
|
||||||
...status.not_added.map((f) => ({ file: f, status: '?', color: '#6b7280' })),
|
...status.not_added.map(f => ({ file: f, status: '?', color: '#6b7280' })),
|
||||||
...status.created.map((f) => ({ file: f, status: 'A', color: '#10b981' })),
|
...status.created.map(f => ({ file: f, status: 'A', color: '#10b981' })),
|
||||||
...status.deleted.map((f) => ({ file: f, status: 'D', color: '#ef4444' })),
|
...status.deleted.map(f => ({ file: f, status: 'D', color: '#ef4444' })),
|
||||||
...status.staged.map((f) => ({ file: f, status: 'S', color: '#3b82f6' })),
|
...status.staged.map(f => ({ file: f, status: 'S', color: '#3b82f6' })),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
changesEl.innerHTML = '<p class="git-info">No changes</p>';
|
changesEl.innerHTML = '<p class="git-info">No changes</p>';
|
||||||
} else {
|
} else {
|
||||||
changesEl.innerHTML = files
|
changesEl.innerHTML = files.map(f => `
|
||||||
.map(
|
|
||||||
(f) => `
|
|
||||||
<div class="git-file" data-file="${f.file}">
|
<div class="git-file" data-file="${f.file}">
|
||||||
<span class="git-file-status" style="color:${f.color}">${f.status}</span>
|
<span class="git-file-status" style="color:${f.color}">${f.status}</span>
|
||||||
<span class="git-file-name">${f.file}</span>
|
<span class="git-file-name">${f.file}</span>
|
||||||
<button class="git-stage-btn" data-file="${f.file}" title="Stage file">+</button>
|
<button class="git-stage-btn" data-file="${f.file}" title="Stage file">+</button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`).join('');
|
||||||
)
|
|
||||||
.join('');
|
|
||||||
|
|
||||||
changesEl.querySelectorAll('.git-stage-btn').forEach((btn) => {
|
changesEl.querySelectorAll('.git-stage-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', async (e) => {
|
btn.addEventListener('click', async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
await gitStage([btn.dataset.file]);
|
await gitStage([btn.dataset.file]);
|
||||||
@@ -67,18 +63,12 @@ function renderGitPanel(container, { gitStatus, _gitDiff, gitStage, gitCommit, g
|
|||||||
const log = await gitLog();
|
const log = await gitLog();
|
||||||
const logEl = document.getElementById('git-log');
|
const logEl = document.getElementById('git-log');
|
||||||
if (log && logEl) {
|
if (log && logEl) {
|
||||||
logEl.innerHTML =
|
logEl.innerHTML = (log.all || []).slice(0, 10).map(entry => `
|
||||||
(log.all || [])
|
|
||||||
.slice(0, 10)
|
|
||||||
.map(
|
|
||||||
(entry) => `
|
|
||||||
<div class="git-log-entry">
|
<div class="git-log-entry">
|
||||||
<div class="git-log-msg">${entry.message}</div>
|
<div class="git-log-msg">${entry.message}</div>
|
||||||
<div class="git-log-meta">${entry.date?.substring(0, 10) || ''} · ${entry.author_name || ''}</div>
|
<div class="git-log-meta">${entry.date?.substring(0, 10) || ''} · ${entry.author_name || ''}</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`).join('') || '<p class="git-info">No commits</p>';
|
||||||
)
|
|
||||||
.join('') || '<p class="git-info">No commits</p>';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function renderOutlinePanel(container, { getEditorContent, getActiveLine, onHead
|
|||||||
result.push({
|
result.push({
|
||||||
level: match[1].length,
|
level: match[1].length,
|
||||||
text: match[2].trim(),
|
text: match[2].trim(),
|
||||||
line: i + 1,
|
line: i + 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,21 +64,15 @@ function renderOutlinePanel(container, { getEditorContent, getActiveLine, onHead
|
|||||||
|
|
||||||
const activeHeading = findActiveHeading(activeLine);
|
const activeHeading = findActiveHeading(activeLine);
|
||||||
|
|
||||||
listEl.innerHTML =
|
listEl.innerHTML = headings.map((h, idx) => `
|
||||||
headings
|
|
||||||
.map(
|
|
||||||
(h, idx) => `
|
|
||||||
<div class="outline-item outline-level-${h.level}${activeHeading && h.line === activeHeading.line ? ' active' : ''}"
|
<div class="outline-item outline-level-${h.level}${activeHeading && h.line === activeHeading.line ? ' active' : ''}"
|
||||||
data-line="${h.line}" data-index="${idx}">
|
data-line="${h.line}" data-index="${idx}">
|
||||||
<span class="outline-text">${escapeHtml(h.text)}</span>
|
<span class="outline-text">${escapeHtml(h.text)}</span>
|
||||||
<span class="outline-badge">H${h.level}</span>
|
<span class="outline-badge">H${h.level}</span>
|
||||||
</div>
|
</div>
|
||||||
`
|
`).join('') + `<div class="outline-footer">${headings.length} heading${headings.length !== 1 ? 's' : ''}</div>`;
|
||||||
)
|
|
||||||
.join('') +
|
|
||||||
`<div class="outline-footer">${headings.length} heading${headings.length !== 1 ? 's' : ''}</div>`;
|
|
||||||
|
|
||||||
listEl.querySelectorAll('.outline-item').forEach((item) => {
|
listEl.querySelectorAll('.outline-item').forEach(item => {
|
||||||
item.addEventListener('click', () => {
|
item.addEventListener('click', () => {
|
||||||
const line = parseInt(item.dataset.line, 10);
|
const line = parseInt(item.dataset.line, 10);
|
||||||
onHeadingClick(line);
|
onHeadingClick(line);
|
||||||
@@ -101,7 +95,7 @@ function renderOutlinePanel(container, { getEditorContent, getActiveLine, onHead
|
|||||||
activeLine = line;
|
activeLine = line;
|
||||||
const activeHeading = findActiveHeading(line);
|
const activeHeading = findActiveHeading(line);
|
||||||
|
|
||||||
listEl.querySelectorAll('.outline-item').forEach((item) => {
|
listEl.querySelectorAll('.outline-item').forEach(item => {
|
||||||
const itemLine = parseInt(item.dataset.line, 10);
|
const itemLine = parseInt(item.dataset.line, 10);
|
||||||
if (activeHeading && itemLine === activeHeading.line) {
|
if (activeHeading && itemLine === activeHeading.line) {
|
||||||
item.classList.add('active');
|
item.classList.add('active');
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ class SidebarManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
document.querySelectorAll('.sidebar-icon').forEach((btn) => {
|
document.querySelectorAll('.sidebar-icon').forEach(btn => {
|
||||||
btn.addEventListener('click', () => this.togglePanel(btn.dataset.panel));
|
btn.addEventListener('click', () => this.togglePanel(btn.dataset.panel));
|
||||||
});
|
});
|
||||||
document
|
document.querySelector('.sidebar-panel-close')?.addEventListener('click', () => this.collapse());
|
||||||
.querySelector('.sidebar-panel-close')
|
|
||||||
?.addEventListener('click', () => this.collapse());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPanel(name, { title, render }) {
|
registerPanel(name, { title, render }) {
|
||||||
@@ -37,7 +35,7 @@ class SidebarManager {
|
|||||||
this.panelContent.innerHTML = '';
|
this.panelContent.innerHTML = '';
|
||||||
panel.render(this.panelContent);
|
panel.render(this.panelContent);
|
||||||
this.activePanel = name;
|
this.activePanel = name;
|
||||||
document.querySelectorAll('.sidebar-icon').forEach((btn) => {
|
document.querySelectorAll('.sidebar-icon').forEach(btn => {
|
||||||
btn.classList.toggle('active', btn.dataset.panel === name);
|
btn.classList.toggle('active', btn.dataset.panel === name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -45,7 +43,7 @@ class SidebarManager {
|
|||||||
collapse() {
|
collapse() {
|
||||||
this.sidebar.classList.add('collapsed');
|
this.sidebar.classList.add('collapsed');
|
||||||
this.activePanel = null;
|
this.activePanel = null;
|
||||||
document.querySelectorAll('.sidebar-icon').forEach((btn) => btn.classList.remove('active'));
|
document.querySelectorAll('.sidebar-icon').forEach(btn => btn.classList.remove('active'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function renderSnippetsPanel(container, { getSnippets, saveSnippet, deleteSnippe
|
|||||||
let snippets = [];
|
let snippets = [];
|
||||||
|
|
||||||
async function loadSnippets() {
|
async function loadSnippets() {
|
||||||
snippets = (await getSnippets()) || [];
|
snippets = await getSnippets() || [];
|
||||||
renderList(document.getElementById('snippets-search')?.value || '');
|
renderList(document.getElementById('snippets-search')?.value || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,17 +21,10 @@ function renderSnippetsPanel(container, { getSnippets, saveSnippet, deleteSnippe
|
|||||||
if (!list) return;
|
if (!list) return;
|
||||||
|
|
||||||
const filtered = query
|
const filtered = query
|
||||||
? snippets.filter(
|
? snippets.filter(s => s.name.toLowerCase().includes(query.toLowerCase()) || (s.language || '').toLowerCase().includes(query.toLowerCase()))
|
||||||
(s) =>
|
|
||||||
s.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
||||||
(s.language || '').toLowerCase().includes(query.toLowerCase())
|
|
||||||
)
|
|
||||||
: snippets;
|
: snippets;
|
||||||
|
|
||||||
list.innerHTML = filtered.length
|
list.innerHTML = filtered.length ? filtered.map(s => `
|
||||||
? filtered
|
|
||||||
.map(
|
|
||||||
(s) => `
|
|
||||||
<div class="snippet-item" data-id="${s.id}">
|
<div class="snippet-item" data-id="${s.id}">
|
||||||
<div class="snippet-header">
|
<div class="snippet-header">
|
||||||
<span class="snippet-name">${s.name}</span>
|
<span class="snippet-name">${s.name}</span>
|
||||||
@@ -43,19 +36,16 @@ function renderSnippetsPanel(container, { getSnippets, saveSnippet, deleteSnippe
|
|||||||
<button class="snippet-delete" data-id="${s.id}" title="Delete">×</button>
|
<button class="snippet-delete" data-id="${s.id}" title="Delete">×</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`).join('') : '<p class="git-info">No snippets yet. Click + to add one.</p>';
|
||||||
)
|
|
||||||
.join('')
|
|
||||||
: '<p class="git-info">No snippets yet. Click + to add one.</p>';
|
|
||||||
|
|
||||||
list.querySelectorAll('.snippet-insert').forEach((btn) => {
|
list.querySelectorAll('.snippet-insert').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
const s = snippets.find((sn) => sn.id === btn.dataset.id);
|
const s = snippets.find(sn => sn.id === btn.dataset.id);
|
||||||
if (s) onInsert(s.code);
|
if (s) onInsert(s.code);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
list.querySelectorAll('.snippet-delete').forEach((btn) => {
|
list.querySelectorAll('.snippet-delete').forEach(btn => {
|
||||||
btn.addEventListener('click', async () => {
|
btn.addEventListener('click', async () => {
|
||||||
await deleteSnippet(btn.dataset.id);
|
await deleteSnippet(btn.dataset.id);
|
||||||
loadSnippets();
|
loadSnippets();
|
||||||
@@ -63,9 +53,7 @@ function renderSnippetsPanel(container, { getSnippets, saveSnippet, deleteSnippe
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document
|
document.getElementById('snippets-search')?.addEventListener('input', (e) => renderList(e.target.value));
|
||||||
.getElementById('snippets-search')
|
|
||||||
?.addEventListener('input', (e) => renderList(e.target.value));
|
|
||||||
|
|
||||||
document.getElementById('snippets-add')?.addEventListener('click', () => {
|
document.getElementById('snippets-add')?.addEventListener('click', () => {
|
||||||
const name = prompt('Snippet name:');
|
const name = prompt('Snippet name:');
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const templates = [
|
const templates = [
|
||||||
{ name: 'Blog Post', file: 'blog-post.md', description: 'Article with frontmatter' },
|
{ name: 'Blog Post', file: 'blog-post.md', description: 'Article with frontmatter' },
|
||||||
{ name: 'Meeting Notes', file: 'meeting-notes.md', description: 'Agenda, notes, action items' },
|
{ name: 'Meeting Notes', file: 'meeting-notes.md', description: 'Agenda, notes, action items' },
|
||||||
{
|
{ name: 'Technical Spec', file: 'technical-spec.md', description: 'Requirements and architecture' },
|
||||||
name: 'Technical Spec',
|
|
||||||
file: 'technical-spec.md',
|
|
||||||
description: 'Requirements and architecture',
|
|
||||||
},
|
|
||||||
{ name: 'Changelog', file: 'changelog.md', description: 'Keep a Changelog format' },
|
{ name: 'Changelog', file: 'changelog.md', description: 'Keep a Changelog format' },
|
||||||
{ name: 'README', file: 'readme.md', description: 'Project documentation' },
|
{ name: 'README', file: 'readme.md', description: 'Project documentation' },
|
||||||
{ name: 'Project Plan', file: 'project-plan.md', description: 'Goals, milestones, timeline' },
|
{ name: 'Project Plan', file: 'project-plan.md', description: 'Goals, milestones, timeline' },
|
||||||
@@ -18,20 +17,16 @@ const templates = [
|
|||||||
function renderTemplatesPanel(container, onSelect) {
|
function renderTemplatesPanel(container, onSelect) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="panel-list">
|
<div class="panel-list">
|
||||||
${templates
|
${templates.map(t => `
|
||||||
.map(
|
|
||||||
(t) => `
|
|
||||||
<div class="panel-list-item template-item" data-file="${t.file}">
|
<div class="panel-list-item template-item" data-file="${t.file}">
|
||||||
<div class="panel-list-item-title">${t.name}</div>
|
<div class="panel-list-item-title">${t.name}</div>
|
||||||
<div class="panel-list-item-desc">${t.description}</div>
|
<div class="panel-list-item-desc">${t.description}</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`).join('')}
|
||||||
)
|
|
||||||
.join('')}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
container.querySelectorAll('.template-item').forEach((el) => {
|
container.querySelectorAll('.template-item').forEach(el => {
|
||||||
el.addEventListener('click', () => onSelect(el.dataset.file));
|
el.addEventListener('click', () => onSelect(el.dataset.file));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -675,33 +675,18 @@ body.theme-concreteinfo-dark .modal-footer {
|
|||||||
Animations
|
Animations
|
||||||
============================================ */
|
============================================ */
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from { opacity: 0; }
|
||||||
opacity: 0;
|
to { opacity: 1; }
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideUp {
|
@keyframes slideUp {
|
||||||
from {
|
from { transform: translateY(20px); opacity: 0; }
|
||||||
transform: translateY(20px);
|
to { transform: translateY(0); opacity: 1; }
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%,
|
0%, 100% { transform: scale(1); }
|
||||||
100% {
|
50% { transform: scale(1.05); }
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-fade-in {
|
.animate-fade-in {
|
||||||
|
|||||||
+62
-447
@@ -60,15 +60,7 @@
|
|||||||
|
|
||||||
/* Base styles - Reset is in styles.css */
|
/* Base styles - Reset is in styles.css */
|
||||||
body {
|
body {
|
||||||
font-family:
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
'Inter',
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
'Helvetica Neue',
|
|
||||||
Arial,
|
|
||||||
sans-serif;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
/* Background controlled by themes - do not set here */
|
/* Background controlled by themes - do not set here */
|
||||||
@@ -76,15 +68,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes gradientShift {
|
@keyframes gradientShift {
|
||||||
0% {
|
0% { background-position: 0% 50%; }
|
||||||
background-position: 0% 50%;
|
50% { background-position: 100% 50%; }
|
||||||
}
|
100% { background-position: 0% 50%; }
|
||||||
50% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@@ -359,9 +345,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Preview Styles - Modern Typography */
|
/* Preview Styles - Modern Typography */
|
||||||
.preview-content h1,
|
.preview-content h1, .preview-content h2, .preview-content h3 {
|
||||||
.preview-content h2,
|
|
||||||
.preview-content h3 {
|
|
||||||
/* Color controlled by theme */
|
/* Color controlled by theme */
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-top: 28px;
|
margin-top: 28px;
|
||||||
@@ -711,7 +695,11 @@ body {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
background: linear-gradient(90deg,
|
||||||
|
transparent,
|
||||||
|
rgba(255, 255, 255, 0.3),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
animation: shimmer 2s infinite;
|
animation: shimmer 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -810,9 +798,7 @@ body {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(255, 255, 255, 0.4);
|
background: rgba(255, 255, 255, 0.4);
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
transition:
|
transition: width 0.6s, height 0.6s;
|
||||||
width 0.6s,
|
|
||||||
height 0.6s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar button:hover::before,
|
.toolbar button:hover::before,
|
||||||
@@ -1060,7 +1046,7 @@ body.theme-paper .pdf-viewer-toolbar,
|
|||||||
body.theme-rosepine-dawn .pdf-viewer-toolbar {
|
body.theme-rosepine-dawn .pdf-viewer-toolbar {
|
||||||
background: linear-gradient(180deg, #fafafa 0%, #f0f0f0 100%) !important;
|
background: linear-gradient(180deg, #fafafa 0%, #f0f0f0 100%) !important;
|
||||||
border-bottom: 1px solid #d0d0d0 !important;
|
border-bottom: 1px solid #d0d0d0 !important;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-light .pdf-viewer-toolbar button,
|
body.theme-light .pdf-viewer-toolbar button,
|
||||||
@@ -1075,7 +1061,7 @@ body.theme-rosepine-dawn .pdf-viewer-toolbar button {
|
|||||||
background: #fff !important;
|
background: #fff !important;
|
||||||
border: 1px solid #ccc !important;
|
border: 1px solid #ccc !important;
|
||||||
color: #333 !important;
|
color: #333 !important;
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-light .pdf-viewer-toolbar button:hover,
|
body.theme-light .pdf-viewer-toolbar button:hover,
|
||||||
@@ -1458,7 +1444,7 @@ body.theme-rosepine-dawn .pdf-viewer {
|
|||||||
.export-dialog,
|
.export-dialog,
|
||||||
.batch-dialog,
|
.batch-dialog,
|
||||||
.editor-pane,
|
.editor-pane,
|
||||||
[id^='editor-pane-'],
|
[id^="editor-pane-"],
|
||||||
.new-tab-button,
|
.new-tab-button,
|
||||||
.tab-close {
|
.tab-close {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
@@ -1466,9 +1452,9 @@ body.theme-rosepine-dawn .pdf-viewer {
|
|||||||
|
|
||||||
/* Show preview content */
|
/* Show preview content */
|
||||||
.preview-pane,
|
.preview-pane,
|
||||||
[id^='preview-pane-'],
|
[id^="preview-pane-"],
|
||||||
.preview-content,
|
.preview-content,
|
||||||
[id^='preview-'] {
|
[id^="preview-"] {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
padding: 20px !important;
|
padding: 20px !important;
|
||||||
@@ -1505,9 +1491,9 @@ body.theme-rosepine-dawn .pdf-viewer {
|
|||||||
body.printing-no-styles .tab-content,
|
body.printing-no-styles .tab-content,
|
||||||
body.printing-no-styles .pane,
|
body.printing-no-styles .pane,
|
||||||
body.printing-no-styles .preview-pane,
|
body.printing-no-styles .preview-pane,
|
||||||
body.printing-no-styles [id^='preview-pane-'],
|
body.printing-no-styles [id^="preview-pane-"],
|
||||||
body.printing-no-styles .preview-content,
|
body.printing-no-styles .preview-content,
|
||||||
body.printing-no-styles [id^='preview-'] {
|
body.printing-no-styles [id^="preview-"] {
|
||||||
background: white !important;
|
background: white !important;
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
}
|
}
|
||||||
@@ -1553,27 +1539,27 @@ body.theme-rosepine-dawn .pdf-viewer {
|
|||||||
|
|
||||||
/* Common dark theme styles for preview content */
|
/* Common dark theme styles for preview content */
|
||||||
body.theme-dark .preview-content,
|
body.theme-dark .preview-content,
|
||||||
body.theme-dark [id^='preview-'],
|
body.theme-dark [id^="preview-"],
|
||||||
body.theme-one-dark .preview-content,
|
body.theme-one-dark .preview-content,
|
||||||
body.theme-one-dark [id^='preview-'],
|
body.theme-one-dark [id^="preview-"],
|
||||||
body.theme-dracula .preview-content,
|
body.theme-dracula .preview-content,
|
||||||
body.theme-dracula [id^='preview-'],
|
body.theme-dracula [id^="preview-"],
|
||||||
body.theme-nord .preview-content,
|
body.theme-nord .preview-content,
|
||||||
body.theme-nord [id^='preview-'],
|
body.theme-nord [id^="preview-"],
|
||||||
body.theme-tokyo-night .preview-content,
|
body.theme-tokyo-night .preview-content,
|
||||||
body.theme-tokyo-night [id^='preview-'],
|
body.theme-tokyo-night [id^="preview-"],
|
||||||
body.theme-palenight .preview-content,
|
body.theme-palenight .preview-content,
|
||||||
body.theme-palenight [id^='preview-'],
|
body.theme-palenight [id^="preview-"],
|
||||||
body.theme-ayu-dark .preview-content,
|
body.theme-ayu-dark .preview-content,
|
||||||
body.theme-ayu-dark [id^='preview-'],
|
body.theme-ayu-dark [id^="preview-"],
|
||||||
body.theme-ayu-mirage .preview-content,
|
body.theme-ayu-mirage .preview-content,
|
||||||
body.theme-ayu-mirage [id^='preview-'],
|
body.theme-ayu-mirage [id^="preview-"],
|
||||||
body.theme-oceanic-next .preview-content,
|
body.theme-oceanic-next .preview-content,
|
||||||
body.theme-oceanic-next [id^='preview-'],
|
body.theme-oceanic-next [id^="preview-"],
|
||||||
body.theme-gruvbox-dark .preview-content,
|
body.theme-gruvbox-dark .preview-content,
|
||||||
body.theme-gruvbox-dark [id^='preview-'],
|
body.theme-gruvbox-dark [id^="preview-"],
|
||||||
body.theme-cobalt2 .preview-content,
|
body.theme-cobalt2 .preview-content,
|
||||||
body.theme-cobalt2 [id^='preview-'] {
|
body.theme-cobalt2 [id^="preview-"] {
|
||||||
color: #e6e6e6;
|
color: #e6e6e6;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
@@ -2221,20 +2207,20 @@ body.theme-rosepine-dawn .batch-dialog-footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Dark theme command palette */
|
/* Dark theme command palette */
|
||||||
body[class*='dark'] .command-palette {
|
body[class*="dark"] .command-palette {
|
||||||
background: #1e1e1e;
|
background: #1e1e1e;
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .command-palette-input {
|
body[class*="dark"] .command-palette-input {
|
||||||
background: #1e1e1e;
|
background: #1e1e1e;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
border-color: #333;
|
border-color: #333;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .command-item:hover,
|
body[class*="dark"] .command-item:hover,
|
||||||
body[class*='dark'] .command-item.selected {
|
body[class*="dark"] .command-item.selected {
|
||||||
background: #2d2d2d;
|
background: #2d2d2d;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .command-shortcut {
|
body[class*="dark"] .command-shortcut {
|
||||||
background: #333;
|
background: #333;
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
@@ -2254,7 +2240,7 @@ body[class*='dark'] .command-shortcut {
|
|||||||
background: var(--gray-50, #f9fafb);
|
background: var(--gray-50, #f9fafb);
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .breadcrumb-bar {
|
body[class*="dark"] .breadcrumb-bar {
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
color: #888;
|
color: #888;
|
||||||
border-color: #333;
|
border-color: #333;
|
||||||
@@ -2292,7 +2278,7 @@ body[class*='dark'] .breadcrumb-bar {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.print-preview-content iframe {
|
.print-preview-content iframe {
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.print-option-group {
|
.print-option-group {
|
||||||
@@ -2306,7 +2292,7 @@ body[class*='dark'] .breadcrumb-bar {
|
|||||||
color: var(--gray-600, #4b5563);
|
color: var(--gray-600, #4b5563);
|
||||||
}
|
}
|
||||||
.print-option-group select,
|
.print-option-group select,
|
||||||
.print-option-group input[type='text'] {
|
.print-option-group input[type="text"] {
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border: 1px solid var(--gray-300, #d1d5db);
|
border: 1px solid var(--gray-300, #d1d5db);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -2318,7 +2304,7 @@ body[class*='dark'] .breadcrumb-bar {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.scale-control input[type='range'] {
|
.scale-control input[type="range"] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.checkbox-label {
|
.checkbox-label {
|
||||||
@@ -2362,398 +2348,27 @@ body[class*='dark'] .breadcrumb-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Writing Analytics Modal */
|
/* Writing Analytics Modal */
|
||||||
.analytics-overlay {
|
.analytics-overlay { position:fixed; inset:0; background:rgba(0,0,0,0.5); backdrop-filter:blur(4px); display:flex; align-items:center; justify-content:center; z-index:10000; animation:analyticsFadeIn 0.2s ease; }
|
||||||
position: fixed;
|
.analytics-modal { background:var(--bg-primary,#fff); border-radius:12px; width:520px; max-height:80vh; overflow-y:auto; box-shadow:0 20px 60px rgba(0,0,0,0.3); animation:analyticsSlideUp 0.3s ease; }
|
||||||
inset: 0;
|
.analytics-header { display:flex; justify-content:space-between; align-items:center; padding:20px 24px; border-bottom:1px solid var(--border-color,#e5e7eb); }
|
||||||
background: rgba(0, 0, 0, 0.5);
|
.analytics-header h2 { margin:0; font-size:18px; font-weight:600; color:var(--text-primary); }
|
||||||
backdrop-filter: blur(4px);
|
.analytics-close { background:none; border:none; font-size:24px; cursor:pointer; color:var(--text-muted); padding:4px 8px; border-radius:4px; }
|
||||||
display: flex;
|
.analytics-close:hover { background:var(--bg-tertiary); color:var(--text-primary); }
|
||||||
align-items: center;
|
.analytics-body { padding:16px 24px 24px; }
|
||||||
justify-content: center;
|
.analytics-section { margin-bottom:20px; }
|
||||||
z-index: 10000;
|
.analytics-section h3 { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:0.05em; color:var(--text-muted,#9ca3af); margin:0 0 10px; }
|
||||||
animation: analyticsFadeIn 0.2s ease;
|
.analytics-row { display:flex; justify-content:space-between; align-items:baseline; padding:6px 0; font-size:14px; }
|
||||||
}
|
.analytics-label { color:var(--text-secondary,#6b7280); }
|
||||||
.analytics-modal {
|
.analytics-value { font-weight:500; color:var(--text-primary); }
|
||||||
background: var(--bg-primary, #fff);
|
.analytics-value small { color:var(--text-muted); font-weight:400; margin-left:6px; }
|
||||||
border-radius: 12px;
|
.readability-meter { height:4px; background:var(--bg-tertiary,#f3f4f6); border-radius:2px; margin-top:8px; overflow:hidden; }
|
||||||
width: 520px;
|
.readability-fill { height:100%; background:linear-gradient(90deg,#ef4444,#f59e0b,#10b981); border-radius:2px; transition:width 0.5s ease; }
|
||||||
max-height: 80vh;
|
.word-cloud { display:flex; flex-wrap:wrap; gap:8px; margin-top:8px; }
|
||||||
overflow-y: auto;
|
.word-tag { background:var(--bg-tertiary,#f3f4f6); padding:4px 10px; border-radius:12px; font-size:13px; color:var(--text-secondary); }
|
||||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
.word-tag small { opacity:0.5; font-size:10px; margin-left:2px; }
|
||||||
animation: analyticsSlideUp 0.3s ease;
|
.analytics-longest { flex-direction:column; gap:4px; }
|
||||||
}
|
.analytics-sentence-preview { font-style:italic; font-size:13px; color:var(--text-secondary); font-weight:400; }
|
||||||
.analytics-header {
|
@keyframes analyticsFadeIn { from{opacity:0} to{opacity:1} }
|
||||||
display: flex;
|
@keyframes analyticsSlideUp { from{transform:translateY(16px);opacity:0} to{transform:translateY(0);opacity:1} }
|
||||||
justify-content: space-between;
|
body[class*="dark"] .analytics-modal { background:var(--gray-800,#1f2937); }
|
||||||
align-items: center;
|
body[class*="dark"] .word-tag { background:var(--gray-700,#374151); }
|
||||||
padding: 20px 24px;
|
|
||||||
border-bottom: 1px solid var(--border-color, #e5e7eb);
|
|
||||||
}
|
|
||||||
.analytics-header h2 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
.analytics-close {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--text-muted);
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.analytics-close:hover {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
.analytics-body {
|
|
||||||
padding: 16px 24px 24px;
|
|
||||||
}
|
|
||||||
.analytics-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.analytics-section h3 {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: var(--text-muted, #9ca3af);
|
|
||||||
margin: 0 0 10px;
|
|
||||||
}
|
|
||||||
.analytics-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: baseline;
|
|
||||||
padding: 6px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.analytics-label {
|
|
||||||
color: var(--text-secondary, #6b7280);
|
|
||||||
}
|
|
||||||
.analytics-value {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
.analytics-value small {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-weight: 400;
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
.readability-meter {
|
|
||||||
height: 4px;
|
|
||||||
background: var(--bg-tertiary, #f3f4f6);
|
|
||||||
border-radius: 2px;
|
|
||||||
margin-top: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.readability-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, #ef4444, #f59e0b, #10b981);
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: width 0.5s ease;
|
|
||||||
}
|
|
||||||
.word-cloud {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
.word-tag {
|
|
||||||
background: var(--bg-tertiary, #f3f4f6);
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
.word-tag small {
|
|
||||||
opacity: 0.5;
|
|
||||||
font-size: 10px;
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
.analytics-longest {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.analytics-sentence-preview {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
@keyframes analyticsFadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
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'] .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);
|
|
||||||
}
|
|
||||||
|
|||||||
+69
-288
@@ -115,28 +115,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Dark theme support */
|
/* Dark theme support */
|
||||||
body[class*='dark'] .sidebar-icons,
|
body[class*="dark"] .sidebar-icons,
|
||||||
body[class*='dark'] .sidebar-panel {
|
body[class*="dark"] .sidebar-panel {
|
||||||
background: #1e1e1e;
|
background: #1e1e1e;
|
||||||
border-color: #333;
|
border-color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[class*='dark'] .sidebar-icon {
|
body[class*="dark"] .sidebar-icon {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[class*='dark'] .sidebar-icon:hover {
|
body[class*="dark"] .sidebar-icon:hover {
|
||||||
background: #333;
|
background: #333;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[class*='dark'] .sidebar-icon.active {
|
body[class*="dark"] .sidebar-icon.active {
|
||||||
background: #333;
|
background: #333;
|
||||||
color: #8b9aff;
|
color: #8b9aff;
|
||||||
box-shadow: inset 3px 0 0 #8b9aff;
|
box-shadow: inset 3px 0 0 #8b9aff;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[class*='dark'] .sidebar-panel-header {
|
body[class*="dark"] .sidebar-panel-header {
|
||||||
border-color: #333;
|
border-color: #333;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
@@ -162,13 +162,13 @@ body[class*='dark'] .sidebar-panel-header {
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[class*='dark'] .panel-list-item:hover {
|
body[class*="dark"] .panel-list-item:hover {
|
||||||
background: #333;
|
background: #333;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .panel-list-item-title {
|
body[class*="dark"] .panel-list-item-title {
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .panel-list-item-desc {
|
body[class*="dark"] .panel-list-item-desc {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,304 +220,85 @@ body[class*='dark'] .panel-list-item-desc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Git Panel */
|
/* Git Panel */
|
||||||
.git-section {
|
.git-section { margin-bottom: 16px; }
|
||||||
margin-bottom: 16px;
|
.git-section-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--gray-500); margin-bottom: 8px; }
|
||||||
}
|
.git-file { display: flex; align-items: center; padding: 4px 6px; border-radius: 4px; font-size: 13px; gap: 6px; }
|
||||||
.git-section-title {
|
.git-file:hover { background: var(--gray-100, #f3f4f6); }
|
||||||
font-size: 11px;
|
.git-file-status { font-weight: 700; font-family: monospace; width: 16px; text-align: center; }
|
||||||
text-transform: uppercase;
|
.git-file-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
letter-spacing: 0.05em;
|
.git-stage-btn { border: none; background: var(--gray-200); border-radius: 4px; cursor: pointer; font-size: 14px; width: 22px; height: 22px; }
|
||||||
color: var(--gray-500);
|
.git-commit-input { width: 100%; padding: 8px; border: 1px solid var(--gray-300); border-radius: 6px; font-size: 13px; font-family: inherit; resize: vertical; box-sizing: border-box; }
|
||||||
margin-bottom: 8px;
|
.git-commit-btn { width: 100%; margin-top: 8px; padding: 8px; background: var(--primary-dark, #5661b3); color: white; border: none; border-radius: 6px; font-size: 13px; cursor: pointer; }
|
||||||
}
|
.git-commit-btn:hover { opacity: 0.9; }
|
||||||
.git-file {
|
.git-log-entry { padding: 6px 0; border-bottom: 1px solid var(--gray-100); }
|
||||||
display: flex;
|
.git-log-msg { font-size: 13px; }
|
||||||
align-items: center;
|
.git-log-meta { font-size: 11px; color: var(--gray-500); margin-top: 2px; }
|
||||||
padding: 4px 6px;
|
.git-info { font-size: 13px; color: var(--gray-500); padding: 8px 0; }
|
||||||
border-radius: 4px;
|
.git-loading { font-size: 13px; color: var(--gray-400); }
|
||||||
font-size: 13px;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
.git-file:hover {
|
|
||||||
background: var(--gray-100, #f3f4f6);
|
|
||||||
}
|
|
||||||
.git-file-status {
|
|
||||||
font-weight: 700;
|
|
||||||
font-family: monospace;
|
|
||||||
width: 16px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.git-file-name {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.git-stage-btn {
|
|
||||||
border: none;
|
|
||||||
background: var(--gray-200);
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
.git-commit-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid var(--gray-300);
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: inherit;
|
|
||||||
resize: vertical;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.git-commit-btn {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 8px;
|
|
||||||
padding: 8px;
|
|
||||||
background: var(--primary-dark, #5661b3);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.git-commit-btn:hover {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.git-log-entry {
|
|
||||||
padding: 6px 0;
|
|
||||||
border-bottom: 1px solid var(--gray-100);
|
|
||||||
}
|
|
||||||
.git-log-msg {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.git-log-meta {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--gray-500);
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
.git-info {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--gray-500);
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
.git-loading {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--gray-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Snippets Panel */
|
/* Snippets Panel */
|
||||||
.snippets-toolbar {
|
.snippets-toolbar { display: flex; gap: 4px; margin-bottom: 8px; }
|
||||||
display: flex;
|
.snippets-search { flex: 1; padding: 6px 10px; border: 1px solid var(--gray-300); border-radius: 6px; font-size: 13px; }
|
||||||
gap: 4px;
|
.snippets-add-btn { width: 32px; border: 1px solid var(--gray-300); border-radius: 6px; background: white; font-size: 18px; cursor: pointer; }
|
||||||
margin-bottom: 8px;
|
.snippet-item { padding: 8px; border: 1px solid var(--gray-200); border-radius: 6px; margin-bottom: 6px; }
|
||||||
}
|
.snippet-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
|
||||||
.snippets-search {
|
.snippet-name { font-size: 13px; font-weight: 500; }
|
||||||
flex: 1;
|
.snippet-lang { font-size: 11px; background: var(--gray-100); padding: 2px 6px; border-radius: 4px; color: var(--gray-500); }
|
||||||
padding: 6px 10px;
|
.snippet-preview { font-size: 12px; background: var(--gray-50); padding: 6px; border-radius: 4px; margin: 4px 0; overflow: hidden; max-height: 60px; }
|
||||||
border: 1px solid var(--gray-300);
|
.snippet-preview code { font-family: 'JetBrains Mono', monospace; }
|
||||||
border-radius: 6px;
|
.snippet-actions { display: flex; gap: 4px; }
|
||||||
font-size: 13px;
|
.snippet-insert { font-size: 12px; padding: 4px 8px; border: 1px solid var(--gray-300); border-radius: 4px; background: white; cursor: pointer; }
|
||||||
}
|
.snippet-delete { font-size: 14px; padding: 4px 8px; border: 1px solid var(--gray-300); border-radius: 4px; background: white; cursor: pointer; color: #ef4444; }
|
||||||
.snippets-add-btn {
|
|
||||||
width: 32px;
|
|
||||||
border: 1px solid var(--gray-300);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: white;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.snippet-item {
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid var(--gray-200);
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
.snippet-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.snippet-name {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.snippet-lang {
|
|
||||||
font-size: 11px;
|
|
||||||
background: var(--gray-100);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--gray-500);
|
|
||||||
}
|
|
||||||
.snippet-preview {
|
|
||||||
font-size: 12px;
|
|
||||||
background: var(--gray-50);
|
|
||||||
padding: 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 4px 0;
|
|
||||||
overflow: hidden;
|
|
||||||
max-height: 60px;
|
|
||||||
}
|
|
||||||
.snippet-preview code {
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
}
|
|
||||||
.snippet-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.snippet-insert {
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border: 1px solid var(--gray-300);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.snippet-delete {
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border: 1px solid var(--gray-300);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: white;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #ef4444;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme for sidebar panels */
|
/* Dark theme for sidebar panels */
|
||||||
body[class*='dark'] .explorer-path,
|
body[class*="dark"] .explorer-path,
|
||||||
body[class*='dark'] .explorer-browse-btn,
|
body[class*="dark"] .explorer-browse-btn,
|
||||||
body[class*='dark'] .snippets-search,
|
body[class*="dark"] .snippets-search,
|
||||||
body[class*='dark'] .snippets-add-btn,
|
body[class*="dark"] .snippets-add-btn,
|
||||||
body[class*='dark'] .snippet-insert,
|
body[class*="dark"] .snippet-insert,
|
||||||
body[class*='dark'] .snippet-delete {
|
body[class*="dark"] .snippet-delete {
|
||||||
background: #2d2d2d;
|
background: #2d2d2d;
|
||||||
border-color: #444;
|
border-color: #444;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .git-commit-input {
|
body[class*="dark"] .git-commit-input {
|
||||||
background: #2d2d2d;
|
background: #2d2d2d;
|
||||||
border-color: #444;
|
border-color: #444;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .snippet-item {
|
body[class*="dark"] .snippet-item {
|
||||||
border-color: #444;
|
border-color: #444;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .snippet-preview {
|
body[class*="dark"] .snippet-preview {
|
||||||
background: #2d2d2d;
|
background: #2d2d2d;
|
||||||
}
|
}
|
||||||
body[class*='dark'] .tree-item:hover,
|
body[class*="dark"] .tree-item:hover,
|
||||||
body[class*='dark'] .git-file:hover {
|
body[class*="dark"] .git-file:hover {
|
||||||
background: #333;
|
background: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Outline Panel */
|
/* Outline Panel */
|
||||||
.outline-panel {
|
.outline-panel { display: flex; flex-direction: column; height: 100%; }
|
||||||
display: flex;
|
.outline-list { flex: 1; overflow-y: auto; padding: 4px 0; }
|
||||||
flex-direction: column;
|
.outline-item { display: flex; align-items: center; justify-content: space-between; padding: 4px 12px; cursor: pointer; font-size: 13px; color: var(--text-secondary, #6b7280); transition: background 0.15s, color 0.15s; border-left: 2px solid transparent; }
|
||||||
height: 100%;
|
.outline-item:hover { background: var(--bg-tertiary, #f3f4f6); color: var(--text-primary, #1f2937); }
|
||||||
}
|
.outline-item.active { color: var(--accent-blue, #3b82f6); background: rgba(59, 130, 246, 0.08); border-left-color: var(--accent-blue, #3b82f6); font-weight: 600; }
|
||||||
.outline-list {
|
.outline-level-1 { padding-left: 12px; }
|
||||||
flex: 1;
|
.outline-level-2 { padding-left: 24px; }
|
||||||
overflow-y: auto;
|
.outline-level-3 { padding-left: 36px; }
|
||||||
padding: 4px 0;
|
.outline-level-4 { padding-left: 48px; }
|
||||||
}
|
.outline-level-5 { padding-left: 56px; }
|
||||||
.outline-item {
|
.outline-level-6 { padding-left: 64px; }
|
||||||
display: flex;
|
.outline-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
align-items: center;
|
.outline-badge { font-size: 10px; opacity: 0.5; margin-left: 8px; flex-shrink: 0; }
|
||||||
justify-content: space-between;
|
.outline-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 32px 16px; color: var(--text-muted, #9ca3af); text-align: center; }
|
||||||
padding: 4px 12px;
|
.outline-empty p { margin: 4px 0; }
|
||||||
cursor: pointer;
|
.outline-hint { font-family: monospace; font-size: 12px; opacity: 0.7; }
|
||||||
font-size: 13px;
|
.outline-footer { padding: 8px 12px; border-top: 1px solid var(--border-color, #e5e7eb); font-size: 11px; color: var(--text-muted, #9ca3af); }
|
||||||
color: var(--text-secondary, #6b7280);
|
|
||||||
transition:
|
|
||||||
background 0.15s,
|
|
||||||
color 0.15s;
|
|
||||||
border-left: 2px solid transparent;
|
|
||||||
}
|
|
||||||
.outline-item:hover {
|
|
||||||
background: var(--bg-tertiary, #f3f4f6);
|
|
||||||
color: var(--text-primary, #1f2937);
|
|
||||||
}
|
|
||||||
.outline-item.active {
|
|
||||||
color: var(--accent-blue, #3b82f6);
|
|
||||||
background: rgba(59, 130, 246, 0.08);
|
|
||||||
border-left-color: var(--accent-blue, #3b82f6);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.outline-level-1 {
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
.outline-level-2 {
|
|
||||||
padding-left: 24px;
|
|
||||||
}
|
|
||||||
.outline-level-3 {
|
|
||||||
padding-left: 36px;
|
|
||||||
}
|
|
||||||
.outline-level-4 {
|
|
||||||
padding-left: 48px;
|
|
||||||
}
|
|
||||||
.outline-level-5 {
|
|
||||||
padding-left: 56px;
|
|
||||||
}
|
|
||||||
.outline-level-6 {
|
|
||||||
padding-left: 64px;
|
|
||||||
}
|
|
||||||
.outline-text {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.outline-badge {
|
|
||||||
font-size: 10px;
|
|
||||||
opacity: 0.5;
|
|
||||||
margin-left: 8px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.outline-empty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 32px 16px;
|
|
||||||
color: var(--text-muted, #9ca3af);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.outline-empty p {
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
.outline-hint {
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
.outline-footer {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-top: 1px solid var(--border-color, #e5e7eb);
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--text-muted, #9ca3af);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Outline dark mode */
|
/* Outline dark mode */
|
||||||
body[class*='dark'] .outline-item {
|
body[class*="dark"] .outline-item { color: #9ca3af; }
|
||||||
color: #9ca3af;
|
body[class*="dark"] .outline-item:hover { background: #374151; color: #e5e7eb; }
|
||||||
}
|
body[class*="dark"] .outline-item.active { color: #8b9aff; background: rgba(139, 154, 255, 0.1); border-left-color: #8b9aff; }
|
||||||
body[class*='dark'] .outline-item:hover {
|
body[class*="dark"] .outline-empty { color: #6b7280; }
|
||||||
background: #374151;
|
body[class*="dark"] .outline-footer { border-color: #333; color: #6b7280; }
|
||||||
color: #e5e7eb;
|
|
||||||
}
|
|
||||||
body[class*='dark'] .outline-item.active {
|
|
||||||
color: #8b9aff;
|
|
||||||
background: rgba(139, 154, 255, 0.1);
|
|
||||||
border-left-color: #8b9aff;
|
|
||||||
}
|
|
||||||
body[class*='dark'] .outline-empty {
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
body[class*='dark'] .outline-footer {
|
|
||||||
border-color: #333;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
|
|||||||
+24
-121
@@ -1,121 +1,24 @@
|
|||||||
.welcome-container {
|
.welcome-container { padding: 40px; max-width: 900px; margin: 0 auto; }
|
||||||
padding: 40px;
|
.welcome-hero { text-align: center; margin-bottom: 40px; }
|
||||||
max-width: 900px;
|
.welcome-title { font-size: 32px; font-weight: 700; color: var(--gray-800, #1f2937); }
|
||||||
margin: 0 auto;
|
.welcome-version { font-size: 14px; color: var(--primary-dark, #5661b3); margin-top: 4px; font-weight: 500; }
|
||||||
}
|
.welcome-subtitle { font-size: 16px; color: var(--gray-500, #6b7280); margin-top: 8px; }
|
||||||
.welcome-hero {
|
.welcome-grid { display: grid; gap: 32px; }
|
||||||
text-align: center;
|
.welcome-section h2 { font-size: 18px; margin-bottom: 16px; color: var(--gray-700, #374151); }
|
||||||
margin-bottom: 40px;
|
.welcome-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
|
||||||
}
|
.welcome-card { padding: 20px; border: 1px solid var(--gray-200, #e5e7eb); border-radius: 12px; cursor: pointer; text-align: center; transition: all 0.2s; }
|
||||||
.welcome-title {
|
.welcome-card:hover { border-color: var(--primary-dark, #5661b3); box-shadow: 0 4px 12px rgba(0,0,0,0.08); transform: translateY(-2px); }
|
||||||
font-size: 32px;
|
.welcome-card-icon { font-size: 28px; margin-bottom: 8px; }
|
||||||
font-weight: 700;
|
.welcome-card h3 { font-size: 15px; margin-bottom: 4px; }
|
||||||
color: var(--gray-800, #1f2937);
|
.welcome-card p { font-size: 13px; color: var(--gray-500); }
|
||||||
}
|
.welcome-card kbd { display: inline-block; margin-top: 8px; padding: 2px 8px; background: var(--gray-100); border: 1px solid var(--gray-300); border-radius: 4px; font-size: 12px; font-family: 'JetBrains Mono', monospace; }
|
||||||
.welcome-version {
|
.welcome-features { list-style: none; padding: 0; }
|
||||||
font-size: 14px;
|
.welcome-features li { padding: 6px 0; font-size: 14px; border-bottom: 1px solid var(--gray-100, #f3f4f6); }
|
||||||
color: var(--primary-dark, #5661b3);
|
.welcome-features strong { color: var(--primary-dark, #5661b3); }
|
||||||
margin-top: 4px;
|
.welcome-recent-item { padding: 8px 12px; border-radius: 6px; cursor: pointer; margin-bottom: 4px; }
|
||||||
font-weight: 500;
|
.welcome-recent-item:hover { background: var(--gray-100); }
|
||||||
}
|
.welcome-recent-name { font-size: 14px; font-weight: 500; display: block; }
|
||||||
.welcome-subtitle {
|
.welcome-recent-path { font-size: 12px; color: var(--gray-500); display: block; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; }
|
||||||
font-size: 16px;
|
.welcome-muted { color: var(--gray-400); font-size: 14px; }
|
||||||
color: var(--gray-500, #6b7280);
|
.welcome-footer { margin-top: 32px; text-align: center; }
|
||||||
margin-top: 8px;
|
.welcome-checkbox { font-size: 13px; color: var(--gray-500); cursor: pointer; }
|
||||||
}
|
|
||||||
.welcome-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 32px;
|
|
||||||
}
|
|
||||||
.welcome-section h2 {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: var(--gray-700, #374151);
|
|
||||||
}
|
|
||||||
.welcome-cards {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.welcome-card {
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid var(--gray-200, #e5e7eb);
|
|
||||||
border-radius: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
.welcome-card:hover {
|
|
||||||
border-color: var(--primary-dark, #5661b3);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
.welcome-card-icon {
|
|
||||||
font-size: 28px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.welcome-card h3 {
|
|
||||||
font-size: 15px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.welcome-card p {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--gray-500);
|
|
||||||
}
|
|
||||||
.welcome-card kbd {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 8px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
background: var(--gray-100);
|
|
||||||
border: 1px solid var(--gray-300);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
}
|
|
||||||
.welcome-features {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.welcome-features li {
|
|
||||||
padding: 6px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
border-bottom: 1px solid var(--gray-100, #f3f4f6);
|
|
||||||
}
|
|
||||||
.welcome-features strong {
|
|
||||||
color: var(--primary-dark, #5661b3);
|
|
||||||
}
|
|
||||||
.welcome-recent-item {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.welcome-recent-item:hover {
|
|
||||||
background: var(--gray-100);
|
|
||||||
}
|
|
||||||
.welcome-recent-name {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.welcome-recent-path {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--gray-500);
|
|
||||||
display: block;
|
|
||||||
margin-top: 2px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
.welcome-muted {
|
|
||||||
color: var(--gray-400);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.welcome-footer {
|
|
||||||
margin-top: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.welcome-checkbox {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--gray-500);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -97,6 +97,6 @@ body.zen-mode .editor-wrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode — HUD stays dark in both themes */
|
/* Dark mode — HUD stays dark in both themes */
|
||||||
body[class*='dark'].zen-mode .zen-hud {
|
body[class*="dark"].zen-mode .zen-hud {
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
}
|
}
|
||||||
|
|||||||
+208
-393
File diff suppressed because it is too large
Load Diff
+6
-10
@@ -31,8 +31,7 @@
|
|||||||
z-index: calc(var(--z-modal, 200) + 1);
|
z-index: calc(var(--z-modal, 200) + 1);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition:
|
transition: opacity var(--transition-normal, 200ms cubic-bezier(0.4, 0, 0.2, 1)),
|
||||||
opacity var(--transition-normal, 200ms cubic-bezier(0.4, 0, 0.2, 1)),
|
|
||||||
visibility var(--transition-normal, 200ms cubic-bezier(0.4, 0, 0.2, 1));
|
visibility var(--transition-normal, 200ms cubic-bezier(0.4, 0, 0.2, 1));
|
||||||
padding: var(--spacing-4, 1rem);
|
padding: var(--spacing-4, 1rem);
|
||||||
}
|
}
|
||||||
@@ -104,8 +103,7 @@
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition:
|
transition: background-color var(--transition-fast, 150ms),
|
||||||
background-color var(--transition-fast, 150ms),
|
|
||||||
color var(--transition-fast, 150ms);
|
color var(--transition-fast, 150ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,17 +233,15 @@
|
|||||||
* ============================================ */
|
* ============================================ */
|
||||||
|
|
||||||
.dark .modal-content,
|
.dark .modal-content,
|
||||||
[data-theme='dark'] .modal-content {
|
[data-theme="dark"] .modal-content {
|
||||||
background: hsl(var(--background));
|
background: hsl(var(--background));
|
||||||
box-shadow:
|
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.3);
|
||||||
0 20px 25px -5px rgb(0 0 0 / 0.4),
|
|
||||||
0 8px 10px -6px rgb(0 0 0 / 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .modal-header,
|
.dark .modal-header,
|
||||||
.dark .modal-footer,
|
.dark .modal-footer,
|
||||||
[data-theme='dark'] .modal-header,
|
[data-theme="dark"] .modal-header,
|
||||||
[data-theme='dark'] .modal-footer {
|
[data-theme="dark"] .modal-footer {
|
||||||
background: hsl(var(--muted));
|
background: hsl(var(--muted));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-49
@@ -148,7 +148,7 @@
|
|||||||
* ============================================ */
|
* ============================================ */
|
||||||
|
|
||||||
.dark,
|
.dark,
|
||||||
[data-theme='dark'] {
|
[data-theme="dark"] {
|
||||||
/* Background and foreground */
|
/* Background and foreground */
|
||||||
--background: 222.2 84% 4.9%;
|
--background: 222.2 84% 4.9%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 210 40% 98%;
|
||||||
@@ -205,56 +205,24 @@
|
|||||||
* Semantic Color Classes
|
* Semantic Color Classes
|
||||||
* ============================================ */
|
* ============================================ */
|
||||||
|
|
||||||
.bg-background {
|
.bg-background { background-color: hsl(var(--background)); }
|
||||||
background-color: hsl(var(--background));
|
.bg-foreground { background-color: hsl(var(--foreground)); }
|
||||||
}
|
.bg-card { background-color: hsl(var(--card)); }
|
||||||
.bg-foreground {
|
.bg-primary { background-color: hsl(var(--primary)); }
|
||||||
background-color: hsl(var(--foreground));
|
.bg-secondary { background-color: hsl(var(--secondary)); }
|
||||||
}
|
.bg-muted { background-color: hsl(var(--muted)); }
|
||||||
.bg-card {
|
.bg-accent { background-color: hsl(var(--accent)); }
|
||||||
background-color: hsl(var(--card));
|
.bg-destructive { background-color: hsl(var(--destructive)); }
|
||||||
}
|
|
||||||
.bg-primary {
|
|
||||||
background-color: hsl(var(--primary));
|
|
||||||
}
|
|
||||||
.bg-secondary {
|
|
||||||
background-color: hsl(var(--secondary));
|
|
||||||
}
|
|
||||||
.bg-muted {
|
|
||||||
background-color: hsl(var(--muted));
|
|
||||||
}
|
|
||||||
.bg-accent {
|
|
||||||
background-color: hsl(var(--accent));
|
|
||||||
}
|
|
||||||
.bg-destructive {
|
|
||||||
background-color: hsl(var(--destructive));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-foreground {
|
.text-foreground { color: hsl(var(--foreground)); }
|
||||||
color: hsl(var(--foreground));
|
.text-primary { color: hsl(var(--primary)); }
|
||||||
}
|
.text-secondary { color: hsl(var(--secondary-foreground)); }
|
||||||
.text-primary {
|
.text-muted-foreground { color: hsl(var(--muted-foreground)); }
|
||||||
color: hsl(var(--primary));
|
.text-destructive { color: hsl(var(--destructive)); }
|
||||||
}
|
|
||||||
.text-secondary {
|
|
||||||
color: hsl(var(--secondary-foreground));
|
|
||||||
}
|
|
||||||
.text-muted-foreground {
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
.text-destructive {
|
|
||||||
color: hsl(var(--destructive));
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-border {
|
.border-border { border-color: hsl(var(--border)); }
|
||||||
border-color: hsl(var(--border));
|
.border-primary { border-color: hsl(var(--primary)); }
|
||||||
}
|
.border-input { border-color: hsl(var(--input)); }
|
||||||
.border-primary {
|
|
||||||
border-color: hsl(var(--primary));
|
|
||||||
}
|
|
||||||
.border-input {
|
|
||||||
border-color: hsl(var(--input));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
* Utility Classes
|
* Utility Classes
|
||||||
|
|||||||
+20
-41
@@ -1,13 +1,10 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Table Generator - MarkdownConverter</title>
|
<title>Table Generator - MarkdownConverter</title>
|
||||||
<link
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--ci-dark-gray: #464646;
|
--ci-dark-gray: #464646;
|
||||||
@@ -89,8 +86,7 @@
|
|||||||
color: var(--ci-medium-gray);
|
color: var(--ci-medium-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-input,
|
.form-input, .form-select {
|
||||||
.form-select {
|
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border: 2px solid var(--ci-light-gray);
|
border: 2px solid var(--ci-light-gray);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -100,8 +96,7 @@
|
|||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-input:focus,
|
.form-input:focus, .form-select:focus {
|
||||||
.form-select:focus {
|
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--ci-accent);
|
border-color: var(--ci-accent);
|
||||||
}
|
}
|
||||||
@@ -267,8 +262,8 @@
|
|||||||
color: var(--ci-white);
|
color: var(--ci-white);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>Table Generator</h1>
|
<h1>Table Generator</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,27 +274,11 @@
|
|||||||
<div class="controls-row">
|
<div class="controls-row">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<span class="control-label">Rows</span>
|
<span class="control-label">Rows</span>
|
||||||
<input
|
<input type="number" id="rows" class="form-input" min="1" max="50" value="4" style="width: 80px;">
|
||||||
type="number"
|
|
||||||
id="rows"
|
|
||||||
class="form-input"
|
|
||||||
min="1"
|
|
||||||
max="50"
|
|
||||||
value="4"
|
|
||||||
style="width: 80px"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<span class="control-label">Columns</span>
|
<span class="control-label">Columns</span>
|
||||||
<input
|
<input type="number" id="cols" class="form-input" min="1" max="20" value="4" style="width: 80px;">
|
||||||
type="number"
|
|
||||||
id="cols"
|
|
||||||
class="form-input"
|
|
||||||
min="1"
|
|
||||||
max="20"
|
|
||||||
value="4"
|
|
||||||
style="width: 80px"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<span class="control-label">Alignment</span>
|
<span class="control-label">Alignment</span>
|
||||||
@@ -309,9 +288,9 @@
|
|||||||
<option value="right">Right</option>
|
<option value="right">Right</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group" style="justify-content: flex-end">
|
<div class="control-group" style="justify-content: flex-end;">
|
||||||
<label class="checkbox-label">
|
<label class="checkbox-label">
|
||||||
<input type="checkbox" id="has-header" checked />
|
<input type="checkbox" id="has-header" checked>
|
||||||
Include Header Row
|
Include Header Row
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -381,7 +360,7 @@
|
|||||||
table.innerHTML = html;
|
table.innerHTML = html;
|
||||||
|
|
||||||
// Add event listeners for live preview
|
// Add event listeners for live preview
|
||||||
table.querySelectorAll('input').forEach((input) => {
|
table.querySelectorAll('input').forEach(input => {
|
||||||
input.addEventListener('input', updatePreview);
|
input.addEventListener('input', updatePreview);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -446,7 +425,7 @@
|
|||||||
for (let i = startRow; i < data.length; i++) {
|
for (let i = startRow; i < data.length; i++) {
|
||||||
if (i === -1) {
|
if (i === -1) {
|
||||||
// Generate empty header for tables without header
|
// Generate empty header for tables without header
|
||||||
markdown += '| ' + colWidths.map((w) => ' '.repeat(w)).join(' | ') + ' |\n';
|
markdown += '| ' + colWidths.map(w => ' '.repeat(w)).join(' | ') + ' |\n';
|
||||||
} else {
|
} else {
|
||||||
const row = data[i];
|
const row = data[i];
|
||||||
const cells = row.map((cell, j) => alignCell(cell, colWidths[j]));
|
const cells = row.map((cell, j) => alignCell(cell, colWidths[j]));
|
||||||
@@ -455,7 +434,7 @@
|
|||||||
|
|
||||||
// Add separator after first row
|
// Add separator after first row
|
||||||
if ((hasHeader && i === 0) || (!hasHeader && i === -1)) {
|
if ((hasHeader && i === 0) || (!hasHeader && i === -1)) {
|
||||||
const separators = colWidths.map((w) => getSeparator(w));
|
const separators = colWidths.map(w => getSeparator(w));
|
||||||
markdown += '| ' + separators.join(' | ') + ' |\n';
|
markdown += '| ' + separators.join(' | ') + ' |\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -490,7 +469,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Quick templates
|
// Quick templates
|
||||||
document.querySelectorAll('.template-chip').forEach((chip) => {
|
document.querySelectorAll('.template-chip').forEach(chip => {
|
||||||
chip.addEventListener('click', () => {
|
chip.addEventListener('click', () => {
|
||||||
currentRows = parseInt(chip.dataset.rows);
|
currentRows = parseInt(chip.dataset.rows);
|
||||||
currentCols = parseInt(chip.dataset.cols);
|
currentCols = parseInt(chip.dataset.cols);
|
||||||
@@ -502,7 +481,7 @@
|
|||||||
|
|
||||||
// Clear all
|
// Clear all
|
||||||
document.getElementById('btn-clear').addEventListener('click', () => {
|
document.getElementById('btn-clear').addEventListener('click', () => {
|
||||||
document.querySelectorAll('#editable-table input').forEach((input) => {
|
document.querySelectorAll('#editable-table input').forEach(input => {
|
||||||
input.value = '';
|
input.value = '';
|
||||||
});
|
});
|
||||||
updatePreview();
|
updatePreview();
|
||||||
@@ -515,7 +494,7 @@
|
|||||||
['Task 1', 'First task description', 'In Progress', 'High', '2024-01-15'],
|
['Task 1', 'First task description', 'In Progress', 'High', '2024-01-15'],
|
||||||
['Task 2', 'Second task description', 'Completed', 'Medium', '2024-01-10'],
|
['Task 2', 'Second task description', 'Completed', 'Medium', '2024-01-10'],
|
||||||
['Task 3', 'Third task description', 'Pending', 'Low', '2024-01-20'],
|
['Task 3', 'Third task description', 'Pending', 'Low', '2024-01-20'],
|
||||||
['Task 4', 'Fourth task description', 'In Review', 'High', '2024-01-18'],
|
['Task 4', 'Fourth task description', 'In Review', 'High', '2024-01-18']
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0; i < currentRows; i++) {
|
for (let i = 0; i < currentRows; i++) {
|
||||||
@@ -562,5 +541,5 @@
|
|||||||
// Initialize
|
// Initialize
|
||||||
generateEditableTable();
|
generateEditableTable();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -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 }),
|
||||||
|
};
|
||||||
@@ -23,7 +23,7 @@ GET /resources
|
|||||||
**Query Parameters:**
|
**Query Parameters:**
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| --------- | ------- | -------- | ---------------------------- |
|
|-----------|------|----------|-------------|
|
||||||
| page | integer | No | Page number (default: 1) |
|
| page | integer | No | Page number (default: 1) |
|
||||||
| limit | integer | No | Items per page (default: 20) |
|
| limit | integer | No | Items per page (default: 20) |
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ DELETE /resources/:id
|
|||||||
## Error Codes
|
## Error Codes
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| ---- | --------------------- |
|
|------|-------------|
|
||||||
| 400 | Bad Request |
|
| 400 | Bad Request |
|
||||||
| 401 | Unauthorized |
|
| 401 | Unauthorized |
|
||||||
| 404 | Not Found |
|
| 404 | Not Found |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Blog Post Title
|
title: Blog Post Title
|
||||||
date: { { DATE } }
|
date: {{DATE}}
|
||||||
author: Your Name
|
author: Your Name
|
||||||
tags: []
|
tags: []
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -3,23 +3,18 @@
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- New feature
|
- New feature
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Updated feature
|
- Updated feature
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Bug fix
|
- Bug fix
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed feature
|
- Removed feature
|
||||||
|
|
||||||
## [1.0.0] - {{DATE}}
|
## [1.0.0] - {{DATE}}
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Initial release
|
- Initial release
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Brief description of what is being compared and why.
|
|||||||
## Feature Comparison
|
## Feature Comparison
|
||||||
|
|
||||||
| Feature | Option A | Option B |
|
| Feature | Option A | Option B |
|
||||||
| --------- | -------- | -------- |
|
|---------|----------|----------|
|
||||||
| Feature 1 | Yes | Yes |
|
| Feature 1 | Yes | Yes |
|
||||||
| Feature 2 | Yes | No |
|
| Feature 2 | Yes | No |
|
||||||
| Feature 3 | No | Yes |
|
| Feature 3 | No | Yes |
|
||||||
@@ -19,7 +19,7 @@ Brief description of what is being compared and why.
|
|||||||
## Pricing
|
## Pricing
|
||||||
|
|
||||||
| Plan | Option A | Option B |
|
| Plan | Option A | Option B |
|
||||||
| ---------- | -------- | -------- |
|
|------|----------|----------|
|
||||||
| Free | Limited | Limited |
|
| Free | Limited | Limited |
|
||||||
| Pro | $10/mo | $15/mo |
|
| Pro | $10/mo | $15/mo |
|
||||||
| Enterprise | Custom | Custom |
|
| Enterprise | Custom | Custom |
|
||||||
@@ -29,31 +29,27 @@ Brief description of what is being compared and why.
|
|||||||
### Option A
|
### Option A
|
||||||
|
|
||||||
**Pros:**
|
**Pros:**
|
||||||
|
|
||||||
- Pro 1
|
- Pro 1
|
||||||
- Pro 2
|
- Pro 2
|
||||||
|
|
||||||
**Cons:**
|
**Cons:**
|
||||||
|
|
||||||
- Con 1
|
- Con 1
|
||||||
- Con 2
|
- Con 2
|
||||||
|
|
||||||
### Option B
|
### Option B
|
||||||
|
|
||||||
**Pros:**
|
**Pros:**
|
||||||
|
|
||||||
- Pro 1
|
- Pro 1
|
||||||
- Pro 2
|
- Pro 2
|
||||||
|
|
||||||
**Cons:**
|
**Cons:**
|
||||||
|
|
||||||
- Con 1
|
- Con 1
|
||||||
- Con 2
|
- Con 2
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
| Metric | Option A | Option B |
|
| Metric | Option A | Option B |
|
||||||
| ----------- | -------- | --------- |
|
|--------|----------|----------|
|
||||||
| Speed | Fast | Moderate |
|
| Speed | Fast | Moderate |
|
||||||
| Memory | Low | Medium |
|
| Memory | Low | Medium |
|
||||||
| Scalability | Good | Excellent |
|
| Scalability | Good | Excellent |
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
**Date:** {{DATE}}
|
**Date:** {{DATE}}
|
||||||
**Attendees:**
|
**Attendees:**
|
||||||
|
|
||||||
- Name 1
|
- Name 1
|
||||||
- Name 2
|
- Name 2
|
||||||
|
|
||||||
|
|||||||
@@ -12,18 +12,16 @@
|
|||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
### In Scope
|
### In Scope
|
||||||
|
|
||||||
- Item 1
|
- Item 1
|
||||||
- Item 2
|
- Item 2
|
||||||
|
|
||||||
### Out of Scope
|
### Out of Scope
|
||||||
|
|
||||||
- Item 1
|
- Item 1
|
||||||
|
|
||||||
## Milestones
|
## Milestones
|
||||||
|
|
||||||
| Milestone | Target Date | Status |
|
| Milestone | Target Date | Status |
|
||||||
| --------- | ----------- | ----------- |
|
|-----------|-------------|--------|
|
||||||
| Kickoff | {{DATE}} | Not Started |
|
| Kickoff | {{DATE}} | Not Started |
|
||||||
| MVP | TBD | Not Started |
|
| MVP | TBD | Not Started |
|
||||||
| Launch | TBD | Not Started |
|
| Launch | TBD | Not Started |
|
||||||
@@ -31,29 +29,26 @@
|
|||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
| Role | Person | Allocation |
|
| Role | Person | Allocation |
|
||||||
| --------- | ------ | ---------- |
|
|------|--------|------------|
|
||||||
| Lead | Name | 100% |
|
| Lead | Name | 100% |
|
||||||
| Developer | Name | 50% |
|
| Developer | Name | 50% |
|
||||||
|
|
||||||
## Risks
|
## Risks
|
||||||
|
|
||||||
| Risk | Impact | Likelihood | Mitigation |
|
| Risk | Impact | Likelihood | Mitigation |
|
||||||
| ------ | ------ | ---------- | ---------- |
|
|------|--------|------------|------------|
|
||||||
| Risk 1 | High | Medium | Plan |
|
| Risk 1 | High | Medium | Plan |
|
||||||
|
|
||||||
## Timeline
|
## Timeline
|
||||||
|
|
||||||
### Phase 1: Planning
|
### Phase 1: Planning
|
||||||
|
|
||||||
- [ ] Define requirements
|
- [ ] Define requirements
|
||||||
- [ ] Create design documents
|
- [ ] Create design documents
|
||||||
|
|
||||||
### Phase 2: Development
|
### Phase 2: Development
|
||||||
|
|
||||||
- [ ] Implement core features
|
- [ ] Implement core features
|
||||||
- [ ] Write tests
|
- [ ] Write tests
|
||||||
|
|
||||||
### Phase 3: Launch
|
### Phase 3: Launch
|
||||||
|
|
||||||
- [ ] Deploy to production
|
- [ ] Deploy to production
|
||||||
- [ ] Monitor and iterate
|
- [ ] Monitor and iterate
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const project = require('project-name');
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
| ------- | ------- | ----------- |
|
|--------|---------|-------------|
|
||||||
| option1 | true | Description |
|
| option1 | true | Description |
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|||||||
@@ -12,12 +12,10 @@ Brief description of what this feature does.
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Functional Requirements
|
### Functional Requirements
|
||||||
|
|
||||||
1. Requirement 1
|
1. Requirement 1
|
||||||
2. Requirement 2
|
2. Requirement 2
|
||||||
|
|
||||||
### Non-Functional Requirements
|
### Non-Functional Requirements
|
||||||
|
|
||||||
1. Performance requirement
|
1. Performance requirement
|
||||||
2. Security requirement
|
2. Security requirement
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ Describe the technical approach.
|
|||||||
### Endpoints
|
### Endpoints
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
| ------ | ------------- | ------------ |
|
|--------|------|-------------|
|
||||||
| GET | /api/resource | Get resource |
|
| GET | /api/resource | Get resource |
|
||||||
|
|
||||||
## Testing Strategy
|
## Testing Strategy
|
||||||
@@ -40,5 +38,5 @@ Describe how this will be tested.
|
|||||||
## Timeline
|
## Timeline
|
||||||
|
|
||||||
| Phase | Duration | Deliverables |
|
| Phase | Duration | Deliverables |
|
||||||
| ------- | -------- | ------------------- |
|
|-------|----------|-------------|
|
||||||
| Phase 1 | 1 week | Core implementation |
|
| Phase 1 | 1 week | Core implementation |
|
||||||
|
|||||||
@@ -53,12 +53,10 @@ How to verify everything works.
|
|||||||
## Common Issues
|
## Common Issues
|
||||||
|
|
||||||
### Issue 1
|
### Issue 1
|
||||||
|
|
||||||
**Problem:** Description of the problem.
|
**Problem:** Description of the problem.
|
||||||
**Solution:** How to fix it.
|
**Solution:** How to fix it.
|
||||||
|
|
||||||
### Issue 2
|
### Issue 2
|
||||||
|
|
||||||
**Problem:** Description of the problem.
|
**Problem:** Description of the problem.
|
||||||
**Solution:** How to fix it.
|
**Solution:** How to fix it.
|
||||||
|
|
||||||
|
|||||||
+10
-11
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* ModalManager - Unified modal system with accessibility support
|
* ModalManager - Unified modal system with accessibility support
|
||||||
* @version 4.4.5
|
* @version 4.3.0
|
||||||
*/
|
*/
|
||||||
class ModalManager {
|
class ModalManager {
|
||||||
#modal;
|
#modal;
|
||||||
@@ -21,7 +21,7 @@ class ModalManager {
|
|||||||
focusFirst: true,
|
focusFirst: true,
|
||||||
onOpen: null,
|
onOpen: null,
|
||||||
onClose: null,
|
onClose: null,
|
||||||
...options,
|
...options
|
||||||
};
|
};
|
||||||
this.#isOpen = false;
|
this.#isOpen = false;
|
||||||
this.#eventListeners = [];
|
this.#eventListeners = [];
|
||||||
@@ -58,7 +58,7 @@ class ModalManager {
|
|||||||
|
|
||||||
// Elements with data-close attribute
|
// Elements with data-close attribute
|
||||||
const closeTriggers = this.#modal.querySelectorAll('[data-close]');
|
const closeTriggers = this.#modal.querySelectorAll('[data-close]');
|
||||||
closeTriggers.forEach((el) => {
|
closeTriggers.forEach(el => {
|
||||||
if (el.classList.contains('modal-backdrop') && !this.#options.closeOnBackdrop) {
|
if (el.classList.contains('modal-backdrop') && !this.#options.closeOnBackdrop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -78,12 +78,11 @@ class ModalManager {
|
|||||||
'select:not([disabled])',
|
'select:not([disabled])',
|
||||||
'textarea:not([disabled])',
|
'textarea:not([disabled])',
|
||||||
'a[href]',
|
'a[href]',
|
||||||
'[tabindex]:not([tabindex="-1"])',
|
'[tabindex]:not([tabindex="-1"])'
|
||||||
].join(', ');
|
].join(', ');
|
||||||
|
|
||||||
return Array.from(this.#modal.querySelectorAll(selector)).filter(
|
return Array.from(this.#modal.querySelectorAll(selector))
|
||||||
(el) => el.offsetParent !== null && !el.classList.contains('modal-backdrop')
|
.filter(el => el.offsetParent !== null && !el.classList.contains('modal-backdrop'));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#trapFocus(e) {
|
#trapFocus(e) {
|
||||||
@@ -199,11 +198,11 @@ class ModalManager {
|
|||||||
|
|
||||||
// Remove keyboard listener
|
// Remove keyboard listener
|
||||||
const keydownListener = this.#eventListeners.find(
|
const keydownListener = this.#eventListeners.find(
|
||||||
(l) => l.el === document && l.type === 'keydown'
|
l => l.el === document && l.type === 'keydown'
|
||||||
);
|
);
|
||||||
if (keydownListener) {
|
if (keydownListener) {
|
||||||
document.removeEventListener('keydown', keydownListener.handler);
|
document.removeEventListener('keydown', keydownListener.handler);
|
||||||
this.#eventListeners = this.#eventListeners.filter((l) => l !== keydownListener);
|
this.#eventListeners = this.#eventListeners.filter(l => l !== keydownListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore focus
|
// Restore focus
|
||||||
@@ -238,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,
|
||||||
|
});
|
||||||
+2
-4
@@ -1,11 +1,9 @@
|
|||||||
function createWelcomeContent(recentFiles = [], appVersion = '') {
|
function createWelcomeContent(recentFiles = [], appVersion = '') {
|
||||||
const recentHtml = recentFiles.length
|
const recentHtml = recentFiles.length
|
||||||
? recentFiles
|
? recentFiles.map(f => {
|
||||||
.map((f) => {
|
|
||||||
const name = f.split(/[/\\]/).pop();
|
const name = f.split(/[/\\]/).pop();
|
||||||
return `<div class="welcome-recent-item" data-path="${f}"><span class="welcome-recent-name">${name}</span><span class="welcome-recent-path">${f}</span></div>`;
|
return `<div class="welcome-recent-item" data-path="${f}"><span class="welcome-recent-name">${name}</span><span class="welcome-recent-path">${f}</span></div>`;
|
||||||
})
|
}).join('')
|
||||||
.join('')
|
|
||||||
: '<p class="welcome-muted">No recent files</p>';
|
: '<p class="welcome-muted">No recent files</p>';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
|||||||
+22
-106
@@ -7,6 +7,7 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const PizZip = require('pizzip');
|
const PizZip = require('pizzip');
|
||||||
|
const Docx = require('docx4js').default;
|
||||||
|
|
||||||
class WordTemplateExporter {
|
class WordTemplateExporter {
|
||||||
constructor(templatePath, startPage = 3, pageSettings = null) {
|
constructor(templatePath, startPage = 3, pageSettings = null) {
|
||||||
@@ -15,26 +16,6 @@ class WordTemplateExporter {
|
|||||||
this.pageSettings = pageSettings; // Page size and orientation settings
|
this.pageSettings = pageSettings; // Page size and orientation settings
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Strip HTML artifacts that Pandoc / Word cannot render and that would
|
|
||||||
* otherwise appear as visible text in DOCX output.
|
|
||||||
* Removes HTML comments, <style> blocks, and alignment <div> tags.
|
|
||||||
*/
|
|
||||||
static preprocessMarkdownForWordExport(markdown) {
|
|
||||||
if (typeof markdown !== 'string') return markdown;
|
|
||||||
return (
|
|
||||||
markdown
|
|
||||||
// HTML comments (multi-line)
|
|
||||||
.replace(/<!--[\s\S]*?-->/g, '')
|
|
||||||
// <style> blocks (case-insensitive, multi-line)
|
|
||||||
.replace(/<style\b[\s\S]*?<\/style>/gi, '')
|
|
||||||
// Opening <div align="..."> tags
|
|
||||||
.replace(/<div\b[^>]*?\balign\s*=\s*["'][^"']*["'][^>]*>/gi, '')
|
|
||||||
// Closing </div> tags
|
|
||||||
.replace(/<\/div\s*>/gi, '')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert markdown to Word document using template
|
* Convert markdown to Word document using template
|
||||||
*/
|
*/
|
||||||
@@ -53,8 +34,7 @@ class WordTemplateExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse markdown and generate Word XML
|
// Parse markdown and generate Word XML
|
||||||
const cleanedContent = WordTemplateExporter.preprocessMarkdownForWordExport(markdownContent);
|
const newContentXml = this.markdownToWordXml(markdownContent);
|
||||||
const newContentXml = this.markdownToWordXml(cleanedContent);
|
|
||||||
|
|
||||||
// Insert new content after the specified start page
|
// Insert new content after the specified start page
|
||||||
const modifiedXml = this.insertContentAfterPage(documentXml, newContentXml, this.startPage);
|
const modifiedXml = this.insertContentAfterPage(documentXml, newContentXml, this.startPage);
|
||||||
@@ -86,7 +66,7 @@ class WordTemplateExporter {
|
|||||||
b5: { width: 9979, height: 14170 },
|
b5: { width: 9979, height: 14170 },
|
||||||
letter: { width: 12240, height: 15840 },
|
letter: { width: 12240, height: 15840 },
|
||||||
legal: { width: 12240, height: 20160 },
|
legal: { width: 12240, height: 20160 },
|
||||||
tabloid: { width: 15840, height: 24480 },
|
tabloid: { width: 15840, height: 24480 }
|
||||||
};
|
};
|
||||||
|
|
||||||
let width, height;
|
let width, height;
|
||||||
@@ -393,10 +373,7 @@ class WordTemplateExporter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Split by | and trim
|
// Split by | and trim
|
||||||
const cells = line
|
const cells = line.split('|').filter(cell => cell.trim()).map(cell => cell.trim());
|
||||||
.split('|')
|
|
||||||
.filter((cell) => cell.trim())
|
|
||||||
.map((cell) => cell.trim());
|
|
||||||
if (cells.length > 0) {
|
if (cells.length > 0) {
|
||||||
rows.push(cells);
|
rows.push(cells);
|
||||||
}
|
}
|
||||||
@@ -405,7 +382,7 @@ class WordTemplateExporter {
|
|||||||
if (rows.length === 0) return '';
|
if (rows.length === 0) return '';
|
||||||
|
|
||||||
// Calculate number of columns
|
// Calculate number of columns
|
||||||
const numCols = Math.max(...rows.map((row) => row.length));
|
const numCols = Math.max(...rows.map(row => row.length));
|
||||||
|
|
||||||
// Calculate column width in twips (1440 twips = 1 inch)
|
// Calculate column width in twips (1440 twips = 1 inch)
|
||||||
// Assume standard page width of 9360 twips (6.5 inches usable)
|
// Assume standard page width of 9360 twips (6.5 inches usable)
|
||||||
@@ -452,7 +429,7 @@ class WordTemplateExporter {
|
|||||||
rowCells.push('');
|
rowCells.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
rowCells.forEach((cellText, _colIndex) => {
|
rowCells.forEach((cellText, colIndex) => {
|
||||||
tableXml += '<w:tc>';
|
tableXml += '<w:tc>';
|
||||||
tableXml += '<w:tcPr>';
|
tableXml += '<w:tcPr>';
|
||||||
|
|
||||||
@@ -467,8 +444,7 @@ class WordTemplateExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cell borders
|
// Cell borders
|
||||||
tableXml +=
|
tableXml += '<w:tcBorders>' +
|
||||||
'<w:tcBorders>' +
|
|
||||||
'<w:top w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
|
'<w:top w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
|
||||||
'<w:left w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
|
'<w:left w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
|
||||||
'<w:bottom w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
|
'<w:bottom w:val="single" w:sz="8" w:space="0" w:color="F58220"/>' +
|
||||||
@@ -476,8 +452,7 @@ class WordTemplateExporter {
|
|||||||
'</w:tcBorders>';
|
'</w:tcBorders>';
|
||||||
|
|
||||||
// Cell margins for proper padding
|
// Cell margins for proper padding
|
||||||
tableXml +=
|
tableXml += '<w:tcMar>' +
|
||||||
'<w:tcMar>' +
|
|
||||||
'<w:top w:w="80" w:type="dxa"/>' +
|
'<w:top w:w="80" w:type="dxa"/>' +
|
||||||
'<w:left w:w="120" w:type="dxa"/>' +
|
'<w:left w:w="120" w:type="dxa"/>' +
|
||||||
'<w:bottom w:w="80" w:type="dxa"/>' +
|
'<w:bottom w:w="80" w:type="dxa"/>' +
|
||||||
@@ -524,7 +499,7 @@ class WordTemplateExporter {
|
|||||||
// Don't treat markdown tables as ASCII art
|
// Don't treat markdown tables as ASCII art
|
||||||
if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
|
if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
|
||||||
// Check if it's a proper markdown table (has multiple cells)
|
// Check if it's a proper markdown table (has multiple cells)
|
||||||
const cells = line.split('|').filter((c) => c.trim());
|
const cells = line.split('|').filter(c => c.trim());
|
||||||
if (cells.length >= 2) {
|
if (cells.length >= 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -532,72 +507,17 @@ class WordTemplateExporter {
|
|||||||
|
|
||||||
// Common ASCII art characters (Unicode box drawing and symbols)
|
// Common ASCII art characters (Unicode box drawing and symbols)
|
||||||
const asciiArtChars = [
|
const asciiArtChars = [
|
||||||
'─',
|
'─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼', // Box drawing
|
||||||
'│',
|
'═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬', // Double box
|
||||||
'┌',
|
'╭', '╮', '╯', '╰', // Rounded corners
|
||||||
'┐',
|
'▲', '▼', '◄', '►', '♦', '●', '○', '■', '□', '◆', '◇', // Shapes
|
||||||
'└',
|
'↓', '→', '←', '↑', '↔', '↕', '⇒', '⇐', '⇓', '⇑', // Arrows
|
||||||
'┘',
|
'┃', '━', '┏', '┓', '┗', '┛', '┣', '┫', '┳', '┻', '╋', // Heavy box
|
||||||
'├',
|
'░', '▒', '▓', '█', // Shading
|
||||||
'┤',
|
|
||||||
'┬',
|
|
||||||
'┴',
|
|
||||||
'┼', // Box drawing
|
|
||||||
'═',
|
|
||||||
'║',
|
|
||||||
'╔',
|
|
||||||
'╗',
|
|
||||||
'╚',
|
|
||||||
'╝',
|
|
||||||
'╠',
|
|
||||||
'╣',
|
|
||||||
'╦',
|
|
||||||
'╩',
|
|
||||||
'╬', // Double box
|
|
||||||
'╭',
|
|
||||||
'╮',
|
|
||||||
'╯',
|
|
||||||
'╰', // Rounded corners
|
|
||||||
'▲',
|
|
||||||
'▼',
|
|
||||||
'◄',
|
|
||||||
'►',
|
|
||||||
'♦',
|
|
||||||
'●',
|
|
||||||
'○',
|
|
||||||
'■',
|
|
||||||
'□',
|
|
||||||
'◆',
|
|
||||||
'◇', // Shapes
|
|
||||||
'↓',
|
|
||||||
'→',
|
|
||||||
'←',
|
|
||||||
'↑',
|
|
||||||
'↔',
|
|
||||||
'↕',
|
|
||||||
'⇒',
|
|
||||||
'⇐',
|
|
||||||
'⇓',
|
|
||||||
'⇑', // Arrows
|
|
||||||
'┃',
|
|
||||||
'━',
|
|
||||||
'┏',
|
|
||||||
'┓',
|
|
||||||
'┗',
|
|
||||||
'┛',
|
|
||||||
'┣',
|
|
||||||
'┫',
|
|
||||||
'┳',
|
|
||||||
'┻',
|
|
||||||
'╋', // Heavy box
|
|
||||||
'░',
|
|
||||||
'▒',
|
|
||||||
'▓',
|
|
||||||
'█', // Shading
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check for box drawing characters
|
// Check for box drawing characters
|
||||||
if (asciiArtChars.some((char) => line.includes(char))) {
|
if (asciiArtChars.some(char => line.includes(char))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,7 +537,7 @@ class WordTemplateExporter {
|
|||||||
/^\s*\*[-=\*]+\*/, // *----*
|
/^\s*\*[-=\*]+\*/, // *----*
|
||||||
];
|
];
|
||||||
|
|
||||||
return asciiPatterns.some((pattern) => pattern.test(line));
|
return asciiPatterns.some(pattern => pattern.test(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -644,7 +564,7 @@ class WordTemplateExporter {
|
|||||||
|
|
||||||
// Check if line contains arrow characters and color them red
|
// Check if line contains arrow characters and color them red
|
||||||
const arrowChars = ['↓', '→', '←', '↑', '▼', '►', '◄', '▲'];
|
const arrowChars = ['↓', '→', '←', '↑', '▼', '►', '◄', '▲'];
|
||||||
const hasArrow = arrowChars.some((arrow) => line.includes(arrow));
|
const hasArrow = arrowChars.some(arrow => line.includes(arrow));
|
||||||
|
|
||||||
if (hasArrow) {
|
if (hasArrow) {
|
||||||
// Split line into parts and color arrows red
|
// Split line into parts and color arrows red
|
||||||
@@ -735,13 +655,14 @@ class WordTemplateExporter {
|
|||||||
*/
|
*/
|
||||||
parseInlineFormatting(text) {
|
parseInlineFormatting(text) {
|
||||||
let xml = '';
|
let xml = '';
|
||||||
|
let pos = 0;
|
||||||
|
|
||||||
// Patterns for inline formatting
|
// Patterns for inline formatting
|
||||||
const patterns = [
|
const patterns = [
|
||||||
{ regex: /\*\*\*(.+?)\*\*\*/g, bold: true, italic: true },
|
{ regex: /\*\*\*(.+?)\*\*\*/g, bold: true, italic: true },
|
||||||
{ regex: /\*\*(.+?)\*\*/g, bold: true },
|
{ regex: /\*\*(.+?)\*\*/g, bold: true },
|
||||||
{ regex: /\*(.+?)\*/g, italic: true },
|
{ regex: /\*(.+?)\*/g, italic: true },
|
||||||
{ regex: /`(.+?)`/g, code: true },
|
{ regex: /`(.+?)`/g, code: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Simple approach: process text sequentially
|
// Simple approach: process text sequentially
|
||||||
@@ -772,12 +693,7 @@ class WordTemplateExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add formatted text
|
// Add formatted text
|
||||||
xml += this.createRunXml(
|
xml += this.createRunXml(match[1], matchedPattern.bold, matchedPattern.italic, matchedPattern.code);
|
||||||
match[1],
|
|
||||||
matchedPattern.bold,
|
|
||||||
matchedPattern.italic,
|
|
||||||
matchedPattern.code
|
|
||||||
);
|
|
||||||
|
|
||||||
remaining = remaining.substring(earliestPos + match[0].length);
|
remaining = remaining.substring(earliestPos + match[0].length);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+28
-20
@@ -4,7 +4,12 @@
|
|||||||
* Toggle with F11, exit with Escape.
|
* Toggle with F11, exit with Escape.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { EditorView, ViewPlugin, Decoration } = require('@codemirror/view');
|
const {
|
||||||
|
EditorView,
|
||||||
|
ViewPlugin,
|
||||||
|
Decoration,
|
||||||
|
DecorationSet,
|
||||||
|
} = require('@codemirror/view');
|
||||||
const { RangeSetBuilder } = require('@codemirror/state');
|
const { RangeSetBuilder } = require('@codemirror/state');
|
||||||
|
|
||||||
class ZenMode {
|
class ZenMode {
|
||||||
@@ -40,7 +45,10 @@ class ZenMode {
|
|||||||
|
|
||||||
// Add CM6 extensions to the active editor
|
// Add CM6 extensions to the active editor
|
||||||
if (tab.editorView) {
|
if (tab.editorView) {
|
||||||
this._extensions = [typewriterScrollExtension, lineDimmingExtension];
|
this._extensions = [
|
||||||
|
typewriterScrollExtension,
|
||||||
|
lineDimmingExtension,
|
||||||
|
];
|
||||||
// Reconfigure is not straightforward with CM6's immutable state.
|
// Reconfigure is not straightforward with CM6's immutable state.
|
||||||
// Instead we dispatch effects or simply rebuild with new extensions.
|
// Instead we dispatch effects or simply rebuild with new extensions.
|
||||||
// Since we cannot hot-swap extensions on an existing EditorView,
|
// Since we cannot hot-swap extensions on an existing EditorView,
|
||||||
@@ -108,6 +116,9 @@ 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.
|
||||||
|
let scrollTimeout = null;
|
||||||
|
|
||||||
const scrollFn = () => {
|
const scrollFn = () => {
|
||||||
if (!this.active) return;
|
if (!this.active) return;
|
||||||
const pos = view.state.selection.main.head;
|
const pos = view.state.selection.main.head;
|
||||||
@@ -118,6 +129,13 @@ class ZenMode {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Listen for selection/doc changes via a polling approach
|
||||||
|
// since we cannot add extensions to an existing view.
|
||||||
|
// Instead, use the DOM scrollIntoView after selection changes.
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
// No-op, we use interval below
|
||||||
|
});
|
||||||
|
|
||||||
// Use interval to keep cursor centered during typing
|
// Use interval to keep cursor centered during typing
|
||||||
this._scrollInterval = setInterval(() => {
|
this._scrollInterval = setInterval(() => {
|
||||||
if (!this.active) {
|
if (!this.active) {
|
||||||
@@ -139,7 +157,7 @@ class ZenMode {
|
|||||||
scroller.scrollTop += diff;
|
scroller.scrollTop += diff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (e) {
|
||||||
// Ignore errors from destroyed views
|
// Ignore errors from destroyed views
|
||||||
}
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
@@ -170,12 +188,7 @@ class ZenMode {
|
|||||||
if (!this.active || !this._hud) return;
|
if (!this.active || !this._hud) return;
|
||||||
|
|
||||||
const content = this.tabManager.getEditorContent();
|
const content = this.tabManager.getEditorContent();
|
||||||
const words = content.trim()
|
const words = content.trim() ? content.trim().split(/\s+/).filter(w => w.length > 0).length : 0;
|
||||||
? content
|
|
||||||
.trim()
|
|
||||||
.split(/\s+/)
|
|
||||||
.filter((w) => w.length > 0).length
|
|
||||||
: 0;
|
|
||||||
const readingTime = Math.max(1, Math.ceil(words / 200));
|
const readingTime = Math.max(1, Math.ceil(words / 200));
|
||||||
|
|
||||||
const wordsEl = document.getElementById('zen-words');
|
const wordsEl = document.getElementById('zen-words');
|
||||||
@@ -215,8 +228,7 @@ class ZenMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Typewriter scroll ViewPlugin
|
// Typewriter scroll ViewPlugin
|
||||||
const typewriterScrollExtension = ViewPlugin.fromClass(
|
const typewriterScrollExtension = ViewPlugin.fromClass(class {
|
||||||
class {
|
|
||||||
constructor(view) {
|
constructor(view) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
}
|
}
|
||||||
@@ -233,8 +245,7 @@ const typewriterScrollExtension = ViewPlugin.fromClass(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// Line dimming ViewPlugin
|
// Line dimming ViewPlugin
|
||||||
function getOpacity(distance) {
|
function getOpacity(distance) {
|
||||||
@@ -245,8 +256,7 @@ function getOpacity(distance) {
|
|||||||
return 0.3;
|
return 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineDimmingExtension = ViewPlugin.fromClass(
|
const lineDimmingExtension = ViewPlugin.fromClass(class {
|
||||||
class {
|
|
||||||
constructor(view) {
|
constructor(view) {
|
||||||
this.decorations = this.buildDecorations(view);
|
this.decorations = this.buildDecorations(view);
|
||||||
}
|
}
|
||||||
@@ -275,10 +285,8 @@ const lineDimmingExtension = ViewPlugin.fromClass(
|
|||||||
|
|
||||||
return builder.finish();
|
return builder.finish();
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
decorations: v => v.decorations,
|
||||||
decorations: (v) => v.decorations,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
module.exports = { ZenMode };
|
module.exports = { ZenMode };
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for bundled external tools
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
describe('Bundled Pandoc binary', () => {
|
|
||||||
const rootDir = path.resolve(__dirname, '..');
|
|
||||||
const platform = process.platform;
|
|
||||||
const binaryPath = path.join(rootDir, 'bin', platform, 'pandoc');
|
|
||||||
const windowsBinaryPath = path.join(rootDir, 'bin', platform, 'pandoc.exe');
|
|
||||||
|
|
||||||
test('Pandoc binary exists for current platform', () => {
|
|
||||||
const exists = fs.existsSync(binaryPath) || fs.existsSync(windowsBinaryPath);
|
|
||||||
expect(exists).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Pandoc binary is executable', () => {
|
|
||||||
if (platform === 'win32') {
|
|
||||||
expect(fs.existsSync(windowsBinaryPath)).toBe(true);
|
|
||||||
} else {
|
|
||||||
expect(fs.existsSync(binaryPath)).toBe(true);
|
|
||||||
const stats = fs.statSync(binaryPath);
|
|
||||||
expect(stats.mode & 0o111).toBeGreaterThan(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -48,9 +48,7 @@ describe('EventBus', () => {
|
|||||||
|
|
||||||
test('handler errors are caught and logged, not thrown', () => {
|
test('handler errors are caught and logged, not thrown', () => {
|
||||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
bus.on('bad:event', () => {
|
bus.on('bad:event', () => { throw new Error('boom'); });
|
||||||
throw new Error('boom');
|
|
||||||
});
|
|
||||||
expect(() => bus.emit('bad:event', {})).not.toThrow();
|
expect(() => bus.emit('bad:event', {})).not.toThrow();
|
||||||
expect(errorSpy).toHaveBeenCalled();
|
expect(errorSpy).toHaveBeenCalled();
|
||||||
errorSpy.mockRestore();
|
errorSpy.mockRestore();
|
||||||
|
|||||||
@@ -1,117 +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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -8,9 +8,7 @@ describe('GoalTracker', () => {
|
|||||||
store = {};
|
store = {};
|
||||||
tracker = new GoalTracker({
|
tracker = new GoalTracker({
|
||||||
get: (key) => store[key],
|
get: (key) => store[key],
|
||||||
set: (key, value) => {
|
set: (key, value) => { store[key] = value; }
|
||||||
store[key] = value;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests ensuring the codebase has no ESLint warnings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { execSync } = require('child_process');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
describe('ESLint quality gate', () => {
|
|
||||||
test('src and tests have no ESLint warnings', () => {
|
|
||||||
const rootDir = path.resolve(__dirname, '..');
|
|
||||||
const result = execSync('npm run lint', {
|
|
||||||
cwd: rootDir,
|
|
||||||
encoding: 'utf8',
|
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
|
||||||
});
|
|
||||||
expect(result).not.toMatch(/warning/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -12,11 +12,13 @@ describe('sanitizeErrorMessage', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
test('strips Windows absolute paths', () => {
|
test('strips Windows absolute paths', () => {
|
||||||
expect(sanitizeErrorMessage('Error in C:\\Users\\test\\file.js')).toBe('Error in file.js');
|
expect(sanitizeErrorMessage('Error in C:\\Users\\test\\file.js'))
|
||||||
|
.toBe('Error in file.js');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('strips Unix absolute paths', () => {
|
test('strips Unix absolute paths', () => {
|
||||||
expect(sanitizeErrorMessage('Error in /home/user/project/file.js')).toBe('Error in file.js');
|
expect(sanitizeErrorMessage('Error in /home/user/project/file.js'))
|
||||||
|
.toBe('Error in file.js');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles non-string input', () => {
|
test('handles non-string input', () => {
|
||||||
@@ -25,19 +27,18 @@ describe('sanitizeErrorMessage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('preserves messages without paths', () => {
|
test('preserves messages without paths', () => {
|
||||||
expect(sanitizeErrorMessage('Something went wrong')).toBe('Something went wrong');
|
expect(sanitizeErrorMessage('Something went wrong'))
|
||||||
|
.toBe('Something went wrong');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('strips nested Windows paths', () => {
|
test('strips nested Windows paths', () => {
|
||||||
expect(sanitizeErrorMessage('Cannot read C:\\Users\\admin\\AppData\\Local\\config.json')).toBe(
|
expect(sanitizeErrorMessage('Cannot read C:\\Users\\admin\\AppData\\Local\\config.json'))
|
||||||
'Cannot read config.json'
|
.toBe('Cannot read config.json');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('strips nested Unix paths', () => {
|
test('strips nested Unix paths', () => {
|
||||||
expect(sanitizeErrorMessage('File not found: /var/log/app/error.log')).toBe(
|
expect(sanitizeErrorMessage('File not found: /var/log/app/error.log'))
|
||||||
'File not found: error.log'
|
.toBe('File not found: error.log');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles undefined input', () => {
|
test('handles undefined input', () => {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe('Markdown Extensions', () => {
|
|||||||
|
|
||||||
test('matches all admonition types', () => {
|
test('matches all admonition types', () => {
|
||||||
const types = ['note', 'warning', 'tip', 'danger', 'info'];
|
const types = ['note', 'warning', 'tip', 'danger', 'info'];
|
||||||
types.forEach((type) => {
|
types.forEach(type => {
|
||||||
const src = `:::${type}\nContent\n:::\n`;
|
const src = `:::${type}\nContent\n:::\n`;
|
||||||
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);
|
||||||
expect(match).not.toBeNull();
|
expect(match).not.toBeNull();
|
||||||
@@ -76,81 +76,10 @@ 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(() => '<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'))
|
||||||
.map((b) => b.toString(16).padStart(2, '0'))
|
.map(b => b.toString(16).padStart(2, '0'))
|
||||||
.join('');
|
.join('');
|
||||||
return '~h' + hex;
|
return '~h' + hex;
|
||||||
};
|
};
|
||||||
@@ -198,65 +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,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests ensuring production code does not contain leftover diagnostic console.log statements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
describe('No leftover diagnostic console.log statements', () => {
|
|
||||||
const rootDir = path.resolve(__dirname, '..');
|
|
||||||
const filesToCheck = ['src/main.js', 'src/renderer.js'];
|
|
||||||
|
|
||||||
filesToCheck.forEach((relativePath) => {
|
|
||||||
test(`${relativePath} has no console.log statements`, () => {
|
|
||||||
const content = fs.readFileSync(path.join(rootDir, relativePath), 'utf8');
|
|
||||||
const matches = content.match(/console\.log\(/g) || [];
|
|
||||||
expect(matches).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,92 +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 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 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 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -13,14 +13,9 @@ describe('PluginContext', () => {
|
|||||||
statusBar: { registerIndicator: jest.fn() },
|
statusBar: { registerIndicator: jest.fn() },
|
||||||
eventBus: new EventBus(),
|
eventBus: new EventBus(),
|
||||||
settings: { get: jest.fn(), set: jest.fn(), onChanged: jest.fn() },
|
settings: { get: jest.fn(), set: jest.fn(), onChanged: jest.fn() },
|
||||||
editor: {
|
editor: { getContent: jest.fn(), getSelection: jest.fn(), insertAtCursor: jest.fn(), onContentChanged: jest.fn() },
|
||||||
getContent: jest.fn(),
|
|
||||||
getSelection: jest.fn(),
|
|
||||||
insertAtCursor: jest.fn(),
|
|
||||||
onContentChanged: jest.fn(),
|
|
||||||
},
|
|
||||||
ipc: { invoke: jest.fn(), on: jest.fn() },
|
ipc: { invoke: jest.fn(), on: jest.fn() },
|
||||||
exportHooks: { preHooks: [], postHooks: [] },
|
exportHooks: { preHooks: [], postHooks: [] }
|
||||||
};
|
};
|
||||||
context = new PluginContext(mockDeps);
|
context = new PluginContext(mockDeps);
|
||||||
});
|
});
|
||||||
@@ -28,17 +23,11 @@ describe('PluginContext', () => {
|
|||||||
test('exposes sidebar.registerPanel with namespaced id', () => {
|
test('exposes sidebar.registerPanel with namespaced id', () => {
|
||||||
const handler = jest.fn();
|
const handler = jest.fn();
|
||||||
context.sidebar.registerPanel('my-panel', { icon: 'test', title: 'Test', render: handler });
|
context.sidebar.registerPanel('my-panel', { icon: 'test', title: 'Test', render: handler });
|
||||||
expect(mockDeps.sidebar.registerPanel).toHaveBeenCalledWith('test-plugin:my-panel', {
|
expect(mockDeps.sidebar.registerPanel).toHaveBeenCalledWith('test-plugin:my-panel', { icon: 'test', title: 'Test', render: handler });
|
||||||
icon: 'test',
|
|
||||||
title: 'Test',
|
|
||||||
render: handler,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('exposes commands.register with crash-safe wrapper', () => {
|
test('exposes commands.register with crash-safe wrapper', () => {
|
||||||
const badHandler = () => {
|
const badHandler = () => { throw new Error('boom'); };
|
||||||
throw new Error('boom');
|
|
||||||
};
|
|
||||||
context.commands.register('bad-cmd', 'Bad', badHandler, 'Ctrl+Alt+T');
|
context.commands.register('bad-cmd', 'Bad', badHandler, 'Ctrl+Alt+T');
|
||||||
const registeredHandler = mockDeps.commands.register.mock.calls[0][2];
|
const registeredHandler = mockDeps.commands.register.mock.calls[0][2];
|
||||||
expect(() => registeredHandler()).not.toThrow();
|
expect(() => registeredHandler()).not.toThrow();
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe('PluginLoader', () => {
|
|||||||
id: 'my-plugin',
|
id: 'my-plugin',
|
||||||
name: 'My Plugin',
|
name: 'My Plugin',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test',
|
description: 'Test'
|
||||||
});
|
});
|
||||||
const loader = new PluginLoader([tempDir]);
|
const loader = new PluginLoader([tempDir]);
|
||||||
const plugins = loader.discoverPlugins();
|
const plugins = loader.discoverPlugins();
|
||||||
@@ -46,7 +46,7 @@ describe('PluginLoader', () => {
|
|||||||
id: 'test-plugin',
|
id: 'test-plugin',
|
||||||
name: 'Test Plugin',
|
name: 'Test Plugin',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'A test plugin',
|
description: 'A test plugin'
|
||||||
};
|
};
|
||||||
const loader = new PluginLoader([]);
|
const loader = new PluginLoader([]);
|
||||||
expect(loader.validateManifest(manifest)).toBe(true);
|
expect(loader.validateManifest(manifest)).toBe(true);
|
||||||
@@ -67,32 +67,18 @@ describe('PluginLoader', () => {
|
|||||||
test('validateManifest — rejects manifest with duplicate id', () => {
|
test('validateManifest — rejects manifest with duplicate id', () => {
|
||||||
const loader = new PluginLoader([]);
|
const loader = new PluginLoader([]);
|
||||||
loader.loadedIds = new Set(['existing-plugin']);
|
loader.loadedIds = new Set(['existing-plugin']);
|
||||||
expect(() =>
|
expect(() => loader.validateManifest({ id: 'existing-plugin', name: 'Dup', version: '1.0.0', description: 'x' }))
|
||||||
loader.validateManifest({
|
.toThrow(/duplicate/i);
|
||||||
id: 'existing-plugin',
|
|
||||||
name: 'Dup',
|
|
||||||
version: '1.0.0',
|
|
||||||
description: 'x',
|
|
||||||
})
|
|
||||||
).toThrow(/duplicate/i);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('discoverPlugins — loads index.js Plugin export if present', () => {
|
test('discoverPlugins — loads index.js Plugin export if present', () => {
|
||||||
const pluginDir = path.join(tempDir, 'with-index');
|
const pluginDir = path.join(tempDir, 'with-index');
|
||||||
writeManifest(pluginDir, {
|
writeManifest(pluginDir, { id: 'with-index', name: 'With Index', version: '1.0.0', description: 'Test' });
|
||||||
id: 'with-index',
|
|
||||||
name: 'With Index',
|
|
||||||
version: '1.0.0',
|
|
||||||
description: 'Test',
|
|
||||||
});
|
|
||||||
// Write a simple index.js with a Plugin export
|
// Write a simple index.js with a Plugin export
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(path.join(pluginDir, 'index.js'), `
|
||||||
path.join(pluginDir, 'index.js'),
|
|
||||||
`
|
|
||||||
class SimplePlugin { init() {} }
|
class SimplePlugin { init() {} }
|
||||||
module.exports = { Plugin: SimplePlugin };
|
module.exports = { Plugin: SimplePlugin };
|
||||||
`
|
`);
|
||||||
);
|
|
||||||
const loader = new PluginLoader([tempDir]);
|
const loader = new PluginLoader([tempDir]);
|
||||||
const plugins = loader.discoverPlugins();
|
const plugins = loader.discoverPlugins();
|
||||||
expect(plugins).toHaveLength(1);
|
expect(plugins).toHaveLength(1);
|
||||||
|
|||||||
@@ -3,16 +3,9 @@ const { PluginAPI } = require('../src/plugins/plugin-api');
|
|||||||
const { EventBus } = require('../src/plugins/event-bus');
|
const { EventBus } = require('../src/plugins/event-bus');
|
||||||
|
|
||||||
class TestPlugin extends PluginAPI {
|
class TestPlugin extends PluginAPI {
|
||||||
init(context) {
|
init(context) { this.initialized = true; this.ctx = context; }
|
||||||
this.initialized = true;
|
activate() { this.activated = true; }
|
||||||
this.ctx = context;
|
deactivate() { this.deactivated = true; }
|
||||||
}
|
|
||||||
activate() {
|
|
||||||
this.activated = true;
|
|
||||||
}
|
|
||||||
deactivate() {
|
|
||||||
this.deactivated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('PluginRegistry', () => {
|
describe('PluginRegistry', () => {
|
||||||
@@ -26,26 +19,16 @@ describe('PluginRegistry', () => {
|
|||||||
statusBar: { registerIndicator: jest.fn() },
|
statusBar: { registerIndicator: jest.fn() },
|
||||||
eventBus: new EventBus(),
|
eventBus: new EventBus(),
|
||||||
settings: { get: jest.fn(), set: jest.fn(), onChanged: jest.fn() },
|
settings: { get: jest.fn(), set: jest.fn(), onChanged: jest.fn() },
|
||||||
editor: {
|
editor: { getContent: jest.fn(() => ''), getSelection: jest.fn(() => ''), insertAtCursor: jest.fn(), onContentChanged: jest.fn() },
|
||||||
getContent: jest.fn(() => ''),
|
ipc: { invoke: jest.fn(), on: jest.fn() }
|
||||||
getSelection: jest.fn(() => ''),
|
|
||||||
insertAtCursor: jest.fn(),
|
|
||||||
onContentChanged: jest.fn(),
|
|
||||||
},
|
|
||||||
ipc: { invoke: jest.fn(), on: jest.fn() },
|
|
||||||
};
|
};
|
||||||
registry = new PluginRegistry(mockDeps);
|
registry = new PluginRegistry(mockDeps);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('register — stores plugin and calls init', () => {
|
test('register — stores plugin and calls init', () => {
|
||||||
registry.register({
|
registry.register({
|
||||||
id: 'test',
|
id: 'test', name: 'Test', version: '1.0.0', description: 'desc',
|
||||||
name: 'Test',
|
manifest: {}, PluginClass: TestPlugin, dir: '/tmp/test'
|
||||||
version: '1.0.0',
|
|
||||||
description: 'desc',
|
|
||||||
manifest: {},
|
|
||||||
PluginClass: TestPlugin,
|
|
||||||
dir: '/tmp/test',
|
|
||||||
});
|
});
|
||||||
const entry = registry.getPlugin('test');
|
const entry = registry.getPlugin('test');
|
||||||
expect(entry).toBeDefined();
|
expect(entry).toBeDefined();
|
||||||
@@ -54,32 +37,20 @@ describe('PluginRegistry', () => {
|
|||||||
|
|
||||||
test('register — works without PluginClass', () => {
|
test('register — works without PluginClass', () => {
|
||||||
registry.register({
|
registry.register({
|
||||||
id: 'manifest-only',
|
id: 'manifest-only', name: 'Manifest Only', version: '1.0.0', description: 'desc',
|
||||||
name: 'Manifest Only',
|
manifest: {}, PluginClass: null, dir: '/tmp/test'
|
||||||
version: '1.0.0',
|
|
||||||
description: 'desc',
|
|
||||||
manifest: {},
|
|
||||||
PluginClass: null,
|
|
||||||
dir: '/tmp/test',
|
|
||||||
});
|
});
|
||||||
expect(registry.getPlugin('manifest-only')).toBeDefined();
|
expect(registry.getPlugin('manifest-only')).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('register — init error does not crash, plugin not registered', () => {
|
test('register — init error does not crash, plugin not registered', () => {
|
||||||
class BadPlugin extends PluginAPI {
|
class BadPlugin extends PluginAPI {
|
||||||
init() {
|
init() { throw new Error('init fail'); }
|
||||||
throw new Error('init fail');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
expect(() => {
|
expect(() => {
|
||||||
registry.register({
|
registry.register({
|
||||||
id: 'bad',
|
id: 'bad', name: 'Bad', version: '1.0.0', description: 'desc',
|
||||||
name: 'Bad',
|
manifest: {}, PluginClass: BadPlugin, dir: '/tmp/test'
|
||||||
version: '1.0.0',
|
|
||||||
description: 'desc',
|
|
||||||
manifest: {},
|
|
||||||
PluginClass: BadPlugin,
|
|
||||||
dir: '/tmp/test',
|
|
||||||
});
|
});
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
expect(registry.getPlugin('bad')).toBeUndefined();
|
expect(registry.getPlugin('bad')).toBeUndefined();
|
||||||
@@ -90,65 +61,25 @@ describe('PluginRegistry', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('getAll — returns all registered plugins', () => {
|
test('getAll — returns all registered plugins', () => {
|
||||||
registry.register({
|
registry.register({ id: 'a', name: 'A', version: '1', description: '', manifest: {}, PluginClass: null, dir: '' });
|
||||||
id: 'a',
|
registry.register({ id: 'b', name: 'B', version: '1', description: '', manifest: {}, PluginClass: null, dir: '' });
|
||||||
name: 'A',
|
|
||||||
version: '1',
|
|
||||||
description: '',
|
|
||||||
manifest: {},
|
|
||||||
PluginClass: null,
|
|
||||||
dir: '',
|
|
||||||
});
|
|
||||||
registry.register({
|
|
||||||
id: 'b',
|
|
||||||
name: 'B',
|
|
||||||
version: '1',
|
|
||||||
description: '',
|
|
||||||
manifest: {},
|
|
||||||
PluginClass: null,
|
|
||||||
dir: '',
|
|
||||||
});
|
|
||||||
expect(registry.getAll()).toHaveLength(2);
|
expect(registry.getAll()).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('activate — calls activate on plugin instance', () => {
|
test('activate — calls activate on plugin instance', () => {
|
||||||
registry.register({
|
registry.register({ id: 'test', name: 'Test', version: '1', description: '', manifest: {}, PluginClass: TestPlugin, dir: '' });
|
||||||
id: 'test',
|
|
||||||
name: 'Test',
|
|
||||||
version: '1',
|
|
||||||
description: '',
|
|
||||||
manifest: {},
|
|
||||||
PluginClass: TestPlugin,
|
|
||||||
dir: '',
|
|
||||||
});
|
|
||||||
registry.activate('test');
|
registry.activate('test');
|
||||||
expect(registry.getPlugin('test').instance.activated).toBe(true);
|
expect(registry.getPlugin('test').instance.activated).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('deactivate — calls deactivate on plugin instance', () => {
|
test('deactivate — calls deactivate on plugin instance', () => {
|
||||||
registry.register({
|
registry.register({ id: 'test', name: 'Test', version: '1', description: '', manifest: {}, PluginClass: TestPlugin, dir: '' });
|
||||||
id: 'test',
|
|
||||||
name: 'Test',
|
|
||||||
version: '1',
|
|
||||||
description: '',
|
|
||||||
manifest: {},
|
|
||||||
PluginClass: TestPlugin,
|
|
||||||
dir: '',
|
|
||||||
});
|
|
||||||
registry.deactivate('test');
|
registry.deactivate('test');
|
||||||
expect(registry.getPlugin('test').instance.deactivated).toBe(true);
|
expect(registry.getPlugin('test').instance.deactivated).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('exportHooks are available and populated', () => {
|
test('exportHooks are available and populated', () => {
|
||||||
registry.register({
|
registry.register({ id: 'test', name: 'Test', version: '1', description: '', manifest: {}, PluginClass: TestPlugin, dir: '' });
|
||||||
id: 'test',
|
|
||||||
name: 'Test',
|
|
||||||
version: '1',
|
|
||||||
description: '',
|
|
||||||
manifest: {},
|
|
||||||
PluginClass: TestPlugin,
|
|
||||||
dir: '',
|
|
||||||
});
|
|
||||||
const handler = jest.fn();
|
const handler = jest.fn();
|
||||||
registry.getPlugin('test').instance.ctx.exports.registerPreHook(handler);
|
registry.getPlugin('test').instance.ctx.exports.registerPreHook(handler);
|
||||||
expect(registry.exportHooks.preHooks).toContain(handler);
|
expect(registry.exportHooks.preHooks).toContain(handler);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe('Preload Security', () => {
|
|||||||
'execute-code',
|
'execute-code',
|
||||||
'show-pdf-editor-from-toolbar',
|
'show-pdf-editor-from-toolbar',
|
||||||
'menu-open',
|
'menu-open',
|
||||||
'export',
|
'export'
|
||||||
];
|
];
|
||||||
|
|
||||||
const EXPECTED_RECEIVE_CHANNELS = [
|
const EXPECTED_RECEIVE_CHANNELS = [
|
||||||
@@ -127,7 +127,7 @@ describe('Preload Security', () => {
|
|||||||
'load-template-menu',
|
'load-template-menu',
|
||||||
'toggle-command-palette',
|
'toggle-command-palette',
|
||||||
'toggle-sidebar-panel',
|
'toggle-sidebar-panel',
|
||||||
'toggle-bottom-panel',
|
'toggle-bottom-panel'
|
||||||
];
|
];
|
||||||
|
|
||||||
test('should define all expected send channels', () => {
|
test('should define all expected send channels', () => {
|
||||||
|
|||||||
@@ -10,13 +10,9 @@
|
|||||||
describe('PrintPreview', () => {
|
describe('PrintPreview', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Mock electron require for executePrint
|
// Mock electron require for executePrint
|
||||||
jest.mock(
|
jest.mock('electron', () => ({
|
||||||
'electron',
|
ipcRenderer: { send: jest.fn(), invoke: jest.fn() }
|
||||||
() => ({
|
}), { virtual: true });
|
||||||
ipcRenderer: { send: jest.fn(), invoke: jest.fn() },
|
|
||||||
}),
|
|
||||||
{ virtual: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
document.body.innerHTML = `
|
document.body.innerHTML = `
|
||||||
<div class="dialog-overlay hidden" id="print-preview-overlay">
|
<div class="dialog-overlay hidden" id="print-preview-overlay">
|
||||||
@@ -55,13 +51,9 @@ describe('PrintPreview', () => {
|
|||||||
const { PrintPreview } = require('../src/print-preview');
|
const { PrintPreview } = require('../src/print-preview');
|
||||||
const preview = new PrintPreview();
|
const preview = new PrintPreview();
|
||||||
preview.open('<p>Test</p>');
|
preview.open('<p>Test</p>');
|
||||||
expect(document.getElementById('print-preview-overlay').classList.contains('hidden')).toBe(
|
expect(document.getElementById('print-preview-overlay').classList.contains('hidden')).toBe(false);
|
||||||
false
|
|
||||||
);
|
|
||||||
preview.close();
|
preview.close();
|
||||||
expect(document.getElementById('print-preview-overlay').classList.contains('hidden')).toBe(
|
expect(document.getElementById('print-preview-overlay').classList.contains('hidden')).toBe(true);
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateScaleLabel reflects slider value', () => {
|
test('updateScaleLabel reflects slider value', () => {
|
||||||
@@ -77,9 +69,7 @@ describe('PrintPreview', () => {
|
|||||||
const preview = new PrintPreview();
|
const preview = new PrintPreview();
|
||||||
preview.open('<p>Test</p>');
|
preview.open('<p>Test</p>');
|
||||||
document.getElementById('print-preview-close').click();
|
document.getElementById('print-preview-close').click();
|
||||||
expect(document.getElementById('print-preview-overlay').classList.contains('hidden')).toBe(
|
expect(document.getElementById('print-preview-overlay').classList.contains('hidden')).toBe(true);
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cancel button closes preview', () => {
|
test('cancel button closes preview', () => {
|
||||||
@@ -87,9 +77,7 @@ describe('PrintPreview', () => {
|
|||||||
const preview = new PrintPreview();
|
const preview = new PrintPreview();
|
||||||
preview.open('<p>Test</p>');
|
preview.open('<p>Test</p>');
|
||||||
document.getElementById('print-cancel').click();
|
document.getElementById('print-cancel').click();
|
||||||
expect(document.getElementById('print-preview-overlay').classList.contains('hidden')).toBe(
|
expect(document.getElementById('print-preview-overlay').classList.contains('hidden')).toBe(true);
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getOptions reflects changed values', () => {
|
test('getOptions reflects changed values', () => {
|
||||||
|
|||||||
@@ -8,22 +8,15 @@ describe('ProjectManager', () => {
|
|||||||
files = {};
|
files = {};
|
||||||
pm = new ProjectManager({
|
pm = new ProjectManager({
|
||||||
readFile: (p) => files[p] || null,
|
readFile: (p) => files[p] || null,
|
||||||
writeFile: (p, c) => {
|
writeFile: (p, c) => { files[p] = c; },
|
||||||
files[p] = c;
|
|
||||||
},
|
|
||||||
fileExists: (p) => p in files,
|
fileExists: (p) => p in files,
|
||||||
listDir: (p) =>
|
listDir: (p) => Object.keys(files).filter(f => f.startsWith(p)).map(f => f.slice(p.length + 1))
|
||||||
Object.keys(files)
|
|
||||||
.filter((f) => f.startsWith(p))
|
|
||||||
.map((f) => f.slice(p.length + 1)),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createProject writes .project.json', () => {
|
test('createProject writes .project.json', () => {
|
||||||
const project = pm.createProject('/manuscripts/novel', {
|
const project = pm.createProject('/manuscripts/novel', {
|
||||||
title: 'My Novel',
|
title: 'My Novel', type: 'manuscript', targetWords: 80000
|
||||||
type: 'manuscript',
|
|
||||||
targetWords: 80000,
|
|
||||||
});
|
});
|
||||||
expect(project.title).toBe('My Novel');
|
expect(project.title).toBe('My Novel');
|
||||||
expect(files['/manuscripts/novel/.project.json']).toBeDefined();
|
expect(files['/manuscripts/novel/.project.json']).toBeDefined();
|
||||||
@@ -33,11 +26,8 @@ describe('ProjectManager', () => {
|
|||||||
|
|
||||||
test('loadProject reads and returns project data', () => {
|
test('loadProject reads and returns project data', () => {
|
||||||
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
||||||
title: 'Test',
|
title: 'Test', type: 'manuscript', target: { words: 50000 },
|
||||||
type: 'manuscript',
|
chapters: [], metadata: {}
|
||||||
target: { words: 50000 },
|
|
||||||
chapters: [],
|
|
||||||
metadata: {},
|
|
||||||
});
|
});
|
||||||
const project = pm.loadProject('/manuscripts/novel');
|
const project = pm.loadProject('/manuscripts/novel');
|
||||||
expect(project.title).toBe('Test');
|
expect(project.title).toBe('Test');
|
||||||
@@ -49,17 +39,10 @@ describe('ProjectManager', () => {
|
|||||||
|
|
||||||
test('addChapter appends chapter and saves', () => {
|
test('addChapter appends chapter and saves', () => {
|
||||||
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
||||||
title: 'Test',
|
title: 'Test', type: 'manuscript', target: { words: 50000 },
|
||||||
type: 'manuscript',
|
chapters: [], metadata: {}
|
||||||
target: { words: 50000 },
|
|
||||||
chapters: [],
|
|
||||||
metadata: {},
|
|
||||||
});
|
|
||||||
pm.addChapter('/manuscripts/novel', {
|
|
||||||
file: '01-chapter.md',
|
|
||||||
title: 'Chapter One',
|
|
||||||
status: 'draft',
|
|
||||||
});
|
});
|
||||||
|
pm.addChapter('/manuscripts/novel', { file: '01-chapter.md', title: 'Chapter One', status: 'draft' });
|
||||||
const parsed = JSON.parse(files['/manuscripts/novel/.project.json']);
|
const parsed = JSON.parse(files['/manuscripts/novel/.project.json']);
|
||||||
expect(parsed.chapters.length).toBe(1);
|
expect(parsed.chapters.length).toBe(1);
|
||||||
expect(parsed.chapters[0].title).toBe('Chapter One');
|
expect(parsed.chapters[0].title).toBe('Chapter One');
|
||||||
@@ -67,14 +50,11 @@ describe('ProjectManager', () => {
|
|||||||
|
|
||||||
test('compileManuscript concatenates chapter files', () => {
|
test('compileManuscript concatenates chapter files', () => {
|
||||||
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
||||||
title: 'Test',
|
title: 'Test', type: 'manuscript', target: { words: 50000 },
|
||||||
type: 'manuscript',
|
|
||||||
target: { words: 50000 },
|
|
||||||
chapters: [
|
chapters: [
|
||||||
{ file: '01.md', title: 'One', status: 'draft' },
|
{ file: '01.md', title: 'One', status: 'draft' },
|
||||||
{ file: '02.md', title: 'Two', status: 'draft' },
|
{ file: '02.md', title: 'Two', status: 'draft' }
|
||||||
],
|
], metadata: {}
|
||||||
metadata: {},
|
|
||||||
});
|
});
|
||||||
files['/manuscripts/novel/01.md'] = 'First chapter content.';
|
files['/manuscripts/novel/01.md'] = 'First chapter content.';
|
||||||
files['/manuscripts/novel/02.md'] = 'Second chapter content.';
|
files['/manuscripts/novel/02.md'] = 'Second chapter content.';
|
||||||
@@ -84,14 +64,11 @@ describe('ProjectManager', () => {
|
|||||||
|
|
||||||
test('compileManuscript skips missing files', () => {
|
test('compileManuscript skips missing files', () => {
|
||||||
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
||||||
title: 'Test',
|
title: 'Test', type: 'manuscript', target: { words: 50000 },
|
||||||
type: 'manuscript',
|
|
||||||
target: { words: 50000 },
|
|
||||||
chapters: [
|
chapters: [
|
||||||
{ file: '01.md', title: 'One', status: 'draft' },
|
{ file: '01.md', title: 'One', status: 'draft' },
|
||||||
{ file: '02.md', title: 'Two', status: 'draft' },
|
{ file: '02.md', title: 'Two', status: 'draft' }
|
||||||
],
|
], metadata: {}
|
||||||
metadata: {},
|
|
||||||
});
|
});
|
||||||
files['/manuscripts/novel/01.md'] = 'Only chapter one.';
|
files['/manuscripts/novel/01.md'] = 'Only chapter one.';
|
||||||
const result = pm.compileManuscript('/manuscripts/novel');
|
const result = pm.compileManuscript('/manuscripts/novel');
|
||||||
@@ -100,14 +77,11 @@ describe('ProjectManager', () => {
|
|||||||
|
|
||||||
test('getStats returns total word count across chapters', () => {
|
test('getStats returns total word count across chapters', () => {
|
||||||
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
||||||
title: 'Test',
|
title: 'Test', type: 'manuscript', target: { words: 50000 },
|
||||||
type: 'manuscript',
|
|
||||||
target: { words: 50000 },
|
|
||||||
chapters: [
|
chapters: [
|
||||||
{ file: '01.md', title: 'One', status: 'draft' },
|
{ file: '01.md', title: 'One', status: 'draft' },
|
||||||
{ file: '02.md', title: 'Two', status: 'draft' },
|
{ file: '02.md', title: 'Two', status: 'draft' }
|
||||||
],
|
], metadata: {}
|
||||||
metadata: {},
|
|
||||||
});
|
});
|
||||||
files['/manuscripts/novel/01.md'] = 'word '.repeat(100).trim();
|
files['/manuscripts/novel/01.md'] = 'word '.repeat(100).trim();
|
||||||
files['/manuscripts/novel/02.md'] = 'more '.repeat(50).trim();
|
files['/manuscripts/novel/02.md'] = 'more '.repeat(50).trim();
|
||||||
@@ -120,11 +94,9 @@ describe('ProjectManager', () => {
|
|||||||
|
|
||||||
test('updateChapter modifies a chapter by index', () => {
|
test('updateChapter modifies a chapter by index', () => {
|
||||||
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
files['/manuscripts/novel/.project.json'] = JSON.stringify({
|
||||||
title: 'Test',
|
title: 'Test', type: 'manuscript', target: { words: 50000 },
|
||||||
type: 'manuscript',
|
|
||||||
target: { words: 50000 },
|
|
||||||
chapters: [{ file: '01.md', title: 'Old Title', status: 'draft' }],
|
chapters: [{ file: '01.md', title: 'Old Title', status: 'draft' }],
|
||||||
metadata: {},
|
metadata: {}
|
||||||
});
|
});
|
||||||
pm.updateChapter('/manuscripts/novel', 0, { title: 'New Title', status: 'revised' });
|
pm.updateChapter('/manuscripts/novel', 0, { title: 'New Title', status: 'revised' });
|
||||||
const parsed = JSON.parse(files['/manuscripts/novel/.project.json']);
|
const parsed = JSON.parse(files['/manuscripts/novel/.project.json']);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user