Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2b34572a9 | |||
| b76886e3ec | |||
| 92dd3bcab3 | |||
| f22714c37f | |||
| 5cfbd48282 | |||
| 8425d44658 | |||
| 499db16fce | |||
| 0b975558d0 | |||
| 91abda1744 | |||
| f0b0f678f1 | |||
| c27efaec90 |
2
.npmrc
2
.npmrc
@@ -1 +1 @@
|
|||||||
@add-ideas:registry=https://git.add-ideas.de/api/packages/zemion/npm/
|
@add-ideas:registry=https://git.add-ideas.de/api/packages/lotobo/npm/
|
||||||
|
|||||||
45
CHANGELOG.md
45
CHANGELOG.md
@@ -4,6 +4,51 @@ All notable changes to `pdf-tools` are documented here.
|
|||||||
|
|
||||||
The project follows a pragmatic versioning scheme while the app is still below `1.0.0`: minor versions mark coherent user-facing milestones; patch versions mark fixes and small improvements.
|
The project follows a pragmatic versioning scheme while the app is still below `1.0.0`: minor versions mark coherent user-facing milestones; patch versions mark fixes and small improvements.
|
||||||
|
|
||||||
|
## 0.4.4 — LocalToolBox organization migration
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Moved canonical repository, Toolbox schema, corresponding-source, and package-registry URLs from the personal `zemion` namespace to the `lotobo` LocalToolBox organization.
|
||||||
|
- Upgraded the Toolbox contract, React shell, and validation testkit to `0.2.3` from the organization-owned registry.
|
||||||
|
- Bumped the app/package version to `0.4.4`.
|
||||||
|
|
||||||
|
## 0.4.3 — Aligned Toolbox content width
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Expanded the PDF workspace's maximum content width from `900px` to `90rem` so it aligns with the shared Toolbox shell bar and central catalogue.
|
||||||
|
- Bumped the app/package version to `0.4.3`.
|
||||||
|
|
||||||
|
## 0.4.2 — Corrected Toolbox header orientation
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgraded the Toolbox contract, React shell, and validation testkit to `0.2.2`.
|
||||||
|
- Moved the Toolbox icon and `add·ideas Toolbox` label to the fixed far-left block, kept the app title and one tagline centered, and moved the fixed control cluster to the far right in the reverse order: Help, the PDF-specific Gitea source link, Apps, and Personalize.
|
||||||
|
- Bumped the app/package version to `0.4.2`.
|
||||||
|
|
||||||
|
## 0.4.1 — Stable Toolbox header layout
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgraded the Toolbox contract, React shell, and validation testkit to `0.2.1`.
|
||||||
|
- Adopted the stable shared top bar: the fixed left cluster contains Personalize, Apps, the PDF-specific Gitea source link, and Help; the app title and one tagline remain centered; and the fixed far-right block contains the Toolbox icon and `add·ideas Toolbox` label.
|
||||||
|
|
||||||
|
## 0.4.0 — Shared Toolbox header and themes
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the standard Toolbox header controls for in-app help, the app-specific Gitea source link, appearance personalization, and application switching.
|
||||||
|
- Added explicit light, dark, and system-following themes for the PDF workspace, saved-workspace list, merge queue, dialogs, status messages, and controls.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgraded the Toolbox contract, React shell, and validation testkit to `0.2.0`.
|
||||||
|
- The header now uses the manifest's repository metadata for the source link instead of carrying a release-specific source action.
|
||||||
|
- Replaced application color literals with semantic PDF workspace variables while intentionally keeping rendered PDF pages on a white paper surface.
|
||||||
|
- Scoped application button and card styles to the shell's main content so they cannot override shared header controls.
|
||||||
|
- Bumped the app/package version to `0.4.0`.
|
||||||
|
|
||||||
## 0.3.4 — PDF.js compatibility and Toolbox integration
|
## 0.3.4 — PDF.js compatibility and Toolbox integration
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Current hosted version: <https://pdftools.add-ideas.de>
|
Current hosted version: <https://pdftools.add-ideas.de>
|
||||||
|
|
||||||
Current release branch: **v0.3.4 — PDF.js `toHex` browser compatibility fix**. See [`CHANGELOG.md`](CHANGELOG.md) for milestone history.
|
Current development release: **v0.4.4 — LocalToolBox organization migration**. See [`CHANGELOG.md`](CHANGELOG.md) for milestone history.
|
||||||
|
|
||||||
Active work is tracked in Gitea issues. Durable project context is mirrored into the Gitea wiki from repository docs, including [`ROADMAP.md`](ROADMAP.md).
|
Active work is tracked in Gitea issues. Durable project context is mirrored into the Gitea wiki from repository docs, including [`ROADMAP.md`](ROADMAP.md).
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ This makes the project especially useful for self-hosted environments, public-se
|
|||||||
- **Stable page references**: Duplicated pages and reordered pages are tracked as workspace page references rather than only by original page number.
|
- **Stable page references**: Duplicated pages and reordered pages are tracked as workspace page references rather than only by original page number.
|
||||||
- **Merge choices**: Loading another PDF can replace the current document, append pages, or insert pages at a chosen position.
|
- **Merge choices**: Loading another PDF can replace the current document, append pages, or insert pages at a chosen position.
|
||||||
- **PDF.js compatibility setup**: PDF.js is configured through one shared module and a local worker wrapper so the app can polyfill `Uint8Array.prototype.toHex` before PDF.js runs in older browsers.
|
- **PDF.js compatibility setup**: PDF.js is configured through one shared module and a local worker wrapper so the app can polyfill `Uint8Array.prototype.toHex` before PDF.js runs in older browsers.
|
||||||
|
- **Toolbox appearance controls**: The shared header provides light, dark, and system-following themes and keeps the app switcher, help, and source controls in the same place as the Toolbox portal.
|
||||||
- **In-app help**: The app includes a Help/Tutorial dialog with keyboard shortcuts and workflow explanations.
|
- **In-app help**: The app includes a Help/Tutorial dialog with keyboard shortcuts and workflow explanations.
|
||||||
|
|
||||||
## Current features
|
## Current features
|
||||||
@@ -96,7 +97,7 @@ Keyboard shortcuts are ignored while typing in form fields.
|
|||||||
|
|
||||||
## In-app documentation concept
|
## In-app documentation concept
|
||||||
|
|
||||||
The app includes a Help/Tutorial dialog reachable from the header via **Help ?**, `F1`, or `?`.
|
The app includes a Help/Tutorial dialog reachable from the header's **?** control, `F1`, or `?`.
|
||||||
|
|
||||||
Recommended structure for in-app documentation:
|
Recommended structure for in-app documentation:
|
||||||
|
|
||||||
@@ -121,15 +122,16 @@ browser <-- HTTPS --> static web server / reverse proxy --> built app assets
|
|||||||
There is no application server to operate. Administrators only need to host the built files from `dist/`.
|
There is no application server to operate. Administrators only need to host the built files from `dist/`.
|
||||||
|
|
||||||
The production build is relocatable and can also be assembled into an
|
The production build is relocatable and can also be assembled into an
|
||||||
[Add Ideas Toolbox](https://git.add-ideas.de/zemion/toolbox-portal) deployment.
|
[Add Ideas Toolbox](https://git.add-ideas.de/lotobo/toolbox-portal) deployment.
|
||||||
`toolbox-app.json` describes this app through the framework-independent
|
`toolbox-app.json` describes this app through the framework-independent
|
||||||
[Toolbox App Contract](https://git.add-ideas.de/zemion/toolbox-sdk). The same
|
[Toolbox App Contract](https://git.add-ideas.de/lotobo/toolbox-sdk). The same
|
||||||
artifact remains fully usable on its own.
|
artifact remains fully usable on its own.
|
||||||
|
|
||||||
When launched with a same-origin catalogue URL, for example
|
The shared first-party shell always provides help, source, and appearance
|
||||||
`?toolbox=/toolbox.catalog.json`, the shared first-party shell adds a toolbox
|
controls. When launched with a same-origin catalogue URL, for example
|
||||||
home link and application switcher. Missing, invalid, and cross-origin
|
`?toolbox=/toolbox.catalog.json`, it also connects the application switcher to
|
||||||
catalogues are ignored so the app falls back to standalone mode.
|
the current Toolbox catalogue. Missing, invalid, and cross-origin catalogues
|
||||||
|
are ignored so the app falls back to standalone mode.
|
||||||
|
|
||||||
### Build and test from source
|
### Build and test from source
|
||||||
|
|
||||||
@@ -255,10 +257,10 @@ The application version shown in the header is defined in `src/version.ts`. The
|
|||||||
The current development baseline is:
|
The current development baseline is:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
v0.3.4 — PDF.js toHex browser compatibility fix
|
v0.4.4 — LocalToolBox organization migration
|
||||||
```
|
```
|
||||||
|
|
||||||
This branch preserves the browser-only workspace baseline, includes the multi-file merge queue from the `0.3.2` milestone, bumps the displayed app/package version to `0.3.4`, and adds a shared PDF.js setup with a `toHex` compatibility shim for older browsers. Workspace state, thumbnail handling, generated download URLs, page-grid components, tests, type-checking, linting, and formatting are separated enough to support additional feature work without turning `App.tsx` back into a monolith.
|
This branch preserves the browser-only workspace baseline and PDF.js compatibility setup while using Toolbox SDK `0.2.3` from the `lotobo` package registry. The displayed app/package version is `0.4.4`; canonical source, schema, and release links use the LocalToolBox organization. The shared header keeps the Toolbox icon and `add·ideas Toolbox` label in a fixed far-left block, the app title and one tagline centered, and Help, the PDF source link, Apps, and Personalize in a fixed far-right cluster. The PDF workspace shares the shell bar's `90rem` maximum width. Workspace state, thumbnail handling, generated download URLs, page-grid components, tests, type-checking, linting, and formatting remain separated enough to support additional feature work without turning `App.tsx` back into a monolith.
|
||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
|
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "pdf-tools",
|
"name": "pdf-tools",
|
||||||
"version": "0.3.4",
|
"version": "0.4.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pdf-tools",
|
"name": "pdf-tools",
|
||||||
"version": "0.3.4",
|
"version": "0.4.4",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@add-ideas/toolbox-contract": "^0.1.0",
|
"@add-ideas/toolbox-contract": "^0.2.3",
|
||||||
"@add-ideas/toolbox-shell-react": "^0.1.0",
|
"@add-ideas/toolbox-shell-react": "^0.2.3",
|
||||||
"fflate": "^0.8.3",
|
"fflate": "^0.8.3",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^5.7.284",
|
"pdfjs-dist": "^5.7.284",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"react-dom": "^19.2.6"
|
"react-dom": "^19.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@add-ideas/toolbox-testkit": "^0.1.0",
|
"@add-ideas/toolbox-testkit": "^0.2.3",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
@@ -40,18 +40,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@add-ideas/toolbox-contract": {
|
"node_modules/@add-ideas/toolbox-contract": {
|
||||||
"version": "0.1.1",
|
"version": "0.2.3",
|
||||||
"resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-contract/-/0.1.1/toolbox-contract-0.1.1.tgz",
|
"resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-contract/-/0.2.3/toolbox-contract-0.2.3.tgz",
|
||||||
"integrity": "sha512-0q2whA5Y4SqG4PNNleXYR8nGbT3UYsZ1x+6ZWVmxuc/WjMxmujhD7ilIISmFzmnNdbyEyv/Zpr+U0fnHqGODqw==",
|
"integrity": "sha512-T0PVSuMT40GjTDfQJhEEY3ZawQq8zz1/ry95JdKI6W39CdLacaRXdGnEpDCMHt+jUbf1Jz7Nat/M5dFCgKVM9A==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@add-ideas/toolbox-shell-react": {
|
"node_modules/@add-ideas/toolbox-shell-react": {
|
||||||
"version": "0.1.1",
|
"version": "0.2.3",
|
||||||
"resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.1.1/toolbox-shell-react-0.1.1.tgz",
|
"resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.2.3/toolbox-shell-react-0.2.3.tgz",
|
||||||
"integrity": "sha512-Di2mu1BoJrWJg4/v5jDExROXDpvLXorlUgqcsBQV8ea/LklKoBFTAfgTkk/23HZ2BzixyMIjrhl9kD507s+iPQ==",
|
"integrity": "sha512-DT5lQDH48BFkFcmFLZnQh7+Cm73JzBPcmp5WzUXypfkUXpEyDYHzaXgmW4kZ0edSwh4RK4sPmx+JPtK0X4aKCQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@add-ideas/toolbox-contract": "0.1.1"
|
"@add-ideas/toolbox-contract": "0.2.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=18 <20",
|
"react": ">=18 <20",
|
||||||
@@ -59,13 +59,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@add-ideas/toolbox-testkit": {
|
"node_modules/@add-ideas/toolbox-testkit": {
|
||||||
"version": "0.1.1",
|
"version": "0.2.3",
|
||||||
"resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-testkit/-/0.1.1/toolbox-testkit-0.1.1.tgz",
|
"resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-testkit/-/0.2.3/toolbox-testkit-0.2.3.tgz",
|
||||||
"integrity": "sha512-o+H7LfM7ipdgmYz18S0lLuN6JsXUR+8ubAbcjTWTk8rSqNfp1JyeoRHOPbJVEHXrQwIFe/OB86UzRpex2gnKUQ==",
|
"integrity": "sha512-sq1MwhKWfFKen+N+124hl74qQimRSvmQ9sOU7jdcI+2qCKZ67+2B8rWyezeV80uTFu4Jv6deHksfYQ/tKNV6XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@add-ideas/toolbox-contract": "0.1.1"
|
"@add-ideas/toolbox-contract": "0.2.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"toolbox-check": "dist/cli.js"
|
"toolbox-check": "dist/cli.js"
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pdf-tools",
|
"name": "pdf-tools",
|
||||||
"version": "0.3.4",
|
"version": "0.4.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -23,8 +23,8 @@
|
|||||||
"check": "npm run manifest:check && npm run typecheck && npm run lint && npm run test && npm run build && npm run test:toolbox"
|
"check": "npm run manifest:check && npm run typecheck && npm run lint && npm run test && npm run build && npm run test:toolbox"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@add-ideas/toolbox-contract": "^0.1.0",
|
"@add-ideas/toolbox-contract": "^0.2.3",
|
||||||
"@add-ideas/toolbox-shell-react": "^0.1.0",
|
"@add-ideas/toolbox-shell-react": "^0.2.3",
|
||||||
"fflate": "^0.8.3",
|
"fflate": "^0.8.3",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^5.7.284",
|
"pdfjs-dist": "^5.7.284",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"react-dom": "^19.2.6"
|
"react-dom": "^19.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@add-ideas/toolbox-testkit": "^0.1.0",
|
"@add-ideas/toolbox-testkit": "^0.2.3",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
@@ -52,5 +52,13 @@
|
|||||||
"vite": "^8.0.13",
|
"vite": "^8.0.13",
|
||||||
"vitest": "^4.1.6"
|
"vitest": "^4.1.6"
|
||||||
},
|
},
|
||||||
"description": "Browser-only, self-hostable PDF workbench for page-level PDF operations."
|
"description": "Browser-only, self-hostable PDF workbench for page-level PDF operations.",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://git.add-ideas.de/lotobo/pdf-tools.git"
|
||||||
|
},
|
||||||
|
"homepage": "https://git.add-ideas.de/lotobo/pdf-tools",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://git.add-ideas.de/lotobo/pdf-tools/issues"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
|
"$schema": "https://git.add-ideas.de/lotobo/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "de.add-ideas.pdf-tools",
|
"id": "de.add-ideas.pdf-tools",
|
||||||
"name": "PDF Workbench",
|
"name": "PDF Workbench",
|
||||||
@@ -8,13 +8,6 @@
|
|||||||
"icon": "./favicon.svg",
|
"icon": "./favicon.svg",
|
||||||
"categories": ["documents", "pdf"],
|
"categories": ["documents", "pdf"],
|
||||||
"tags": ["merge", "split", "rotate", "reorder"],
|
"tags": ["merge", "split", "rotate", "reorder"],
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"id": "source",
|
|
||||||
"label": "Source",
|
|
||||||
"url": "https://git.add-ideas.de/zemion/pdf-tools/src/tag/v0.3.4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"integration": {
|
"integration": {
|
||||||
"contextVersion": 1,
|
"contextVersion": 1,
|
||||||
"launchModes": ["navigate", "new-tab"],
|
"launchModes": ["navigate", "new-tab"],
|
||||||
@@ -32,8 +25,8 @@
|
|||||||
"telemetry": false
|
"telemetry": false
|
||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"repository": "https://git.add-ideas.de/zemion/pdf-tools",
|
"repository": "https://git.add-ideas.de/lotobo/pdf-tools",
|
||||||
"license": "AGPL-3.0-only"
|
"license": "AGPL-3.0-only"
|
||||||
},
|
},
|
||||||
"version": "0.3.4"
|
"version": "0.4.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ if (sourceVersion !== packageJson.version) {
|
|||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
$schema:
|
$schema:
|
||||||
'https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json',
|
'https://git.add-ideas.de/lotobo/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json',
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
...definition,
|
...definition,
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ files['SOURCE.md'] = Buffer.from(
|
|||||||
'',
|
'',
|
||||||
`The source code corresponding to PDF Tools ${packageJson.version} is available at:`,
|
`The source code corresponding to PDF Tools ${packageJson.version} is available at:`,
|
||||||
'',
|
'',
|
||||||
`https://git.add-ideas.de/zemion/pdf-tools/src/tag/v${packageJson.version}`,
|
`https://git.add-ideas.de/lotobo/pdf-tools/src/tag/v${packageJson.version}`,
|
||||||
'',
|
'',
|
||||||
'Third-party components remain available under the separate terms reproduced in LICENSES/.',
|
'Third-party components remain available under the separate terms reproduced in LICENSES/.',
|
||||||
'',
|
'',
|
||||||
|
|||||||
21
src/App.tsx
21
src/App.tsx
@@ -1143,17 +1143,10 @@ const App: React.FC = () => {
|
|||||||
<AppShell
|
<AppShell
|
||||||
app={toolboxApp}
|
app={toolboxApp}
|
||||||
className="pdf-toolbox-shell"
|
className="pdf-toolbox-shell"
|
||||||
appActions={
|
helpAction={{
|
||||||
<button
|
onClick: () => setHelpOpen(true),
|
||||||
type="button"
|
title: 'Open help and keyboard shortcuts (F1 or ?)',
|
||||||
className="app-help-button"
|
}}
|
||||||
onClick={() => setHelpOpen(true)}
|
|
||||||
aria-haspopup="dialog"
|
|
||||||
title="Open help and keyboard shortcuts (F1 or ?)"
|
|
||||||
>
|
|
||||||
Help <span aria-hidden="true">?</span>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<FileLoader pdf={pdf} onFilesLoaded={handleFilesLoaded} />
|
<FileLoader pdf={pdf} onFilesLoaded={handleFilesLoaded} />
|
||||||
|
|
||||||
@@ -1237,7 +1230,11 @@ const App: React.FC = () => {
|
|||||||
{error && (
|
{error && (
|
||||||
<div
|
<div
|
||||||
className="card"
|
className="card"
|
||||||
style={{ border: '1px solid #fecaca', background: '#fef2f2' }}
|
style={{
|
||||||
|
border: '1px solid var(--pdf-danger-border)',
|
||||||
|
background: 'var(--pdf-danger-surface)',
|
||||||
|
color: 'var(--pdf-danger-text)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<strong>Error:</strong> {error}
|
<strong>Error:</strong> {error}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,18 +21,18 @@ const backgroundByVariant: Record<
|
|||||||
NonNullable<ActionDialogAction['variant']>,
|
NonNullable<ActionDialogAction['variant']>,
|
||||||
string
|
string
|
||||||
> = {
|
> = {
|
||||||
primary: '#2563eb',
|
primary: 'var(--pdf-primary)',
|
||||||
secondary: '#e5e7eb',
|
secondary: 'var(--pdf-control)',
|
||||||
danger: '#dc2626',
|
danger: 'var(--pdf-danger-text)',
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorByVariant: Record<
|
const colorByVariant: Record<
|
||||||
NonNullable<ActionDialogAction['variant']>,
|
NonNullable<ActionDialogAction['variant']>,
|
||||||
string
|
string
|
||||||
> = {
|
> = {
|
||||||
primary: 'white',
|
primary: 'var(--pdf-on-primary)',
|
||||||
secondary: '#111827',
|
secondary: 'var(--pdf-control-text)',
|
||||||
danger: 'white',
|
danger: 'var(--pdf-on-primary)',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionDialog: React.FC<ActionDialogProps> = ({
|
const ActionDialog: React.FC<ActionDialogProps> = ({
|
||||||
@@ -75,7 +75,7 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
|
|||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
zIndex: 70,
|
zIndex: 70,
|
||||||
background: 'rgba(15, 23, 42, 0.55)',
|
background: 'var(--pdf-overlay)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -86,9 +86,10 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: '440px',
|
maxWidth: '440px',
|
||||||
background: 'white',
|
background: 'var(--pdf-surface-raised)',
|
||||||
|
color: 'var(--pdf-text)',
|
||||||
borderRadius: '0.75rem',
|
borderRadius: '0.75rem',
|
||||||
boxShadow: '0 20px 40px rgba(15, 23, 42, 0.35)',
|
boxShadow: 'var(--pdf-dialog-shadow)',
|
||||||
padding: '1rem',
|
padding: '1rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@@ -121,8 +122,8 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
|
|||||||
borderRadius: '999px',
|
borderRadius: '999px',
|
||||||
width: '1.8rem',
|
width: '1.8rem',
|
||||||
height: '1.8rem',
|
height: '1.8rem',
|
||||||
background: '#e5e7eb',
|
background: 'var(--pdf-control)',
|
||||||
color: '#111827',
|
color: 'var(--pdf-control-text)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: '1.1rem',
|
fontSize: '1.1rem',
|
||||||
lineHeight: 1,
|
lineHeight: 1,
|
||||||
@@ -139,7 +140,7 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
color: '#4b5563',
|
color: 'var(--pdf-muted)',
|
||||||
lineHeight: 1.45,
|
lineHeight: 1.45,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -173,9 +174,11 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
|
|||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
padding: '0.45rem 0.8rem',
|
padding: '0.45rem 0.8rem',
|
||||||
background: action.disabled
|
background: action.disabled
|
||||||
? '#e5e7eb'
|
? 'var(--pdf-control)'
|
||||||
: backgroundByVariant[variant],
|
: backgroundByVariant[variant],
|
||||||
color: action.disabled ? '#6b7280' : colorByVariant[variant],
|
color: action.disabled
|
||||||
|
? 'var(--pdf-muted-soft)'
|
||||||
|
: colorByVariant[variant],
|
||||||
cursor: action.disabled ? 'default' : 'pointer',
|
cursor: action.disabled ? 'default' : 'pointer',
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const ActionsPanel: React.FC<ActionsPanelProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Tools</h2>
|
<h2>Tools</h2>
|
||||||
<p style={{ fontSize: '0.85rem', color: '#6b7280' }}>
|
<p style={{ fontSize: '0.85rem', color: 'var(--pdf-muted-soft)' }}>
|
||||||
Use these tools on the current in-memory document (reordered, rotated,
|
Use these tools on the current in-memory document (reordered, rotated,
|
||||||
with deletions). Nothing is uploaded to a server.
|
with deletions). Nothing is uploaded to a server.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -30,7 +30,13 @@ const FileLoader: React.FC<FileLoaderProps> = ({ pdf, onFilesLoaded }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{pdf && (
|
{pdf && (
|
||||||
<div style={{ marginTop: '0.75rem', fontSize: '0.9rem' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: '0.75rem',
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
color: 'var(--pdf-muted)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<strong>Loaded:</strong> {pdf.name}
|
<strong>Loaded:</strong> {pdf.name}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -54,22 +54,25 @@ const MergeQueuePanel: React.FC<MergeQueuePanelProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="card merge-queue-card"
|
className="card merge-queue-card"
|
||||||
style={{ border: '1px solid #bfdbfe', background: '#eff6ff' }}
|
style={{
|
||||||
|
border: '1px solid var(--pdf-info-border)',
|
||||||
|
background: 'var(--pdf-info-surface)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<h2>Merge PDF queue</h2>
|
<h2>Merge PDF queue</h2>
|
||||||
<p style={{ fontSize: '0.85rem', color: '#374151' }}>
|
<p style={{ fontSize: '0.85rem', color: 'var(--pdf-info-text)' }}>
|
||||||
Add several PDFs, reorder the queue, then merge them into a new unsaved
|
Add several PDFs, reorder the queue, then merge them into a new unsaved
|
||||||
workspace. Processing still happens entirely in your browser.
|
workspace. Processing still happens entirely in your browser.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{hasCurrentPdf ? (
|
{hasCurrentPdf ? (
|
||||||
<p style={{ fontSize: '0.85rem', color: '#374151' }}>
|
<p style={{ fontSize: '0.85rem', color: 'var(--pdf-info-text)' }}>
|
||||||
Current workspace:{' '}
|
Current workspace:{' '}
|
||||||
<strong>{currentPdfName ?? 'Untitled document'}</strong> with{' '}
|
<strong>{currentPdfName ?? 'Untitled document'}</strong> with{' '}
|
||||||
{currentPageCount} {currentPageCount === 1 ? 'page' : 'pages'}.
|
{currentPageCount} {currentPageCount === 1 ? 'page' : 'pages'}.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p style={{ fontSize: '0.85rem', color: '#374151' }}>
|
<p style={{ fontSize: '0.85rem', color: 'var(--pdf-info-text)' }}>
|
||||||
No current workspace is open. The queue will become a new workspace.
|
No current workspace is open. The queue will become a new workspace.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -122,7 +125,7 @@ const MergeQueuePanel: React.FC<MergeQueuePanelProps> = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style={{ fontSize: '0.85rem', color: '#374151' }}>
|
<p style={{ fontSize: '0.85rem', color: 'var(--pdf-info-text)' }}>
|
||||||
Queue total:{' '}
|
Queue total:{' '}
|
||||||
<strong>
|
<strong>
|
||||||
{items.length} {items.length === 1 ? 'PDF' : 'PDFs'}
|
{items.length} {items.length === 1 ? 'PDF' : 'PDFs'}
|
||||||
@@ -186,7 +189,7 @@ const MergeQueuePanel: React.FC<MergeQueuePanelProps> = ({
|
|||||||
fontSize: '0.85rem',
|
fontSize: '0.85rem',
|
||||||
}}
|
}}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
<span style={{ color: '#6b7280' }}>
|
<span style={{ color: 'var(--pdf-muted-soft)' }}>
|
||||||
(1 = before first page, {currentPageCount + 1} = after last page)
|
(1 = before first page, {currentPageCount + 1} = after last page)
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
zIndex: 60,
|
zIndex: 60,
|
||||||
background: 'rgba(15, 23, 42, 0.55)',
|
background: 'var(--pdf-overlay)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -60,9 +60,10 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: '420px',
|
maxWidth: '420px',
|
||||||
background: 'white',
|
background: 'var(--pdf-surface-raised)',
|
||||||
|
color: 'var(--pdf-text)',
|
||||||
borderRadius: '0.75rem',
|
borderRadius: '0.75rem',
|
||||||
boxShadow: '0 20px 40px rgba(15, 23, 42, 0.35)',
|
boxShadow: 'var(--pdf-dialog-shadow)',
|
||||||
padding: '1rem',
|
padding: '1rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@@ -95,8 +96,8 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
borderRadius: '999px',
|
borderRadius: '999px',
|
||||||
width: '1.8rem',
|
width: '1.8rem',
|
||||||
height: '1.8rem',
|
height: '1.8rem',
|
||||||
background: '#e5e7eb',
|
background: 'var(--pdf-control)',
|
||||||
color: '#111827',
|
color: 'var(--pdf-control-text)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: '1.1rem',
|
fontSize: '1.1rem',
|
||||||
lineHeight: 1,
|
lineHeight: 1,
|
||||||
@@ -114,7 +115,7 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
color: '#4b5563',
|
color: 'var(--pdf-muted)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy{' '}
|
Copy{' '}
|
||||||
@@ -145,7 +146,7 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
padding: '0.45rem 0.55rem',
|
padding: '0.45rem 0.55rem',
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
border: '1px solid #d1d5db',
|
border: '1px solid var(--pdf-border-strong)',
|
||||||
fontSize: '0.95rem',
|
fontSize: '0.95rem',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -154,7 +155,7 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
color: '#6b7280',
|
color: 'var(--pdf-muted-soft)',
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -166,9 +167,9 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
background: '#fef2f2',
|
background: 'var(--pdf-danger-surface)',
|
||||||
border: '1px solid #fecaca',
|
border: '1px solid var(--pdf-danger-border)',
|
||||||
color: '#b91c1c',
|
color: 'var(--pdf-danger-text)',
|
||||||
padding: '0.5rem',
|
padding: '0.5rem',
|
||||||
fontSize: '0.85rem',
|
fontSize: '0.85rem',
|
||||||
}}
|
}}
|
||||||
@@ -192,8 +193,8 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
padding: '0.45rem 0.8rem',
|
padding: '0.45rem 0.8rem',
|
||||||
background: '#e5e7eb',
|
background: 'var(--pdf-control)',
|
||||||
color: '#111827',
|
color: 'var(--pdf-control-text)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
}}
|
}}
|
||||||
@@ -207,8 +208,8 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
|
|||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
padding: '0.45rem 0.8rem',
|
padding: '0.45rem 0.8rem',
|
||||||
background: '#16a34a',
|
background: 'var(--pdf-success-text)',
|
||||||
color: 'white',
|
color: 'var(--pdf-on-primary)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const background = isDraggingCard
|
const background = isDraggingCard
|
||||||
? isCopyDragging
|
? isCopyDragging
|
||||||
? '#dcfce7'
|
? 'var(--pdf-success-soft)'
|
||||||
: '#dbeafe'
|
: 'var(--pdf-info-surface)'
|
||||||
: selected
|
: selected
|
||||||
? '#eff6ff'
|
? 'var(--pdf-info-surface)'
|
||||||
: '#f9fafb';
|
: 'var(--pdf-surface-soft)';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -71,7 +71,7 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
width: '162px',
|
width: '162px',
|
||||||
padding: '0.4rem',
|
padding: '0.4rem',
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
border: '1px solid #e5e7eb',
|
border: '1px solid var(--pdf-border)',
|
||||||
background,
|
background,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@@ -91,9 +91,11 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
width: '20px',
|
width: '20px',
|
||||||
height: '20px',
|
height: '20px',
|
||||||
borderRadius: '0.4rem',
|
borderRadius: '0.4rem',
|
||||||
border: '1px solid #9ca3af',
|
border: '1px solid var(--pdf-border-strong)',
|
||||||
background: selected ? '#2563eb' : 'rgba(255,255,255,0.9)',
|
background: selected
|
||||||
color: selected ? 'white' : 'transparent',
|
? 'var(--pdf-primary)'
|
||||||
|
: 'color-mix(in srgb, var(--pdf-surface) 92%, transparent)',
|
||||||
|
color: selected ? 'var(--pdf-on-primary)' : 'transparent',
|
||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -131,8 +133,8 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
height: 'auto',
|
height: 'auto',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
borderRadius: '0.25rem',
|
borderRadius: '0.25rem',
|
||||||
border: '1px solid #e5e7eb',
|
border: '1px solid var(--pdf-paper-border)',
|
||||||
background: 'white',
|
background: 'var(--pdf-paper)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -141,8 +143,8 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
width: '60px',
|
width: '60px',
|
||||||
height: '80px',
|
height: '80px',
|
||||||
borderRadius: '0.25rem',
|
borderRadius: '0.25rem',
|
||||||
border: '1px dashed #d1d5db',
|
border: '1px dashed var(--pdf-paper-border)',
|
||||||
background: '#f3f4f6',
|
background: 'var(--pdf-paper-placeholder)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -151,7 +153,7 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
<span style={{ fontSize: '0.8rem' }}>
|
<span style={{ fontSize: '0.8rem' }}>
|
||||||
Page {page.sourcePageIndex + 1}
|
Page {page.sourcePageIndex + 1}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: '0.7rem', color: '#6b7280' }}>
|
<span style={{ fontSize: '0.7rem', color: 'var(--pdf-muted-soft)' }}>
|
||||||
Pos {visualIndex + 1} · Rot {page.rotation}°
|
Pos {visualIndex + 1} · Rot {page.rotation}°
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -170,7 +172,8 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
...pageActionButtonStyle,
|
...pageActionButtonStyle,
|
||||||
background: '#e5e7eb',
|
background: 'var(--pdf-control)',
|
||||||
|
color: 'var(--pdf-control-text)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
↻ 90°
|
↻ 90°
|
||||||
@@ -184,7 +187,8 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
...pageActionButtonStyle,
|
...pageActionButtonStyle,
|
||||||
background: '#e5e7eb',
|
background: 'var(--pdf-control)',
|
||||||
|
color: 'var(--pdf-control-text)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
↺ 90°
|
↺ 90°
|
||||||
@@ -198,8 +202,8 @@ const PageCard: React.FC<PageCardProps> = ({
|
|||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
...pageActionButtonStyle,
|
...pageActionButtonStyle,
|
||||||
background: '#fecaca',
|
background: 'var(--pdf-danger-soft)',
|
||||||
color: '#b91c1c',
|
color: 'var(--pdf-danger-text)',
|
||||||
}}
|
}}
|
||||||
title="Remove this page from the exported PDF"
|
title="Remove this page from the exported PDF"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ const PageSelectionToolbar: React.FC<PageSelectionToolbarProps> = ({
|
|||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
style={{
|
style={{
|
||||||
...pillButtonStyle,
|
...pillButtonStyle,
|
||||||
background: '#dcfce7',
|
background: 'var(--pdf-success-soft)',
|
||||||
color: '#166534',
|
color: 'var(--pdf-success-text)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
title="Copy selected pages to another position"
|
title="Copy selected pages to another position"
|
||||||
@@ -69,8 +69,8 @@ const PageSelectionToolbar: React.FC<PageSelectionToolbarProps> = ({
|
|||||||
onClick={onDeleteSelected}
|
onClick={onDeleteSelected}
|
||||||
style={{
|
style={{
|
||||||
...pillButtonStyle,
|
...pillButtonStyle,
|
||||||
background: '#fee2e2',
|
background: 'var(--pdf-danger-soft)',
|
||||||
color: '#b91c1c',
|
color: 'var(--pdf-danger-text)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -83,8 +83,8 @@ const PageSelectionToolbar: React.FC<PageSelectionToolbarProps> = ({
|
|||||||
onClick={onSelectAll}
|
onClick={onSelectAll}
|
||||||
style={{
|
style={{
|
||||||
...pillButtonStyle,
|
...pillButtonStyle,
|
||||||
background: '#8dcd8d',
|
background: 'var(--pdf-success-border)',
|
||||||
color: '#111827',
|
color: 'var(--pdf-control-text)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -97,8 +97,10 @@ const PageSelectionToolbar: React.FC<PageSelectionToolbarProps> = ({
|
|||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
style={{
|
style={{
|
||||||
...pillButtonStyle,
|
...pillButtonStyle,
|
||||||
background: '#e5e7eb',
|
background: 'var(--pdf-control)',
|
||||||
color: hasSelection ? '#111827' : '#6b7280',
|
color: hasSelection
|
||||||
|
? 'var(--pdf-control-text)'
|
||||||
|
: 'var(--pdf-muted-soft)',
|
||||||
cursor: hasSelection ? 'pointer' : 'default',
|
cursor: hasSelection ? 'pointer' : 'default',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -279,13 +279,15 @@ const ReorderPanel: React.FC<ReorderPanelProps> = ({
|
|||||||
draggingPage != null &&
|
draggingPage != null &&
|
||||||
selectedPageIds.length > 0 &&
|
selectedPageIds.length > 0 &&
|
||||||
selectedPageIds.includes(draggingPage.id);
|
selectedPageIds.includes(draggingPage.id);
|
||||||
const dropIndicatorColor = isCopyDragging ? '#16a34a' : '#2563eb';
|
const dropIndicatorColor = isCopyDragging
|
||||||
|
? 'var(--pdf-success-text)'
|
||||||
|
: 'var(--pdf-primary)';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Pages</h2>
|
<h2>Pages</h2>
|
||||||
<p style={{ fontSize: '0.85rem', color: '#6b7280' }}>
|
<p style={{ fontSize: '0.85rem', color: 'var(--pdf-muted-soft)' }}>
|
||||||
Tap/click a page to preview it. Use the checkbox to select pages
|
Tap/click a page to preview it. Use the checkbox to select pages
|
||||||
(Shift for ranges). Drag to reorder; dragging a selected page moves
|
(Shift for ranges). Drag to reorder; dragging a selected page moves
|
||||||
the whole selection. Hold Ctrl/⌘ while dropping to copy instead of
|
the whole selection. Hold Ctrl/⌘ while dropping to copy instead of
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Workspace</h2>
|
<h2>Workspace</h2>
|
||||||
|
|
||||||
<p style={{ fontSize: '0.85rem', color: '#6b7280' }}>
|
<p style={{ fontSize: '0.85rem', color: 'var(--pdf-muted-soft)' }}>
|
||||||
Save named workspaces in this browser. PDF binaries are stored in
|
Save named workspaces in this browser. PDF binaries are stored in
|
||||||
IndexedDB; nothing is uploaded.
|
IndexedDB; nothing is uploaded.
|
||||||
</p>
|
</p>
|
||||||
@@ -78,7 +78,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
padding: '0.45rem 0.55rem',
|
padding: '0.45rem 0.55rem',
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
border: '1px solid #d1d5db',
|
border: '1px solid var(--pdf-border-strong)',
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -140,7 +140,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
marginTop: '0.5rem',
|
marginTop: '0.5rem',
|
||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
color: '#92400e',
|
color: 'var(--pdf-warning-text)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Unsaved workspace changes.
|
Unsaved workspace changes.
|
||||||
@@ -152,7 +152,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
marginTop: '0.5rem',
|
marginTop: '0.5rem',
|
||||||
fontSize: '0.85rem',
|
fontSize: '0.85rem',
|
||||||
color: '#166534',
|
color: 'var(--pdf-success-text)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{workspaceMessage}
|
{workspaceMessage}
|
||||||
@@ -178,10 +178,12 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
<div
|
<div
|
||||||
key={workspace.id}
|
key={workspace.id}
|
||||||
style={{
|
style={{
|
||||||
border: '1px solid #e5e7eb',
|
border: '1px solid var(--pdf-border)',
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.5rem',
|
||||||
padding: '0.5rem',
|
padding: '0.5rem',
|
||||||
background: active ? '#eff6ff' : '#f9fafb',
|
background: active
|
||||||
|
? 'var(--pdf-info-surface)'
|
||||||
|
: 'var(--pdf-surface-soft)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
gap: '0.75rem',
|
gap: '0.75rem',
|
||||||
@@ -193,11 +195,19 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
<div style={{ fontSize: '0.9rem' }}>
|
<div style={{ fontSize: '0.9rem' }}>
|
||||||
<strong>{workspace.name}</strong>
|
<strong>{workspace.name}</strong>
|
||||||
{active && (
|
{active && (
|
||||||
<span style={{ color: '#2563eb' }}> · active</span>
|
<span style={{ color: 'var(--pdf-primary)' }}>
|
||||||
|
{' '}
|
||||||
|
· active
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ fontSize: '0.75rem', color: '#6b7280' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
color: 'var(--pdf-muted-soft)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{workspace.pdfName} · source pages:{' '}
|
{workspace.pdfName} · source pages:{' '}
|
||||||
{workspace.sourcePageCount} · workspace pages:{' '}
|
{workspace.sourcePageCount} · workspace pages:{' '}
|
||||||
{workspace.workspacePageCount} · undo:{' '}
|
{workspace.workspacePageCount} · undo:{' '}
|
||||||
@@ -228,8 +238,8 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
onClick={() => onDeleteWorkspace(workspace.id)}
|
onClick={() => onDeleteWorkspace(workspace.id)}
|
||||||
style={{
|
style={{
|
||||||
background: '#fee2e2',
|
background: 'var(--pdf-danger-soft)',
|
||||||
color: '#991b1b',
|
color: 'var(--pdf-danger-text)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -261,8 +271,8 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
key={entry.id}
|
key={entry.id}
|
||||||
style={{
|
style={{
|
||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
color: '#374151',
|
color: 'var(--pdf-muted)',
|
||||||
borderLeft: '3px solid #2563eb',
|
borderLeft: '3px solid var(--pdf-primary)',
|
||||||
paddingLeft: '0.45rem',
|
paddingLeft: '0.45rem',
|
||||||
paddingTop: '0.2rem',
|
paddingTop: '0.2rem',
|
||||||
paddingBottom: '0.2rem',
|
paddingBottom: '0.2rem',
|
||||||
@@ -272,7 +282,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
Undo {history.length - index}. {entry.label}
|
Undo {history.length - index}. {entry.label}
|
||||||
</strong>
|
</strong>
|
||||||
<br />
|
<br />
|
||||||
<span style={{ color: '#6b7280' }}>
|
<span style={{ color: 'var(--pdf-muted-soft)' }}>
|
||||||
{new Date(entry.timestamp).toLocaleString()}
|
{new Date(entry.timestamp).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -282,12 +292,12 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
margin: '0.25rem 0',
|
margin: '0.25rem 0',
|
||||||
borderRadius: '999px',
|
borderRadius: '999px',
|
||||||
background: '#ecfdf5',
|
background: 'var(--pdf-success-surface)',
|
||||||
color: '#166534',
|
color: 'var(--pdf-success-text)',
|
||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
border: '2px solid #166534',
|
border: '2px solid var(--pdf-success-text)',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
@@ -300,8 +310,8 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
key={entry.id}
|
key={entry.id}
|
||||||
style={{
|
style={{
|
||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
color: '#9ca3af',
|
color: 'var(--pdf-muted-faint)',
|
||||||
borderLeft: '3px solid #d1d5db',
|
borderLeft: '3px solid var(--pdf-border-strong)',
|
||||||
paddingLeft: '0.45rem',
|
paddingLeft: '0.45rem',
|
||||||
paddingTop: '0.2rem',
|
paddingTop: '0.2rem',
|
||||||
paddingBottom: '0.2rem',
|
paddingBottom: '0.2rem',
|
||||||
@@ -312,7 +322,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
|
|||||||
Redo {index + 1}. {entry.label}
|
Redo {index + 1}. {entry.label}
|
||||||
</strong>
|
</strong>
|
||||||
<br />
|
<br />
|
||||||
<span style={{ color: '#9ca3af' }}>
|
<span style={{ color: 'var(--pdf-muted-faint)' }}>
|
||||||
{new Date(entry.timestamp).toLocaleString()}
|
{new Date(entry.timestamp).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
316
src/styles.css
316
src/styles.css
@@ -12,89 +12,30 @@ body {
|
|||||||
BlinkMacSystemFont,
|
BlinkMacSystemFont,
|
||||||
'Segoe UI',
|
'Segoe UI',
|
||||||
sans-serif;
|
sans-serif;
|
||||||
background-color: #f3f4f6;
|
background-color: var(--toolbox-background);
|
||||||
color: #111827;
|
color: var(--toolbox-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-shell {
|
.pdf-toolbox-shell .toolbox-shell__main .card {
|
||||||
display: flex;
|
border: 1px solid var(--pdf-border);
|
||||||
min-height: 100vh;
|
background: var(--pdf-surface);
|
||||||
}
|
|
||||||
|
|
||||||
.app-sidebar {
|
|
||||||
width: 260px;
|
|
||||||
background: #111827;
|
|
||||||
color: #e5e7eb;
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-sidebar h1 {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-sidebar small {
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-nav {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-nav button {
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
padding: 0.4rem 0.6rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: #d1d5db;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-nav button.active {
|
|
||||||
background: #374151;
|
|
||||||
color: #f9fafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-nav button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-main {
|
|
||||||
flex: 1;
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: white;
|
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12);
|
box-shadow: var(--pdf-card-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card h2 {
|
.pdf-toolbox-shell .toolbox-shell__main .card h2 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.primary {
|
.pdf-toolbox-shell .toolbox-shell__main button.primary {
|
||||||
background: #2563eb;
|
background: var(--pdf-primary);
|
||||||
color: white;
|
color: var(--pdf-on-primary);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding: 0.45rem 0.9rem;
|
padding: 0.45rem 0.9rem;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -102,14 +43,14 @@ button.primary {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.primary:disabled {
|
.pdf-toolbox-shell .toolbox-shell__main button.primary:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.secondary {
|
.pdf-toolbox-shell .toolbox-shell__main button.secondary {
|
||||||
background: #e5e7eb;
|
background: var(--pdf-control);
|
||||||
color: #111827;
|
color: var(--pdf-control-text);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding: 0.45rem 0.9rem;
|
padding: 0.45rem 0.9rem;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -117,7 +58,7 @@ button.secondary {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-row {
|
.pdf-toolbox-shell .toolbox-shell__main .button-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@@ -125,64 +66,167 @@ button.secondary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pdf-toolbox-shell {
|
.pdf-toolbox-shell {
|
||||||
color-scheme: light;
|
--pdf-surface: var(--toolbox-surface);
|
||||||
--toolbox-background: #f3f4f6;
|
--pdf-surface-soft: #f9fafb;
|
||||||
--toolbox-surface: #ffffff;
|
--pdf-surface-raised: #ffffff;
|
||||||
--toolbox-text: #111827;
|
--pdf-text: var(--toolbox-text);
|
||||||
--toolbox-muted: #4b5563;
|
--pdf-muted: #4b5563;
|
||||||
--toolbox-border: #d1d5db;
|
--pdf-muted-soft: #6b7280;
|
||||||
--toolbox-accent: #2563eb;
|
--pdf-muted-faint: #9ca3af;
|
||||||
--toolbox-accent-contrast: #ffffff;
|
--pdf-border: #e5e7eb;
|
||||||
|
--pdf-border-strong: #d1d5db;
|
||||||
|
--pdf-primary: #2563eb;
|
||||||
|
--pdf-on-primary: #ffffff;
|
||||||
|
--pdf-control: #e5e7eb;
|
||||||
|
--pdf-control-text: #111827;
|
||||||
|
--pdf-info-surface: #eff6ff;
|
||||||
|
--pdf-info-border: #bfdbfe;
|
||||||
|
--pdf-info-text: #1e3a8a;
|
||||||
|
--pdf-success-surface: #ecfdf5;
|
||||||
|
--pdf-success-soft: #dcfce7;
|
||||||
|
--pdf-success-border: #86efac;
|
||||||
|
--pdf-success-text: #166534;
|
||||||
|
--pdf-warning-surface: #fff7ed;
|
||||||
|
--pdf-warning-border: #fed7aa;
|
||||||
|
--pdf-warning-text: #9a3412;
|
||||||
|
--pdf-danger-surface: #fef2f2;
|
||||||
|
--pdf-danger-soft: #fee2e2;
|
||||||
|
--pdf-danger-border: #fecaca;
|
||||||
|
--pdf-danger-text: #b91c1c;
|
||||||
|
--pdf-overlay: rgba(15, 23, 42, 0.62);
|
||||||
|
--pdf-dialog-shadow: 0 20px 40px rgba(15, 23, 42, 0.35);
|
||||||
|
--pdf-card-shadow: 0 1px 3px rgba(15, 23, 42, 0.12);
|
||||||
|
--pdf-paper: #ffffff;
|
||||||
|
--pdf-paper-placeholder: #f3f4f6;
|
||||||
|
--pdf-paper-border: #d1d5db;
|
||||||
|
--pdf-paper-text: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-toolbox-shell[data-toolbox-theme='dark'] {
|
||||||
|
--pdf-surface-soft: #20283a;
|
||||||
|
--pdf-surface-raised: #1b2334;
|
||||||
|
--pdf-muted: #b6bfd2;
|
||||||
|
--pdf-muted-soft: #a9b3ca;
|
||||||
|
--pdf-muted-faint: #8792aa;
|
||||||
|
--pdf-border: #303a50;
|
||||||
|
--pdf-border-strong: #465169;
|
||||||
|
--pdf-primary: #8798f5;
|
||||||
|
--pdf-on-primary: #101728;
|
||||||
|
--pdf-control: #293247;
|
||||||
|
--pdf-control-text: #edf1fb;
|
||||||
|
--pdf-info-surface: #182946;
|
||||||
|
--pdf-info-border: #345a8c;
|
||||||
|
--pdf-info-text: #c6dcff;
|
||||||
|
--pdf-success-surface: #12382e;
|
||||||
|
--pdf-success-soft: #174436;
|
||||||
|
--pdf-success-border: #33765d;
|
||||||
|
--pdf-success-text: #a7e8cf;
|
||||||
|
--pdf-warning-surface: #402814;
|
||||||
|
--pdf-warning-border: #80522d;
|
||||||
|
--pdf-warning-text: #ffd2a6;
|
||||||
|
--pdf-danger-surface: #421f29;
|
||||||
|
--pdf-danger-soft: #4f2530;
|
||||||
|
--pdf-danger-border: #81394b;
|
||||||
|
--pdf-danger-text: #ffc0cc;
|
||||||
|
--pdf-overlay: rgba(2, 6, 16, 0.76);
|
||||||
|
--pdf-dialog-shadow: 0 24px 52px rgba(0, 0, 0, 0.5);
|
||||||
|
--pdf-card-shadow: 0 2px 7px rgba(0, 0, 0, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.pdf-toolbox-shell[data-toolbox-theme='system'] {
|
||||||
|
--pdf-surface-soft: #20283a;
|
||||||
|
--pdf-surface-raised: #1b2334;
|
||||||
|
--pdf-muted: #b6bfd2;
|
||||||
|
--pdf-muted-soft: #a9b3ca;
|
||||||
|
--pdf-muted-faint: #8792aa;
|
||||||
|
--pdf-border: #303a50;
|
||||||
|
--pdf-border-strong: #465169;
|
||||||
|
--pdf-primary: #8798f5;
|
||||||
|
--pdf-on-primary: #101728;
|
||||||
|
--pdf-control: #293247;
|
||||||
|
--pdf-control-text: #edf1fb;
|
||||||
|
--pdf-info-surface: #182946;
|
||||||
|
--pdf-info-border: #345a8c;
|
||||||
|
--pdf-info-text: #c6dcff;
|
||||||
|
--pdf-success-surface: #12382e;
|
||||||
|
--pdf-success-soft: #174436;
|
||||||
|
--pdf-success-border: #33765d;
|
||||||
|
--pdf-success-text: #a7e8cf;
|
||||||
|
--pdf-warning-surface: #402814;
|
||||||
|
--pdf-warning-border: #80522d;
|
||||||
|
--pdf-warning-text: #ffd2a6;
|
||||||
|
--pdf-danger-surface: #421f29;
|
||||||
|
--pdf-danger-soft: #4f2530;
|
||||||
|
--pdf-danger-border: #81394b;
|
||||||
|
--pdf-danger-text: #ffc0cc;
|
||||||
|
--pdf-overlay: rgba(2, 6, 16, 0.76);
|
||||||
|
--pdf-dialog-shadow: 0 24px 52px rgba(0, 0, 0, 0.5);
|
||||||
|
--pdf-card-shadow: 0 2px 7px rgba(0, 0, 0, 0.26);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pdf-toolbox-shell .toolbox-shell__main {
|
.pdf-toolbox-shell .toolbox-shell__main {
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
max-width: 900px;
|
max-width: 90rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make cards full-width on mobile */
|
.pdf-toolbox-shell .toolbox-shell__main input,
|
||||||
.card {
|
.pdf-toolbox-shell .toolbox-shell__main select,
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main textarea {
|
||||||
|
border-color: var(--pdf-border-strong);
|
||||||
|
background: var(--pdf-surface);
|
||||||
|
color: var(--pdf-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main input::placeholder,
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main textarea::placeholder {
|
||||||
|
color: var(--pdf-muted-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main a {
|
||||||
|
color: var(--toolbox-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main a:hover {
|
||||||
|
color: var(--toolbox-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main button:disabled {
|
||||||
|
opacity: 0.58;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main button:focus-visible,
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main a:focus-visible,
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main input:focus-visible,
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main select:focus-visible {
|
||||||
|
outline: 3px solid color-mix(in srgb, var(--toolbox-focus) 72%, transparent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-toolbox-shell .toolbox-shell__main .card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-link {
|
.pdf-toolbox-shell .toolbox-shell__main .download-link {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0.15rem 0;
|
margin: 0.15rem 0;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card hr {
|
.pdf-toolbox-shell .toolbox-shell__main .card hr {
|
||||||
border: none;
|
border: none;
|
||||||
border-top: 1px solid #e5e7eb;
|
border-top: 1px solid var(--pdf-border);
|
||||||
}
|
|
||||||
|
|
||||||
.app-help-button {
|
|
||||||
border: 1px solid var(--toolbox-border);
|
|
||||||
border-radius: 999px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--toolbox-accent);
|
|
||||||
padding: 0.35rem 0.65rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-help-button:hover,
|
|
||||||
.app-help-button:focus-visible {
|
|
||||||
background: color-mix(in srgb, var(--toolbox-accent) 10%, transparent);
|
|
||||||
outline: 2px solid var(--toolbox-accent);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-dialog-backdrop {
|
.help-dialog-backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 80;
|
z-index: 80;
|
||||||
background: rgba(15, 23, 42, 0.65);
|
background: var(--pdf-overlay);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -193,9 +237,9 @@ button.secondary {
|
|||||||
width: min(920px, 100%);
|
width: min(920px, 100%);
|
||||||
max-height: min(88vh, 760px);
|
max-height: min(88vh, 760px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: #ffffff;
|
background: var(--pdf-surface-raised);
|
||||||
border-radius: 0.9rem;
|
border-radius: 0.9rem;
|
||||||
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.4);
|
box-shadow: var(--pdf-dialog-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-dialog-header {
|
.help-dialog-header {
|
||||||
@@ -207,8 +251,8 @@ button.secondary {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #ffffff;
|
background: var(--pdf-surface-raised);
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid var(--pdf-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-dialog-header h2 {
|
.help-dialog-header h2 {
|
||||||
@@ -218,7 +262,7 @@ button.secondary {
|
|||||||
|
|
||||||
.help-dialog-header p {
|
.help-dialog-header p {
|
||||||
margin: 0.35rem 0 0;
|
margin: 0.35rem 0 0;
|
||||||
color: #4b5563;
|
color: var(--pdf-muted);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
@@ -229,8 +273,8 @@ button.secondary {
|
|||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
background: #e5e7eb;
|
background: var(--pdf-control);
|
||||||
color: #111827;
|
color: var(--pdf-control-text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -244,10 +288,10 @@ button.secondary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.help-section {
|
.help-section {
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--pdf-border);
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
padding: 0.9rem;
|
padding: 0.9rem;
|
||||||
background: #f9fafb;
|
background: var(--pdf-surface-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-section h3 {
|
.help-section h3 {
|
||||||
@@ -263,8 +307,8 @@ button.secondary {
|
|||||||
|
|
||||||
.help-step {
|
.help-step {
|
||||||
border-radius: 0.65rem;
|
border-radius: 0.65rem;
|
||||||
background: #ffffff;
|
background: var(--pdf-surface);
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--pdf-border);
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +320,7 @@ button.secondary {
|
|||||||
.help-step p,
|
.help-step p,
|
||||||
.help-note {
|
.help-note {
|
||||||
margin: 0.35rem 0 0;
|
margin: 0.35rem 0 0;
|
||||||
color: #4b5563;
|
color: var(--pdf-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
@@ -292,18 +336,18 @@ button.secondary {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid var(--pdf-border-strong);
|
||||||
background: #ffffff;
|
background: var(--pdf-surface);
|
||||||
padding: 0.2rem 0.45rem;
|
padding: 0.2rem 0.45rem;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
color: #111827;
|
color: var(--pdf-text);
|
||||||
box-shadow: inset 0 -1px 0 #d1d5db;
|
box-shadow: inset 0 -1px 0 var(--pdf-border-strong);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shortcut-grid span {
|
.shortcut-grid span {
|
||||||
color: #4b5563;
|
color: var(--pdf-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
@@ -320,8 +364,8 @@ button.secondary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.help-concepts dl > div {
|
.help-concepts dl > div {
|
||||||
background: #ffffff;
|
background: var(--pdf-surface);
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--pdf-border);
|
||||||
border-radius: 0.65rem;
|
border-radius: 0.65rem;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
@@ -334,7 +378,7 @@ button.secondary {
|
|||||||
|
|
||||||
.help-concepts dd {
|
.help-concepts dd {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #4b5563;
|
color: var(--pdf-muted);
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
@@ -358,16 +402,16 @@ button.secondary {
|
|||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
border: 1px solid #bfdbfe;
|
border: 1px solid var(--pdf-info-border);
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: color-mix(in srgb, var(--pdf-surface) 86%, transparent);
|
||||||
padding: 0.6rem;
|
padding: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.merge-queue-order {
|
.merge-queue-order {
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: #dbeafe;
|
background: var(--pdf-info-surface);
|
||||||
color: #1e3a8a;
|
color: var(--pdf-info-text);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
padding: 0.2rem 0.5rem;
|
padding: 0.2rem 0.5rem;
|
||||||
@@ -386,7 +430,7 @@ button.secondary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.merge-queue-details span {
|
.merge-queue-details span {
|
||||||
color: #4b5563;
|
color: var(--pdf-muted);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,10 +462,10 @@ button.secondary {
|
|||||||
|
|
||||||
.merge-warning {
|
.merge-warning {
|
||||||
margin: 0.75rem 0 0;
|
margin: 0.75rem 0 0;
|
||||||
border: 1px solid #fed7aa;
|
border: 1px solid var(--pdf-warning-border);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
background: #fff7ed;
|
background: var(--pdf-warning-surface);
|
||||||
color: #9a3412;
|
color: var(--pdf-warning-text);
|
||||||
padding: 0.55rem 0.65rem;
|
padding: 0.55rem 0.65rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,6 @@
|
|||||||
"icon": "./favicon.svg",
|
"icon": "./favicon.svg",
|
||||||
"categories": ["documents", "pdf"],
|
"categories": ["documents", "pdf"],
|
||||||
"tags": ["merge", "split", "rotate", "reorder"],
|
"tags": ["merge", "split", "rotate", "reorder"],
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"id": "source",
|
|
||||||
"label": "Source",
|
|
||||||
"url": "https://git.add-ideas.de/zemion/pdf-tools/src/tag/v0.3.4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"integration": {
|
"integration": {
|
||||||
"contextVersion": 1,
|
"contextVersion": 1,
|
||||||
"launchModes": ["navigate", "new-tab"],
|
"launchModes": ["navigate", "new-tab"],
|
||||||
@@ -30,7 +23,7 @@
|
|||||||
"telemetry": false
|
"telemetry": false
|
||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"repository": "https://git.add-ideas.de/zemion/pdf-tools",
|
"repository": "https://git.add-ideas.de/lotobo/pdf-tools",
|
||||||
"license": "AGPL-3.0-only"
|
"license": "AGPL-3.0-only"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,11 @@ describe('toolbox app definition', () => {
|
|||||||
it('matches the generated public manifest', () => {
|
it('matches the generated public manifest', () => {
|
||||||
expect(toolboxApp).toEqual(parseToolboxApp(generatedManifest));
|
expect(toolboxApp).toEqual(parseToolboxApp(generatedManifest));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses repository metadata for the shared source control', () => {
|
||||||
|
expect(toolboxApp.source?.repository).toBe(
|
||||||
|
'https://git.add-ideas.de/lotobo/pdf-tools'
|
||||||
|
);
|
||||||
|
expect(toolboxApp.actions).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export const APP_VERSION = '0.3.4';
|
export const APP_VERSION = '0.4.4';
|
||||||
|
|||||||
13
tests/layoutStyles.test.ts
Normal file
13
tests/layoutStyles.test.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
describe('Toolbox layout styles', () => {
|
||||||
|
it('keeps the PDF workspace aligned to the 90rem shell width', async () => {
|
||||||
|
const stylesheet = await readFile('src/styles.css', 'utf8');
|
||||||
|
const mainRule = stylesheet.match(
|
||||||
|
/\.pdf-toolbox-shell \.toolbox-shell__main\s*\{(?<body>[^}]*)\}/u
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mainRule?.groups?.body).toMatch(/\bmax-width:\s*90rem\s*;/u);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user