mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69afd5fc54 | ||
|
|
9e315af1b9 | ||
|
|
78914a4d65 | ||
|
|
3c4cc985fb | ||
|
|
09d9f6bdd1 | ||
|
|
f36d918871 | ||
|
|
ab16998922 | ||
|
|
58ca3014d8 | ||
|
|
7c1a79c724 |
@@ -94,8 +94,42 @@ jobs:
|
|||||||
dist/*.zip
|
dist/*.zip
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
build-macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Download external tools (pandoc)
|
||||||
|
run: node scripts/download-tools.js
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Build macOS packages (unsigned)
|
||||||
|
env:
|
||||||
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
||||||
|
run: npm run build:mac -- --publish=never
|
||||||
|
|
||||||
|
- name: Upload macOS artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos-artifacts
|
||||||
|
path: |
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*.zip
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: [build-linux, build-windows]
|
needs: [build-linux, build-windows, build-macos]
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -115,6 +149,13 @@ jobs:
|
|||||||
name: windows-artifacts
|
name: windows-artifacts
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
|
- name: Download macOS artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
name: macos-artifacts
|
||||||
|
path: dist
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,270 @@
|
|||||||
|
# Packaging — All-Platform Build + GitHub Releases
|
||||||
|
|
||||||
|
**Goal:** Make `npm run dist:all` produce Linux + Windows + macOS artifacts, and have the existing `.github/workflows/release.yml` create a GitHub Release on tag push with those artifacts. No code signing in this iteration.
|
||||||
|
|
||||||
|
**Scope (user decisions 2026-06-06):**
|
||||||
|
- Target platforms: **all three** (Linux .deb/AppImage/snap, Windows NSIS/portable/zip, macOS dmg/zip)
|
||||||
|
- Distribution: **GitHub Releases** (no auto-update; static artifacts only)
|
||||||
|
- Code signing: **skip for now** (unsigned binaries; Windows SmartScreen will warn, macOS Gatekeeper will quarantine)
|
||||||
|
|
||||||
|
**Architecture:** This is mostly about completing the existing scaffold. `electron-builder` 26.0.12, `scripts/download-tools.js`, `scripts/generate-icons.js`, the `package.json` scripts, and `release.yml` are all already in place. We need to (1) close a real bug in the `files` config, (2) add the macOS job to the workflow, and (3) declare macOS targets in the package config.
|
||||||
|
|
||||||
|
**Tech stack:** electron-builder 26.0.12, GitHub Actions `ubuntu-latest`/`windows-latest`/`macos-latest` runners, softprops/action-gh-release@v2 for the release creation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1 — Fix the `files` config in `package.json`
|
||||||
|
|
||||||
|
The current `files: ["src/**/*", ...]` matches `src/renderer/**/*.tsx` and ships dev source into the asar. The shipped renderer should be the built `dist/renderer/**/*` output, not TS source. asarUnpack for ffmpeg-static is correct and stays.
|
||||||
|
|
||||||
|
**Files:** `package.json` (build.files, build.extraResources)
|
||||||
|
|
||||||
|
### Step 1.1 — Replace `files` block
|
||||||
|
|
||||||
|
```json
|
||||||
|
"files": [
|
||||||
|
"src/main/**/*",
|
||||||
|
"src/preload.js",
|
||||||
|
"src/plugins/**/*",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "dist/renderer",
|
||||||
|
"to": "renderer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `src/main/**/*` — main process source (entry point + all main-process modules)
|
||||||
|
- `src/preload.js` — single preload file at the root of `src/`
|
||||||
|
- `src/plugins/**/*` — built-in plugins (referenced from main process; the `extraFiles: []` line in the current config is empty, so this is the right pattern)
|
||||||
|
- `package.json` — required for the asar `app.asar/package.json` lookup electron does at runtime
|
||||||
|
- `dist/renderer` moves to `extraResources` because it should live alongside the asar (not inside it) — the asar is sealed and we want the renderer to be a separately-updatable resource path. Production main loads it via `loadFile(path.join(__dirname, '../../dist/renderer/index.html'))` which, in the packaged app, resolves to `process.resourcesPath/renderer/index.html`. **This requires changing the loadFile path in `src/main/window/index.js` too** — see Task 4.
|
||||||
|
|
||||||
|
### Step 1.2 — Verify
|
||||||
|
|
||||||
|
- `npm run build:linux` should produce a working .AppImage
|
||||||
|
- Inside the .AppImage (extract with `--appimage-extract`), check `resources/` has `app.asar` and a `renderer/` directory containing `index.html` and `assets/`
|
||||||
|
- Inside `app.asar`, check `src/main/index.js` is present and `src/renderer/` is absent
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2 — Add macOS targets + icon to `package.json` build config
|
||||||
|
|
||||||
|
**Files:** `package.json`
|
||||||
|
|
||||||
|
### Step 2.1 — Replace the existing `mac` block
|
||||||
|
|
||||||
|
```json
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.productivity",
|
||||||
|
"identity": null,
|
||||||
|
"target": [
|
||||||
|
{ "target": "dmg", "arch": ["x64", "arm64"] },
|
||||||
|
{ "target": "zip", "arch": ["x64", "arm64"] }
|
||||||
|
],
|
||||||
|
"icon": "assets/icon.icns",
|
||||||
|
"darkModeSupport": true,
|
||||||
|
"hardenedRuntime": false,
|
||||||
|
"gatekeeperAssess": false,
|
||||||
|
"entitlements": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `identity: null` is intentional for unsigned builds
|
||||||
|
- `darkModeSupport: true` — the app already supports dark mode; this is just metadata
|
||||||
|
- `hardenedRuntime: false` and `gatekeeperAssess: false` — without signing, these are the only way the build succeeds
|
||||||
|
- `entitlements: null` — no entitlements file; some features (jit) won't work but Electron's main process doesn't need them in this app
|
||||||
|
- **`.icns` is missing.** For this iteration we'll use the default Electron icon and add a follow-up to generate one. The build will succeed without it but the produced .app will have the default icon.
|
||||||
|
|
||||||
|
### Step 2.2 — Generate a placeholder `.icns` (optional, can be deferred)
|
||||||
|
|
||||||
|
If we want a proper icon, the path is:
|
||||||
|
1. `npm run generate-icons` to produce `assets/icons/*.png` (already done)
|
||||||
|
2. Use `png2icns` (Linux) or `iconutil` (mac) to bundle them into `assets/icon.icns`
|
||||||
|
3. We don't have either on this Linux container; defer to a follow-up plan
|
||||||
|
|
||||||
|
For this iteration: ship without `.icns` and accept the default icon.
|
||||||
|
|
||||||
|
### Step 2.3 — Add `publish` config
|
||||||
|
|
||||||
|
```json
|
||||||
|
"publish": {
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "amitwh",
|
||||||
|
"repo": "markdown-converter",
|
||||||
|
"releaseType": "release"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Even without auto-update, this tells `electron-builder` to write `latest-mac.yml` / `latest-linux.yml` next to the artifacts. Useful for future auto-update wiring. CI passes `--publish=never` to override.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3 — Add macOS job to `.github/workflows/release.yml`
|
||||||
|
|
||||||
|
**Files:** `.github/workflows/release.yml`
|
||||||
|
|
||||||
|
### Step 3.1 — Add a third job between `build-windows` and `release`
|
||||||
|
|
||||||
|
Insert after the `build-windows` job (around line 96):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
build-macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Download external tools (pandoc)
|
||||||
|
run: node scripts/download-tools.js
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Build macOS packages (unsigned)
|
||||||
|
env:
|
||||||
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
||||||
|
run: npm run build:mac -- --publish=never
|
||||||
|
|
||||||
|
- name: Upload macOS artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos-artifacts
|
||||||
|
path: |
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*.zip
|
||||||
|
retention-days: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3.2 — Wire macOS into the `release` job's `needs` and downloads
|
||||||
|
|
||||||
|
Change the `release` job:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
release:
|
||||||
|
needs: [build-linux, build-windows, build-macos]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
And add a third download step (after the Windows download):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Download macOS artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
name: macos-artifacts
|
||||||
|
path: dist
|
||||||
|
```
|
||||||
|
|
||||||
|
`continue-on-error: true` is intentional — a failed macOS build shouldn't block a release of working Linux+Windows artifacts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4 — Update `src/main/window/index.js` for the resource path
|
||||||
|
|
||||||
|
**Files:** `src/main/window/index.js`
|
||||||
|
|
||||||
|
### Step 4.1 — Update the loadFile path
|
||||||
|
|
||||||
|
The current code (line 35):
|
||||||
|
```js
|
||||||
|
const prodPath = path.join(__dirname, '../../dist/renderer/index.html');
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the packaged app, `__dirname` points into the asar. `process.resourcesPath` points to the directory holding `app.asar` + the `renderer/` extraResources dir. Change to:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const rendererIndex = app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, 'renderer', 'index.html')
|
||||||
|
: path.join(__dirname, '../../dist/renderer/index.html');
|
||||||
|
win.loadFile(rendererIndex);
|
||||||
|
```
|
||||||
|
|
||||||
|
This needs to happen after the dev/prod branch is decided. The existing `if (!app.isPackaged && devServerUrl)` block already handles dev mode; the else branch needs this update.
|
||||||
|
|
||||||
|
### Step 4.2 — Add a guard for missing renderer in prod
|
||||||
|
|
||||||
|
```js
|
||||||
|
if (app.isPackaged) {
|
||||||
|
try {
|
||||||
|
fs.accessSync(path.join(process.resourcesPath, 'renderer', 'index.html'));
|
||||||
|
} catch {
|
||||||
|
console.error('[WINDOW] Renderer not found at', process.resourcesPath, '— did you run `npm run build:renderer` before packaging?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Failure mode: someone runs `npm run build` without first building the renderer. Without this guard, the window opens blank and the user has no idea why.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5 — Test on this Linux box
|
||||||
|
|
||||||
|
### Step 5.1 — Local verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm run download-tools # idempotent; bin/linux/pandoc already exists
|
||||||
|
npm run build:renderer
|
||||||
|
npm run build:linux # produces .deb, .AppImage in dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect the .AppImage:
|
||||||
|
```bash
|
||||||
|
chmod +x dist/*.AppImage
|
||||||
|
./dist/MarkdownConverter-*.AppImage --appimage-extract
|
||||||
|
ls squashfs-root/resources/
|
||||||
|
# Expect: app.asar renderer/
|
||||||
|
ls squashfs-root/resources/renderer/
|
||||||
|
# Expect: index.html assets/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5.2 — Run the produced app
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dist/MarkdownConverter-*.AppImage
|
||||||
|
```
|
||||||
|
|
||||||
|
The app should:
|
||||||
|
- Launch with the AppShell, header, sidebar
|
||||||
|
- Allow opening a folder / file
|
||||||
|
- Export to PDF/DOCX/HTML work (pandoc bundled)
|
||||||
|
|
||||||
|
### Step 5.3 — Tag + push to test the release workflow (optional)
|
||||||
|
|
||||||
|
Only do this if the user wants to verify the full pipeline. A dry-run on the CI side via `act` would be cleaner but `act` doesn't run macos-latest jobs locally. If the user wants to verify the workflow, push a `v5.0.1-rc1` tag and watch the Actions tab.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success criteria
|
||||||
|
|
||||||
|
1. `npm run dist:all` produces all three platform families on this Linux box (or at least Linux + mac, since Windows is built on Windows)
|
||||||
|
2. The .AppImage launches and the app works end-to-end
|
||||||
|
3. `release.yml` runs all three jobs on a tag push and creates a GitHub release with the union of artifacts
|
||||||
|
4. No regressions: 306/306 tests still pass, dev workflow still works
|
||||||
|
5. The `files` bug is fixed: no `.tsx` in the packaged asar
|
||||||
|
|
||||||
|
## Known limitations (intentional, can be follow-ups)
|
||||||
|
|
||||||
|
- **No `.icns`** — the macOS app will have the default Electron icon. Generating a proper `.icns` requires `iconutil` (mac) or `png2icns` (cross-platform); neither is on this Linux box.
|
||||||
|
- **No code signing** — Windows SmartScreen will warn; macOS Gatekeeper will quarantine unsigned `.dmg`. Setting up signing later is a config-only change (no structural rework): add `CSC_LINK`/`CSC_KEY_PASSWORD` for Windows, `CSC_LINK` (Apple Developer ID) for mac.
|
||||||
|
- **No auto-update** — user decisions for this iteration. Wiring `electron-updater` is a follow-up; the `publish` config from Task 2 puts metadata in the right place.
|
||||||
|
- **Cross-build quirks** — building macOS from Linux produces a `.dmg` that is generally usable but may show a "this app is from an unidentified developer" prompt the first time. Right-click → Open to bypass once. Documented in the release notes.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- CHANGELOG.md updates (not part of packaging)
|
||||||
|
- electron-updater wiring
|
||||||
|
- Code signing cert procurement
|
||||||
|
- Snap store / Microsoft Store / Mac App Store submission
|
||||||
Generated
+394
-3
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.4.2",
|
"version": "5.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "4.4.2",
|
"version": "5.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.20.1",
|
"@codemirror/autocomplete": "^6.20.1",
|
||||||
@@ -86,6 +86,7 @@
|
|||||||
"autoprefixer": "^10.5.0",
|
"autoprefixer": "^10.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"concurrently": "^10.0.3",
|
||||||
"cross-env": "^10.0.0",
|
"cross-env": "^10.0.0",
|
||||||
"electron": "^41.1.1",
|
"electron": "^41.1.1",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
@@ -104,7 +105,8 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.0.16",
|
"vite": "^8.0.16",
|
||||||
"vitest": "^4.1.8"
|
"vitest": "^4.1.8",
|
||||||
|
"wait-on": "^9.0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@adobe/css-tools": {
|
"node_modules/@adobe/css-tools": {
|
||||||
@@ -1902,6 +1904,60 @@
|
|||||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@hapi/address": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@hapi/hoek": "^11.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hapi/formula": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@hapi/hoek": {
|
||||||
|
"version": "11.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz",
|
||||||
|
"integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@hapi/pinpoint": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@hapi/tlds": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hapi/topo": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@hapi/hoek": "^11.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@hookform/resolvers": {
|
"node_modules/@hookform/resolvers": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.4.0.tgz",
|
||||||
@@ -6982,6 +7038,46 @@
|
|||||||
"postcss": "^8.1.0"
|
"postcss": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz",
|
||||||
|
"integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.16.0",
|
||||||
|
"form-data": "^4.0.5",
|
||||||
|
"https-proxy-agent": "^5.0.1",
|
||||||
|
"proxy-from-env": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/axios/node_modules/agent-base": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/axios/node_modules/https-proxy-agent": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "6",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-jest": {
|
"node_modules/babel-jest": {
|
||||||
"version": "30.3.0",
|
"version": "30.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz",
|
||||||
@@ -7889,6 +7985,185 @@
|
|||||||
"typedarray": "^0.0.6"
|
"typedarray": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/concurrently": {
|
||||||
|
"version": "10.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz",
|
||||||
|
"integrity": "sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "5.6.2",
|
||||||
|
"rxjs": "7.8.2",
|
||||||
|
"shell-quote": "1.8.4",
|
||||||
|
"supports-color": "10.2.2",
|
||||||
|
"tree-kill": "1.2.2",
|
||||||
|
"yargs": "18.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"conc": "dist/bin/index.js",
|
||||||
|
"concurrently": "dist/bin/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/ansi-regex": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/ansi-styles": {
|
||||||
|
"version": "6.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||||
|
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/chalk": {
|
||||||
|
"version": "5.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||||
|
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/cliui": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"strip-ansi": "^7.1.0",
|
||||||
|
"wrap-ansi": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/emoji-regex": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/string-width": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
|
"get-east-asian-width": "^1.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/strip-ansi": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/supports-color": {
|
||||||
|
"version": "10.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
|
||||||
|
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/wrap-ansi": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^6.2.1",
|
||||||
|
"string-width": "^7.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/yargs": {
|
||||||
|
"version": "18.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
|
||||||
|
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^9.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^22.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/concurrently/node_modules/yargs-parser": {
|
||||||
|
"version": "22.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
|
||||||
|
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/conf": {
|
"node_modules/conf": {
|
||||||
"version": "14.0.0",
|
"version": "14.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/conf/-/conf-14.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/conf/-/conf-14.0.0.tgz",
|
||||||
@@ -10378,6 +10653,27 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||||
|
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fontkit": {
|
"node_modules/fontkit": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
|
||||||
@@ -10563,6 +10859,19 @@
|
|||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-east-asian-width": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@@ -12696,6 +13005,25 @@
|
|||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/joi": {
|
||||||
|
"version": "18.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz",
|
||||||
|
"integrity": "sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@hapi/address": "^5.1.1",
|
||||||
|
"@hapi/formula": "^3.0.2",
|
||||||
|
"@hapi/hoek": "^11.0.7",
|
||||||
|
"@hapi/pinpoint": "^2.0.1",
|
||||||
|
"@hapi/tlds": "^1.1.1",
|
||||||
|
"@hapi/topo": "^6.0.2",
|
||||||
|
"@standard-schema/spec": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jpeg-exif": {
|
"node_modules/jpeg-exif": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz",
|
||||||
@@ -15183,6 +15511,16 @@
|
|||||||
"signal-exit": "^3.0.2"
|
"signal-exit": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pump": {
|
"node_modules/pump": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
||||||
@@ -15758,6 +16096,16 @@
|
|||||||
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
|
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/rxjs": {
|
||||||
|
"version": "7.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||||
|
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -15958,6 +16306,19 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shell-quote": {
|
||||||
|
"version": "1.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
|
||||||
|
"integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/siginfo": {
|
"node_modules/siginfo": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||||
@@ -16878,6 +17239,16 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tree-kill": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"tree-kill": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/truncate-utf8-bytes": {
|
"node_modules/truncate-utf8-bytes": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||||
@@ -17441,6 +17812,26 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wait-on": {
|
||||||
|
"version": "9.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.10.tgz",
|
||||||
|
"integrity": "sha512-rCoJEhvMr0X6alHmwc9abbrA5ZrLZFKpFQVKPNFwl2h7DapXOGdmimIHDtLOWhT4PjhZhxFEtZoQgEXbkDWdZw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.16.0",
|
||||||
|
"joi": "^18.2.1",
|
||||||
|
"lodash": "^4.18.1",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
|
"rxjs": "^7.8.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"wait-on": "bin/wait-on"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/walker": {
|
"node_modules/walker": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
||||||
|
|||||||
+36
-8
@@ -1,10 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "markdown-converter",
|
"name": "markdown-converter",
|
||||||
"version": "5.0.0",
|
"version": "5.0.1",
|
||||||
"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/index.js",
|
"main": "src/main/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
|
"dev:renderer": "vite --config vite.renderer.config.ts",
|
||||||
|
"dev:electron": "wait-on tcp:5173 && cross-env VITE_DEV_SERVER_URL=http://localhost:5173 electron . -- --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage",
|
||||||
|
"dev": "concurrently -k -n vite,electron -c blue,green \"npm:dev:renderer\" \"npm:dev:electron\"",
|
||||||
|
"build:renderer": "vite build --config vite.renderer.config.ts",
|
||||||
|
"preview": "npm run build:renderer && electron .",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
@@ -27,7 +32,8 @@
|
|||||||
"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",
|
||||||
|
"build:icon-icns": "node scripts/build-icon-icns.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"markdown",
|
"markdown",
|
||||||
@@ -60,6 +66,7 @@
|
|||||||
"autoprefixer": "^10.5.0",
|
"autoprefixer": "^10.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"concurrently": "^10.0.3",
|
||||||
"cross-env": "^10.0.0",
|
"cross-env": "^10.0.0",
|
||||||
"electron": "^41.1.1",
|
"electron": "^41.1.1",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
@@ -78,7 +85,8 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.0.16",
|
"vite": "^8.0.16",
|
||||||
"vitest": "^4.1.8"
|
"vitest": "^4.1.8",
|
||||||
|
"wait-on": "^9.0.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.20.1",
|
"@codemirror/autocomplete": "^6.20.1",
|
||||||
@@ -159,15 +167,20 @@
|
|||||||
},
|
},
|
||||||
"icon": "assets/icon",
|
"icon": "assets/icon",
|
||||||
"files": [
|
"files": [
|
||||||
"src/**/*",
|
"src/main/**/*",
|
||||||
"assets/**/*",
|
"src/preload.js",
|
||||||
"scripts/**/*",
|
"src/plugins/**/*",
|
||||||
"node_modules/**/*",
|
|
||||||
"package.json"
|
"package.json"
|
||||||
],
|
],
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"node_modules/ffmpeg-static/**"
|
"node_modules/ffmpeg-static/**"
|
||||||
],
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "dist/renderer",
|
||||||
|
"to": "renderer"
|
||||||
|
}
|
||||||
|
],
|
||||||
"extraFiles": [],
|
"extraFiles": [],
|
||||||
"fileAssociations": [
|
"fileAssociations": [
|
||||||
{
|
{
|
||||||
@@ -194,7 +207,16 @@
|
|||||||
],
|
],
|
||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.productivity",
|
"category": "public.app-category.productivity",
|
||||||
"identity": null
|
"identity": null,
|
||||||
|
"target": [
|
||||||
|
{ "target": "dmg", "arch": ["x64", "arm64"] },
|
||||||
|
{ "target": "zip", "arch": ["x64", "arm64"] }
|
||||||
|
],
|
||||||
|
"icon": "assets/icon.icns",
|
||||||
|
"darkModeSupport": true,
|
||||||
|
"hardenedRuntime": false,
|
||||||
|
"gatekeeperAssess": false,
|
||||||
|
"entitlements": null
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": [
|
"target": [
|
||||||
@@ -267,6 +289,12 @@
|
|||||||
],
|
],
|
||||||
"description": "Professional Markdown editor and universal file converter",
|
"description": "Professional Markdown editor and universal file converter",
|
||||||
"maintainer": "ConcreteInfo <amit.wh@gmail.com>"
|
"maintainer": "ConcreteInfo <amit.wh@gmail.com>"
|
||||||
|
},
|
||||||
|
"publish": {
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "amitwh",
|
||||||
|
"repo": "markdown-converter",
|
||||||
|
"releaseType": "release"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Build assets/icon.icns from the PNGs in assets/icons/.
|
||||||
|
*
|
||||||
|
* ICNS file format (Apple Icon Image):
|
||||||
|
* Header: 'icns' (4 bytes) + total file length (uint32 BE, includes header)
|
||||||
|
* For each size:
|
||||||
|
* Type tag (4 bytes) + length (uint32 BE, includes the 8-byte entry header) + PNG data
|
||||||
|
*
|
||||||
|
* Standard PNG type tags we'll include:
|
||||||
|
* icp4 = 16x16, icp5 = 32x32, icp6 = 64x64
|
||||||
|
* ic07 = 128x128, ic08 = 256x256, ic09 = 512x512, ic10 = 1024x1024
|
||||||
|
* icsb = 48x48 (small)
|
||||||
|
* ic11 = 16x16@2x (32x32 retina), ic12 = 32x32@2x (64x64 retina)
|
||||||
|
* ic13 = 128x128@2x (256x256 retina), ic14 = 256x256@2x (512x512 retina)
|
||||||
|
*/
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const iconsDir = path.join(__dirname, '..', 'assets', 'icons');
|
||||||
|
const outFile = path.join(__dirname, '..', 'assets', 'icon.icns');
|
||||||
|
|
||||||
|
// (size-in-pixels, icns type tag) — we skip sizes we don't have on disk
|
||||||
|
const sizes = [
|
||||||
|
{ size: 16, type: 'icp4' },
|
||||||
|
{ size: 32, type: 'icp5' },
|
||||||
|
{ size: 64, type: 'icp6' },
|
||||||
|
{ size: 128, type: 'ic07' },
|
||||||
|
{ size: 256, type: 'ic08' },
|
||||||
|
{ size: 512, type: 'ic09' },
|
||||||
|
{ size: 1024, type: 'ic10' },
|
||||||
|
{ size: 48, type: 'icsb' },
|
||||||
|
{ size: 32, type: 'ic11' }, // 16@2x
|
||||||
|
{ size: 64, type: 'ic12' }, // 32@2x
|
||||||
|
{ size: 256, type: 'ic13' }, // 128@2x
|
||||||
|
{ size: 512, type: 'ic14' }, // 256@2x
|
||||||
|
];
|
||||||
|
|
||||||
|
const entries = [];
|
||||||
|
for (const { size, type } of sizes) {
|
||||||
|
const pngPath = path.join(iconsDir, `${size}x${size}.png`);
|
||||||
|
if (!fs.existsSync(pngPath)) continue;
|
||||||
|
const png = fs.readFileSync(pngPath);
|
||||||
|
// ICNS requires 8-byte alignment per entry. paddedLen is the TOTAL entry size
|
||||||
|
// (8-byte type+length header + PNG + padding), rounded up to a multiple of 8.
|
||||||
|
const paddedLen = 8 + Math.ceil(png.length / 8) * 8;
|
||||||
|
entries.push({ type, png, paddedLen });
|
||||||
|
console.log(` ${type} (${size}x${size}) — ${png.length} bytes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.length === 0) {
|
||||||
|
console.error('No source PNGs found in', iconsDir);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalLen = 8 + entries.reduce((s, e) => s + e.paddedLen, 0);
|
||||||
|
const buf = Buffer.alloc(totalLen);
|
||||||
|
let off = 0;
|
||||||
|
|
||||||
|
// Header
|
||||||
|
buf.write('icns', off, 4, 'ascii'); off += 4;
|
||||||
|
buf.writeUInt32BE(totalLen, off); off += 4;
|
||||||
|
|
||||||
|
// Entries
|
||||||
|
for (const { type, png, paddedLen } of entries) {
|
||||||
|
buf.write(type, off, 4, 'ascii'); off += 4;
|
||||||
|
buf.writeUInt32BE(paddedLen, off); off += 4;
|
||||||
|
png.copy(buf, off);
|
||||||
|
off += png.length;
|
||||||
|
// Pad to 8-byte boundary within the entry. paddedLen already includes the 8-byte header.
|
||||||
|
const pad = paddedLen - 8 - png.length;
|
||||||
|
if (pad > 0) off += pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(outFile, buf);
|
||||||
|
console.log(`\nWrote ${outFile} (${buf.length} bytes, ${entries.length} sizes)`);
|
||||||
@@ -14,6 +14,30 @@ const { execSync } = require('child_process');
|
|||||||
|
|
||||||
const PANDOC_VERSION = '3.9.0.2';
|
const PANDOC_VERSION = '3.9.0.2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pandoc's archive inner layout has shifted across releases and platforms:
|
||||||
|
* linux tarball: pandoc-3.9.0.2/bin/pandoc
|
||||||
|
* win32 zip: pandoc-3.9.0.2/pandoc.exe
|
||||||
|
* darwin zip: pandoc-3.9.0.2-x86_64/bin/pandoc (arch-suffixed inner dir)
|
||||||
|
* pandoc-3.9.0.2-arm64/bin/pandoc (on Apple Silicon builds)
|
||||||
|
* Rather than hard-coding the intermediate path — which has already broken
|
||||||
|
* once when pandoc 3.9 added the arch suffix to the macOS archive — we walk
|
||||||
|
* the extracted tree and find the binary by name. This is robust to future
|
||||||
|
* layout shifts (e.g., universal binaries renaming the inner dir).
|
||||||
|
*/
|
||||||
|
function findFile(rootDir, targetName) {
|
||||||
|
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
const full = path.join(rootDir, entry.name);
|
||||||
|
if (entry.isFile() && entry.name === targetName) return full;
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
const found = findFile(full, targetName);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const PANDOC_CONFIG = {
|
const PANDOC_CONFIG = {
|
||||||
linux: {
|
linux: {
|
||||||
url: `https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz`,
|
url: `https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz`,
|
||||||
@@ -22,8 +46,9 @@ const PANDOC_CONFIG = {
|
|||||||
extract(archivePath, destDir) {
|
extract(archivePath, destDir) {
|
||||||
const tmpDir = path.join(os.tmpdir(), `pandoc-${Date.now()}`);
|
const tmpDir = path.join(os.tmpdir(), `pandoc-${Date.now()}`);
|
||||||
fs.mkdirSync(tmpDir, { recursive: true });
|
fs.mkdirSync(tmpDir, { recursive: true });
|
||||||
execSync(`tar -xzf "${archivePath}" -C "${tmpDir}" pandoc-${PANDOC_VERSION}/bin/pandoc`);
|
execSync(`tar -xzf "${archivePath}" -C "${tmpDir}"`);
|
||||||
const src = path.join(tmpDir, `pandoc-${PANDOC_VERSION}`, 'bin', 'pandoc');
|
const src = findFile(tmpDir, 'pandoc');
|
||||||
|
if (!src) throw new Error(`pandoc binary not found under ${tmpDir}`);
|
||||||
fs.copyFileSync(src, path.join(destDir, 'pandoc'));
|
fs.copyFileSync(src, path.join(destDir, 'pandoc'));
|
||||||
fs.chmodSync(path.join(destDir, 'pandoc'), 0o755);
|
fs.chmodSync(path.join(destDir, 'pandoc'), 0o755);
|
||||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||||
@@ -39,7 +64,8 @@ const PANDOC_CONFIG = {
|
|||||||
execSync(
|
execSync(
|
||||||
`powershell -Command "Expand-Archive -Force '${archivePath}' '${tmpDir}'"`,
|
`powershell -Command "Expand-Archive -Force '${archivePath}' '${tmpDir}'"`,
|
||||||
);
|
);
|
||||||
const src = path.join(tmpDir, `pandoc-${PANDOC_VERSION}`, 'pandoc.exe');
|
const src = findFile(tmpDir, 'pandoc.exe');
|
||||||
|
if (!src) throw new Error(`pandoc.exe not found under ${tmpDir}`);
|
||||||
fs.copyFileSync(src, path.join(destDir, 'pandoc.exe'));
|
fs.copyFileSync(src, path.join(destDir, 'pandoc.exe'));
|
||||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||||
},
|
},
|
||||||
@@ -52,7 +78,8 @@ const PANDOC_CONFIG = {
|
|||||||
const tmpDir = path.join(os.tmpdir(), `pandoc-${Date.now()}`);
|
const tmpDir = path.join(os.tmpdir(), `pandoc-${Date.now()}`);
|
||||||
fs.mkdirSync(tmpDir, { recursive: true });
|
fs.mkdirSync(tmpDir, { recursive: true });
|
||||||
execSync(`unzip -o "${archivePath}" -d "${tmpDir}"`);
|
execSync(`unzip -o "${archivePath}" -d "${tmpDir}"`);
|
||||||
const src = path.join(tmpDir, `pandoc-${PANDOC_VERSION}`, 'bin', 'pandoc');
|
const src = findFile(tmpDir, 'pandoc');
|
||||||
|
if (!src) throw new Error(`pandoc binary not found under ${tmpDir}`);
|
||||||
fs.copyFileSync(src, path.join(destDir, 'pandoc'));
|
fs.copyFileSync(src, path.join(destDir, 'pandoc'));
|
||||||
fs.chmodSync(path.join(destDir, 'pandoc'), 0o755);
|
fs.chmodSync(path.join(destDir, 'pandoc'), 0o755);
|
||||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||||
|
|||||||
+65
-65
@@ -16,7 +16,7 @@ function buildRecentFilesMenu(mainWindow) {
|
|||||||
const items = existing.map(file => ({
|
const items = existing.map(file => ({
|
||||||
label: path.basename(file),
|
label: path.basename(file),
|
||||||
click: () => {
|
click: () => {
|
||||||
const { openFileFromPath } = require('../main');
|
const { openFileFromPath } = require('../index');
|
||||||
openFileFromPath(file);
|
openFileFromPath(file);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -48,7 +48,7 @@ function fileItems(mainWindow) {
|
|||||||
label: 'Open',
|
label: 'Open',
|
||||||
accelerator: 'CmdOrCtrl+O',
|
accelerator: 'CmdOrCtrl+O',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { openFile } = require('../main');
|
const { openFile } = require('../index');
|
||||||
openFile();
|
openFile();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -56,7 +56,7 @@ function fileItems(mainWindow) {
|
|||||||
label: 'Open PDF',
|
label: 'Open PDF',
|
||||||
accelerator: 'CmdOrCtrl+Shift+O',
|
accelerator: 'CmdOrCtrl+Shift+O',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { openPdfFile } = require('../main');
|
const { openPdfFile } = require('../index');
|
||||||
openPdfFile();
|
openPdfFile();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -69,7 +69,7 @@ function fileItems(mainWindow) {
|
|||||||
label: 'Save As',
|
label: 'Save As',
|
||||||
accelerator: 'CmdOrCtrl+Shift+S',
|
accelerator: 'CmdOrCtrl+Shift+S',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { saveAsFile } = require('../main');
|
const { saveAsFile } = require('../index');
|
||||||
saveAsFile();
|
saveAsFile();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -101,7 +101,7 @@ function fileItems(mainWindow) {
|
|||||||
label: 'Import Document...',
|
label: 'Import Document...',
|
||||||
accelerator: 'CmdOrCtrl+I',
|
accelerator: 'CmdOrCtrl+I',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { importDocument } = require('../main');
|
const { importDocument } = require('../index');
|
||||||
importDocument();
|
importDocument();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -110,64 +110,64 @@ function fileItems(mainWindow) {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'HTML', click: () => {
|
label: 'HTML', click: () => {
|
||||||
const { exportFile } = require('../main');
|
const { exportFile } = require('../index');
|
||||||
exportFile('html');
|
exportFile('html');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'PDF', click: () => {
|
label: 'PDF', click: () => {
|
||||||
const { exportFile } = require('../main');
|
const { exportFile } = require('../index');
|
||||||
exportFile('pdf');
|
exportFile('pdf');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'PDF (Enhanced)', click: () => {
|
label: 'PDF (Enhanced)', click: () => {
|
||||||
const { exportPDFViaWordTemplate } = require('../main');
|
const { exportPDFViaWordTemplate } = require('../index');
|
||||||
exportPDFViaWordTemplate();
|
exportPDFViaWordTemplate();
|
||||||
}, accelerator: 'Ctrl+Shift+P'
|
}, accelerator: 'Ctrl+Shift+P'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'DOCX', click: () => {
|
label: 'DOCX', click: () => {
|
||||||
const { exportFile } = require('../main');
|
const { exportFile } = require('../index');
|
||||||
exportFile('docx');
|
exportFile('docx');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'DOCX (Enhanced)', click: () => {
|
label: 'DOCX (Enhanced)', click: () => {
|
||||||
const { exportWordWithTemplate } = require('../main');
|
const { exportWordWithTemplate } = require('../index');
|
||||||
exportWordWithTemplate();
|
exportWordWithTemplate();
|
||||||
}, accelerator: 'Ctrl+Shift+W'
|
}, accelerator: 'Ctrl+Shift+W'
|
||||||
},
|
},
|
||||||
{ label: 'LaTeX', click: () => { const { exportFile } = require('../main'); exportFile('latex'); } },
|
{ label: 'LaTeX', click: () => { const { exportFile } = require('../index'); exportFile('latex'); } },
|
||||||
{ label: 'RTF', click: () => { const { exportFile } = require('../main'); exportFile('rtf'); } },
|
{ label: 'RTF', click: () => { const { exportFile } = require('../index'); exportFile('rtf'); } },
|
||||||
{ label: 'ODT', click: () => { const { exportFile } = require('../main'); exportFile('odt'); } },
|
{ label: 'ODT', click: () => { const { exportFile } = require('../index'); exportFile('odt'); } },
|
||||||
{ label: 'EPUB', click: () => { const { exportFile } = require('../main'); exportFile('epub'); } },
|
{ label: 'EPUB', click: () => { const { exportFile } = require('../index'); exportFile('epub'); } },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'PowerPoint (PPTX)', click: () => { const { exportFile } = require('../main'); exportFile('pptx'); } },
|
{ label: 'PowerPoint (PPTX)', click: () => { const { exportFile } = require('../index'); exportFile('pptx'); } },
|
||||||
{ label: 'OpenDocument Presentation (ODP)', click: () => { const { exportFile } = require('../main'); exportFile('odp'); } },
|
{ label: 'OpenDocument Presentation (ODP)', click: () => { const { exportFile } = require('../index'); exportFile('odp'); } },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'CSV (Tables)', click: () => { const { exportSpreadsheet } = require('../main'); exportSpreadsheet('csv'); } },
|
{ label: 'CSV (Tables)', click: () => { const { exportSpreadsheet } = require('../index'); exportSpreadsheet('csv'); } },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'JSON (.json)', click: () => { const { exportFile } = require('../main'); exportFile('json'); } },
|
{ label: 'JSON (.json)', click: () => { const { exportFile } = require('../index'); exportFile('json'); } },
|
||||||
{ label: 'YAML (.yaml)', click: () => { const { exportFile } = require('../main'); exportFile('yaml'); } },
|
{ label: 'YAML (.yaml)', click: () => { const { exportFile } = require('../index'); exportFile('yaml'); } },
|
||||||
{ label: 'XML (.xml)', click: () => { const { exportFile } = require('../main'); exportFile('xml'); } },
|
{ label: 'XML (.xml)', click: () => { const { exportFile } = require('../index'); exportFile('xml'); } },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Confluence Wiki (.txt)', click: () => { const { exportFile } = require('../main'); exportFile('confluence'); } },
|
{ label: 'Confluence Wiki (.txt)', click: () => { const { exportFile } = require('../index'); exportFile('confluence'); } },
|
||||||
{ label: 'MOBI E-book (.mobi)', click: () => { const { exportFile } = require('../main'); exportFile('mobi'); } }
|
{ label: 'MOBI E-book (.mobi)', click: () => { const { exportFile } = require('../index'); exportFile('mobi'); } }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Select Word Template...',
|
label: 'Select Word Template...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { selectWordTemplate } = require('../main');
|
const { selectWordTemplate } = require('../index');
|
||||||
selectWordTemplate();
|
selectWordTemplate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Template Settings...',
|
label: 'Template Settings...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showTemplateSettings } = require('../main');
|
const { showTemplateSettings } = require('../index');
|
||||||
showTemplateSettings();
|
showTemplateSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -240,32 +240,32 @@ function viewItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'Theme',
|
label: 'Theme',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Atom One Light (Default)', click: () => { const { setTheme } = require('../main'); setTheme('atomonelight'); } },
|
{ label: 'Atom One Light (Default)', click: () => { const { setTheme } = require('../index'); setTheme('atomonelight'); } },
|
||||||
{ label: 'GitHub Light', click: () => { const { setTheme } = require('../main'); setTheme('github'); } },
|
{ label: 'GitHub Light', click: () => { const { setTheme } = require('../index'); setTheme('github'); } },
|
||||||
{ label: 'Light', click: () => { const { setTheme } = require('../main'); setTheme('light'); } },
|
{ label: 'Light', click: () => { const { setTheme } = require('../index'); setTheme('light'); } },
|
||||||
{ label: 'Solarized Light', click: () => { const { setTheme } = require('../main'); setTheme('solarized'); } },
|
{ label: 'Solarized Light', click: () => { const { setTheme } = require('../index'); setTheme('solarized'); } },
|
||||||
{ label: 'Gruvbox Light', click: () => { const { setTheme } = require('../main'); setTheme('gruvbox-light'); } },
|
{ label: 'Gruvbox Light', click: () => { const { setTheme } = require('../index'); setTheme('gruvbox-light'); } },
|
||||||
{ label: 'Ayu Light', click: () => { const { setTheme } = require('../main'); setTheme('ayu-light'); } },
|
{ label: 'Ayu Light', click: () => { const { setTheme } = require('../index'); setTheme('ayu-light'); } },
|
||||||
{ label: 'Sepia', click: () => { const { setTheme } = require('../main'); setTheme('sepia'); } },
|
{ label: 'Sepia', click: () => { const { setTheme } = require('../index'); setTheme('sepia'); } },
|
||||||
{ label: 'Paper', click: () => { const { setTheme } = require('../main'); setTheme('paper'); } },
|
{ label: 'Paper', click: () => { const { setTheme } = require('../index'); setTheme('paper'); } },
|
||||||
{ label: 'Rose Pine Dawn', click: () => { const { setTheme } = require('../main'); setTheme('rosepine-dawn'); } },
|
{ label: 'Rose Pine Dawn', click: () => { const { setTheme } = require('../index'); setTheme('rosepine-dawn'); } },
|
||||||
{ label: 'Concrete Light', click: () => { const { setTheme } = require('../main'); setTheme('concrete-light'); } },
|
{ label: 'Concrete Light', click: () => { const { setTheme } = require('../index'); setTheme('concrete-light'); } },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Dark', click: () => { const { setTheme } = require('../main'); setTheme('dark'); } },
|
{ label: 'Dark', click: () => { const { setTheme } = require('../index'); setTheme('dark'); } },
|
||||||
{ label: 'One Dark', click: () => { const { setTheme } = require('../main'); setTheme('onedark'); } },
|
{ label: 'One Dark', click: () => { const { setTheme } = require('../index'); setTheme('onedark'); } },
|
||||||
{ label: 'Dracula', click: () => { const { setTheme } = require('../main'); setTheme('dracula'); } },
|
{ label: 'Dracula', click: () => { const { setTheme } = require('../index'); setTheme('dracula'); } },
|
||||||
{ label: 'Nord', click: () => { const { setTheme } = require('../main'); setTheme('nord'); } },
|
{ label: 'Nord', click: () => { const { setTheme } = require('../index'); setTheme('nord'); } },
|
||||||
{ label: 'Monokai', click: () => { const { setTheme } = require('../main'); setTheme('monokai'); } },
|
{ label: 'Monokai', click: () => { const { setTheme } = require('../index'); setTheme('monokai'); } },
|
||||||
{ label: 'Material', click: () => { const { setTheme } = require('../main'); setTheme('material'); } },
|
{ label: 'Material', click: () => { const { setTheme } = require('../index'); setTheme('material'); } },
|
||||||
{ label: 'Gruvbox Dark', click: () => { const { setTheme } = require('../main'); setTheme('gruvbox-dark'); } },
|
{ label: 'Gruvbox Dark', click: () => { const { setTheme } = require('../index'); setTheme('gruvbox-dark'); } },
|
||||||
{ label: 'Tokyo Night', click: () => { const { setTheme } = require('../main'); setTheme('tokyonight'); } },
|
{ label: 'Tokyo Night', click: () => { const { setTheme } = require('../index'); setTheme('tokyonight'); } },
|
||||||
{ label: 'Palenight', click: () => { const { setTheme } = require('../main'); setTheme('palenight'); } },
|
{ label: 'Palenight', click: () => { const { setTheme } = require('../index'); setTheme('palenight'); } },
|
||||||
{ label: 'Ayu Dark', click: () => { const { setTheme } = require('../main'); setTheme('ayu-dark'); } },
|
{ label: 'Ayu Dark', click: () => { const { setTheme } = require('../index'); setTheme('ayu-dark'); } },
|
||||||
{ label: 'Ayu Mirage', click: () => { const { setTheme } = require('../main'); setTheme('ayu-mirage'); } },
|
{ label: 'Ayu Mirage', click: () => { const { setTheme } = require('../index'); setTheme('ayu-mirage'); } },
|
||||||
{ label: 'Oceanic Next', click: () => { const { setTheme } = require('../main'); setTheme('oceanic-next'); } },
|
{ label: 'Oceanic Next', click: () => { const { setTheme } = require('../index'); setTheme('oceanic-next'); } },
|
||||||
{ label: 'Cobalt2', click: () => { const { setTheme } = require('../main'); setTheme('cobalt2'); } },
|
{ label: 'Cobalt2', click: () => { const { setTheme } = require('../index'); setTheme('cobalt2'); } },
|
||||||
{ label: 'Concrete Dark', click: () => { const { setTheme } = require('../main'); setTheme('concrete-dark'); } },
|
{ label: 'Concrete Dark', click: () => { const { setTheme } = require('../index'); setTheme('concrete-dark'); } },
|
||||||
{ label: 'Concrete Warm', click: () => { const { setTheme } = require('../main'); setTheme('concrete-warm'); } }
|
{ label: 'Concrete Warm', click: () => { const { setTheme } = require('../index'); setTheme('concrete-warm'); } }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -327,7 +327,7 @@ function batchItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'Convert Markdown Folder...',
|
label: 'Convert Markdown Folder...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showBatchConversionDialog } = require('../main');
|
const { showBatchConversionDialog } = require('../index');
|
||||||
showBatchConversionDialog();
|
showBatchConversionDialog();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -357,7 +357,7 @@ function convertItems(mainWindow) {
|
|||||||
label: 'Universal File Converter...',
|
label: 'Universal File Converter...',
|
||||||
accelerator: 'CmdOrCtrl+Shift+C',
|
accelerator: 'CmdOrCtrl+Shift+C',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showUniversalConverterDialog } = require('../main');
|
const { showUniversalConverterDialog } = require('../index');
|
||||||
showUniversalConverterDialog();
|
showUniversalConverterDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,7 +370,7 @@ function pdfEditorItems(mainWindow) {
|
|||||||
label: 'Open PDF File...',
|
label: 'Open PDF File...',
|
||||||
accelerator: 'CmdOrCtrl+Shift+O',
|
accelerator: 'CmdOrCtrl+Shift+O',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { openPdfFile } = require('../main');
|
const { openPdfFile } = require('../index');
|
||||||
openPdfFile();
|
openPdfFile();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -378,21 +378,21 @@ function pdfEditorItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'Merge PDFs...',
|
label: 'Merge PDFs...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('merge');
|
showPDFEditorDialog('merge');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Split PDF...',
|
label: 'Split PDF...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('split');
|
showPDFEditorDialog('split');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Compress PDF...',
|
label: 'Compress PDF...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('compress');
|
showPDFEditorDialog('compress');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -400,21 +400,21 @@ function pdfEditorItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'Rotate Pages...',
|
label: 'Rotate Pages...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('rotate');
|
showPDFEditorDialog('rotate');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Delete Pages...',
|
label: 'Delete Pages...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('delete');
|
showPDFEditorDialog('delete');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Reorder Pages...',
|
label: 'Reorder Pages...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('reorder');
|
showPDFEditorDialog('reorder');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -422,7 +422,7 @@ function pdfEditorItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'Add Watermark...',
|
label: 'Add Watermark...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('watermark');
|
showPDFEditorDialog('watermark');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -433,21 +433,21 @@ function pdfEditorItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'Add Password Protection...',
|
label: 'Add Password Protection...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('encrypt');
|
showPDFEditorDialog('encrypt');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Remove Password...',
|
label: 'Remove Password...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('decrypt');
|
showPDFEditorDialog('decrypt');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Set Permissions...',
|
label: 'Set Permissions...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showPDFEditorDialog } = require('../main');
|
const { showPDFEditorDialog } = require('../index');
|
||||||
showPDFEditorDialog('permissions');
|
showPDFEditorDialog('permissions');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,7 +457,7 @@ function pdfEditorItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'About PDF Editor',
|
label: 'About PDF Editor',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showAboutDialog } = require('../main');
|
const { showAboutDialog } = require('../index');
|
||||||
showAboutDialog();
|
showAboutDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -482,7 +482,7 @@ function helpItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'About MarkdownConverter',
|
label: 'About MarkdownConverter',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showAboutDialog } = require('../main');
|
const { showAboutDialog } = require('../index');
|
||||||
showAboutDialog();
|
showAboutDialog();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -490,7 +490,7 @@ function helpItems(mainWindow) {
|
|||||||
{
|
{
|
||||||
label: 'Dependencies & Requirements',
|
label: 'Dependencies & Requirements',
|
||||||
click: () => {
|
click: () => {
|
||||||
const { showDependenciesDialog } = require('../main');
|
const { showDependenciesDialog } = require('../index');
|
||||||
showDependenciesDialog();
|
showDependenciesDialog();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// src/main/window/index.js
|
// src/main/window/index.js
|
||||||
// Main window creation
|
// Main window creation
|
||||||
|
|
||||||
const { BrowserWindow } = require('electron');
|
const { app, BrowserWindow } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
const state = require('./state');
|
const state = require('./state');
|
||||||
const menu = require('../menu');
|
const menu = require('../menu');
|
||||||
|
|
||||||
@@ -13,16 +14,47 @@ function createMainWindow() {
|
|||||||
height: bounds.height,
|
height: bounds.height,
|
||||||
x: bounds.x,
|
x: bounds.x,
|
||||||
y: bounds.y,
|
y: bounds.y,
|
||||||
show: false,
|
show: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
spellcheck: true
|
spellcheck: true
|
||||||
},
|
},
|
||||||
icon: path.join(__dirname, '../../assets/icon.png')
|
icon: path.join(__dirname, '../../../assets/icon.png')
|
||||||
});
|
});
|
||||||
|
|
||||||
win.loadFile(path.join(__dirname, '../../renderer/index.html'));
|
// Dev (Vite): load the running dev server so .tsx is transformed on the fly.
|
||||||
|
// Production (running from dist/ directly): load the built renderer at the
|
||||||
|
// relative path from this file.
|
||||||
|
// Production (packaged installer build): the renderer ships under
|
||||||
|
// process.resourcesPath/renderer (configured via build.extraResources in
|
||||||
|
// package.json) — not inside the asar.
|
||||||
|
// VITE_DEV_SERVER_URL is set by `npm run dev` via cross-env.
|
||||||
|
// app.isPackaged is set by electron-builder for installer builds.
|
||||||
|
const devServerUrl = process.env.VITE_DEV_SERVER_URL;
|
||||||
|
if (!app.isPackaged && devServerUrl) {
|
||||||
|
console.log('[WINDOW] Dev mode — loading', devServerUrl);
|
||||||
|
win.loadURL(devServerUrl);
|
||||||
|
} else {
|
||||||
|
const rendererIndex = app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, 'renderer', 'index.html')
|
||||||
|
: path.join(__dirname, '../../../dist/renderer/index.html');
|
||||||
|
|
||||||
|
if (app.isPackaged) {
|
||||||
|
try {
|
||||||
|
fs.accessSync(rendererIndex);
|
||||||
|
} catch {
|
||||||
|
console.error(
|
||||||
|
'[WINDOW] Renderer not found at',
|
||||||
|
rendererIndex,
|
||||||
|
'— did you run `npm run build:renderer` before packaging?',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[WINDOW] Production mode — loading', rendererIndex);
|
||||||
|
win.loadFile(rendererIndex);
|
||||||
|
}
|
||||||
|
|
||||||
// Show window only after content is ready — avoids blank flash
|
// Show window only after content is ready — avoids blank flash
|
||||||
win.once('ready-to-show', () => {
|
win.once('ready-to-show', () => {
|
||||||
@@ -31,8 +63,10 @@ function createMainWindow() {
|
|||||||
|
|
||||||
menu.register(win);
|
menu.register(win);
|
||||||
|
|
||||||
win.on('closed', () => {
|
// Use 'close' (fires before destruction) — 'closed' fires after the
|
||||||
state.save(win);
|
// BrowserWindow object is destroyed, so getBounds() would throw.
|
||||||
|
win.on('close', () => {
|
||||||
|
if (!win.isDestroyed()) state.save(win);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Spell check context menu
|
// Spell check context menu
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function AppShell() {
|
|||||||
<main className="h-screen w-screen overflow-hidden bg-background">
|
<main className="h-screen w-screen overflow-hidden bg-background">
|
||||||
<ResizablePanelGroup
|
<ResizablePanelGroup
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
onLayout={(sizes) => setPaneSizes({ sidebar: 0, editor: sizes[0], preview: sizes[1] })}
|
onLayoutChange={(sizes) => setPaneSizes({ sidebar: 0, editor: sizes[0], preview: sizes[1] })}
|
||||||
>
|
>
|
||||||
<ResizablePanel defaultSize={previewVisible ? 50 : 100} minSize={20}>
|
<ResizablePanel defaultSize={previewVisible ? 50 : 100} minSize={20}>
|
||||||
<section className="h-full bg-background">
|
<section className="h-full bg-background">
|
||||||
@@ -58,7 +58,7 @@ export function AppShell() {
|
|||||||
<main className="flex-1 overflow-hidden">
|
<main className="flex-1 overflow-hidden">
|
||||||
<ResizablePanelGroup
|
<ResizablePanelGroup
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
onLayout={(sizes) => setPaneSizes({ sidebar: sizes[0], editor: sizes[1], preview: sizes[2] })}
|
onLayoutChange={(sizes) => setPaneSizes({ sidebar: sizes[0], editor: sizes[1], preview: sizes[2] })}
|
||||||
>
|
>
|
||||||
{sidebarVisible && (
|
{sidebarVisible && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ export function AboutDialog() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open={isOpen} onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="about-desc">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>About MarkdownConverter</DialogTitle>
|
<DialogTitle>About MarkdownConverter</DialogTitle>
|
||||||
<DialogDescription id="about-desc">
|
<DialogDescription>
|
||||||
Professional Markdown editor and universal file converter.
|
Professional Markdown editor and universal file converter.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ export function AsciiGeneratorDialog() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="ascii-desc" className="max-w-2xl">
|
<DialogContent className="max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>ASCII generator</DialogTitle>
|
<DialogTitle>ASCII generator</DialogTitle>
|
||||||
<DialogDescription id="ascii-desc">Type text, pick a font, see ASCII art</DialogDescription>
|
<DialogDescription>Type text, pick a font, see ASCII art</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ export function ConfirmDialog(props: ConfirmProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && handleCancel()}>
|
<Dialog open onOpenChange={(o) => !o && handleCancel()}>
|
||||||
<DialogContent aria-describedby="confirm-body">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogDescription id="confirm-body">{body}</DialogDescription>
|
<DialogDescription>{body}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="ghost" onClick={handleCancel}>
|
<Button variant="ghost" onClick={handleCancel}>
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ export function ExportBatchDialog({ sourcePaths }: { sourcePaths: string[] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="batch-desc">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Batch export</DialogTitle>
|
<DialogTitle>Batch export</DialogTitle>
|
||||||
<DialogDescription id="batch-desc">{sourcePaths.length} files</DialogDescription>
|
<DialogDescription>{sourcePaths.length} files</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3 text-sm">
|
<div className="space-y-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ export function ExportDocxDialog({ sourcePath }: { sourcePath: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="docx-desc">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Export to DOCX</DialogTitle>
|
<DialogTitle>Export to DOCX</DialogTitle>
|
||||||
<DialogDescription id="docx-desc">{sourcePath}</DialogDescription>
|
<DialogDescription>{sourcePath}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3 text-sm">
|
<div className="space-y-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ export function ExportHtmlDialog({ sourcePath }: { sourcePath: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="html-desc">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Export to HTML</DialogTitle>
|
<DialogTitle>Export to HTML</DialogTitle>
|
||||||
<DialogDescription id="html-desc">{sourcePath}</DialogDescription>
|
<DialogDescription>{sourcePath}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3 text-sm">
|
<div className="space-y-3 text-sm">
|
||||||
<label className="flex items-center gap-2">
|
<label className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ export function ExportPdfDialog({ sourcePath }: { sourcePath: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="pdf-desc">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Export to PDF</DialogTitle>
|
<DialogTitle>Export to PDF</DialogTitle>
|
||||||
<DialogDescription id="pdf-desc">{sourcePath}</DialogDescription>
|
<DialogDescription>{sourcePath}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3 text-sm">
|
<div className="space-y-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ export function FindInFilesDialog() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="find-desc" className="max-w-2xl">
|
<DialogContent className="max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Find in files</DialogTitle>
|
<DialogTitle>Find in files</DialogTitle>
|
||||||
<DialogDescription id="find-desc">
|
<DialogDescription>
|
||||||
{rootPath ? `Search in ${rootPath}` : 'No folder open'}
|
{rootPath ? `Search in ${rootPath}` : 'No folder open'}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export function SettingsSheet() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open onOpenChange={(o) => !o && closeModal()}>
|
<Sheet open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<SheetContent aria-describedby="settings-desc" side="right" className="w-full sm:max-w-[480px]">
|
<SheetContent side="right" className="w-full sm:max-w-[480px]">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>Settings</SheetTitle>
|
<SheetTitle>Settings</SheetTitle>
|
||||||
<SheetDescription id="settings-desc">Editor, theme, and export preferences</SheetDescription>
|
<SheetDescription>Editor, theme, and export preferences</SheetDescription>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<Tabs defaultValue="editor" className="mt-4">
|
<Tabs defaultValue="editor" className="mt-4">
|
||||||
<TabsList className="grid w-full grid-cols-5">
|
<TabsList className="grid w-full grid-cols-5">
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ export function TableGeneratorDialog() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="table-desc" className="max-w-2xl">
|
<DialogContent className="max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Table generator</DialogTitle>
|
<DialogTitle>Table generator</DialogTitle>
|
||||||
<DialogDescription id="table-desc">Specify rows × columns to generate a markdown table</DialogDescription>
|
<DialogDescription>Specify rows × columns to generate a markdown table</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ export function WelcomeDialog() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && handleClose()}>
|
<Dialog open onOpenChange={(o) => !o && handleClose()}>
|
||||||
<DialogContent aria-describedby="welcome-desc" className="max-w-xl">
|
<DialogContent className="max-w-xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Welcome to MarkdownConverter</DialogTitle>
|
<DialogTitle>Welcome to MarkdownConverter</DialogTitle>
|
||||||
<DialogDescription id="welcome-desc">
|
<DialogDescription>
|
||||||
A polished editor for Markdown, with PDF, DOCX, and HTML export.
|
A polished editor for Markdown, with PDF, DOCX, and HTML export.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ export function WordExportDialog({ sourcePath }: { sourcePath: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
<Dialog open onOpenChange={(o) => !o && closeModal()}>
|
||||||
<DialogContent aria-describedby="word-desc">
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Export to Word (.docx)</DialogTitle>
|
<DialogTitle>Export to Word (.docx)</DialogTitle>
|
||||||
<DialogDescription id="word-desc">{sourcePath}</DialogDescription>
|
<DialogDescription>{sourcePath}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3 text-sm">
|
<div className="space-y-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self';">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com;">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>MarkdownConverter</title>
|
<title>MarkdownConverter</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import './styles/globals.css';
|
|||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ThemeProvider defaultTheme="dark" attribute="class" enableSystem>
|
<ThemeProvider defaultTheme="light" attribute="class" enableSystem>
|
||||||
<App />
|
<App />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useAppStore } from '@/stores/app-store';
|
|||||||
|
|
||||||
// Mock react-resizable-panels for jsdom environment
|
// Mock react-resizable-panels for jsdom environment
|
||||||
vi.mock('@/components/ui/resizable', () => ({
|
vi.mock('@/components/ui/resizable', () => ({
|
||||||
ResizablePanelGroup: ({ children, direction, onLayout }: any) => (
|
ResizablePanelGroup: ({ children, direction, onLayoutChange }: any) => (
|
||||||
<div data-testid="resizable-panel-group" data-direction={direction}>
|
<div data-testid="resizable-panel-group" data-direction={direction}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,4 +31,27 @@ describe('WelcomeDialog', () => {
|
|||||||
await userEvent.click(screen.getByRole('button', { name: /get started/i }));
|
await userEvent.click(screen.getByRole('button', { name: /get started/i }));
|
||||||
expect(useSettingsStore.getState().welcomeDismissed).toBe(true);
|
expect(useSettingsStore.getState().welcomeDismissed).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression guard: Radix Dialog emits a console.warn when the Content's
|
||||||
|
// aria-describedby points at an id that isn't in the DOM. The default
|
||||||
|
// shadcn/ui pattern (custom id="X-desc" on DialogDescription) breaks that
|
||||||
|
// link because Radix's internal descriptionId is auto-generated via useId.
|
||||||
|
// The fix is to drop the override and let Radix manage the id itself.
|
||||||
|
it('does not emit the Radix "Missing Description" warning', async () => {
|
||||||
|
const warnings: string[] = [];
|
||||||
|
const spy = vi.spyOn(console, 'warn').mockImplementation((...args) => {
|
||||||
|
warnings.push(args.map(String).join(' '));
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
render(<WelcomeDialog />);
|
||||||
|
// Effects that fire the warning run in a microtask; one tick is enough.
|
||||||
|
await Promise.resolve();
|
||||||
|
const offending = warnings.filter((w) =>
|
||||||
|
/Missing\s+`?Description`?|aria-describedby=\{undefined\}/.test(w),
|
||||||
|
);
|
||||||
|
expect(offending).toEqual([]);
|
||||||
|
} finally {
|
||||||
|
spy.mockRestore();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user