mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
473a59b134
@ -99,13 +99,18 @@ query is needed.
|
||||
|
||||
**Breaking changes override everything:** an issue with the `breaking change`
|
||||
label goes under `### :boom: Breaking changes & Deprecations`, no matter its
|
||||
issue type. This is the only label-based rule — it is an explicit exception
|
||||
to the "never by labels" principle above. The `:boom:` subsection goes first
|
||||
in the version, before `:rocket:` (matching the existing precedent).
|
||||
issue type. This and `release highlight` below are the only label-based
|
||||
rules — explicit exceptions to the "never by labels" principle above. The
|
||||
`:boom:` subsection goes first in the version, before `:rocket:` (matching
|
||||
the existing precedent).
|
||||
|
||||
**Preserve highlighted entries:** if an entry already sits under
|
||||
`### :rocket: Epics and highlights`, keep it there when refreshing. Do not
|
||||
move it down just because its type would place it under `:sparkles:`.
|
||||
**Highlights come from the `release highlight` label:** an issue with that
|
||||
label also gets an entry under `### :rocket: Epics and highlights`, on top of
|
||||
its regular entry (usually `:sparkles:`). In the section being refreshed,
|
||||
`:rocket:` lists exactly the labelled issues: never pick highlights yourself
|
||||
and never keep an unlabelled entry there. Product decides the highlights by
|
||||
labelling issues on GitHub, so a regeneration cannot undo that choice. Older
|
||||
version sections are left as they are.
|
||||
|
||||
**Community attribution:** if the issue or its fix PR has the
|
||||
`community contribution` label, add `(by @<github_username>)` on the entry
|
||||
@ -207,7 +212,10 @@ not trust cached data):
|
||||
6. **Issue type changed to `Task`** → remove the entry.
|
||||
7. **Breaking change misplaced** (issue has the `breaking change` label but
|
||||
sits in another section) → move the entry to `:boom:`.
|
||||
8. **Missing valid issues** (closed, non-excluded, unreferenced anywhere) →
|
||||
8. **Highlight out of sync** (`:rocket:` entry without the `release
|
||||
highlight` label, or a labelled issue missing from `:rocket:`) → remove
|
||||
the `:rocket:` entry or add it (step 7b). The regular entry stays.
|
||||
9. **Missing valid issues** (closed, non-excluded, unreferenced anywhere) →
|
||||
add them to the current section per step 5.
|
||||
|
||||
### 7. Build the description text
|
||||
@ -225,14 +233,22 @@ behavior:
|
||||
Insert the new version section right after the `# CHANGELOG` header with the
|
||||
`edit` tool and enough context for a unique match.
|
||||
|
||||
### 7b. Propose and populate `:rocket: Epics and highlights`
|
||||
### 7b. Populate `:rocket: Epics and highlights` from the label
|
||||
|
||||
Create the subsection if missing (place it before `### :sparkles:`) and pick
|
||||
2–5 of the most impactful/user-visible `:sparkles:` entries: new visible
|
||||
features, big capabilities, items that make self-hosted users want to update.
|
||||
`frontend/src/app/main/ui/releases/v2_<MINOR>.cljs` slide titles are optional
|
||||
hints (they may not exist for every version). Every `:rocket:` entry MUST
|
||||
carry issue AND PR references; never remove entries from a prior run.
|
||||
List the milestone issues that pass step 2 exclusions and carry the
|
||||
`release highlight` label:
|
||||
|
||||
```bash
|
||||
python3 scripts/gh.py issues "2.16.0" --exclude "release blocker,no changelog" \
|
||||
| python3 -c "import sys,json; [print(i['number'], i['title']) for i in json.load(sys.stdin) if 'release highlight' in i['labels']]"
|
||||
```
|
||||
|
||||
Create the subsection if missing (place it before `### :sparkles:`) with one
|
||||
entry per labelled issue, same text and references as its regular entry.
|
||||
Every `:rocket:` entry MUST carry issue AND PR references. If no issue is
|
||||
labelled, leave `:rocket:` out and tell the user: the highlights are
|
||||
product's call, not the agent's. To change them, add or remove the label on
|
||||
GitHub and re-run; never edit `:rocket:` by hand.
|
||||
|
||||
### 8. Cross-reference milestone PRs against the changelog
|
||||
|
||||
@ -264,6 +280,8 @@ pairing is misleading and a human must judge intent):
|
||||
3. `:boom:` entry whose issue lacks the `breaking change` label. These stay
|
||||
listed in the report **and** in the changelog: either label the issue or
|
||||
move the entry to its regular section.
|
||||
4. `:rocket:` out of sync with the `release highlight` label. Step 6
|
||||
pre-flight should have fixed it: if it shows up, re-run the workflow.
|
||||
|
||||
**Highlight gaps are warnings, not anomalies:** missing `:rocket:` on a
|
||||
released X.Y.0 (patches never carry highlights); `:rocket:` entry without
|
||||
@ -277,7 +295,7 @@ pre-flight. If one shows up in the report, re-run the workflow.
|
||||
- **Issue = changelog unit**, PR = implementation detail inline.
|
||||
- **Latest version first**, below the `# CHANGELOG` header.
|
||||
- **Issue Type decides the section — exclusively**, except `breaking change`
|
||||
label → `:boom:` first.
|
||||
label → `:boom:` first, and `release highlight` label → also `:rocket:`.
|
||||
- **User-facing descriptions** in imperative mood.
|
||||
- **Community attribution** uses the PR author, placed before the issue link.
|
||||
- **Only closed issues**; **Rejected** project status excludes.
|
||||
|
||||
@ -6,7 +6,7 @@ JVM `clojure.test` (kaocha runner) under `backend/test/backend_tests/`.
|
||||
- All CLI commands must be executed from the `backend/` subdirectory.
|
||||
- Tests are invoked directly via `clojure -M:dev:test` (kaocha) — there is no pnpm wrapper. Kaocha auto-discovers test namespaces, so no runner registration is needed.
|
||||
- **Preferred way to run them: `scripts/ci <module> --test`**, which runs lint, tests and format per module and writes a log per task under `.ci-logs/`. It exports `PENPOT_FLAGS` with `enable-backend-asserts` appended, so use it rather than calling `clojure -M:dev:test` by hand.
|
||||
- **Why the flag matters:** `clojure.core/assert` is elided at compile time when `*assert*` is false, and `app.config` only sets that var from the flags when it loads. The test fixture redefines the flags after that, so they never reach it: without the flag the suite runs with every assertion compiled out, and a test that depends on an assert only means something with it set. GitHub Actions sets it too. When running the suite directly, the command is `PENPOT_FLAGS="enable-backend-asserts" clojure -M:dev:test`.
|
||||
- **Why the flag matters:** `clojure.core/assert` is elided at compile time when `*assert*` is false, and `app.config` only sets that var from the flags when it loads. The test fixture redefines the flags after that, so they never reach it: without the flag the suite runs with every assertion compiled out, and a test that depends on an assert only means something with it set. `scripts/ci` and `tests-backend.yml` export the flag (GitHub Actions sets it too). When running the suite directly — including a `--focus` run — the command is `PENPOT_FLAGS="enable-backend-asserts" clojure -M:dev:test`.
|
||||
- Coverage: if code is added or modified in `src/`, corresponding tests in `test/backend_tests/` must be added or updated.
|
||||
- Isolated run: `clojure -M:dev:test --focus backend-tests.my-ns-test` for a specific test namespace, or `clojure -M:dev:test --focus backend-tests.my-ns-test/my-test-var` for a specific test var.
|
||||
- Multiple focus symbols are supported by REPEATING the flag (kaocha accumulates them, last-wins does not apply): `clojure -M:dev:test --focus backend-tests.ns-one --focus backend-tests.ns-two` — preferred over a shell loop of isolated runs.
|
||||
|
||||
@ -61,6 +61,9 @@ Flags:
|
||||
|
||||
- `mcp` has no lint task (shows as skipped). `render-wasm` uses `./lint`,
|
||||
`./test`, and `cargo fmt`.
|
||||
- Exports `PENPOT_FLAGS` with `enable-backend-asserts` appended to any
|
||||
existing value, so backend test runs compile in and exercise
|
||||
precondition asserts; see `mem:backend/testing`.
|
||||
- Test tasks are long-running (backend: `clojure -M:dev:test`); use a
|
||||
generous timeout when calling it from an agent shell.
|
||||
- Skill entry point: `.agents/skills/local-ci/SKILL.md`.
|
||||
|
||||
92
CHANGES.md
92
CHANGES.md
@ -6,12 +6,12 @@
|
||||
|
||||
- Add configurable keyboard shortcuts [#9924](https://github.com/penpot/penpot/issues/9924) (PR: [#10237](https://github.com/penpot/penpot/pull/10237))
|
||||
- Improve path operations and edition in the path editor [#10889](https://github.com/penpot/penpot/issues/10889) (PR: [#10807](https://github.com/penpot/penpot/pull/10807))
|
||||
- Add auto-linking of libraries during import based on slugified name [#9263](https://github.com/penpot/penpot/issues/9263) (PR: [#9958](https://github.com/penpot/penpot/pull/9958))
|
||||
- Add individual stroke widths per side (top, right, bottom, left) [#9854](https://github.com/penpot/penpot/issues/9854) (PR: [#10648](https://github.com/penpot/penpot/pull/10648))
|
||||
- Add support for internal libraries and file sync for Design Tokens [#9334](https://github.com/penpot/penpot/issues/9334) (PR: [#10293](https://github.com/penpot/penpot/pull/10293))
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix copying text from Penpot to the clipboard not working on MS Windows [#11303](https://github.com/penpot/penpot/issues/11303) (PR: [#11305](https://github.com/penpot/penpot/pull/11305))
|
||||
- Fix performance issue with WebGL render [#11240](https://github.com/penpot/penpot/issues/11240) (PR: [#11259](https://github.com/penpot/penpot/pull/11259))
|
||||
- Fix comment bubbles rendering on top of workspace dropdown menus [#10283](https://github.com/penpot/penpot/issues/10283) (PR: [#11201](https://github.com/penpot/penpot/pull/11201))
|
||||
- Fix inconsistent Mixed label in blur options and numeric inputs across 24 locales (by @filipsajdak) [#11148](https://github.com/penpot/penpot/issues/11148) (PR: [#11151](https://github.com/penpot/penpot/pull/11151))
|
||||
- Fix overlay shifting left when shown with top-center alignment in viewer prototype (by @filipsajdak) [#9048](https://github.com/penpot/penpot/issues/9048) (PR: [#10454](https://github.com/penpot/penpot/pull/10454))
|
||||
@ -21,31 +21,105 @@
|
||||
- Fix plugin postMessage channel broadcasting messages to all plugins without origin validation [#10968](https://github.com/penpot/penpot/issues/10968) (PR: [#10970](https://github.com/penpot/penpot/pull/10970))
|
||||
- Fix MCP plugin page navigation while connected crashing the workspace (by @makesomethingshit) [#11001](https://github.com/penpot/penpot/issues/11001) (PR: [#11521](https://github.com/penpot/penpot/pull/11521))
|
||||
- Fix shortcut search never matching on key combination, only on action label [#11003](https://github.com/penpot/penpot/issues/11003) (PR: [#11081](https://github.com/penpot/penpot/pull/11081))
|
||||
- Fix Shift + special character key shortcut capturing the shifted character instead of the physical key [#11004](https://github.com/penpot/penpot/issues/11004) (PR: [#11081](https://github.com/penpot/penpot/pull/11081))
|
||||
- Fix Shift + special character key shortcut capturing the shifted character instead of the physical key [#11004](https://github.com/penpot/penpot/issues/11004) (PR: [#11081](https://github.com/penpot/penpot/pull/11081), [#11669](https://github.com/penpot/penpot/pull/11669))
|
||||
- Fix reassigning the "Paste" shortcut not updating the UI or taking effect in the workspace [#11005](https://github.com/penpot/penpot/issues/11005) (PR: [#11081](https://github.com/penpot/penpot/pull/11081))
|
||||
- Fix font-size dropdown clipping multi-digit values in Firefox (by @0xTHAC0) [#11008](https://github.com/penpot/penpot/issues/11008) (PR: [#11162](https://github.com/penpot/penpot/pull/11162), [#11500](https://github.com/penpot/penpot/pull/11500))
|
||||
- Fix exporting shortcuts producing an invalid "toggle-fullscreen" entry that breaks re-import [#11032](https://github.com/penpot/penpot/issues/11032) (PR: [#11081](https://github.com/penpot/penpot/pull/11081))
|
||||
- Fix plugin API missing permission checks in tokens, shapes, variants, flows, layouts, and user identity [#11137](https://github.com/penpot/penpot/issues/11137) (PR: [#11139](https://github.com/penpot/penpot/pull/11139))
|
||||
- Fix library summary Redis cache keys omitting the tenant [#11407](https://github.com/penpot/penpot/issues/11407) (PR: [#11408](https://github.com/penpot/penpot/pull/11408))
|
||||
- Fix active theme name in the inspect tab displaying an id instead of the name [#11437](https://github.com/penpot/penpot/issues/11437) (PR: [#11439](https://github.com/penpot/penpot/pull/11439))
|
||||
- Fix triple-click not selecting the full line in text editor v3 [#11483](https://github.com/penpot/penpot/issues/11483) (PR: [#11493](https://github.com/penpot/penpot/pull/11493))
|
||||
- Fix pasted text losing formatting on last lines after resizing and adding new lines from the top [#11501](https://github.com/penpot/penpot/issues/11501) (PR: [#11503](https://github.com/penpot/penpot/pull/11503))
|
||||
- Fix variant property dropdown appearing empty and throwing an internal error when the component has no sibling variants [#11524](https://github.com/penpot/penpot/issues/11524) (PR: [#11499](https://github.com/penpot/penpot/pull/11499))
|
||||
- Fix invitation links not allowing registration when `disable-registration` is enabled [#5178](https://github.com/penpot/penpot/issues/5178) (PR: [#11912](https://github.com/penpot/penpot/pull/11912))
|
||||
- Fix Windows build failures caused by path resolution and start command conflicts (by @0xTHAC0) [#8637](https://github.com/penpot/penpot/issues/8637) (PR: [#11197](https://github.com/penpot/penpot/pull/11197))
|
||||
- Fix editing a path on an image, rectangle or ellipse corrupting the shape and failing backend validation [#10621](https://github.com/penpot/penpot/issues/10621) (PR: [#10807](https://github.com/penpot/penpot/pull/10807))
|
||||
- Fix board content disappearing when dragging a board near another board [#10689](https://github.com/penpot/penpot/issues/10689) (PR: [#11620](https://github.com/penpot/penpot/pull/11620))
|
||||
- Fix dashboard context menu being cut off for long lists, hiding the bottom options [#10724](https://github.com/penpot/penpot/issues/10724) (PR: [#11511](https://github.com/penpot/penpot/pull/11511))
|
||||
- Fix moving an element changing its size [#10751](https://github.com/penpot/penpot/issues/10751) (PR: [#11560](https://github.com/penpot/penpot/pull/11560))
|
||||
- Fix remote MCP websocket connecting but never receiving live browser commands [#10753](https://github.com/penpot/penpot/issues/10753)
|
||||
- Fix stroke to path not working with path ends (by @ShreyashAgare26) [#10825](https://github.com/penpot/penpot/issues/10825) (PR: [#11614](https://github.com/penpot/penpot/pull/11614))
|
||||
- Fix border style not working with stroke per side [#10841](https://github.com/penpot/penpot/issues/10841) (PR: [#11883](https://github.com/penpot/penpot/pull/11883))
|
||||
- Fix multiple selection not showing stroke per side when all shapes share the same per side values [#10844](https://github.com/penpot/penpot/issues/10844) (PR: [#10913](https://github.com/penpot/penpot/pull/10913))
|
||||
- Fix stroke width tokens not overriding stroke per side values [#10850](https://github.com/penpot/penpot/issues/10850) (PR: [#10913](https://github.com/penpot/penpot/pull/10913))
|
||||
- Fix internal error when applying a dimension token to stroke width [#10918](https://github.com/penpot/penpot/issues/10918) (PR: [#10913](https://github.com/penpot/penpot/pull/10913))
|
||||
- Fix stroke per side button detaching the applied token [#10920](https://github.com/penpot/penpot/issues/10920) (PR: [#10913](https://github.com/penpot/penpot/pull/10913))
|
||||
- Fix WebSocket subscription handlers missing authorization checks [#11067](https://github.com/penpot/penpot/issues/11067) (PR: [#11069](https://github.com/penpot/penpot/pull/11069))
|
||||
- Fix project title being truncated in the dashboard despite available space [#11306](https://github.com/penpot/penpot/issues/11306) (PR: [#11564](https://github.com/penpot/penpot/pull/11564), [#11675](https://github.com/penpot/penpot/pull/11675))
|
||||
- Fix `rect` attribute NaN length error in the console when panning a file [#11327](https://github.com/penpot/penpot/issues/11327) (PR: [#11856](https://github.com/penpot/penpot/pull/11856))
|
||||
- Fix unclipped and rotated shapes being cut on render tiles [#11401](https://github.com/penpot/penpot/issues/11401) (PR: [#11423](https://github.com/penpot/penpot/pull/11423))
|
||||
- Fix Plugin API rejecting the documented `fontFamilies` applyToken property (by @makesomethingshit) [#11405](https://github.com/penpot/penpot/issues/11405) (PR: [#11566](https://github.com/penpot/penpot/pull/11566))
|
||||
- Fix flattening text ignoring non-zero letter spacing [#11462](https://github.com/penpot/penpot/issues/11462) (PR: [#11555](https://github.com/penpot/penpot/pull/11555))
|
||||
- Fix boolean results serialized as unordered subpaths, deforming them in the WebGL render and the exporter [#11482](https://github.com/penpot/penpot/issues/11482) (PR: [#11551](https://github.com/penpot/penpot/pull/11551))
|
||||
- Fix MCP reconnecting endlessly in inactive tabs and eventually freezing the browser (by @breken-ai) [#11510](https://github.com/penpot/penpot/issues/11510) (PR: [#11520](https://github.com/penpot/penpot/pull/11520), [#11906](https://github.com/penpot/penpot/pull/11906))
|
||||
- Fix dashed strokes losing their properties when changing color (by @Shlok1729) [#11549](https://github.com/penpot/penpot/issues/11549) (PR: [#11557](https://github.com/penpot/penpot/pull/11557))
|
||||
- Fix text decoration not applying per text span in the WebGL render [#11582](https://github.com/penpot/penpot/issues/11582) (PR: [#11583](https://github.com/penpot/penpot/pull/11583))
|
||||
- Fix binfile import exhausting backend memory when a ZIP JSON entry decompresses without limit [#11606](https://github.com/penpot/penpot/issues/11606) (PR: [#11607](https://github.com/penpot/penpot/pull/11607))
|
||||
- Fix modal not closing when going to the plans page via "See my current plan" (by @makesomethingshit) [#11611](https://github.com/penpot/penpot/issues/11611) (PR: [#11615](https://github.com/penpot/penpot/pull/11615))
|
||||
- Fix view-only share links disclosing unshared library pages when the file links a library [#11617](https://github.com/penpot/penpot/issues/11617) (PR: [#11627](https://github.com/penpot/penpot/pull/11627))
|
||||
- Fix canvas tiles showing only the background color while dragging the canvas background color picker [#11642](https://github.com/penpot/penpot/issues/11642) (PR: [#11643](https://github.com/penpot/penpot/pull/11643))
|
||||
- Fix title spacing and "added" pill padding in the libraries modal [#11645](https://github.com/penpot/penpot/issues/11645) (PR: [#11650](https://github.com/penpot/penpot/pull/11650))
|
||||
- Fix workspace crash when pasting before the page finishes loading [#11666](https://github.com/penpot/penpot/issues/11666) (PR: [#11674](https://github.com/penpot/penpot/pull/11674))
|
||||
- Fix tap area not matching the visible screen when zoomed out in the WebGL render (by @makesomethingshit) [#11689](https://github.com/penpot/penpot/issues/11689) (PR: [#11793](https://github.com/penpot/penpot/pull/11793))
|
||||
- Fix app crash when logging receives an empty level, and add the missing fatal level [#11690](https://github.com/penpot/penpot/issues/11690) (PR: [#11693](https://github.com/penpot/penpot/pull/11693))
|
||||
- Fix WebGL render ignoring drop and inner shadows on masked groups [#11697](https://github.com/penpot/penpot/issues/11697) (PR: [#11754](https://github.com/penpot/penpot/pull/11754))
|
||||
- Fix curves being created while Ctrl+dragging a path segment [#11702](https://github.com/penpot/penpot/issues/11702) (PR: [#11715](https://github.com/penpot/penpot/pull/11715))
|
||||
- Fix viewer role showing the search and replace option [#11706](https://github.com/penpot/penpot/issues/11706) (PR: [#11713](https://github.com/penpot/penpot/pull/11713))
|
||||
- Fix toggling the visibility of a child in a flex layout main component deleting the prototype interactions of its copies (by @ShreyashAgare26) [#11746](https://github.com/penpot/penpot/issues/11746) (PR: [#11788](https://github.com/penpot/penpot/pull/11788))
|
||||
- Fix cursor not visible on page load in the WebGL render [#11769](https://github.com/penpot/penpot/issues/11769) (PR: [#11770](https://github.com/penpot/penpot/pull/11770))
|
||||
- Fix library text styles detaching when copying and pasting an element [#11776](https://github.com/penpot/penpot/issues/11776) (PR: [#11785](https://github.com/penpot/penpot/pull/11785))
|
||||
- Fix Plugin API crashing the workspace when a shapechange listener is registered with an empty shape id [#11786](https://github.com/penpot/penpot/issues/11786) (PR: [#11787](https://github.com/penpot/penpot/pull/11787))
|
||||
- Fix borders not being fully rendered [#11805](https://github.com/penpot/penpot/issues/11805) (PR: [#11807](https://github.com/penpot/penpot/pull/11807))
|
||||
- Fix design token controls being enabled for fills and strokes below the first position [#11819](https://github.com/penpot/penpot/issues/11819) (PR: [#10913](https://github.com/penpot/penpot/pull/10913))
|
||||
- Fix viewer role showing the open libraries modal button [#11824](https://github.com/penpot/penpot/issues/11824) (PR: [#11849](https://github.com/penpot/penpot/pull/11849))
|
||||
- Fix extra padding on the "Sets" title in the tokens panel [#11826](https://github.com/penpot/penpot/issues/11826) (PR: [#11849](https://github.com/penpot/penpot/pull/11849))
|
||||
- Fix thumbnails failing on older Chromium browsers and each thumbnail rendering several times [#11878](https://github.com/penpot/penpot/issues/11878) (PR: [#11879](https://github.com/penpot/penpot/pull/11879))
|
||||
- Fix internal error when opening the libraries modal [#11943](https://github.com/penpot/penpot/issues/11943) (PR: [#11946](https://github.com/penpot/penpot/pull/11946))
|
||||
- Fix Plugin API `connectLibrary` linking unpublished files as libraries [#12001](https://github.com/penpot/penpot/issues/12001) (PR: [#12002](https://github.com/penpot/penpot/pull/12002))
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
- Make backend storage resilient to interrupted writes, missing files and stalled cleanup [#11344](https://github.com/penpot/penpot/issues/11344) (PR: [#11345](https://github.com/penpot/penpot/pull/11345))
|
||||
- Implement RTL support in the text editor v3 [#11262](https://github.com/penpot/penpot/issues/11262)
|
||||
- Improve path operations and edition in the path editor [#10889](https://github.com/penpot/penpot/issues/10889) (PR: [#10807](https://github.com/penpot/penpot/pull/10807))
|
||||
- Add configurable keyboard shortcuts [#9924](https://github.com/penpot/penpot/issues/9924) (PR: [#10237](https://github.com/penpot/penpot/pull/10237))
|
||||
- Add auto-linking of libraries during import based on slugified name [#9263](https://github.com/penpot/penpot/issues/9263) (PR: [#9958](https://github.com/penpot/penpot/pull/9958))
|
||||
- Add support for internal libraries and file sync for Design Tokens [#9334](https://github.com/penpot/penpot/issues/9334)
|
||||
- Add support for internal libraries and file sync for Design Tokens [#9334](https://github.com/penpot/penpot/issues/9334) (PR: [#10293](https://github.com/penpot/penpot/pull/10293))
|
||||
- Warn self-hosted users when their Penpot version is outdated and surface what they're missing [#10497](https://github.com/penpot/penpot/issues/10497) (PR: [#11411](https://github.com/penpot/penpot/pull/11411))
|
||||
- Add dedicated RPC methods for plugin registry operations with permission validation [#10952](https://github.com/penpot/penpot/issues/10952) (PR: [#10957](https://github.com/penpot/penpot/pull/10957))
|
||||
- Document MCP and internal resolver environment variables (by @ShreyashAgare26) [#11318](https://github.com/penpot/penpot/issues/11318) (PR: [#11572](https://github.com/penpot/penpot/pull/11572))
|
||||
- Add tokens source indicator to assets tab [#11365](https://github.com/penpot/penpot/issues/11365) (PR: [#11439](https://github.com/penpot/penpot/pull/11439))
|
||||
- Export multiple fills to SVG [#11466](https://github.com/penpot/penpot/issues/11466) (PR: [#11467](https://github.com/penpot/penpot/pull/11467))
|
||||
- Add Penpot-specific board size presets (file thumbnail, template cover, plugin icon/cover) [#11561](https://github.com/penpot/penpot/issues/11561) (PR: [#11565](https://github.com/penpot/penpot/pull/11565))
|
||||
- Apply tokens to stroke per side [#9842](https://github.com/penpot/penpot/issues/9842) (PR: [#10913](https://github.com/penpot/penpot/pull/10913))
|
||||
- Add individual stroke widths per side (top, right, bottom, left) [#9854](https://github.com/penpot/penpot/issues/9854) (PR: [#10648](https://github.com/penpot/penpot/pull/10648))
|
||||
- Expand the token tree right after importing it (by @AKnassa) [#9819](https://github.com/penpot/penpot/issues/9819) (PR: [#10664](https://github.com/penpot/penpot/pull/10664))
|
||||
- Update the selection size badge live during resize, replacing the resize tooltip [#10259](https://github.com/penpot/penpot/issues/10259)
|
||||
- Add color variants, rotation correction, viewport flip and hide-when-too-small to the selection size badge [#10264](https://github.com/penpot/penpot/issues/10264) (PR: [#11884](https://github.com/penpot/penpot/pull/11884))
|
||||
- Support single-file SVG export with the WebGL render [#10546](https://github.com/penpot/penpot/issues/10546)
|
||||
- Persist the hide resolved comments preference and separate the filter options in the comments menu [#10686](https://github.com/penpot/penpot/issues/10686) (PR: [#10694](https://github.com/penpot/penpot/pull/10694))
|
||||
- Add rich link previews (Open Graph) for shared Penpot links [#10699](https://github.com/penpot/penpot/issues/10699) (PR: [#10700](https://github.com/penpot/penpot/pull/10700))
|
||||
- Add 3x export scale option [#10740](https://github.com/penpot/penpot/issues/10740) (PR: [#11598](https://github.com/penpot/penpot/pull/11598))
|
||||
- Show a validation message in the input instead of a toast when the email is already in use (by @ShreyashAgare26) [#10890](https://github.com/penpot/penpot/issues/10890) (PR: [#11647](https://github.com/penpot/penpot/pull/11647))
|
||||
- Take `waitForLayoutUpdate` into account in MCP instructions and implementation [#10901](https://github.com/penpot/penpot/issues/10901) (PR: [#11433](https://github.com/penpot/penpot/pull/11433))
|
||||
- Add a standard context menu (Cut, Copy, Paste, Select all) on right-click when editing text [#10914](https://github.com/penpot/penpot/issues/10914) (PR: [#11677](https://github.com/penpot/penpot/pull/11677))
|
||||
- Process export requests as export jobs [#11133](https://github.com/penpot/penpot/issues/11133) (PR: [#11296](https://github.com/penpot/penpot/pull/11296))
|
||||
- Enable closed schemas for RPC method parameters [#11135](https://github.com/penpot/penpot/issues/11135) (PR: [#11136](https://github.com/penpot/penpot/pull/11136))
|
||||
- Add Content-Security-Policy and Strict-Transport-Security headers to the frontend image [#11374](https://github.com/penpot/penpot/issues/11374) (PR: [#11375](https://github.com/penpot/penpot/pull/11375))
|
||||
- Send a notification email when the password is changed or set [#11392](https://github.com/penpot/penpot/issues/11392) (PR: [#11393](https://github.com/penpot/penpot/pull/11393))
|
||||
- Lock accounts after repeated failed login attempts [#11397](https://github.com/penpot/penpot/issues/11397) (PR: [#11402](https://github.com/penpot/penpot/pull/11402), [#11895](https://github.com/penpot/penpot/pull/11895))
|
||||
- Enforce an absolute maximum session duration [#11444](https://github.com/penpot/penpot/issues/11444) (PR: [#11447](https://github.com/penpot/penpot/pull/11447))
|
||||
- Add a loading message to the libraries section [#11452](https://github.com/penpot/penpot/issues/11452) (PR: [#11594](https://github.com/penpot/penpot/pull/11594))
|
||||
- Resize image bounds independently from the image content on the canvas [#11516](https://github.com/penpot/penpot/issues/11516) (PR: [#11590](https://github.com/penpot/penpot/pull/11590))
|
||||
- Limit binfile import database connection time and stop rescanning ZIP entries [#11579](https://github.com/penpot/penpot/issues/11579) (PR: [#11584](https://github.com/penpot/penpot/pull/11584))
|
||||
- Apply the same validation rules in the Plugin API as in the UI [#11586](https://github.com/penpot/penpot/issues/11586) (PR: [#11587](https://github.com/penpot/penpot/pull/11587))
|
||||
- Fetch only the caller's share link in the view-only bundle (by @makesomethingshit) [#11633](https://github.com/penpot/penpot/issues/11633) (PR: [#11657](https://github.com/penpot/penpot/pull/11657))
|
||||
- Add Prometheus metrics for backend storage operations [#11676](https://github.com/penpot/penpot/issues/11676) (PR: [#11700](https://github.com/penpot/penpot/pull/11700))
|
||||
- Make "Fix when scrolling" work in the WebGL render [#11684](https://github.com/penpot/penpot/issues/11684) (PR: [#11752](https://github.com/penpot/penpot/pull/11752))
|
||||
- Complete Catalan translations [#11739](https://github.com/penpot/penpot/issues/11739) (PR: [#11741](https://github.com/penpot/penpot/pull/11741))
|
||||
- Remove the optional client-provided id from creation RPC endpoints [#11783](https://github.com/penpot/penpot/issues/11783) (PR: [#11784](https://github.com/penpot/penpot/pull/11784))
|
||||
- Allow color tokens for the canvas background [#11791](https://github.com/penpot/penpot/issues/11791) (PR: [#11923](https://github.com/penpot/penpot/pull/11923))
|
||||
- Show a "no assets yet" message for empty files in the libraries modal [#11811](https://github.com/penpot/penpot/issues/11811) (PR: [#11818](https://github.com/penpot/penpot/pull/11818))
|
||||
- Upgrade the MCP SDK to v2 and adopt the new protocol version [#11827](https://github.com/penpot/penpot/issues/11827) (PR: [#11841](https://github.com/penpot/penpot/pull/11841))
|
||||
- Remove legacy SSE support from the MCP server [#11846](https://github.com/penpot/penpot/issues/11846) (PR: [#11841](https://github.com/penpot/penpot/pull/11841))
|
||||
- Delete selected path handles with Backspace or Delete [#11858](https://github.com/penpot/penpot/issues/11858) (PR: [#11896](https://github.com/penpot/penpot/pull/11896))
|
||||
- Use the tooltip component in the token pill info [#11886](https://github.com/penpot/penpot/issues/11886) (PR: [#11900](https://github.com/penpot/penpot/pull/11900))
|
||||
- Hide the duplicate cursor when alt-dragging a shape that cannot be duplicated (by @0xTHAC0) [#11905](https://github.com/penpot/penpot/issues/11905) (PR: [#11195](https://github.com/penpot/penpot/pull/11195))
|
||||
|
||||
## 2.18.3
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
org.clojure/clojure {:mvn/version "1.12.6"}
|
||||
org.clojure/tools.namespace {:mvn/version "1.5.1"}
|
||||
|
||||
com.github.luben/zstd-jni {:mvn/version "1.5.7-20"}
|
||||
com.github.luben/zstd-jni {:mvn/version "1.5.7-21"}
|
||||
|
||||
io.prometheus/simpleclient {:mvn/version "0.16.0"}
|
||||
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
|
||||
@ -37,7 +37,7 @@
|
||||
{:mvn/version "1.3.1118"}
|
||||
|
||||
metosin/reitit-core {:mvn/version "0.11.0"}
|
||||
nrepl/nrepl {:mvn/version "1.7.0"}
|
||||
nrepl/nrepl {:mvn/version "1.8.0"}
|
||||
|
||||
org.postgresql/postgresql {:mvn/version "42.7.13"}
|
||||
org.xerial/sqlite-jdbc {:mvn/version "3.53.4.0"}
|
||||
@ -50,7 +50,7 @@
|
||||
buddy/buddy-sign {:mvn/version "3.6.1-359"}
|
||||
org.passay/passay {:mvn/version "2.0.0"}
|
||||
|
||||
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.2.4"}
|
||||
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.3.0"}
|
||||
|
||||
org.jsoup/jsoup {:mvn/version "1.23.2"}
|
||||
|
||||
@ -64,10 +64,10 @@
|
||||
|
||||
;; Pretty Print specs
|
||||
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
|
||||
software.amazon.awssdk/s3 {:mvn/version "2.55.6"}
|
||||
software.amazon.awssdk/sts {:mvn/version "2.55.6"}
|
||||
software.amazon.awssdk/s3 {:mvn/version "2.55.11"}
|
||||
software.amazon.awssdk/sts {:mvn/version "2.55.11"}
|
||||
|
||||
com.ladybugdb/lbug {:mvn/version "0.20.4"}
|
||||
com.ladybugdb/lbug {:mvn/version "0.21.2"}
|
||||
;; Required by Arrow RootAllocator (lbug only pulls arrow-memory-core).
|
||||
org.apache.arrow/arrow-memory-netty {:mvn/version "19.0.0"}}
|
||||
|
||||
|
||||
@ -257,6 +257,7 @@
|
||||
(assoc ::bfc/project-id (:default-project-id profile))
|
||||
(assoc ::bfc/team-id (:default-team-id profile))
|
||||
(assoc ::bfc/profile-id (:id profile))
|
||||
(assoc ::bfc/team-id (:default-team-id profile))
|
||||
(assoc ::bfc/input evil)
|
||||
(v3/import-files!))]
|
||||
(t/is (= 1 (count (:file-ids result))))
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.types.shape.text :as ctst]
|
||||
[app.common.types.text :as types.text]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
@ -2151,6 +2152,33 @@
|
||||
(update :pages-index d/update-vals repair-container)
|
||||
(d/update-when :components d/update-vals repair-container))))
|
||||
|
||||
(defmethod migrate-data "0031-migrate-stroke-width-token-attr"
|
||||
;; Stroke width tokens used to be applied to the single `:stroke-width` token
|
||||
;; attribute, replaced by the four per-side attributes. The editor reads only
|
||||
;; the new keys, so a file written before the change keeps the token on
|
||||
;; `:stroke-width` and the token looks unapplied even though its value is
|
||||
;; still set. Copy the legacy reference to every side (matching how applying
|
||||
;; the token resolves all sides) and drop the old key. A side that already
|
||||
;; carries a reference wins over the recycled global one.
|
||||
[data _]
|
||||
(letfn [(repair-shape [shape]
|
||||
(let [applied (:applied-tokens shape)
|
||||
token (:stroke-width applied)]
|
||||
(if (some? token)
|
||||
(update shape :applied-tokens
|
||||
(fn [applied]
|
||||
(-> (zipmap ctt/per-side-stroke-width-keys (repeat token))
|
||||
(merge applied)
|
||||
(dissoc :stroke-width))))
|
||||
shape)))
|
||||
|
||||
(repair-container [container]
|
||||
(d/update-when container :objects d/update-vals repair-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals repair-container)
|
||||
(d/update-when :components d/update-vals repair-container))))
|
||||
|
||||
(def available-migrations
|
||||
(into (d/ordered-set)
|
||||
["legacy-2"
|
||||
@ -2238,4 +2266,5 @@
|
||||
"0027-separate-tokens-status"
|
||||
"0028-normalize-constrained-values"
|
||||
"0029-move-background-blur-out-of-blur"
|
||||
"0030-remove-stroke-per-side-attr"]))
|
||||
"0030-remove-stroke-per-side-attr"
|
||||
"0031-migrate-stroke-width-token-attr"]))
|
||||
|
||||
@ -460,10 +460,13 @@
|
||||
:active-set-ids active-set-ids)))
|
||||
|
||||
(defn ensure-tokens-lib
|
||||
"Ensure file-data has a :tokens-lib or :tokens-source, and also a :tokens-status, creating them if necessary."
|
||||
"Ensure file-data has a :tokens-lib or an external :tokens-source, and also a :tokens-status,
|
||||
creating them if necessary."
|
||||
[file-data]
|
||||
(cond-> file-data
|
||||
(nil? (:tokens-source file-data))
|
||||
(let [tokens-source (:tokens-source file-data)]
|
||||
(or (nil? tokens-source)
|
||||
(= tokens-source (:id file-data))))
|
||||
(update :tokens-lib #(or % (ctob/make-tokens-lib)))
|
||||
|
||||
:always
|
||||
@ -488,9 +491,12 @@
|
||||
(or (:tokens-source file-data) (:id file-data)))
|
||||
|
||||
(defn set-tokens-source
|
||||
"Set the tokens source of the file. Setting it to nil or to the file itself
|
||||
removes the attribute, so the file uses its own tokens-lib."
|
||||
[file-data tokens-source]
|
||||
(assert (or (nil? tokens-source) (uuid? tokens-source)) "expected nil or valid uuid")
|
||||
(if (nil? tokens-source)
|
||||
(if (or (nil? tokens-source)
|
||||
(= tokens-source (:id file-data)))
|
||||
(dissoc file-data :tokens-source)
|
||||
(assoc file-data :tokens-source tokens-source)))
|
||||
|
||||
|
||||
@ -25,11 +25,8 @@
|
||||
:stroke-width 1})
|
||||
|
||||
(defn materialize-stroke-side-widths
|
||||
"Given a stroke (or nil) and the set of edited side keys, returns the stroke
|
||||
with the four per-side width keys concretized: edited sides take `value`,
|
||||
the rest keep their current per-side width, falling back to `:stroke-width`
|
||||
(0 when `stroke` is nil). `:stroke-width` mirrors the top side so legacy
|
||||
consumers that only read it see the top value."
|
||||
"Sets edited widths and fills missing sides from `:stroke-width` (default 0).
|
||||
Keeps `:stroke-width` equal to the top width."
|
||||
[stroke edited-keys value]
|
||||
(let [base-width (or (:stroke-width stroke) 0)
|
||||
current (or stroke default-stroke)
|
||||
@ -42,3 +39,29 @@
|
||||
{}
|
||||
ctt/per-side-stroke-width-keys)]
|
||||
(merge current side-attrs {:stroke-width (get side-attrs :stroke-width-top)})))
|
||||
|
||||
(defn set-width-to-all-sides
|
||||
"Sets every side and `:stroke-width` to `value`."
|
||||
[stroke value]
|
||||
(materialize-stroke-side-widths stroke ctt/per-side-stroke-width-keys value))
|
||||
|
||||
(defn set-width-to-single-side
|
||||
"Sets one side and preserves the other widths.
|
||||
Keeps `:stroke-width` equal to the top width."
|
||||
[stroke attr value]
|
||||
(materialize-stroke-side-widths (or stroke default-stroke) #{attr} value))
|
||||
|
||||
(defn side-width
|
||||
"Returns the side width, falling back to `:stroke-width` when unset."
|
||||
[stroke attr]
|
||||
(d/nilv (get stroke attr) (:stroke-width stroke)))
|
||||
|
||||
(defn width-type
|
||||
"Returns simple when all effective side widths match, multiple otherwise."
|
||||
[stroke]
|
||||
(if (= (side-width stroke :stroke-width-top)
|
||||
(side-width stroke :stroke-width-right)
|
||||
(side-width stroke :stroke-width-bottom)
|
||||
(side-width stroke :stroke-width-left))
|
||||
:simple
|
||||
:multiple))
|
||||
|
||||
@ -254,6 +254,14 @@
|
||||
(t/is (not (contains? file-data' :tokens-lib)))
|
||||
(t/is (= source-id (:tokens-source file-data')))
|
||||
(t/is (contains? file-data' :tokens-status))
|
||||
(t/is (ctos/tokens-status? (:tokens-status file-data')))))
|
||||
|
||||
(t/testing "ensure-tokens-lib should add a tokens-lib if the tokens-source is the file itself"
|
||||
(let [file (thf/sample-file :file1)
|
||||
file-data (-> (ctf/file-data file)
|
||||
(assoc :tokens-source (:id file)))
|
||||
file-data' (cfo/ensure-tokens-lib file-data)]
|
||||
(t/is (ctob/tokens-lib? (:tokens-lib file-data')))
|
||||
(t/is (ctos/tokens-status? (:tokens-status file-data'))))))
|
||||
|
||||
(t/deftest test-get-tokens-lib
|
||||
@ -293,7 +301,16 @@
|
||||
file (thf/sample-file :file1)
|
||||
file-data (:data file)
|
||||
file-data' (cfo/set-tokens-source file-data source-id)]
|
||||
(t/is (= source-id (:tokens-source file-data'))))))
|
||||
(t/is (= source-id (:tokens-source file-data')))))
|
||||
|
||||
(t/testing "removes tokens-source when set to the file itself"
|
||||
(let [source-id (thi/new-id! :tokens-source)
|
||||
file (thf/sample-file :file1)
|
||||
file-data (-> (ctf/file-data file)
|
||||
(assoc :tokens-source source-id))
|
||||
file-data' (cfo/set-tokens-source file-data (:id file))]
|
||||
(t/is (not (contains? file-data' :tokens-source)))
|
||||
(t/is (= (:id file) (cfo/get-effective-tokens-source file-data'))))))
|
||||
|
||||
(t/deftest test-effective-tokens-source?
|
||||
(t/testing "no tokens, no tokens-source, ask for file id -> true"
|
||||
|
||||
@ -8,8 +8,10 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.migrations :as cfm]
|
||||
[app.common.files.tokens :as cfo]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
@ -459,3 +461,96 @@
|
||||
(t/is (nil? (:stroke-per-side stroke'))
|
||||
"obsolete attr removed before schema validation")
|
||||
(t/is (= 5 (:stroke-width stroke')) "stroke width preserved")))
|
||||
|
||||
(t/deftest migration-0031-legacy-applied-token-is-not-recognized-before-migration
|
||||
;; Reproduces #12016: the editor reads only the per-side token attributes, so
|
||||
;; a shape carrying the old global `:stroke-width` reference looks unapplied.
|
||||
(let [shape (-> (cts/setup-shape {:id (uuid/next) :type :rect})
|
||||
(assoc :applied-tokens {:stroke-width "30"}))]
|
||||
(t/is (not (cfo/shapes-token-applied? {:name "30"} [shape]
|
||||
ctt/stroke-width-keys))
|
||||
"a legacy applied token is invisible to the token lookup")))
|
||||
|
||||
(t/deftest migration-0031-recycles-global-stroke-width-token
|
||||
(let [migration-id "0031-migrate-stroke-width-token-attr"
|
||||
page-id (uuid/next)
|
||||
shape-id (uuid/next)
|
||||
shape (-> (cts/setup-shape {:id shape-id :type :rect})
|
||||
(assoc :applied-tokens {:stroke-width "30"
|
||||
:fill "red"}))
|
||||
data {:pages-index {page-id {:objects {shape-id shape}}}}
|
||||
shape' (get-in (cfm/migrate-data data migration-id)
|
||||
[:pages-index page-id :objects shape-id])
|
||||
applied' (:applied-tokens shape')]
|
||||
|
||||
(t/is (nil? (:stroke-width applied')) "legacy key removed")
|
||||
(t/is (= "30" (:stroke-width-top applied')))
|
||||
(t/is (= "30" (:stroke-width-right applied')))
|
||||
(t/is (= "30" (:stroke-width-bottom applied')))
|
||||
(t/is (= "30" (:stroke-width-left applied')))
|
||||
(t/is (= "red" (:fill applied')) "unrelated tokens preserved")
|
||||
(t/is (cfo/shapes-token-applied? {:name "30"} [shape']
|
||||
ctt/stroke-width-keys)
|
||||
"the token is recognized after migration")))
|
||||
|
||||
(t/deftest migration-0031-keeps-side-token-that-was-already-set
|
||||
(let [migration-id "0031-migrate-stroke-width-token-attr"
|
||||
page-id (uuid/next)
|
||||
shape-id (uuid/next)
|
||||
shape (-> (cts/setup-shape {:id shape-id :type :rect})
|
||||
(assoc :applied-tokens {:stroke-width "30"
|
||||
:stroke-width-top "10"}))
|
||||
data {:pages-index {page-id {:objects {shape-id shape}}}}
|
||||
applied' (get-in (cfm/migrate-data data migration-id)
|
||||
[:pages-index page-id :objects shape-id :applied-tokens])]
|
||||
|
||||
(t/is (= "10" (:stroke-width-top applied'))
|
||||
"an explicit side reference is not overwritten")
|
||||
(t/is (= "30" (:stroke-width-right applied')))))
|
||||
|
||||
(t/deftest migration-0031-repairs-component-shapes
|
||||
(let [migration-id "0031-migrate-stroke-width-token-attr"
|
||||
component-id (uuid/next)
|
||||
shape-id (uuid/next)
|
||||
shape (-> (cts/setup-shape {:id shape-id :type :rect})
|
||||
(assoc :applied-tokens {:stroke-width "30"}))
|
||||
data {:components {component-id {:objects {shape-id shape}}}}
|
||||
applied' (get-in (cfm/migrate-data data migration-id)
|
||||
[:components component-id :objects shape-id :applied-tokens])]
|
||||
|
||||
(t/is (nil? (:stroke-width applied')) "legacy key removed")
|
||||
(t/is (= "30" (:stroke-width-left applied'))
|
||||
"component side reference created")))
|
||||
|
||||
(t/deftest migration-0031-leaves-shapes-without-the-legacy-token
|
||||
(let [migration-id "0031-migrate-stroke-width-token-attr"
|
||||
page-id (uuid/next)
|
||||
shape-id (uuid/next)
|
||||
shape (-> (cts/setup-shape {:id shape-id :type :rect})
|
||||
(assoc :applied-tokens {:stroke-width-top "30"
|
||||
:stroke-width-right "30"
|
||||
:stroke-width-bottom "30"
|
||||
:stroke-width-left "30"}))
|
||||
data {:pages-index {page-id {:objects {shape-id shape}}}}]
|
||||
|
||||
(t/is (= data (cfm/migrate-data data migration-id))
|
||||
"a shape without the legacy token is left untouched")))
|
||||
|
||||
(t/deftest migration-0031-runs-through-file-migration
|
||||
(let [migration-id "0031-migrate-stroke-width-token-attr"
|
||||
shape-id (uuid/next)
|
||||
file (ctf/make-file {:name "Legacy stroke width token"})
|
||||
page-id (first (get-in file [:data :pages]))
|
||||
shape (-> (cts/setup-shape {:id shape-id :type :rect})
|
||||
(assoc :applied-tokens {:stroke-width "30"}))
|
||||
file (-> file
|
||||
(assoc :migrations (disj cfm/available-migrations migration-id))
|
||||
(assoc-in [:data :pages-index page-id :objects shape-id] shape))
|
||||
file' (cfm/migrate-file file {})
|
||||
applied' (get-in file' [:data :pages-index page-id :objects shape-id :applied-tokens])]
|
||||
|
||||
(t/is (cfm/need-migration? file) "new migration detected")
|
||||
(t/is (not (cfm/need-migration? file')) "new migration recorded")
|
||||
(t/is (contains? (:migrations file') migration-id) "migration id persisted")
|
||||
(t/is (= "30" (:stroke-width-top applied'))
|
||||
"legacy token applied to the sides")))
|
||||
|
||||
@ -146,7 +146,7 @@
|
||||
(t/is (= (ctos/get-active-set-ids file-tokens-status)
|
||||
(ctos/get-active-set-ids undo-tokens-status)))))
|
||||
|
||||
(t/testing "if the library is the same file, the source is set to self and the status is the same one"
|
||||
(t/testing "if the library is the same file, the source is removed and the status is the same one"
|
||||
(let [set-id (uuid/next)
|
||||
file (tht/sample-file-with-tokens
|
||||
:file-id :file1
|
||||
@ -166,7 +166,7 @@
|
||||
undo-tokens-status (tht/get-tokens-status undo)]
|
||||
|
||||
;; Redo: source and status copied from library, replacing the previous status
|
||||
(t/is (= (:id file) redo-tokens-source))
|
||||
(t/is (nil? redo-tokens-source))
|
||||
(t/is (= (ctos/get-active-theme-ids file-tokens-status)
|
||||
(ctos/get-active-theme-ids redo-tokens-status)))
|
||||
(t/is (= (ctos/get-active-set-ids file-tokens-status)
|
||||
|
||||
@ -72,3 +72,101 @@
|
||||
(t/is (= (:stroke-width-bottom stroke) 5))
|
||||
(t/is (= (:stroke-width-left stroke) 5))
|
||||
(t/is (= (:stroke-width stroke) 10)))))
|
||||
|
||||
;; --- set-width-to-all-sides
|
||||
|
||||
(t/deftest set-width-to-all-sides-uniform
|
||||
(t/testing "all four sides take the value and the alias mirrors the top"
|
||||
(let [stroke (cts/set-width-to-all-sides {:stroke-width 5 :stroke-color "#000000"} 8)]
|
||||
(t/is (= 8 (:stroke-width-top stroke)))
|
||||
(t/is (= 8 (:stroke-width-right stroke)))
|
||||
(t/is (= 8 (:stroke-width-bottom stroke)))
|
||||
(t/is (= 8 (:stroke-width-left stroke)))
|
||||
(t/is (= 8 (:stroke-width stroke)))
|
||||
(t/is (= "#000000" (:stroke-color stroke)))))
|
||||
|
||||
(t/testing "a nil stroke is materialized from the default stroke"
|
||||
(let [stroke (cts/set-width-to-all-sides nil 3)]
|
||||
(t/is (= 3 (:stroke-width stroke)))
|
||||
(t/is (= 3 (:stroke-width-top stroke)))
|
||||
(t/is (= 3 (:stroke-width-right stroke)))
|
||||
(t/is (= :solid (:stroke-style stroke))))))
|
||||
|
||||
;; --- set-width-to-single-side
|
||||
|
||||
(t/deftest set-width-to-single-side-non-top
|
||||
(t/testing "a non-top side only changes that side, keeping the alias on top"
|
||||
(let [stroke (cts/set-width-to-single-side
|
||||
{:stroke-width 5
|
||||
:stroke-width-top 5
|
||||
:stroke-width-right 5
|
||||
:stroke-width-bottom 6
|
||||
:stroke-width-left 7}
|
||||
:stroke-width-right
|
||||
9)]
|
||||
(t/is (= 5 (:stroke-width-top stroke)))
|
||||
(t/is (= 9 (:stroke-width-right stroke)))
|
||||
(t/is (= 6 (:stroke-width-bottom stroke)))
|
||||
(t/is (= 7 (:stroke-width-left stroke)))
|
||||
(t/is (= 5 (:stroke-width stroke))))))
|
||||
|
||||
(t/deftest set-width-to-single-side-top
|
||||
(t/testing "the top side also updates the :stroke-width alias"
|
||||
(let [stroke (cts/set-width-to-single-side {:stroke-width 5} :stroke-width-top 9)]
|
||||
(t/is (= 9 (:stroke-width-top stroke)))
|
||||
(t/is (= 9 (:stroke-width stroke)))))
|
||||
|
||||
(t/testing "a nil stroke is materialized from the default stroke"
|
||||
(let [stroke (cts/set-width-to-single-side nil :stroke-width-bottom 4)]
|
||||
(t/is (= 4 (:stroke-width-bottom stroke)))
|
||||
(t/is (= 1 (:stroke-width stroke)))
|
||||
(t/is (= :solid (:stroke-style stroke))))))
|
||||
|
||||
;; --- side-width
|
||||
|
||||
(t/deftest side-width-falls-back-to-global
|
||||
(t/testing "a side without its own value reads the uniform width"
|
||||
(t/is (= 5 (cts/side-width {:stroke-width 5} :stroke-width-right)))
|
||||
(t/is (= 5 (cts/side-width {:stroke-width 5 :stroke-width-left 4} :stroke-width-right))))
|
||||
|
||||
(t/testing "a side with its own value wins over the global width"
|
||||
(t/is (= 9 (cts/side-width {:stroke-width 5 :stroke-width-right 9} :stroke-width-right))))
|
||||
|
||||
(t/testing "a nil stroke reads nil"
|
||||
(t/is (nil? (cts/side-width nil :stroke-width-top)))))
|
||||
|
||||
(t/deftest width-setters-select-mode
|
||||
(let [uniform (cts/set-width-to-all-sides {:stroke-color "#000000"} 1)
|
||||
top (cts/set-width-to-single-side uniform :stroke-width-top 8)
|
||||
equal (-> top
|
||||
(cts/set-width-to-single-side :stroke-width-right 8)
|
||||
(cts/set-width-to-single-side :stroke-width-bottom 8)
|
||||
(cts/set-width-to-single-side :stroke-width-left 8))]
|
||||
(t/is (= :simple (cts/width-type uniform)))
|
||||
(t/is (= :multiple (cts/width-type top)))
|
||||
(t/is (= [8 1 1 1]
|
||||
(mapv #(cts/side-width top %)
|
||||
[:stroke-width-top :stroke-width-right :stroke-width-bottom :stroke-width-left])))
|
||||
(t/is (= :simple (cts/width-type equal)))
|
||||
(t/is (= 8 (:stroke-width equal)))
|
||||
(t/is (not (contains? equal :stroke-width-type)))
|
||||
(t/is (= :simple (cts/width-type (cts/set-width-to-all-sides equal 3))))))
|
||||
|
||||
(t/deftest top-width-preserves-unset-sides
|
||||
(let [stroke (cts/set-width-to-single-side {:stroke-width 5} :stroke-width-top 9)]
|
||||
(t/is (= [9 5 5 5]
|
||||
(mapv #(cts/side-width stroke %)
|
||||
[:stroke-width-top :stroke-width-right :stroke-width-bottom :stroke-width-left])))))
|
||||
|
||||
(t/deftest width-type-follows-effective-side-widths
|
||||
(let [stroke {:stroke-width 2 :stroke-width-top 2 :stroke-width-right 4
|
||||
:stroke-width-bottom 6 :stroke-width-left 8}
|
||||
simple (cts/set-width-to-all-sides stroke 2)]
|
||||
(t/is (= :multiple (cts/width-type stroke)))
|
||||
(t/is (= :simple (cts/width-type {:stroke-width 2})))
|
||||
(t/is (= :simple (cts/width-type {:stroke-width 2 :stroke-width-top 2})))
|
||||
(t/is (= :multiple (cts/width-type {:stroke-width 2 :stroke-width-right 4})))
|
||||
(t/is (= [2 2 2 2]
|
||||
(mapv simple [:stroke-width-top :stroke-width-right :stroke-width-bottom :stroke-width-left])))
|
||||
(t/is (= :simple (cts/width-type simple)))
|
||||
(t/is (not (contains? simple :stroke-width-type)))))
|
||||
|
||||
@ -112,18 +112,18 @@ RUN set -eux; \
|
||||
|
||||
FROM base AS setup-opencode
|
||||
|
||||
ENV OPENCODE_VERSION=2.0.24
|
||||
ENV OPENCODE_VERSION=2.0.25
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-${OPENCODE_VERSION}.tgz"; \
|
||||
ESUM='9d0cd2bfc060fd6c2afdf6db69837f8690d46a8d8327bb864421851ce004f453'; \
|
||||
ESUM='92ce31022cd32f57ac3ccc775ba3a96f6aa36128a176aea0c7ef266986047537'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-${OPENCODE_VERSION}.tgz"; \
|
||||
ESUM='21b1ee0683841405d69541fc44481f8e5752e95bea72e8b8ec31dbcdb102e7f8'; \
|
||||
ESUM='5b3e50ad1c06214aaf1c26eb30f90a15f66975a6e3e48fef5a31c34466e37b5a'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; exit 1; \
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
"dependencies": {
|
||||
"@11ty/eleventy": "^3.1.6",
|
||||
"@11ty/eleventy-navigation": "^1.0.5",
|
||||
"@11ty/eleventy-plugin-rss": "^3.0.0",
|
||||
"@11ty/eleventy-plugin-rss": "^3.1.0",
|
||||
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2",
|
||||
"@tigersway/eleventy-plugin-ancestry": "^1.0.3",
|
||||
"@types/markdown-it": "14.2.0",
|
||||
|
||||
98
docs/pnpm-lock.yaml
generated
98
docs/pnpm-lock.yaml
generated
@ -173,8 +173,8 @@ importers:
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5
|
||||
'@11ty/eleventy-plugin-rss':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
'@11ty/eleventy-plugin-syntaxhighlight':
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
@ -233,8 +233,8 @@ packages:
|
||||
resolution: {integrity: sha512-QK1tRFBhQdZASnYU8GMzpTdsMMFLVAkuU0gVVILqNyp09xJJZb81kAS3AFrNrwBCsgLxTdWHJ8N64+OTTsoKkA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@11ty/eleventy-plugin-rss@3.0.0':
|
||||
resolution: {integrity: sha512-kKW4DcR57xAyRx0e8gNhKh56ahHVEaAj8/TuXQDnw+B46ig2bWADJAlyj/GdV37IG5ja9dZ4SgKZrs/CHz6YWQ==}
|
||||
'@11ty/eleventy-plugin-rss@3.1.0':
|
||||
resolution: {integrity: sha512-eby7wIYU3XBTAel8qPzbZSEhzNg/WXcAqNULYRqzMBU+gVlx4SL2mUvWDYxkp72KJqefnMMIo2Zz6w09N6S5XQ==}
|
||||
|
||||
'@11ty/eleventy-plugin-syntaxhighlight@5.0.2':
|
||||
resolution: {integrity: sha512-T6xVVRDJuHlrFMHbUiZkHjj5o1IlLzZW+1IL9eUsyXFU7rY2ztcYhZew/64vmceFFpQwzuSfxQOXxTJYmKkQ+A==}
|
||||
@ -293,8 +293,8 @@ packages:
|
||||
resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn@8.18.0:
|
||||
resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
|
||||
acorn@8.19.0:
|
||||
resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
@ -308,8 +308,8 @@ packages:
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
argparse@3.0.1:
|
||||
resolution: {integrity: sha512-nM4mHF/KM1v59ZNKX7zfusQz5wUAxR511YG8Vo6TyiV4aqhu++rbJW4v04xsWhpSsHFj66flT8P7znVpyO20xQ==}
|
||||
argparse@3.0.2:
|
||||
resolution: {integrity: sha512-mFdDM6WqWKraGLsVb+C9CahPnzTXOefAOLq3jYcca2YZ8bEWpr++Tzj+zSaKW9+X9L5uSxcm1AZ3Y6aZJ09OhQ==}
|
||||
|
||||
asap@2.0.6:
|
||||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
||||
@ -333,8 +333,8 @@ packages:
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
||||
brace-expansion@1.1.18:
|
||||
resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==}
|
||||
brace-expansion@1.1.21:
|
||||
resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
@ -462,8 +462,8 @@ packages:
|
||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@8.0.0:
|
||||
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||
entities@8.1.0:
|
||||
resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
errno@1.0.0:
|
||||
@ -601,12 +601,12 @@ packages:
|
||||
resolution: {integrity: sha512-ZFar/L4ngX7wZh2QX+Fiftmuf0igWJsrJtfizrovWifF1gAWkfmRa5Z1m0LQZbm0hKCHRDYhLRSLFrSqNe4EJA==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
||||
js-yaml@3.15.1:
|
||||
resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==}
|
||||
js-yaml@3.15.2:
|
||||
resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==}
|
||||
hasBin: true
|
||||
|
||||
js-yaml@4.3.1:
|
||||
resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
|
||||
js-yaml@4.3.2:
|
||||
resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==}
|
||||
hasBin: true
|
||||
|
||||
junk@3.1.0:
|
||||
@ -627,8 +627,8 @@ packages:
|
||||
linkify-it@6.1.0:
|
||||
resolution: {integrity: sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw==}
|
||||
|
||||
liquidjs@10.29.0:
|
||||
resolution: {integrity: sha512-pCVOhs6FLAR8su3ItJ07diN26t6W5dHQRnmTMy8HPyTFuv1+oSCVJIGp5pGjfQyOZfh50KswvKtMTp6p4JEIdw==}
|
||||
liquidjs@10.30.0:
|
||||
resolution: {integrity: sha512-Fw4wA+8CZsJkaeLEIlPE3AoponfjEZhj7FQvYNcAEcg7jNRik7mEMoaco382FBdrYlykVf6w4vClPNctz2OYjg==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
@ -720,6 +720,10 @@ packages:
|
||||
chokidar:
|
||||
optional: true
|
||||
|
||||
obug@3.0.0:
|
||||
resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@ -867,8 +871,8 @@ packages:
|
||||
uc.micro@3.0.0:
|
||||
resolution: {integrity: sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw==}
|
||||
|
||||
undici@7.29.0:
|
||||
resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==}
|
||||
undici@7.30.0:
|
||||
resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
unpipe@1.0.0:
|
||||
@ -887,8 +891,8 @@ packages:
|
||||
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ws@8.21.3:
|
||||
resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
|
||||
ws@8.22.0:
|
||||
resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
@ -904,7 +908,7 @@ snapshots:
|
||||
'@11ty/dependency-tree-esm@2.0.4':
|
||||
dependencies:
|
||||
'@11ty/eleventy-utils': 2.0.7
|
||||
acorn: 8.18.0
|
||||
acorn: 8.19.0
|
||||
dependency-graph: 1.0.0
|
||||
normalize-path: 3.0.0
|
||||
|
||||
@ -925,7 +929,7 @@ snapshots:
|
||||
send: 1.2.1
|
||||
ssri: 11.0.0
|
||||
urlpattern-polyfill: 10.1.0
|
||||
ws: 8.21.3
|
||||
ws: 8.22.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
@ -954,14 +958,12 @@ snapshots:
|
||||
- posthtml
|
||||
- supports-color
|
||||
|
||||
'@11ty/eleventy-plugin-rss@3.0.0':
|
||||
'@11ty/eleventy-plugin-rss@3.1.0':
|
||||
dependencies:
|
||||
'@11ty/eleventy-utils': 2.0.7
|
||||
'@11ty/posthtml-urls': 1.0.3
|
||||
debug: 4.4.3
|
||||
obug: 3.0.0
|
||||
posthtml: 0.16.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@11ty/eleventy-plugin-syntaxhighlight@5.0.2':
|
||||
dependencies:
|
||||
@ -988,9 +990,9 @@ snapshots:
|
||||
filesize: 10.1.6
|
||||
gray-matter: 4.0.3
|
||||
iso-639-1: 3.1.6
|
||||
js-yaml: 4.3.1
|
||||
js-yaml: 4.3.2
|
||||
kleur: 4.1.5
|
||||
liquidjs: 10.29.0
|
||||
liquidjs: 10.30.0
|
||||
luxon: 3.7.2
|
||||
markdown-it: 14.3.2
|
||||
minimist: 1.2.8
|
||||
@ -1053,9 +1055,9 @@ snapshots:
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
acorn: 8.19.0
|
||||
|
||||
acorn@8.18.0: {}
|
||||
acorn@8.19.0: {}
|
||||
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
@ -1068,7 +1070,7 @@ snapshots:
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
argparse@3.0.1: {}
|
||||
argparse@3.0.2: {}
|
||||
|
||||
asap@2.0.6: {}
|
||||
|
||||
@ -1091,7 +1093,7 @@ snapshots:
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
brace-expansion@1.1.18:
|
||||
brace-expansion@1.1.21:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
@ -1120,7 +1122,7 @@ snapshots:
|
||||
parse5: 7.3.0
|
||||
parse5-htmlparser2-tree-adapter: 7.1.0
|
||||
parse5-parser-stream: 7.1.2
|
||||
undici: 7.29.0
|
||||
undici: 7.30.0
|
||||
whatwg-mimetype: 4.0.0
|
||||
|
||||
chokidar@3.6.0:
|
||||
@ -1242,7 +1244,7 @@ snapshots:
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
entities@8.0.0: {}
|
||||
entities@8.1.0: {}
|
||||
|
||||
errno@1.0.0:
|
||||
dependencies:
|
||||
@ -1254,7 +1256,7 @@ snapshots:
|
||||
|
||||
esm-import-transformer@3.0.5:
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
acorn: 8.19.0
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
@ -1305,7 +1307,7 @@ snapshots:
|
||||
|
||||
gray-matter@4.0.3:
|
||||
dependencies:
|
||||
js-yaml: 3.15.1
|
||||
js-yaml: 3.15.2
|
||||
kind-of: 6.0.3
|
||||
section-matter: 1.0.0
|
||||
strip-bom-string: 1.0.0
|
||||
@ -1367,12 +1369,12 @@ snapshots:
|
||||
|
||||
iso-639-1@3.1.6: {}
|
||||
|
||||
js-yaml@3.15.1:
|
||||
js-yaml@3.15.2:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
|
||||
js-yaml@4.3.1:
|
||||
js-yaml@4.3.2:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@ -1390,7 +1392,7 @@ snapshots:
|
||||
dependencies:
|
||||
uc.micro: 3.0.0
|
||||
|
||||
liquidjs@10.29.0:
|
||||
liquidjs@10.30.0:
|
||||
dependencies:
|
||||
commander: 10.0.1
|
||||
|
||||
@ -1418,8 +1420,8 @@ snapshots:
|
||||
|
||||
markdown-it@15.0.2:
|
||||
dependencies:
|
||||
argparse: 3.0.1
|
||||
entities: 8.0.0
|
||||
argparse: 3.0.2
|
||||
entities: 8.1.0
|
||||
linkify-it: 6.1.0
|
||||
mdurl: 2.1.0
|
||||
punycode.js: 2.3.1
|
||||
@ -1441,7 +1443,7 @@ snapshots:
|
||||
|
||||
minimatch@3.1.5:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.18
|
||||
brace-expansion: 1.1.21
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
@ -1457,7 +1459,7 @@ snapshots:
|
||||
|
||||
node-retrieve-globals@6.0.1:
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
acorn: 8.19.0
|
||||
acorn-walk: 8.3.5
|
||||
esm-import-transformer: 3.0.5
|
||||
|
||||
@ -1475,6 +1477,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
chokidar: 3.6.0
|
||||
|
||||
obug@3.0.0: {}
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
@ -1604,7 +1608,7 @@ snapshots:
|
||||
|
||||
uc.micro@3.0.0: {}
|
||||
|
||||
undici@7.29.0: {}
|
||||
undici@7.30.0: {}
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
@ -1616,4 +1620,4 @@ snapshots:
|
||||
|
||||
whatwg-mimetype@4.0.0: {}
|
||||
|
||||
ws@8.21.3: {}
|
||||
ws@8.22.0: {}
|
||||
|
||||
@ -386,9 +386,13 @@ By default, <code class="language-bash">smtp</code> flag is disabled, the email
|
||||
printed to the console, which means that the emails will be shown in the stdout.
|
||||
|
||||
Note that if you plan to invite members to a team, it is recommended that you enable SMTP
|
||||
as they will need to login to their account after receiving the invite link sent an in email.
|
||||
It is currently not possible to just add someone to a team without them accepting an
|
||||
invitation email.
|
||||
as they will need to login to their account after receiving the invite link sent in an email.
|
||||
|
||||
There is one exception: with the `disable-email-verification` flag set, inviting an email
|
||||
that already has a profile adds that user to the team directly, without sending any
|
||||
invitation email and without requiring acceptance. Emails without an existing profile
|
||||
still follow the normal invitation flow. See the
|
||||
[`disable-email-verification` flag][11] description for more detail.
|
||||
|
||||
If you have an SMTP service, uncomment the appropriate settings section in
|
||||
<code class="language-bash">docker-compose.yml</code> and configure those
|
||||
@ -926,6 +930,13 @@ for the user:
|
||||
<code class="language-bash">penpot-mcp</code> service. Check the [MCP section][8] to get more detail.
|
||||
- <code class="language-bash">enable-prepl-server</code>: enables PREPL server, used by manage.py and other additional
|
||||
tools to communicate internally with Penpot backend. Check the [CLI section][5] to get more detail.
|
||||
- `disable-email-verification`: skips the email verification step on registration, making
|
||||
newly registered profiles active immediately without any verification email. It also
|
||||
changes the team invitation flow: inviting an email that already has a profile adds that
|
||||
user to the team directly, without sending an invitation email (check the
|
||||
[email configuration section][10] for more detail). Note that registering with an email
|
||||
that already has an inactive profile is rejected as "email already exists" while this
|
||||
flag is set. Not recommended for production environments.
|
||||
|
||||
__Since version 1.13.0__
|
||||
|
||||
@ -952,3 +963,5 @@ __Since version 2.0.0__
|
||||
[7]: /technical-guide/integration/#access-tokens
|
||||
[8]: /mcp/
|
||||
[9]: /technical-guide/developer/subsystems/link-preview/
|
||||
[10]: /technical-guide/configuration/#email-configuration
|
||||
[11]: /technical-guide/configuration/#other-flags
|
||||
|
||||
@ -144,6 +144,13 @@ is routed, depending if the email corresponds to an existing account or not. The
|
||||
<code class="language-clojure">:register-profile</code> or <code class="language-clojure">:login</code> services are used, and the invitation token is
|
||||
attached so that the profile is linked to the team at the end.
|
||||
|
||||
There is a shortcut when the `:email-verification` flag is disabled: if the invited email
|
||||
already corresponds to an existing profile, the `create-invitation` function (in
|
||||
`app.rpc.commands.teams-invitations`) adds the profile to the team directly, without
|
||||
creating an invitation token or sending an email, and marks the profile as active if it
|
||||
was inactive. Emails without an existing profile follow the normal invitation flow
|
||||
described above.
|
||||
|
||||
## Handling unfinished registrations and bouncing users
|
||||
|
||||
All tokens have an expiration date, and when they are put in a permanent
|
||||
|
||||
@ -98,6 +98,10 @@ or
|
||||
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile --skip-tutorial --skip-walkthrough
|
||||
```
|
||||
|
||||
**NOTE:** profiles created with this command have no verified email. If the
|
||||
`disable-email-verification` flag is set, inviting these users to a team adds them to it
|
||||
directly, without sending any invitation email, so SMTP is not required for this flow.
|
||||
Check the [Configuration][1] section for more detail.
|
||||
|
||||
**NOTE:** the exact container name depends on your docker version and platform.
|
||||
For example it could be <code class="language-bash">penpot-penpot-backend-1</code> or <code class="language-bash">penpot_penpot-backend-1</code>.
|
||||
|
||||
139
exporter/pnpm-lock.yaml
generated
139
exporter/pnpm-lock.yaml
generated
@ -240,8 +240,8 @@ packages:
|
||||
async@3.2.6:
|
||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||
|
||||
b4a@1.8.1:
|
||||
resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==}
|
||||
b4a@1.9.0:
|
||||
resolution: {integrity: sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==}
|
||||
peerDependencies:
|
||||
react-native-b4a: '*'
|
||||
peerDependenciesMeta:
|
||||
@ -252,32 +252,28 @@ packages:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
bare-events@2.9.1:
|
||||
resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==}
|
||||
bare-events@2.9.2:
|
||||
resolution: {integrity: sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==}
|
||||
peerDependencies:
|
||||
bare-abort-controller: '*'
|
||||
peerDependenciesMeta:
|
||||
bare-abort-controller:
|
||||
optional: true
|
||||
|
||||
bare-fs@4.7.2:
|
||||
resolution: {integrity: sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==}
|
||||
engines: {bare: '>=1.16.0'}
|
||||
bare-fs@4.8.2:
|
||||
resolution: {integrity: sha512-+ZI68KHMUvosXfKbg/UOHK0tbCdRnegbvPEdEcZ3Nd6TetieQsJPRXBRXPdLyy8+3VSEbPXtsumTpEtt78xv9w==}
|
||||
engines: {bare: '>=1.28.0'}
|
||||
peerDependencies:
|
||||
bare-buffer: '*'
|
||||
peerDependenciesMeta:
|
||||
bare-buffer:
|
||||
optional: true
|
||||
|
||||
bare-os@3.9.1:
|
||||
resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==}
|
||||
engines: {bare: '>=1.14.0'}
|
||||
bare-path@3.1.2:
|
||||
resolution: {integrity: sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==}
|
||||
|
||||
bare-path@3.0.1:
|
||||
resolution: {integrity: sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==}
|
||||
|
||||
bare-stream@2.13.1:
|
||||
resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==}
|
||||
bare-stream@2.13.4:
|
||||
resolution: {integrity: sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==}
|
||||
peerDependencies:
|
||||
bare-abort-controller: '*'
|
||||
bare-buffer: '*'
|
||||
@ -290,8 +286,8 @@ packages:
|
||||
bare-events:
|
||||
optional: true
|
||||
|
||||
bare-url@2.4.5:
|
||||
resolution: {integrity: sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==}
|
||||
bare-url@2.5.4:
|
||||
resolution: {integrity: sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
@ -300,8 +296,8 @@ packages:
|
||||
resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
brace-expansion@5.0.9:
|
||||
resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
|
||||
brace-expansion@5.0.12:
|
||||
resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
buffer-crc32@1.0.0:
|
||||
@ -330,8 +326,8 @@ packages:
|
||||
resolution: {integrity: sha512-8BIbcC6tPZMv2/PD4PrB3CtKks7LmAWhA7FRZK/6/b/WJ2RUz00KCga7gq50S4/RhZ22kxefRFGgsuts0v3eoA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
core-js-pure@3.49.0:
|
||||
resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==}
|
||||
core-js-pure@3.50.0:
|
||||
resolution: {integrity: sha512-6GP3Pxz4IKyWjAfa747vIu/jilB5z29JWROLqH/b+pXVcpgh6tM06ZIBwSuglgVqzDYURhOK6oEzTrG0bCHitA==}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
@ -401,8 +397,8 @@ packages:
|
||||
resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
entities@8.0.0:
|
||||
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||
entities@8.1.0:
|
||||
resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
event-target-shim@5.0.1:
|
||||
@ -470,8 +466,8 @@ packages:
|
||||
mdn-data@2.27.1:
|
||||
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
|
||||
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
minimatch@10.2.6:
|
||||
resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
ms@2.1.3:
|
||||
@ -527,15 +523,15 @@ packages:
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
sax@1.6.0:
|
||||
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
|
||||
sax@1.6.1:
|
||||
resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==}
|
||||
engines: {node: '>=11.0.0'}
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
source-map-js@1.2.2:
|
||||
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
@ -552,8 +548,8 @@ packages:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
streamx@2.27.0:
|
||||
resolution: {integrity: sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==}
|
||||
streamx@2.28.1:
|
||||
resolution: {integrity: sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||
@ -561,8 +557,8 @@ packages:
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
tar-stream@3.2.0:
|
||||
resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==}
|
||||
tar-stream@3.2.2:
|
||||
resolution: {integrity: sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==}
|
||||
|
||||
teex@1.0.1:
|
||||
resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==}
|
||||
@ -612,7 +608,7 @@ snapshots:
|
||||
|
||||
'@babel/runtime-corejs3@7.29.7':
|
||||
dependencies:
|
||||
core-js-pure: 3.49.0
|
||||
core-js-pure: 3.50.0
|
||||
|
||||
'@ioredis/commands@2.0.0': {}
|
||||
|
||||
@ -622,7 +618,7 @@ snapshots:
|
||||
css-tree: 3.2.1
|
||||
csso: 5.0.5
|
||||
lodash: 4.18.1
|
||||
sax: 1.6.0
|
||||
sax: 1.6.1
|
||||
|
||||
abort-controller@3.0.0:
|
||||
dependencies:
|
||||
@ -637,7 +633,7 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
readable-stream: 4.7.0
|
||||
readdir-glob: 3.0.0
|
||||
tar-stream: 3.2.0
|
||||
tar-stream: 3.2.2
|
||||
zip-stream: 7.0.5
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
@ -646,47 +642,44 @@ snapshots:
|
||||
|
||||
async@3.2.6: {}
|
||||
|
||||
b4a@1.8.1: {}
|
||||
b4a@1.9.0: {}
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
bare-events@2.9.1: {}
|
||||
bare-events@2.9.2: {}
|
||||
|
||||
bare-fs@4.7.2:
|
||||
bare-fs@4.8.2:
|
||||
dependencies:
|
||||
bare-events: 2.9.1
|
||||
bare-path: 3.0.1
|
||||
bare-stream: 2.13.1(bare-events@2.9.1)
|
||||
bare-url: 2.4.5
|
||||
bare-events: 2.9.2
|
||||
bare-path: 3.1.2
|
||||
bare-stream: 2.13.4(bare-events@2.9.2)
|
||||
bare-url: 2.5.4
|
||||
fast-fifo: 1.3.2
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
bare-os@3.9.1: {}
|
||||
bare-path@3.1.2: {}
|
||||
|
||||
bare-path@3.0.1:
|
||||
bare-stream@2.13.4(bare-events@2.9.2):
|
||||
dependencies:
|
||||
bare-os: 3.9.1
|
||||
|
||||
bare-stream@2.13.1(bare-events@2.9.1):
|
||||
dependencies:
|
||||
streamx: 2.27.0
|
||||
b4a: 1.9.0
|
||||
streamx: 2.28.1
|
||||
teex: 1.0.1
|
||||
optionalDependencies:
|
||||
bare-events: 2.9.1
|
||||
bare-events: 2.9.2
|
||||
transitivePeerDependencies:
|
||||
- react-native-b4a
|
||||
|
||||
bare-url@2.4.5:
|
||||
bare-url@2.5.4:
|
||||
dependencies:
|
||||
bare-path: 3.0.1
|
||||
bare-path: 3.1.2
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
boolbase@2.0.0: {}
|
||||
|
||||
brace-expansion@5.0.9:
|
||||
brace-expansion@5.0.12:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
@ -716,7 +709,7 @@ snapshots:
|
||||
depd: 2.0.0
|
||||
keygrip: 1.1.0
|
||||
|
||||
core-js-pure@3.49.0: {}
|
||||
core-js-pure@3.50.0: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
@ -738,12 +731,12 @@ snapshots:
|
||||
css-tree@2.2.1:
|
||||
dependencies:
|
||||
mdn-data: 2.0.28
|
||||
source-map-js: 1.2.1
|
||||
source-map-js: 1.2.2
|
||||
|
||||
css-tree@3.2.1:
|
||||
dependencies:
|
||||
mdn-data: 2.27.1
|
||||
source-map-js: 1.2.1
|
||||
source-map-js: 1.2.2
|
||||
|
||||
css-what@8.0.0: {}
|
||||
|
||||
@ -765,7 +758,7 @@ snapshots:
|
||||
dependencies:
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
entities: 8.0.0
|
||||
entities: 8.1.0
|
||||
|
||||
domelementtype@3.0.0: {}
|
||||
|
||||
@ -779,13 +772,13 @@ snapshots:
|
||||
domelementtype: 3.0.0
|
||||
domhandler: 6.0.1
|
||||
|
||||
entities@8.0.0: {}
|
||||
entities@8.1.0: {}
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
events-universal@1.0.1:
|
||||
dependencies:
|
||||
bare-events: 2.9.1
|
||||
bare-events: 2.9.2
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
|
||||
@ -841,9 +834,9 @@ snapshots:
|
||||
|
||||
mdn-data@2.27.1: {}
|
||||
|
||||
minimatch@10.2.5:
|
||||
minimatch@10.2.6:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.9
|
||||
brace-expansion: 5.0.12
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
@ -890,7 +883,7 @@ snapshots:
|
||||
|
||||
readdir-glob@3.0.0:
|
||||
dependencies:
|
||||
minimatch: 10.2.5
|
||||
minimatch: 10.2.6
|
||||
|
||||
redis-errors@1.2.0: {}
|
||||
|
||||
@ -898,11 +891,11 @@ snapshots:
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
sax@1.6.0: {}
|
||||
sax@1.6.1: {}
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
source-map-js@1.2.2: {}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
dependencies:
|
||||
@ -915,7 +908,7 @@ snapshots:
|
||||
|
||||
statuses@2.0.2: {}
|
||||
|
||||
streamx@2.27.0:
|
||||
streamx@2.28.1:
|
||||
dependencies:
|
||||
events-universal: 1.0.1
|
||||
fast-fifo: 1.3.2
|
||||
@ -932,12 +925,12 @@ snapshots:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
tar-stream@3.2.0:
|
||||
tar-stream@3.2.2:
|
||||
dependencies:
|
||||
b4a: 1.8.1
|
||||
bare-fs: 4.7.2
|
||||
b4a: 1.9.0
|
||||
bare-fs: 4.8.2
|
||||
fast-fifo: 1.3.2
|
||||
streamx: 2.27.0
|
||||
streamx: 2.28.1
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- bare-buffer
|
||||
@ -945,14 +938,14 @@ snapshots:
|
||||
|
||||
teex@1.0.1:
|
||||
dependencies:
|
||||
streamx: 2.27.0
|
||||
streamx: 2.28.1
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
text-decoder@1.2.7:
|
||||
dependencies:
|
||||
b4a: 1.8.1
|
||||
b4a: 1.9.0
|
||||
transitivePeerDependencies:
|
||||
- react-native-b4a
|
||||
|
||||
@ -968,7 +961,7 @@ snapshots:
|
||||
|
||||
xml-js@1.6.11:
|
||||
dependencies:
|
||||
sax: 1.6.0
|
||||
sax: 1.6.1
|
||||
|
||||
xregexp@5.1.3:
|
||||
dependencies:
|
||||
|
||||
@ -59,15 +59,15 @@
|
||||
"@penpot/tokenscript": "link:packages/tokenscript",
|
||||
"@penpot/ua-parser": "penpot/ua-parser#1.0.0",
|
||||
"@playwright/test": "1.63.0",
|
||||
"@storybook/addon-docs": "10.6.0",
|
||||
"@storybook/addon-themes": "10.6.0",
|
||||
"@storybook/addon-vitest": "10.6.0",
|
||||
"@storybook/react-vite": "10.6.0",
|
||||
"@storybook/addon-docs": "10.6.1",
|
||||
"@storybook/addon-themes": "10.6.1",
|
||||
"@storybook/addon-vitest": "10.6.1",
|
||||
"@storybook/react-vite": "10.6.1",
|
||||
"@tokens-studio/sd-transforms": "2.0.3",
|
||||
"@types/node": "^26.6.3",
|
||||
"@vitest/browser": "5.0.2",
|
||||
"@vitest/browser-playwright": "5.0.2",
|
||||
"@vitest/coverage-v8": "5.0.2",
|
||||
"@types/node": "^26.6.4",
|
||||
"@vitest/browser": "5.0.3",
|
||||
"@vitest/browser-playwright": "5.0.3",
|
||||
"@vitest/coverage-v8": "5.0.3",
|
||||
"@zip.js/zip.js": "2.8.34",
|
||||
"autoprefixer": "^10.6.1",
|
||||
"compression": "^1.8.2",
|
||||
@ -75,17 +75,17 @@
|
||||
"date-fns": "^4.4.0",
|
||||
"esbuild": "^0.28.2",
|
||||
"eventsource-parser": "^4.1.1",
|
||||
"express": "^5.1.0",
|
||||
"express": "^5.2.1",
|
||||
"fancy-log": "^2.0.0",
|
||||
"getopts": "^2.3.0",
|
||||
"gettext-parser": "^9.1.1",
|
||||
"highlight.js": "^11.12.0",
|
||||
"js-beautify": "^2.0.3",
|
||||
"jsdom": "^30.1.1",
|
||||
"jsdom": "^30.1.2",
|
||||
"lodash": "^4.18.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"map-stream": "0.0.7",
|
||||
"marked": "^18.0.14",
|
||||
"marked": "^18.1.0",
|
||||
"mkdirp": "^3.0.1",
|
||||
"mustache": "^4.2.0",
|
||||
"nodemon": "^3.1.14",
|
||||
@ -93,7 +93,7 @@
|
||||
"opentype.js": "^2.0.0",
|
||||
"p-limit": "^7.3.3",
|
||||
"playwright": "1.63.0",
|
||||
"postcss": "^8.5.28",
|
||||
"postcss": "^8.5.29",
|
||||
"postcss-clean": "^1.2.2",
|
||||
"postcss-modules": "^9.0.1",
|
||||
"postcss-scss": "^4.0.9",
|
||||
@ -107,24 +107,24 @@
|
||||
"react-virtualized": "^9.22.6",
|
||||
"rimraf": "^6.1.3",
|
||||
"rxjs": "8.0.0-alpha.14",
|
||||
"sass": "^1.105.0",
|
||||
"sass-embedded": "^1.105.0",
|
||||
"sass": "^1.105.1",
|
||||
"sass-embedded": "^1.105.1",
|
||||
"sax": "^1.6.1",
|
||||
"scheduler": "^0.28.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"storybook": "10.6.0",
|
||||
"storybook": "10.6.1",
|
||||
"style-dictionary": "5.5.5",
|
||||
"stylelint": "^17.15.0",
|
||||
"stylelint": "^17.16.0",
|
||||
"stylelint-config-standard-scss": "^17.0.0",
|
||||
"stylelint-plugin-logical-css": "^2.1.0",
|
||||
"stylelint-scss": "^7.3.0",
|
||||
"svg-sprite": "^2.0.4",
|
||||
"tdigest": "^0.1.3",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"typescript": "^6.0.2",
|
||||
"vite": "^8.3.1",
|
||||
"vitest": "^5.0.2",
|
||||
"wait-on": "^9.1.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.3.3",
|
||||
"vitest": "^5.0.3",
|
||||
"wait-on": "^9.5.1",
|
||||
"watcher": "^2.3.1",
|
||||
"workerpool": "^10.0.3",
|
||||
"xregexp": "^5.1.3"
|
||||
|
||||
@ -25,21 +25,17 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^8.0.6",
|
||||
"@babel/preset-react": "^8.0.1",
|
||||
"@storybook/react": "10.6.0",
|
||||
"@storybook/react-vite": "10.6.0",
|
||||
"@storybook/react": "10.6.1",
|
||||
"@storybook/react-vite": "10.6.1",
|
||||
"@testing-library/dom": "10.4.2",
|
||||
"@testing-library/react": "16.3.3",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"@vitejs/plugin-react": "^6.1.1",
|
||||
"@vitejs/plugin-react": "^6.1.2",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "7.1.1",
|
||||
"react-compiler-runtime": "^1.0.0",
|
||||
"storybook": "10.6.0",
|
||||
"vite-plugin-dts": "^5.1.1"
|
||||
"storybook": "10.6.1",
|
||||
"vite-plugin-dts": "^5.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-aria-components": "^1.20.0"
|
||||
|
||||
@ -143,11 +143,11 @@ export class ShortcutsPage extends BaseWebSocketPage {
|
||||
this.searchInput = page.getByRole("textbox", { name: /shortcuts/i });
|
||||
|
||||
this.allTab = page.getByRole("tab", { name: "All" });
|
||||
this.personalizedTab = page.getByRole("tab", { name: "Personalized" });
|
||||
this.personalizedTab = page.getByRole("tab", { name: "Custom" });
|
||||
this.disabledTab = page.getByRole("tab", { name: "Disabled" });
|
||||
|
||||
this.restoreAllButton = page.getByRole("button", {
|
||||
name: /restore all/i,
|
||||
name: /reset all/i,
|
||||
});
|
||||
|
||||
this.importExportButton = page.getByRole("button", {
|
||||
@ -194,7 +194,7 @@ export class ShortcutsPage extends BaseWebSocketPage {
|
||||
name: new RegExp(`Edit ${shortcutName}`, "i"),
|
||||
});
|
||||
await button.click();
|
||||
const recordingArea = this.page.getByText("Press the key combination");
|
||||
const recordingArea = this.page.getByText("Press the new keys");
|
||||
await expect(recordingArea).toBeVisible();
|
||||
await recordingArea.focus();
|
||||
}
|
||||
@ -265,7 +265,7 @@ export class ShortcutsPage extends BaseWebSocketPage {
|
||||
async restoreAllShortcuts() {
|
||||
await this.restoreAllButton.click();
|
||||
const confirmButton = this.page.getByRole("button", {
|
||||
name: "Restore",
|
||||
name: "Reset",
|
||||
exact: true,
|
||||
});
|
||||
await confirmButton.click();
|
||||
@ -307,7 +307,8 @@ export class ShortcutsPage extends BaseWebSocketPage {
|
||||
|
||||
async confirmImportApply() {
|
||||
const applyButton = this.page.getByRole("button", {
|
||||
name: /apply/i,
|
||||
name: "Import",
|
||||
exact: true,
|
||||
});
|
||||
await applyButton.click();
|
||||
}
|
||||
|
||||
@ -25,12 +25,12 @@ test.describe("Shortcuts Settings Page", () => {
|
||||
await expect(shortcutsPage.personalizedTab).toBeVisible();
|
||||
await expect(shortcutsPage.disabledTab).toBeVisible();
|
||||
|
||||
await shortcutsPage.clickTab("Personalized");
|
||||
await shortcutsPage.clickTab("Custom");
|
||||
await expect(shortcutsPage.personalizedTab).toHaveAttribute(
|
||||
"aria-selected",
|
||||
"true",
|
||||
);
|
||||
const personalizedPlacehonder = page.getByText(/Head to All to start/i);
|
||||
const personalizedPlacehonder = page.getByText(/No custom shortcuts yet/i);
|
||||
await expect(personalizedPlacehonder).toBeVisible();
|
||||
|
||||
await shortcutsPage.clickTab("Disabled");
|
||||
@ -38,7 +38,7 @@ test.describe("Shortcuts Settings Page", () => {
|
||||
"aria-selected",
|
||||
"true",
|
||||
);
|
||||
const disabledPlaceholder = page.getByText(/There are not disabled/i);
|
||||
const disabledPlaceholder = page.getByText(/No disabled shortcuts/i);
|
||||
await expect(disabledPlaceholder).toBeVisible();
|
||||
|
||||
await shortcutsPage.clickTab("All");
|
||||
@ -81,7 +81,7 @@ test.describe("Shortcut Import", () => {
|
||||
|
||||
await shortcutsPage.expectShortcutCustomized("Align bottom");
|
||||
|
||||
await shortcutsPage.clickTab("Personalized");
|
||||
await shortcutsPage.clickTab("Custom");
|
||||
await shortcutsPage.searchForShortcut("Align bottom");
|
||||
await shortcutsPage.expectShortcutVisible("Align bottom");
|
||||
});
|
||||
@ -93,7 +93,9 @@ test.describe("Shortcut Import", () => {
|
||||
await shortcutsPage.importShortcutsRaw("{invalid");
|
||||
|
||||
await expect(
|
||||
page.getByRole("alert").filter({ hasText: /Invalid data/i }),
|
||||
page
|
||||
.getByRole("alert")
|
||||
.filter({ hasText: /not a valid shortcuts export/i }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@ -106,7 +108,9 @@ test.describe("Shortcut Import", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.getByRole("alert").filter({ hasText: /Invalid data/i }),
|
||||
page
|
||||
.getByRole("alert")
|
||||
.filter({ hasText: /not a valid shortcuts export/i }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@ -121,7 +125,9 @@ test.describe("Shortcut Import", () => {
|
||||
await shortcutsPage.confirmImportApply();
|
||||
|
||||
await expect(
|
||||
page.getByRole("alert").filter({ hasText: /Invalid data/i }),
|
||||
page
|
||||
.getByRole("alert")
|
||||
.filter({ hasText: /not a valid shortcuts export/i }),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
@ -134,7 +140,9 @@ test.describe("Shortcut Import", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.getByRole("alert").filter({ hasText: /Invalid data/i }),
|
||||
page
|
||||
.getByRole("alert")
|
||||
.filter({ hasText: /not a valid shortcuts export/i }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@ -213,7 +221,7 @@ test.describe("Shortcut Conflict Detection", () => {
|
||||
await shortcutsPage.pressKey("Alt+a");
|
||||
|
||||
await expect(
|
||||
page.getByRole("alert").filter({ hasText: /Combination assigned to/i }),
|
||||
page.getByRole("alert").filter({ hasText: /Already assigned to/i }),
|
||||
).toBeVisible();
|
||||
|
||||
await shortcutsPage.saveShortcut();
|
||||
@ -259,7 +267,7 @@ test.describe("Shortcut Reset", () => {
|
||||
await shortcutsPage.resetShortcut("Align bottom");
|
||||
|
||||
await expect(
|
||||
page.getByText(/If you save, this shortcut will return/i),
|
||||
page.getByText(/Saving will reset this shortcut/i),
|
||||
).toBeVisible();
|
||||
|
||||
await shortcutsPage.saveShortcut();
|
||||
@ -317,7 +325,7 @@ test.describe("Duplicate Shortcut Prevention", () => {
|
||||
await shortcutsPage.pressKey("Control+y");
|
||||
|
||||
await expect(
|
||||
page.getByRole("alert").filter({ hasText: /Combination assigned to/i }),
|
||||
page.getByRole("alert").filter({ hasText: /Already assigned to/i }),
|
||||
).toBeVisible();
|
||||
|
||||
await shortcutsPage.saveShortcut();
|
||||
|
||||
@ -298,6 +298,83 @@ test.describe("BUG 10910 - Text is not replaced when there is a selection", () =
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("BUG 12187 - Typing after a triple-click selection", () => {
|
||||
// Creates a text and enters the editor again, so the input surface is fresh.
|
||||
async function editNewText(page, text) {
|
||||
const workspace = new WasmWorkspacePage(page, { textEditor: true });
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.goToWorkspace();
|
||||
await workspace.waitForFirstRender();
|
||||
|
||||
await workspace.createAutoWidthTextShape(200, 150, text);
|
||||
await workspace.textEditor.stopEditing();
|
||||
await workspace.textEditor.startEditing();
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
async function tripleClickText(page) {
|
||||
await page
|
||||
.getByTestId("text-editor-container")
|
||||
.click({ position: { x: 10, y: 8 }, clickCount: 3 });
|
||||
}
|
||||
|
||||
// Some browsers (Chrome on Linux) select the whole input surface on a
|
||||
// triple-click. That selection is not editable, so typing fires no input.
|
||||
async function selectWholeInputSurface(page) {
|
||||
await page.evaluate(() => {
|
||||
const range = document.createRange();
|
||||
range.selectNode(document.getElementById("text-editor-wasm-input"));
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
});
|
||||
}
|
||||
|
||||
test("Typing over a triple-click selection replaces it", async ({ page }) => {
|
||||
const workspace = await editNewText(page, "Hello brave new world");
|
||||
|
||||
await tripleClickText(page);
|
||||
await page.keyboard.type("ok");
|
||||
|
||||
await workspace.textEditor.stopEditing();
|
||||
|
||||
await workspace.layers.getByTestId("layer-row").first().click();
|
||||
await workspace.waitForSelectedShapeName("ok");
|
||||
});
|
||||
|
||||
test("Typing works when the browser selects the whole input surface", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = await editNewText(page, "Hello brave new world");
|
||||
|
||||
await tripleClickText(page);
|
||||
await selectWholeInputSurface(page);
|
||||
await page.keyboard.type("ok");
|
||||
|
||||
await workspace.textEditor.stopEditing();
|
||||
|
||||
await workspace.layers.getByTestId("layer-row").first().click();
|
||||
await workspace.waitForSelectedShapeName("ok");
|
||||
});
|
||||
|
||||
test("Typing works after deleting a whole input surface selection", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = await editNewText(page, "Hello brave new world");
|
||||
|
||||
await tripleClickText(page);
|
||||
await selectWholeInputSurface(page);
|
||||
await page.keyboard.press("Backspace");
|
||||
await page.keyboard.type("ok");
|
||||
|
||||
await workspace.textEditor.stopEditing();
|
||||
|
||||
await workspace.layers.getByTestId("layer-row").first().click();
|
||||
await workspace.waitForSelectedShapeName("ok");
|
||||
});
|
||||
});
|
||||
|
||||
test("BUG 10531 - Entering the editor auto-selects the whole text", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
4131
frontend/pnpm-lock.yaml
generated
4131
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -34,3 +34,7 @@ overrides:
|
||||
postcss@<8.5.10: ^8.5.10
|
||||
yaml@>=2.0.0 <2.8.3: ^2.8.3
|
||||
playwright@>=1.61.1 <2.0.0-0: "1.62.1"
|
||||
postcss@<=8.5.11: ^8.5.12
|
||||
postcss@<=8.5.17: ^8.5.18
|
||||
postcss@<=8.5.22: ^8.5.23
|
||||
shell-quote@>=1.8.4 <1.11.0: ^1.11.0
|
||||
|
||||
@ -467,13 +467,10 @@
|
||||
(assoc-in [:strokes index] stroke))))
|
||||
options))))))))
|
||||
|
||||
;; --- Per-side controls expansion (Design tab UI state)
|
||||
;; --- Per-side controls expansion
|
||||
|
||||
(defn toggle-stroke-per-side
|
||||
"Toggle the per-side width controls of the stroke at `index` for the shapes
|
||||
in `ids`. Purely ephemeral UI state: it lives in `:workspace-local` keyed by
|
||||
`[ids index]`, so it survives selecting another shape and coming back, but
|
||||
resets on reload."
|
||||
"Toggles per-side controls for stroke `index` on shapes `ids` in UI state."
|
||||
[ids index]
|
||||
(ptk/reify ::toggle-stroke-per-side
|
||||
ptk/UpdateEvent
|
||||
|
||||
@ -65,15 +65,18 @@
|
||||
[{:keys [objects id]}]
|
||||
(reduce-kv (fn [changes shape-id shape]
|
||||
(if (shape-has-invalid-font-family?? shape)
|
||||
(conj changes {:type :mod-obj
|
||||
:id shape-id
|
||||
:page-id id
|
||||
:operations [{:type :set
|
||||
:attr :content
|
||||
:val (fix-shape-content shape)}
|
||||
{:type :set
|
||||
:attr :position-data
|
||||
:val nil}]})
|
||||
(let [fixed-content (fix-shape-content shape)]
|
||||
(if (not= fixed-content (:content shape))
|
||||
(conj changes {:type :mod-obj
|
||||
:id shape-id
|
||||
:page-id id
|
||||
:operations [{:type :set
|
||||
:attr :content
|
||||
:val fixed-content}
|
||||
{:type :set
|
||||
:attr :position-data
|
||||
:val nil}]})
|
||||
changes))
|
||||
changes))
|
||||
[]
|
||||
objects))
|
||||
@ -82,8 +85,11 @@
|
||||
[fdata]
|
||||
(reduce-kv (fn [changes _ typography]
|
||||
(if (has-invalid-font-family? typography)
|
||||
(conj changes {:type :mod-typography
|
||||
:typography (fix-typography typography)})
|
||||
(let [fixed-typography (fix-typography typography)]
|
||||
(if (not= fixed-typography typography)
|
||||
(conj changes {:type :mod-typography
|
||||
:typography fixed-typography})
|
||||
changes))
|
||||
changes))
|
||||
[]
|
||||
(:typographies fdata)))
|
||||
|
||||
@ -37,13 +37,16 @@
|
||||
(declare change-edit-mode)
|
||||
|
||||
(defn start-created-path-edition
|
||||
[id]
|
||||
(ptk/reify ::start-created-path-edition
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwe/start-edition-mode id)
|
||||
(edition/start-path-edit id)
|
||||
(change-edit-mode :draw)))))
|
||||
"Edits a just created path in `edit-mode` (`:draw` by default)."
|
||||
([id]
|
||||
(start-created-path-edition id :draw))
|
||||
([id edit-mode]
|
||||
(ptk/reify ::start-created-path-edition
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwe/start-edition-mode id)
|
||||
(edition/start-path-edit id)
|
||||
(change-edit-mode edit-mode))))))
|
||||
|
||||
;; Draw-loop stop signals either restart the same path or exit drawing.
|
||||
|
||||
@ -434,11 +437,12 @@
|
||||
(when-let [content (dm/get-in state [:workspace-drawing :object :content])]
|
||||
(cond
|
||||
(and (> (count content) 1) restart?)
|
||||
(rx/of (common/finish-path)
|
||||
(clean-drawn-content)
|
||||
(setup-frame)
|
||||
(dwdc/handle-finish-drawing)
|
||||
(start-created-path-edition shape-id))
|
||||
(let [edit-mode (dm/get-in state [:workspace-local :edit-path shape-id :edit-mode])]
|
||||
(rx/of (common/finish-path)
|
||||
(clean-drawn-content)
|
||||
(setup-frame)
|
||||
(dwdc/handle-finish-drawing)
|
||||
(start-created-path-edition shape-id (d/nilv edit-mode :draw))))
|
||||
|
||||
(> (count content) 1)
|
||||
(rx/of (clean-drawn-content)
|
||||
@ -545,6 +549,10 @@
|
||||
state)))
|
||||
|
||||
(defn change-edit-mode
|
||||
"Switches the active path between `:draw` and `:move`.
|
||||
|
||||
A new path being drawn has no edition yet; switching it to `:move`
|
||||
finishes the drawing, which then reenters edition in `:move`."
|
||||
[mode]
|
||||
(ptk/reify ::change-edit-mode
|
||||
ptk/UpdateEvent
|
||||
@ -552,16 +560,20 @@
|
||||
(if-let [id (dm/get-in state [:workspace-local :edition])]
|
||||
(cond-> (d/update-in-when state [:workspace-local :edit-path id] assoc :edit-mode mode)
|
||||
(= mode :draw) (enter-draw-from-selected-node id))
|
||||
state))
|
||||
(cond-> state
|
||||
(and (= mode :move) (st/drawing? state))
|
||||
(assoc-in [:workspace-local :edit-path (st/get-path-id state) :edit-mode] :move))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [id (dm/get-in state [:workspace-local :edition])]
|
||||
(if-let [id (dm/get-in state [:workspace-local :edition])]
|
||||
(let [mode (dm/get-in state [:workspace-local :edit-path id :edit-mode])]
|
||||
(case mode
|
||||
:move (rx/of (common/finish-path))
|
||||
:draw (rx/of (start-draw-mode))
|
||||
(rx/empty)))))))
|
||||
(rx/empty)))
|
||||
(when (and (= mode :move) (st/drawing? state))
|
||||
(rx/of (common/finish-path)))))))
|
||||
|
||||
(defn reset-last-handler
|
||||
[]
|
||||
|
||||
@ -157,6 +157,7 @@
|
||||
:label (fn [] (tr "shortcuts.deselect-all"))
|
||||
:command (ds/c-mod "shift+a")
|
||||
:subsections [:path-editor]
|
||||
:overwrite true
|
||||
:fn #(st/emit! (drp/deselect-all))}
|
||||
|
||||
:flip-horizontal {:tooltip (ds/shift "H")
|
||||
|
||||
@ -245,7 +245,7 @@
|
||||
:section [:workspace]
|
||||
:fn #(update-attrs-when-no-readonly {:text-decoration "toggle-underline"})}
|
||||
|
||||
:line-through {:tooltip (ds/alt (ds/meta-shift "5"))
|
||||
:line-through {:tooltip (ds/alt (ds/shift "5"))
|
||||
:label (fn [] (tr "shortcuts.line-through"))
|
||||
:command "alt+shift+5"
|
||||
:subsections [:text-editor]
|
||||
|
||||
@ -140,6 +140,13 @@
|
||||
(u/query-string->map (:query uri)))})))
|
||||
|
||||
|
||||
(defn- legacy-query-token
|
||||
"Legacy root links (e.g. `#?template=<url>`) keep their params in a
|
||||
bare `#?…` fragment. Return them as a query token, or nil."
|
||||
[hash]
|
||||
(when (str/starts-with? hash "#?")
|
||||
(subs hash 1)))
|
||||
|
||||
(defn- store-session-params
|
||||
[{:keys [template plugin]}]
|
||||
(binding [storage/*sync* true]
|
||||
@ -226,7 +233,8 @@
|
||||
(let [location (.-location js/document)
|
||||
location-path (dm/str (.-origin location) (.-pathname location))
|
||||
valid-location? (= location-path (dm/str cf/public-uri))
|
||||
legacy-hash (.-hash location)]
|
||||
legacy-hash (.-hash location)
|
||||
legacy-token (legacy-query-token legacy-hash)]
|
||||
|
||||
(cond
|
||||
(not valid-location?)
|
||||
@ -242,6 +250,10 @@
|
||||
(st/emit! (rt/nav name params {::rt/replace true}))
|
||||
(on-query-navigate router token send-event-info?))
|
||||
|
||||
;; TODO(next-version): delete with `legacy-routes`.
|
||||
(some? legacy-token)
|
||||
(on-query-navigate router legacy-token send-event-info?)
|
||||
|
||||
:else
|
||||
(on-query-navigate router token send-event-info?))))
|
||||
|
||||
@ -251,7 +263,10 @@
|
||||
(subs token 1)
|
||||
(or token ""))
|
||||
query-params (u/query-string->map token-query)
|
||||
empty-token? (str/blank? token-query)
|
||||
;; Without a `screen` the token targets the app root; its params
|
||||
;; (e.g. `template`) are forwarded to the dashboard or kept for
|
||||
;; after login.
|
||||
root? (nil? (rt/get-query-param query-params :screen))
|
||||
match (rt/match router token)]
|
||||
(if (some? match)
|
||||
(handle-sso-error-and-navigate match send-event-info? (rt/get-current-href))
|
||||
@ -261,9 +276,13 @@
|
||||
;; on invitations workflows (and probably other cases).
|
||||
(->> (rp/cmd! :get-profile)
|
||||
(rx/mapcat (fn [profile]
|
||||
(->> (rp/cmd! :get-teams {})
|
||||
(rx/map (fn [teams]
|
||||
(assoc profile ::teams (into #{} (map :id) teams)))))))
|
||||
;; get-teams requires a session; anonymous users
|
||||
;; only need their params kept for after login.
|
||||
(if (= (:id profile) uuid/zero)
|
||||
(rx/of profile)
|
||||
(->> (rp/cmd! :get-teams {})
|
||||
(rx/map (fn [teams]
|
||||
(assoc profile ::teams (into #{} (map :id) teams))))))))
|
||||
(rx/subs! (fn [{:keys [id ::teams] :as profile}]
|
||||
(cond
|
||||
(= id uuid/zero)
|
||||
@ -271,7 +290,7 @@
|
||||
(store-session-params query-params)
|
||||
(st/emit! (rt/nav :auth-login)))
|
||||
|
||||
empty-token?
|
||||
root?
|
||||
(let [default-team-id (:default-team-id profile)
|
||||
last-team-id (dtm/get-last-team-id)
|
||||
team-id (if (contains? teams last-team-id)
|
||||
|
||||
@ -106,11 +106,11 @@
|
||||
[:thead
|
||||
[:tr {:class (stl/css :shortcuts-list-header)}
|
||||
[:th {:class (stl/css :shortcut-header-name)}
|
||||
(tr "restore-shortcuts.acction")]
|
||||
(tr "restore-shortcuts.action")]
|
||||
[:th {:class (stl/css :shortcut-header-command)}
|
||||
(tr "labels.current")]
|
||||
[:th {:class (stl/css :shortcut-header-command)}
|
||||
(tr "labels.import")]]]
|
||||
(tr "import-shortcuts.imported")]]]
|
||||
[:tbody {:class (stl/css :shortcuts-list-body)}
|
||||
(let [last-ctx* (volatile! nil)]
|
||||
(for [entry diff-entries]
|
||||
|
||||
@ -97,7 +97,7 @@
|
||||
[:thead
|
||||
[:tr {:class (stl/css :shortcuts-list-header)}
|
||||
[:th {:class (stl/css :shortcut-header-name)}
|
||||
(tr "restore-shortcuts.acction")]
|
||||
(tr "restore-shortcuts.action")]
|
||||
[:th {:class (stl/css :shortcut-header-command)}
|
||||
(tr "labels.current")]
|
||||
[:th {:class (stl/css :shortcut-header-command)}
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
[:> search-bar* {:on-change on-search-term-change
|
||||
:on-clear on-search-clear-click
|
||||
:value filter-term
|
||||
:placeholder (tr "shortcuts.title")
|
||||
:placeholder (tr "shortcuts.search")
|
||||
:icon-id i/search
|
||||
:auto-focus true}]
|
||||
(when (and show-restore-all has-custom-shortcuts)
|
||||
@ -50,7 +50,7 @@
|
||||
:on-click on-restore-all
|
||||
:class (stl/css :restore-all-button)
|
||||
:icon i/reload}
|
||||
(tr "dashboard.restore-all-deleted-button")])])
|
||||
(tr "shortcuts.reset-all")])])
|
||||
|
||||
(defn- filter-shortcuts-tree
|
||||
[tree shortcut-filter search-term]
|
||||
@ -457,9 +457,9 @@
|
||||
:imported-shortcuts shortcuts
|
||||
:custom-shortcuts custom-shortcuts
|
||||
:all-shortcuts-raw all-shortcuts-raw}))
|
||||
(st/emit! (ntf/error (tr "errors.invalid-data")))))
|
||||
(st/emit! (ntf/error (tr "import-shortcuts.invalid-file")))))
|
||||
(catch :default _
|
||||
(st/emit! (ntf/error (tr "errors.invalid-data"))))))))
|
||||
(st/emit! (ntf/error (tr "import-shortcuts.invalid-file"))))))))
|
||||
|
||||
(-> (mf/ref-val input-ref)
|
||||
(dom/set-value! "")))))]
|
||||
|
||||
@ -586,7 +586,7 @@
|
||||
:key command-translate}
|
||||
[:button {:on-click start-editing
|
||||
:disabled is-editing
|
||||
:aria-label (dm/str "Edit " command-translate)
|
||||
:aria-label (tr "shortcuts.edit" command-translate)
|
||||
:class (stl/css-case :shortcut-button-editable true
|
||||
:shortcut-button-editing is-editing)}
|
||||
[:span {:class (stl/css :command-name)
|
||||
|
||||
@ -107,6 +107,58 @@
|
||||
:token-sets token-sets
|
||||
:token-themes token-themes}))
|
||||
|
||||
(defn sort-linked-libraries
|
||||
"Given the linked libraries of a file, each with a `:connected-to`
|
||||
collection of the ids of the files that use it, return the libraries
|
||||
ordered as a tree: each library is followed by the libraries nested
|
||||
under it, marked with `:nested?` and `:parent-name`.
|
||||
|
||||
Direct libraries (`:is-indirect` false) and libraries not used by
|
||||
any other linked library are roots. Every library is returned
|
||||
exactly once, at any depth and even when the dependencies form a
|
||||
cycle."
|
||||
[libraries]
|
||||
(let [by-id (d/index-by :id libraries)
|
||||
sort-by-name (partial sort-by (comp str/lower :name))
|
||||
|
||||
parents
|
||||
(d/update-vals by-id
|
||||
(fn [{:keys [id connected-to]}]
|
||||
(into #{}
|
||||
(filter #(and (not= % id)
|
||||
(contains? by-id %)))
|
||||
connected-to)))
|
||||
|
||||
root?
|
||||
(fn [{:keys [id is-indirect]}]
|
||||
(or (false? is-indirect)
|
||||
(empty? (get parents id))))
|
||||
|
||||
children
|
||||
(fn [parent-id]
|
||||
(->> libraries
|
||||
(remove root?)
|
||||
(filter #(contains? (get parents (:id %)) parent-id))
|
||||
(sort-by-name)))
|
||||
|
||||
visit
|
||||
(fn visit [[result visited :as acc] library parent]
|
||||
(if (contains? visited (:id library))
|
||||
acc
|
||||
(reduce #(visit %1 %2 library)
|
||||
[(conj result (assoc library
|
||||
:nested? (some? parent)
|
||||
:parent-name (:name parent)))
|
||||
(conj visited (:id library))]
|
||||
(children (:id library)))))]
|
||||
|
||||
;; The second pass picks up libraries only reachable through a
|
||||
;; cycle, which have no root to hang from.
|
||||
(as-> [[] #{}] $
|
||||
(reduce #(visit %1 %2 nil) $ (sort-by-name (filter root? libraries)))
|
||||
(reduce #(visit %1 %2 nil) $ (sort-by-name libraries))
|
||||
(first $))))
|
||||
|
||||
(defn- describe-library
|
||||
[components-count graphics-count colors-count typography-count]
|
||||
(let [all-zero? (and (zero? components-count)
|
||||
@ -666,11 +718,6 @@
|
||||
dependencies (mf/with-memo [shared-libraries]
|
||||
(into {} (map (juxt :id :library-file-ids) (vals shared-libraries))))
|
||||
|
||||
library-names (mf/with-memo [shared-libraries]
|
||||
(into {} (map (fn [{:keys [id name]}]
|
||||
[id name])
|
||||
(vals shared-libraries))))
|
||||
|
||||
find-connected-to
|
||||
(mf/use-fn
|
||||
(mf/deps dependencies)
|
||||
@ -679,36 +726,10 @@
|
||||
(keep (fn [[k v]] (when (contains? v library-id) k))))))
|
||||
|
||||
linked-libraries
|
||||
(mf/with-memo [linked-libraries find-connected-to library-names]
|
||||
(let [libs
|
||||
(->> (vals linked-libraries)
|
||||
(map #(assoc % :connected-to (find-connected-to (:id %)))))
|
||||
|
||||
linked-ids
|
||||
(into #{} (map :id) libs)
|
||||
|
||||
sort-by-name
|
||||
(partial sort-by (comp str/lower :name))
|
||||
|
||||
parent-id
|
||||
(fn [{:keys [connected-to]}]
|
||||
(first (filter linked-ids connected-to)))
|
||||
|
||||
nested-by-parent
|
||||
(->> libs
|
||||
(filter parent-id)
|
||||
(map #(assoc %
|
||||
:nested? true
|
||||
:parent-name (library-names (parent-id %))))
|
||||
(group-by parent-id)
|
||||
(d/mapm (fn [_ v] (sort-by-name v))))]
|
||||
|
||||
(->> libs
|
||||
(remove parent-id)
|
||||
(map #(assoc % :nested? false))
|
||||
(sort-by-name)
|
||||
(mapcat (fn [{:keys [id] :as library}]
|
||||
(cons library (get nested-by-parent id [])))))))
|
||||
(mf/with-memo [linked-libraries find-connected-to]
|
||||
(->> (vals linked-libraries)
|
||||
(map #(assoc % :connected-to (find-connected-to (:id %))))
|
||||
(sort-linked-libraries)))
|
||||
|
||||
linked-libraries-ids
|
||||
(mf/with-memo [linked-libraries]
|
||||
|
||||
@ -36,6 +36,9 @@
|
||||
(def path-preview-dasharray 4)
|
||||
(def path-snap-stroke-width 1)
|
||||
|
||||
;; Stand-in for shapes that reach the editor without content.
|
||||
(def ^:private empty-content (path/content nil))
|
||||
|
||||
(def accent-color "var(--color-accent-tertiary)")
|
||||
(def secondary-color "var(--color-accent-quaternary)")
|
||||
(def black-color "var(--app-black)")
|
||||
@ -502,7 +505,7 @@
|
||||
(some? drag-handler))
|
||||
|
||||
base-content
|
||||
(get shape :content)
|
||||
(or (get shape :content) empty-content)
|
||||
|
||||
;; Cache segment midpoints used by insertion previews.
|
||||
insertion-mid-points
|
||||
|
||||
@ -72,6 +72,27 @@
|
||||
(doseq [[mime value] items]
|
||||
(.setData data mime value)))
|
||||
|
||||
(defn- collapse-input-caret
|
||||
"Replaces the browser selection with a collapsed caret at the end of `node`."
|
||||
[^js node]
|
||||
(when-let [sel (.getSelection js/window)]
|
||||
(let [range (.createRange js/document)]
|
||||
(.selectNodeContents range node)
|
||||
(.collapse range false)
|
||||
(.removeAllRanges sel)
|
||||
(.addRange sel range))))
|
||||
|
||||
(defn- ensure-input-caret
|
||||
"Collapses the browser selection into the capture surface when it leaves it:
|
||||
such a selection is not editable, so typed keys would fire no `input`."
|
||||
[^js node]
|
||||
(when (some? node)
|
||||
(let [sel (.getSelection js/window)]
|
||||
(when-not (and (some? sel)
|
||||
(.contains node (.-anchorNode sel))
|
||||
(.contains node (.-focusNode sel)))
|
||||
(collapse-input-caret node)))))
|
||||
|
||||
(defn- reset-input-node
|
||||
"Empties the contenteditable capture surface and restores a collapsed caret
|
||||
inside it.
|
||||
@ -87,12 +108,7 @@
|
||||
(set! (.-textContent node) "")
|
||||
(when (not= (.-activeElement js/document) node)
|
||||
(.focus node))
|
||||
(when-let [sel (.getSelection js/window)]
|
||||
(let [range (.createRange js/document)]
|
||||
(.selectNodeContents range node)
|
||||
(.collapse range true)
|
||||
(.removeAllRanges sel)
|
||||
(.addRange sel range)))))
|
||||
(collapse-input-caret node)))
|
||||
|
||||
(defn- keep-input-alive
|
||||
"Keeps the capture surface able to receive further input WITHOUT clearing it.
|
||||
@ -372,8 +388,10 @@
|
||||
(text-editor/text-editor-move-cursor 5 ctrl? shift?)
|
||||
(wasm.api/render-text-editor-overlay!))
|
||||
|
||||
;; Let contenteditable handle text input via on-input
|
||||
:else nil)))))
|
||||
;; Let contenteditable handle text input via on-input, which
|
||||
;; needs an editable browser selection.
|
||||
:else
|
||||
(ensure-input-caret (mf/ref-val contenteditable-ref)))))))
|
||||
|
||||
;; Native `beforeinput` listener (see the use-effect that registers it).
|
||||
;; We use the native event, not React's synthetic `onBeforeInput`, because
|
||||
@ -490,6 +508,7 @@
|
||||
(triple-click? native-event)
|
||||
(do
|
||||
(wasm.api/text-editor-select-paragraph off-pt)
|
||||
(ensure-input-caret (mf/ref-val contenteditable-ref))
|
||||
(wasm.api/render-text-editor-overlay!))
|
||||
|
||||
;; `dblclick` selects the word right after. Shift+click still goes
|
||||
@ -509,6 +528,7 @@
|
||||
(let [native-event (dom/event->native-event event)
|
||||
off-pt (dom/get-offset-position native-event)]
|
||||
(wasm.api/text-editor-select-word-boundary off-pt)
|
||||
(ensure-input-caret (mf/ref-val contenteditable-ref))
|
||||
(wasm.api/render-text-editor-overlay!))))
|
||||
|
||||
on-context-menu
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
@ -260,18 +261,30 @@
|
||||
[:span {:class (stl/css :radio-text)}
|
||||
text]])
|
||||
|
||||
(def ^:private default-search-state
|
||||
{:show-search false
|
||||
:find-replace-mode? false
|
||||
:search-scope :layers
|
||||
:show-menu false
|
||||
:search-text ""
|
||||
:replace-text ""
|
||||
:filters #{}
|
||||
:num-items 100
|
||||
:current-match-idx 0})
|
||||
|
||||
;; Per-file, session-scoped (in-memory only) so an open search and its
|
||||
;; filters survive switching between the sidebar tabs, which unmounts the
|
||||
;; layers panel, without leaking across files or persisting across reloads.
|
||||
(defonce ^:private session-search*
|
||||
(atom {}))
|
||||
|
||||
(defn use-search
|
||||
[page objects]
|
||||
(let [state* (mf/use-state
|
||||
#(do {:show-search false
|
||||
:find-replace-mode? false
|
||||
:search-scope :layers
|
||||
:show-menu false
|
||||
:search-text ""
|
||||
:replace-text ""
|
||||
:filters #{}
|
||||
:num-items 100
|
||||
:current-match-idx 0}))
|
||||
(let [file-id (mf/use-ctx ctx/current-file-id)
|
||||
state* (mf/use-state
|
||||
#(-> default-search-state
|
||||
(merge (get @session-search* file-id))
|
||||
(assoc :show-menu false)))
|
||||
layers-search (mf/deref ref:layers-search)
|
||||
state (deref state*)
|
||||
current-filters (:filters state)
|
||||
@ -522,6 +535,9 @@
|
||||
(events/unlistenByKey key1)
|
||||
(events/unlistenByKey key2))))
|
||||
|
||||
(mf/with-effect [file-id state]
|
||||
(swap! session-search* assoc file-id state))
|
||||
|
||||
(mf/with-effect [layers-search]
|
||||
(if-let [{:keys [open? find-replace-mode? scope]} layers-search]
|
||||
(when open?
|
||||
@ -821,6 +837,8 @@
|
||||
[filtered-objects show-more filter-component]
|
||||
(use-search page objects)
|
||||
|
||||
searching? (some? filtered-objects)
|
||||
|
||||
intersection-callback
|
||||
(fn [entries]
|
||||
(when (and (.-isIntersecting (first entries)) (some? show-more))
|
||||
@ -864,8 +882,12 @@
|
||||
(mf/use-fn
|
||||
#(st/emit! (dw/toggle-focus-mode)))]
|
||||
|
||||
(sc/use-restore-scroll scroll-store :layers page-id tree-ref)
|
||||
(sc/use-restore-scroll scroll-store :layers-search page-id search-ref)
|
||||
(sc/use-restore-scroll scroll-store :layers page-id tree-ref (not searching?))
|
||||
(sc/use-restore-scroll scroll-store :layers-search page-id search-ref searching?)
|
||||
|
||||
(mf/with-effect [page-id searching?]
|
||||
(when-not ^boolean searching?
|
||||
(sc/forget-scroll! scroll-store [:layers-search page-id])))
|
||||
|
||||
[:div {:id "layers"
|
||||
:class (stl/css :layers)
|
||||
|
||||
@ -50,10 +50,11 @@
|
||||
:show-content
|
||||
:hide-in-viewer
|
||||
|
||||
;; Needed to disable/enable width/height
|
||||
;; Needed to disable/enable width/height and x/y
|
||||
;; otherwise the memo will not detect changes
|
||||
:layout-item-h-sizing
|
||||
:layout-item-v-sizing])
|
||||
:layout-item-v-sizing
|
||||
:layout-item-absolute])
|
||||
|
||||
(def ^:private generic-options
|
||||
#{:size :position :rotation})
|
||||
@ -161,6 +162,8 @@
|
||||
(get n-values :layout-item-h-sizing))
|
||||
(identical? (get o-values :layout-item-v-sizing)
|
||||
(get n-values :layout-item-v-sizing))
|
||||
(identical? (get o-values :layout-item-absolute)
|
||||
(get n-values :layout-item-absolute))
|
||||
(identical? (get o-values :points)
|
||||
(get n-values :points))
|
||||
(identical? (get o-values :selrect)
|
||||
@ -396,10 +399,8 @@
|
||||
(mf/deps ids shapes)
|
||||
(fn [value attr]
|
||||
(if (or (string? value) (number? value))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/update-dimensions-coalesced ids attr value))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/apply-token-from-input {:token (first value)
|
||||
(st/emit! (udw/update-dimensions-coalesced ids attr value))
|
||||
(st/emit! (dwta/apply-token-from-input {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids})))))
|
||||
|
||||
@ -416,11 +417,8 @@
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(if (or (string? value) (number? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(st/emit! (udw/update-positions ids {attr value})))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/apply-token-from-input {:token (first value)
|
||||
(st/emit! (udw/update-positions ids {attr value}))
|
||||
(st/emit! (dwta/apply-token-from-input {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids})))))
|
||||
|
||||
@ -430,10 +428,8 @@
|
||||
(fn [value]
|
||||
(if (or (string? value) (number? value))
|
||||
(let [value (fixed-decimal-value value)]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(st/emit! (udw/increase-rotation-coalesced ids value)))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/apply-token-from-input {:token (first value)
|
||||
(st/emit! (dwta/apply-token-from-input {:token (first value)
|
||||
:attrs #{:rotation}
|
||||
:shape-ids ids})))))
|
||||
|
||||
|
||||
@ -27,6 +27,12 @@
|
||||
(when-let [target (dom/get-target event)]
|
||||
(swap! store* assoc k (.-scrollTop target))))
|
||||
|
||||
(defn forget-scroll!
|
||||
"Drops the position saved under k, so the next restore starts at the
|
||||
top. Plain swap!, never triggers renders."
|
||||
[store* k]
|
||||
(swap! store* dissoc k))
|
||||
|
||||
(defn needs-restore-retry?
|
||||
"Pure decision for the restore loop: keep retrying while the content
|
||||
height is still changing (lazily rendered lists) and attempts remain."
|
||||
@ -59,12 +65,19 @@
|
||||
panel or id change. Retries while the content height keeps changing so
|
||||
deep positions in lazily rendered lists are not clamped to the first
|
||||
chunk. With no saved position the node goes to the top. Missing node is
|
||||
a silent no-op."
|
||||
[store* panel id node-ref]
|
||||
(mf/with-effect [panel id]
|
||||
(let [raf* (volatile! nil)]
|
||||
(when-let [node (mf/ref-val node-ref)]
|
||||
(restore-position! node (get @store* [panel id]) raf*))
|
||||
(fn []
|
||||
(when-some [raf @raf*]
|
||||
(tm/cancel-af! raf))))))
|
||||
a silent no-op.
|
||||
|
||||
`active?` is for panels that swap lists in place: pass whether the list
|
||||
behind node-ref is shown, so its position is restored again each time
|
||||
it comes back. Nothing is restored while it is false."
|
||||
([store* panel id node-ref]
|
||||
(use-restore-scroll store* panel id node-ref true))
|
||||
([store* panel id node-ref active?]
|
||||
(mf/with-effect [panel id active?]
|
||||
(let [raf* (volatile! nil)]
|
||||
(when ^boolean active?
|
||||
(when-let [node (mf/ref-val node-ref)]
|
||||
(restore-position! node (get @store* [panel id]) raf*)))
|
||||
(fn []
|
||||
(when-some [raf @raf*]
|
||||
(tm/cancel-af! raf)))))))
|
||||
|
||||
@ -136,7 +136,7 @@
|
||||
[:> search-bar* {:on-change on-search-term-change-2
|
||||
:on-clear on-search-clear-click
|
||||
:value filter-term
|
||||
:placeholder (tr "shortcuts.title")
|
||||
:placeholder (tr "shortcuts.search")
|
||||
:icon-id i/search
|
||||
:auto-focus true}]]
|
||||
|
||||
|
||||
@ -35,6 +35,17 @@
|
||||
(update fill :fill-opacity #(* (or % 1) edit-fill-opacity)))
|
||||
fills))
|
||||
|
||||
(defn draw-area-kind
|
||||
"Returns the overlay to render for the drawing object `shape`:
|
||||
`:path`, `:curve`, `:generic` or nil. Non-path objects always get the
|
||||
generic overlay, because the active tool can change while a box
|
||||
drawing is still in progress."
|
||||
[tool shape]
|
||||
(cond
|
||||
(not= :path (dm/get-prop shape :type)) :generic
|
||||
(= tool :path) :path
|
||||
(= tool :curve) :curve))
|
||||
|
||||
(defn path-edit-shape
|
||||
"Builds the path shape rendered during editing."
|
||||
[drawing-obj stored]
|
||||
@ -98,15 +109,8 @@
|
||||
[:g {:style {:pointer-events "none"}}
|
||||
[:& shapes/shape-wrapper {:shape render-shape}]]
|
||||
|
||||
(cond
|
||||
(= tool :path)
|
||||
[:> path-draw-area* props]
|
||||
|
||||
(= tool :curve)
|
||||
[:& path-shape {:shape shape :zoom zoom}]
|
||||
|
||||
(= (:type shape) :path)
|
||||
nil
|
||||
|
||||
:else
|
||||
[:> generic-draw-area* props])]))
|
||||
(case (draw-area-kind tool shape)
|
||||
:path [:> path-draw-area* props]
|
||||
:curve [:& path-shape {:shape shape :zoom zoom}]
|
||||
:generic [:> generic-draw-area* props]
|
||||
nil)]))
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.stroke :as cts]
|
||||
[app.plugins.image-data :refer [create-image-data]]
|
||||
[app.util.object :as obj]))
|
||||
|
||||
@ -211,8 +212,9 @@
|
||||
(defn format-stroke
|
||||
[{:keys [stroke-color stroke-color-ref-file stroke-color-ref-id
|
||||
stroke-opacity stroke-style stroke-width stroke-alignment
|
||||
stroke-cap-start stroke-cap-end stroke-color-gradient
|
||||
stroke-image] :as stroke}]
|
||||
stroke-width-top stroke-width-right stroke-width-bottom
|
||||
stroke-width-left stroke-cap-start stroke-cap-end
|
||||
stroke-color-gradient stroke-image] :as stroke}]
|
||||
|
||||
(when (some? stroke)
|
||||
(obj/without-empty
|
||||
@ -222,6 +224,11 @@
|
||||
:strokeOpacity stroke-opacity
|
||||
:strokeStyle (format-key stroke-style)
|
||||
:strokeWidth stroke-width
|
||||
:strokeWidthType (format-key (cts/width-type stroke))
|
||||
:strokeWidthTop stroke-width-top
|
||||
:strokeWidthRight stroke-width-right
|
||||
:strokeWidthBottom stroke-width-bottom
|
||||
:strokeWidthLeft stroke-width-left
|
||||
:strokeAlignment (format-key stroke-alignment)
|
||||
:strokeCapStart (format-key stroke-cap-start)
|
||||
:strokeCapEnd (format-key stroke-cap-end)
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
[app.common.json :as json]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.stroke :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]))
|
||||
@ -223,18 +224,34 @@
|
||||
(defn parse-stroke
|
||||
[^js stroke]
|
||||
(when (some? stroke)
|
||||
(d/without-nils
|
||||
{:stroke-color (-> (obj/get stroke "strokeColor") parse-hex)
|
||||
:stroke-color-ref-file (-> (obj/get stroke "strokeColorRefFile") parse-id)
|
||||
:stroke-color-ref-id (-> (obj/get stroke "strokeColorRefId") parse-id)
|
||||
:stroke-opacity (obj/get stroke "strokeOpacity")
|
||||
:stroke-style (-> (obj/get stroke "strokeStyle") parse-keyword)
|
||||
:stroke-width (obj/get stroke "strokeWidth")
|
||||
:stroke-alignment (-> (obj/get stroke "strokeAlignment") parse-keyword)
|
||||
:stroke-cap-start (-> (obj/get stroke "strokeCapStart") parse-keyword)
|
||||
:stroke-cap-end (-> (obj/get stroke "strokeCapEnd") parse-keyword)
|
||||
:stroke-color-gradient (-> (obj/get stroke "strokeColorGradient") parse-gradient)
|
||||
:stroke-image (-> (obj/get stroke "strokeImage") parse-image-data)})))
|
||||
(let [raw (obj/get stroke "$state")
|
||||
parsed (d/without-nils
|
||||
{:stroke-color (-> (obj/get stroke "strokeColor") parse-hex)
|
||||
:stroke-color-ref-file (-> (obj/get stroke "strokeColorRefFile") parse-id)
|
||||
:stroke-color-ref-id (-> (obj/get stroke "strokeColorRefId") parse-id)
|
||||
:stroke-opacity (obj/get stroke "strokeOpacity")
|
||||
:stroke-style (-> (obj/get stroke "strokeStyle") parse-keyword)
|
||||
:stroke-alignment (-> (obj/get stroke "strokeAlignment") parse-keyword)
|
||||
:stroke-cap-start (-> (obj/get stroke "strokeCapStart") parse-keyword)
|
||||
:stroke-cap-end (-> (obj/get stroke "strokeCapEnd") parse-keyword)
|
||||
:stroke-color-gradient (-> (obj/get stroke "strokeColorGradient") parse-gradient)
|
||||
:stroke-image (-> (obj/get stroke "strokeImage") parse-image-data)})]
|
||||
(if (map? raw)
|
||||
;; Reads the proxy's stored widths without replaying its public getters.
|
||||
(merge parsed (select-keys raw [:stroke-width
|
||||
:stroke-width-top :stroke-width-right
|
||||
:stroke-width-bottom :stroke-width-left]))
|
||||
(let [width (obj/get stroke "strokeWidth")
|
||||
top (obj/get stroke "strokeWidthTop")
|
||||
right (obj/get stroke "strokeWidthRight")
|
||||
bottom (obj/get stroke "strokeWidthBottom")
|
||||
left (obj/get stroke "strokeWidthLeft")]
|
||||
(cond-> parsed
|
||||
(some? width) (cts/set-width-to-all-sides width)
|
||||
(some? top) (cts/set-width-to-single-side :stroke-width-top top)
|
||||
(some? right) (cts/set-width-to-single-side :stroke-width-right right)
|
||||
(some? bottom) (cts/set-width-to-single-side :stroke-width-bottom bottom)
|
||||
(some? left) (cts/set-width-to-single-side :stroke-width-left left)))))))
|
||||
|
||||
(defn parse-strokes
|
||||
[^js strokes]
|
||||
|
||||
@ -31,7 +31,9 @@
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.types.stroke :as types.stroke]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.exports.assets :as de]
|
||||
[app.main.data.exports.wasm :as wasm.exports]
|
||||
@ -64,7 +66,7 @@
|
||||
[app.plugins.strokes :as strokes]
|
||||
[app.plugins.system-events :as se]
|
||||
[app.plugins.text :as text]
|
||||
[app.plugins.tokens :refer [applied-tokens-plugin->applied-tokens token-attr-plugin->token-attr token-attr? valid-token-resolution?]]
|
||||
[app.plugins.tokens :refer [applied-tokens-plugin->applied-tokens expand-token-attrs token-attr-plugin->token-attr token-attr? valid-token-resolution?]]
|
||||
[app.plugins.utils :as u]
|
||||
[app.util.http :as http]
|
||||
[app.util.object :as obj]
|
||||
@ -273,21 +275,27 @@
|
||||
(st/emit! (dwsh/update-shapes [id] #(assoc % :fills value))))))
|
||||
|
||||
(defn commit-strokes!
|
||||
[plugin-id ^js self value]
|
||||
(let [id (obj/get self "$id")
|
||||
value (parser/parse-strokes value)]
|
||||
(cond
|
||||
(not (sm/validate [:vector cts/schema:stroke] value))
|
||||
(u/not-valid plugin-id :strokes value)
|
||||
([plugin-id self value]
|
||||
(commit-strokes! plugin-id self value nil))
|
||||
([plugin-id ^js self value options]
|
||||
(let [id (obj/get self "$id")
|
||||
value (parser/parse-strokes value)]
|
||||
(cond
|
||||
(not (sm/validate [:vector cts/schema:stroke] value))
|
||||
(u/not-valid plugin-id :strokes value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :strokes "Plugin doesn't have 'content:write' permission")
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :strokes "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not (u/page-active? (obj/get self "$page")))
|
||||
(u/not-valid plugin-id :strokes "Cannot modify a page that is not currently active")
|
||||
(not (u/page-active? (obj/get self "$page")))
|
||||
(u/not-valid plugin-id :strokes "Cannot modify a page that is not currently active")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsh/update-shapes [id] #(assoc % :strokes value))))))
|
||||
(and (not (ctt/per-side-stroke-shape? (:type (u/proxy->shape self))))
|
||||
(some #(= :multiple (types.stroke/width-type %)) value))
|
||||
(u/not-valid plugin-id :strokes "Per-side stroke widths require a board or rectangle")
|
||||
|
||||
:else
|
||||
(st/emit! (dwsh/update-shapes [id] #(assoc % :strokes value) options))))))
|
||||
|
||||
(defn commit-shadows!
|
||||
[plugin-id ^js self value]
|
||||
@ -992,8 +1000,12 @@
|
||||
:strokes
|
||||
{:this true
|
||||
:get (fn [^js self]
|
||||
(strokes/format-strokes (-> self u/proxy->shape :strokes)
|
||||
#(commit-strokes! plugin-id self %)))
|
||||
(let [shape (u/proxy->shape self)]
|
||||
(strokes/format-strokes
|
||||
(:strokes shape)
|
||||
#(commit-strokes! plugin-id self %1 %2)
|
||||
{:per-side-allowed? (ctt/per-side-stroke-shape? (:type shape))
|
||||
:not-valid (fn [code value] (u/not-valid plugin-id code value))})))
|
||||
:set (fn [self value] (commit-strokes! plugin-id self value))}
|
||||
|
||||
:layoutChild
|
||||
@ -1782,7 +1794,7 @@
|
||||
(let [set-id (obj/get token "$set-id")
|
||||
token-id (obj/get token "$id")
|
||||
token (u/locate-token file-id set-id token-id)
|
||||
kw-attrs (into #{} (map token-attr-plugin->token-attr attrs))]
|
||||
kw-attrs (expand-token-attrs attrs)]
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/not-valid plugin-id :applyToken "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
@ -6,78 +6,121 @@
|
||||
|
||||
(ns app.plugins.strokes
|
||||
(:require
|
||||
[app.common.types.stroke :as cts]
|
||||
[app.plugins.format :as format]
|
||||
[app.plugins.gradients :as gradients]
|
||||
[app.plugins.parser :as parser]
|
||||
[app.util.object :as obj]))
|
||||
|
||||
(defn- set-side-width!
|
||||
"Sets one side width and reports the edited attribute to the commit callback.
|
||||
Rejects shape types that do not support per-side widths."
|
||||
[state attr value on-change! per-side-allowed? not-valid]
|
||||
(if (and (false? per-side-allowed?) (fn? not-valid))
|
||||
(not-valid attr value)
|
||||
(do
|
||||
(swap! state #(cts/set-width-to-single-side % attr value))
|
||||
(on-change! [attr]))))
|
||||
|
||||
(defn stroke-proxy
|
||||
[stroke-data on-change!]
|
||||
(let [state (atom stroke-data)]
|
||||
(obj/reify {:name "StrokeProxy"}
|
||||
:strokeColor
|
||||
{:get (fn [] (:stroke-color @state))
|
||||
:set (fn [v]
|
||||
(swap! state #(-> % (assoc :stroke-color v) (dissoc :stroke-color-gradient :stroke-image)))
|
||||
(on-change!))}
|
||||
([stroke-data on-change!]
|
||||
(stroke-proxy stroke-data on-change! nil))
|
||||
([stroke-data on-change! {:keys [per-side-allowed? not-valid]}]
|
||||
(let [state (atom stroke-data)]
|
||||
(obj/reify {:name "StrokeProxy"}
|
||||
;; The parser reads stored widths without invoking fallback getters.
|
||||
:$state {:enumerable false :get (fn [] @state)}
|
||||
|
||||
:strokeColorRefFile
|
||||
{:get (fn [] (format/format-id (:stroke-color-ref-file @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-color-ref-file (parser/parse-id v)) (on-change!))}
|
||||
:strokeColor
|
||||
{:get (fn [] (:stroke-color @state))
|
||||
:set (fn [v]
|
||||
(swap! state #(-> % (assoc :stroke-color v) (dissoc :stroke-color-gradient :stroke-image)))
|
||||
(on-change! [:stroke-color]))}
|
||||
|
||||
:strokeColorRefId
|
||||
{:get (fn [] (format/format-id (:stroke-color-ref-id @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-color-ref-id (parser/parse-id v)) (on-change!))}
|
||||
:strokeColorRefFile
|
||||
{:get (fn [] (format/format-id (:stroke-color-ref-file @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-color-ref-file (parser/parse-id v)) (on-change! [:stroke-color]))}
|
||||
|
||||
:strokeOpacity
|
||||
{:get (fn [] (:stroke-opacity @state))
|
||||
:set (fn [v] (swap! state assoc :stroke-opacity v) (on-change!))}
|
||||
:strokeColorRefId
|
||||
{:get (fn [] (format/format-id (:stroke-color-ref-id @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-color-ref-id (parser/parse-id v)) (on-change! [:stroke-color]))}
|
||||
|
||||
:strokeStyle
|
||||
{:get (fn [] (format/format-key (:stroke-style @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-style (parser/parse-keyword v)) (on-change!))}
|
||||
:strokeOpacity
|
||||
{:get (fn [] (:stroke-opacity @state))
|
||||
:set (fn [v] (swap! state assoc :stroke-opacity v) (on-change! [:stroke-color]))}
|
||||
|
||||
:strokeWidth
|
||||
{:get (fn [] (:stroke-width @state))
|
||||
:set (fn [v] (swap! state assoc :stroke-width v) (on-change!))}
|
||||
:strokeStyle
|
||||
{:get (fn [] (format/format-key (:stroke-style @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-style (parser/parse-keyword v)) (on-change! [:stroke-style]))}
|
||||
|
||||
:strokeAlignment
|
||||
{:get (fn [] (format/format-key (:stroke-alignment @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-alignment (parser/parse-keyword v)) (on-change!))}
|
||||
:strokeWidth
|
||||
{:get (fn [] (:stroke-width @state))
|
||||
:set (fn [v]
|
||||
(swap! state #(cts/set-width-to-all-sides % v))
|
||||
(on-change! [:stroke-width]))}
|
||||
|
||||
:strokeCapStart
|
||||
{:get (fn [] (format/format-key (:stroke-cap-start @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-cap-start (parser/parse-keyword v)) (on-change!))}
|
||||
:strokeWidthType
|
||||
{:get (fn [] (format/format-key (cts/width-type @state)))}
|
||||
|
||||
:strokeCapEnd
|
||||
{:get (fn [] (format/format-key (:stroke-cap-end @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-cap-end (parser/parse-keyword v)) (on-change!))}
|
||||
:strokeWidthTop
|
||||
{:get (fn [] (cts/side-width @state :stroke-width-top))
|
||||
:set (fn [v] (set-side-width! state :stroke-width-top v on-change! per-side-allowed? not-valid))}
|
||||
|
||||
:strokeColorGradient
|
||||
{:get (fn []
|
||||
(when-let [gradient (:stroke-color-gradient @state)]
|
||||
(let [gradient-state (atom gradient)
|
||||
gradient-change! (fn []
|
||||
(swap! state assoc :stroke-color-gradient @gradient-state)
|
||||
(on-change!))]
|
||||
(gradients/gradient-proxy gradient-state gradient-change!))))
|
||||
:set (fn [v]
|
||||
(swap! state #(-> % (assoc :stroke-color-gradient (parser/parse-gradient v)) (dissoc :stroke-color :stroke-image)))
|
||||
(on-change!))}
|
||||
:strokeWidthRight
|
||||
{:get (fn [] (cts/side-width @state :stroke-width-right))
|
||||
:set (fn [v] (set-side-width! state :stroke-width-right v on-change! per-side-allowed? not-valid))}
|
||||
|
||||
:strokeImage
|
||||
{:get (fn [] (format/format-image (:stroke-image @state)))
|
||||
:set (fn [v]
|
||||
(swap! state #(-> % (assoc :stroke-image (parser/parse-image-data v)) (dissoc :stroke-color :stroke-color-gradient)))
|
||||
(on-change!))})))
|
||||
:strokeWidthBottom
|
||||
{:get (fn [] (cts/side-width @state :stroke-width-bottom))
|
||||
:set (fn [v] (set-side-width! state :stroke-width-bottom v on-change! per-side-allowed? not-valid))}
|
||||
|
||||
:strokeWidthLeft
|
||||
{:get (fn [] (cts/side-width @state :stroke-width-left))
|
||||
:set (fn [v] (set-side-width! state :stroke-width-left v on-change! per-side-allowed? not-valid))}
|
||||
|
||||
:strokeAlignment
|
||||
{:get (fn [] (format/format-key (:stroke-alignment @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-alignment (parser/parse-keyword v)) (on-change! [:stroke-alignment]))}
|
||||
|
||||
:strokeCapStart
|
||||
{:get (fn [] (format/format-key (:stroke-cap-start @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-cap-start (parser/parse-keyword v)) (on-change! [:stroke-cap-start]))}
|
||||
|
||||
:strokeCapEnd
|
||||
{:get (fn [] (format/format-key (:stroke-cap-end @state)))
|
||||
:set (fn [v] (swap! state assoc :stroke-cap-end (parser/parse-keyword v)) (on-change! [:stroke-cap-end]))}
|
||||
|
||||
:strokeColorGradient
|
||||
{:get (fn []
|
||||
(when-let [gradient (:stroke-color-gradient @state)]
|
||||
(let [gradient-state (atom gradient)
|
||||
gradient-change! (fn []
|
||||
(swap! state assoc :stroke-color-gradient @gradient-state)
|
||||
(on-change! [:stroke-color]))]
|
||||
(gradients/gradient-proxy gradient-state gradient-change!))))
|
||||
:set (fn [v]
|
||||
(swap! state #(-> % (assoc :stroke-color-gradient (parser/parse-gradient v)) (dissoc :stroke-color :stroke-image)))
|
||||
(on-change! [:stroke-color]))}
|
||||
|
||||
:strokeImage
|
||||
{:get (fn [] (format/format-image (:stroke-image @state)))
|
||||
:set (fn [v]
|
||||
(swap! state #(-> % (assoc :stroke-image (parser/parse-image-data v)) (dissoc :stroke-color :stroke-color-gradient)))
|
||||
(on-change! [:stroke-color]))}))))
|
||||
|
||||
(defn format-strokes
|
||||
([strokes] (format-strokes strokes nil))
|
||||
([strokes commit-fn]
|
||||
([strokes] (format-strokes strokes nil nil))
|
||||
([strokes commit-fn] (format-strokes strokes commit-fn nil))
|
||||
([strokes commit-fn options]
|
||||
(if (and (some? strokes) (fn? commit-fn))
|
||||
(let [arr-ref (atom nil)
|
||||
on-change! (fn [] (commit-fn @arr-ref))
|
||||
arr (apply array (mapv #(stroke-proxy % on-change!) strokes))]
|
||||
on-change! (fn [index attrs]
|
||||
(commit-fn @arr-ref {:changed-sub-attr attrs :changed-item-index index}))
|
||||
arr (apply array
|
||||
(map-indexed
|
||||
(fn [index stroke]
|
||||
(stroke-proxy stroke (partial on-change! index) options))
|
||||
strokes))]
|
||||
(reset! arr-ref arr)
|
||||
arr)
|
||||
(format/format-array format/format-stroke strokes))))
|
||||
|
||||
@ -79,15 +79,38 @@
|
||||
(let [k (cond-> k (string? k) keyword)]
|
||||
(get map:token-attr-plugin->token-attr k k)))
|
||||
|
||||
(def ^:private stroke-width-alias
|
||||
"Uniform stroke width alias that expands to all four side token attributes."
|
||||
:stroke-width)
|
||||
|
||||
(defn expand-token-attrs
|
||||
"Resolves token attributes to internal keywords and expands `:stroke-width`
|
||||
to all four side attributes."
|
||||
[attrs]
|
||||
(let [attrs (into #{} (map token-attr-plugin->token-attr) attrs)]
|
||||
(if (contains? attrs stroke-width-alias)
|
||||
(into (disj attrs stroke-width-alias) cto/per-side-stroke-width-keys)
|
||||
attrs)))
|
||||
|
||||
(defn applied-tokens-plugin->applied-tokens
|
||||
[value]
|
||||
(into {}
|
||||
(map (fn [[k v]] [(token-attr->token-attr-plugin k) v]))
|
||||
value))
|
||||
(let [value (into {}
|
||||
(map (fn [[k v]] [(token-attr->token-attr-plugin k) v]))
|
||||
value)
|
||||
per-side (map #(get value %)
|
||||
[:stroke-width-top :stroke-width-right
|
||||
:stroke-width-bottom :stroke-width-left])]
|
||||
(cond-> value
|
||||
;; Exposes `strokeWidth` when all four sides share the same token.
|
||||
(and (every? some? per-side)
|
||||
(apply = per-side))
|
||||
(assoc :stroke-width (first per-side)))))
|
||||
|
||||
(defn token-attr?
|
||||
[attr]
|
||||
(cto/token-attr? (token-attr-plugin->token-attr attr)))
|
||||
(let [attr (token-attr-plugin->token-attr attr)]
|
||||
(or (= attr stroke-width-alias)
|
||||
(cto/token-attr? attr))))
|
||||
|
||||
(defn- token-name-schema
|
||||
[file-id set-id token]
|
||||
@ -180,7 +203,7 @@
|
||||
:else
|
||||
(st/emit!
|
||||
(-> (dwta/toggle-token {:token token
|
||||
:attrs (into #{} (map token-attr-plugin->token-attr) attrs)
|
||||
:attrs (expand-token-attrs attrs)
|
||||
:shape-ids shape-ids
|
||||
:expand-with-children false})
|
||||
(se/add-event plugin-id)))))))
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.data.fix-deleted-fonts-test
|
||||
(:require
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.text :as txt]
|
||||
[app.main.data.changes :as dwc]
|
||||
[app.main.data.workspace.fix-deleted-fonts :as fdf]
|
||||
[app.main.fonts :as fonts]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.state :as ths]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before cthi/reset-idmap!})
|
||||
|
||||
(def ^:private missing-font-id
|
||||
"custom-missing-font-id")
|
||||
|
||||
(defn- text-content-with-font
|
||||
[text font-id font-family]
|
||||
(-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
|
||||
:content
|
||||
(txt/change-text text :font-id font-id :font-family font-family)))
|
||||
|
||||
(defn- file-with-text-shape
|
||||
[font-id font-family]
|
||||
(-> (cthf/sample-file :file1 :page-label :page1)
|
||||
(cths/add-sample-shape :text
|
||||
{:type :text
|
||||
:content (text-content-with-font "TEST"
|
||||
font-id
|
||||
font-family)})))
|
||||
|
||||
(defn- watch-fix-deleted-fonts-events
|
||||
[file]
|
||||
(let [events (atom [])
|
||||
store (ths/setup-store file)
|
||||
state @store]
|
||||
(when-let [result (ptk/watch (fdf/fix-deleted-fonts-for-page (:id file)
|
||||
(cthf/current-page-id file))
|
||||
state
|
||||
(rx/empty))]
|
||||
(->> result (rx/subs! #(swap! events conj %))))
|
||||
@events))
|
||||
|
||||
(t/deftest fix-deleted-fonts-skips-unfixable-missing-font
|
||||
(let [file (file-with-text-shape missing-font-id "Gibson")
|
||||
events (watch-fix-deleted-fonts-events file)]
|
||||
(t/is (empty? events)
|
||||
"missing custom fonts with no replacement must not commit changes")))
|
||||
|
||||
(t/deftest fix-deleted-fonts-remaps-when-replacement-exists
|
||||
(let [replacement-id "custom-gibson-replacement"
|
||||
cleanup #(swap! fonts/fontsdb dissoc replacement-id)]
|
||||
(try
|
||||
(swap! fonts/fontsdb assoc replacement-id
|
||||
{:id replacement-id
|
||||
:family "Gibson"
|
||||
:name "Gibson"
|
||||
:variants []})
|
||||
(let [file (file-with-text-shape missing-font-id "Gibson")
|
||||
events (watch-fix-deleted-fonts-events file)]
|
||||
(t/is (= 1 (count events)))
|
||||
(t/is (ptk/type? ::dwc/commit-changes (first events))))
|
||||
(finally
|
||||
(cleanup)))))
|
||||
@ -25,6 +25,7 @@
|
||||
[app.main.data.workspace.path.streams :as path.streams]
|
||||
[app.main.data.workspace.path.tools :as path.tools]
|
||||
[app.main.data.workspace.path.undo :as path.undo]
|
||||
[app.main.data.workspace.shortcuts :as wsc]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.state :as ths]
|
||||
@ -138,22 +139,23 @@
|
||||
|
||||
(defn- run-handle-drawing-end
|
||||
"Runs the draw-ending flow and passes its events to `callback`."
|
||||
[restart? callback]
|
||||
(let [state (pth/drawing-path-state)
|
||||
stream (rx/subject)
|
||||
emissions (atom [])]
|
||||
(->> (ptk/watch (path.drawing/handle-drawing) state stream)
|
||||
(rx/subs! #(swap! emissions conj %)))
|
||||
(rx/push! stream (ptk/data-event ::path.drawing/end-edition
|
||||
{:restart? restart?}))
|
||||
;; Wait for the asynchronous drawing-end event.
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(let [end-event (last @emissions)
|
||||
end-emissions (atom [])]
|
||||
(->> (ptk/watch end-event state stream)
|
||||
(rx/subs! #(swap! end-emissions conj %)))
|
||||
(callback @end-emissions))))))
|
||||
([restart? callback]
|
||||
(run-handle-drawing-end (pth/drawing-path-state) restart? callback))
|
||||
([state restart? callback]
|
||||
(let [stream (rx/subject)
|
||||
emissions (atom [])]
|
||||
(->> (ptk/watch (path.drawing/handle-drawing) state stream)
|
||||
(rx/subs! #(swap! emissions conj %)))
|
||||
(rx/push! stream (ptk/data-event ::path.drawing/end-edition
|
||||
{:restart? restart?}))
|
||||
;; Wait for the asynchronous drawing-end event.
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(let [end-event (last @emissions)
|
||||
end-emissions (atom [])]
|
||||
(->> (ptk/watch end-event state stream)
|
||||
(rx/subs! #(swap! end-emissions conj %)))
|
||||
(callback @end-emissions)))))))
|
||||
|
||||
(t/deftest escape-ending-new-path-draw-does-not-reenter-edition
|
||||
(t/async
|
||||
@ -182,6 +184,43 @@
|
||||
(mapv ptk/type emissions)))
|
||||
(done)))))
|
||||
|
||||
(t/deftest finishing-new-path-draw-in-move-mode-reenters-edition-in-move-mode
|
||||
(t/async
|
||||
done
|
||||
(let [state (pth/drawing-path-state)
|
||||
id (get-in state [:workspace-drawing :object :id])
|
||||
state (assoc-in state [:workspace-local :edit-path id :edit-mode] :move)]
|
||||
(run-handle-drawing-end
|
||||
state
|
||||
true
|
||||
(fn [emissions]
|
||||
(let [edition-emissions (atom [])]
|
||||
(->> (ptk/watch (last emissions) state nil)
|
||||
(rx/subs! #(swap! edition-emissions conj %)))
|
||||
(let [change-mode (last @edition-emissions)
|
||||
edited {:workspace-local {:edition id
|
||||
:edit-path {id {:edit-mode :draw}}}}]
|
||||
(t/is (= ::path.drawing/change-edit-mode (ptk/type change-mode)))
|
||||
(t/is (= :move (get-in (ptk/update change-mode edited)
|
||||
[:workspace-local :edit-path id :edit-mode])))))
|
||||
(done))))))
|
||||
|
||||
(t/deftest move-mode-while-creating-path-finishes-it
|
||||
(let [id (random-uuid)
|
||||
state {:workspace-local
|
||||
{:edit-path {id {:edit-mode :draw
|
||||
:last-point (gpt/point 10 10)}}}
|
||||
:workspace-drawing {:tool :path
|
||||
:object {:id id :type :path}}}
|
||||
event (path.drawing/change-edit-mode :move)
|
||||
state' (ptk/update event state)
|
||||
emissions (atom [])]
|
||||
(t/is (= :move (get-in state' [:workspace-local :edit-path id :edit-mode])))
|
||||
(->> (ptk/watch event state' nil)
|
||||
(rx/subs! #(swap! emissions conj %)))
|
||||
(t/is (= [::path.common/finish-path]
|
||||
(mapv ptk/type @emissions)))))
|
||||
|
||||
(t/deftest ending-a-draw-collapses-the-nodes-drawn-on-top-of-each-other
|
||||
(t/async
|
||||
done
|
||||
@ -237,6 +276,12 @@
|
||||
(t/is (= [::path.common/finish-path]
|
||||
(mapv ptk/type @emissions)))))
|
||||
|
||||
(t/deftest deselect-all-shortcut-replaces-the-grid-layout-toggle
|
||||
(let [deselect-all (:deselect-all path.shortcuts/shortcuts)
|
||||
grid-toggle (:toggle-layout-grid wsc/shortcuts)]
|
||||
(t/is (= (:command grid-toggle) (:command deselect-all)))
|
||||
(t/is (true? (:overwrite deselect-all)))))
|
||||
|
||||
(t/deftest escape-without-pending-segment-interrupts-edition
|
||||
(let [id (random-uuid)
|
||||
state {:workspace-local
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.math :as m]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.colors :as dwc]
|
||||
[app.main.data.workspace.reflow :as wrf]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.texts :as dwtxt]
|
||||
@ -254,11 +255,49 @@
|
||||
|
||||
(t/testing " - strokes"
|
||||
(set! (.-strokes shape) #js [#js {:strokeColor "#fabada" :strokeOpacity 1 :strokeWidth 5}])
|
||||
(t/is (= (get-in @store (get-shape-path :strokes)) [{:stroke-color "#fabada" :stroke-opacity 1 :stroke-width 5}]))
|
||||
(t/is (= (get-in @store (get-shape-path :strokes)) [{:stroke-color "#fabada" :stroke-opacity 1 :stroke-width 5
|
||||
:stroke-width-top 5 :stroke-width-right 5 :stroke-width-bottom 5 :stroke-width-left 5}]))
|
||||
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeColor")) "#fabada"))
|
||||
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeOpacity")) 1))
|
||||
(t/is (= (-> (. ^js shape -strokes) (aget 0) (aget "strokeWidth")) 5)))
|
||||
|
||||
(t/testing " - strokes per-side widths"
|
||||
(set! (.-strokes shape) #js [#js {:strokeColor "#fabada" :strokeWidth 5}])
|
||||
(t/is (= 5 (-> (. ^js shape -strokes) (aget 0) (aget "strokeWidthTop")))
|
||||
"a uniform stroke reads the global width on every side")
|
||||
(obj/set! (aget (.-strokes shape) 0) "strokeWidthRight" 9)
|
||||
(t/is (= 9 (-> (. ^js shape -strokes) (aget 0) (aget "strokeWidthRight"))))
|
||||
(t/is (= 5 (-> (. ^js shape -strokes) (aget 0) (aget "strokeWidthTop"))))
|
||||
(t/is (= 9 (get-in @store (conj (get-shape-path :strokes) 0 :stroke-width-right)))
|
||||
"the edited side is stored")
|
||||
(t/is (= 5 (get-in @store (conj (get-shape-path :strokes) 0 :stroke-width-top)))
|
||||
"the untouched sides keep their uniform width"))
|
||||
|
||||
(t/testing " - UI toggles preserve equal side widths and their computed mode"
|
||||
(set! (.-strokes shape)
|
||||
#js [#js {:strokeColor "#fabada" :strokeWidth 1
|
||||
:strokeWidthTop 8 :strokeWidthRight 8
|
||||
:strokeWidthBottom 8 :strokeWidthLeft 8}])
|
||||
(t/is (not (contains? (get-in @store (conj (get-shape-path :strokes) 0)) :stroke-width-type)))
|
||||
(t/is (= 8 (get-in @store (conj (get-shape-path :strokes) 0 :stroke-width))))
|
||||
(ptk/emit! store (dwc/toggle-stroke-per-side [(aget shape "$id")] 0))
|
||||
(t/is (true? (get-in @store [:workspace-local :stroke-per-side [[(aget shape "$id")] 0]])))
|
||||
(t/is (= "simple" (aget (aget (.-strokes shape) 0) "strokeWidthType")))
|
||||
(ptk/emit! store (dwc/toggle-stroke-per-side [(aget shape "$id")] 0))
|
||||
(t/is (= "simple" (aget (aget (.-strokes shape) 0) "strokeWidthType")))
|
||||
(t/is (false? (get-in @store [:workspace-local :stroke-per-side [[(aget shape "$id")] 0]])))
|
||||
(t/is (= [8 8 8 8]
|
||||
(mapv (get-in @store (conj (get-shape-path :strokes) 0))
|
||||
[:stroke-width-top :stroke-width-right :stroke-width-bottom :stroke-width-left]))))
|
||||
|
||||
(t/testing " - per-side widths rejected on unsupported shapes"
|
||||
(let [^js ellipse (.createEllipse context)]
|
||||
(set! (.-strokes ellipse) #js [#js {:strokeColor "#fabada" :strokeWidth 1}])
|
||||
(t/is (thrown? js/Error
|
||||
(obj/set! (aget (.-strokes ellipse) 0) "strokeWidthTop" 4)))
|
||||
(t/is (thrown? js/Error
|
||||
(set! (.-strokes ellipse) #js [#js {:strokeColor "#fabada" :strokeWidthTop 4}])))))
|
||||
|
||||
(t/testing " - fills per-element property mutation (bug #8357)"
|
||||
(set! (.-fills shape) #js [#js {:fillColor "#fabada" :fillOpacity 1}])
|
||||
(obj/set! (aget (.-fills shape) 0) "fillColor" "#ff0000")
|
||||
@ -287,13 +326,15 @@
|
||||
(t/testing " - strokes per-element property mutation (bug #8357)"
|
||||
(set! (.-strokes shape) #js [#js {:strokeColor "#fabada" :strokeOpacity 1 :strokeWidth 5}])
|
||||
(obj/set! (aget (.-strokes shape) 0) "strokeColor" "#0000ff")
|
||||
(t/is (= (get-in @store (get-shape-path :strokes)) [{:stroke-color "#0000ff" :stroke-opacity 1 :stroke-width 5}])))
|
||||
(t/is (= (get-in @store (get-shape-path :strokes)) [{:stroke-color "#0000ff" :stroke-opacity 1 :stroke-width 5
|
||||
:stroke-width-top 5 :stroke-width-right 5 :stroke-width-bottom 5 :stroke-width-left 5}])))
|
||||
|
||||
(t/testing " - strokes gradient assignment replaces solid color (bug #8357)"
|
||||
(set! (.-strokes shape) #js [#js {:strokeColor "#fabada" :strokeOpacity 1 :strokeWidth 5}])
|
||||
(obj/set! (aget (.-strokes shape) 0) "strokeColorGradient" (gradient))
|
||||
(t/is (= (get-in @store (get-shape-path :strokes))
|
||||
[{:stroke-opacity 1 :stroke-width 5 :stroke-color-gradient parsed-gradient}])))
|
||||
[{:stroke-opacity 1 :stroke-width 5
|
||||
:stroke-width-top 5 :stroke-width-right 5 :stroke-width-bottom 5 :stroke-width-left 5 :stroke-color-gradient parsed-gradient}])))
|
||||
|
||||
(t/testing " - strokes nested gradient mutation (bug #8357)"
|
||||
(set! (.-strokes shape) #js [#js {:strokeColorGradient (gradient) :strokeOpacity 1 :strokeWidth 5}])
|
||||
@ -304,6 +345,7 @@
|
||||
(t/is (= (get-in @store (get-shape-path :strokes))
|
||||
[{:stroke-opacity 1
|
||||
:stroke-width 5
|
||||
:stroke-width-top 5 :stroke-width-right 5 :stroke-width-bottom 5 :stroke-width-left 5
|
||||
:stroke-color-gradient (-> parsed-gradient
|
||||
(assoc :end-y 0.75)
|
||||
(assoc-in [:stops 1 :opacity] 0.25))}])))))
|
||||
|
||||
@ -55,3 +55,16 @@
|
||||
|
||||
(t/deftest test-shape-type-reports-boolean
|
||||
(t/is (= "boolean" (format/shape-type :bool))))
|
||||
|
||||
(t/deftest test-format-stroke-exposes-per-side-widths
|
||||
(let [stroke (format/format-stroke {:stroke-color "#000000"
|
||||
:stroke-width 1
|
||||
:stroke-width-top 2
|
||||
:stroke-width-right 3
|
||||
:stroke-width-bottom 4
|
||||
:stroke-width-left 5})]
|
||||
(t/is (= 1 (aget stroke "strokeWidth")))
|
||||
(t/is (= 2 (aget stroke "strokeWidthTop")))
|
||||
(t/is (= 3 (aget stroke "strokeWidthRight")))
|
||||
(t/is (= 4 (aget stroke "strokeWidthBottom")))
|
||||
(t/is (= 5 (aget stroke "strokeWidthLeft")))))
|
||||
|
||||
@ -116,6 +116,10 @@
|
||||
["ShapeProxy.rotation" #(set! (.-rotation rect) 45)]
|
||||
["ShapeProxy.fills" #(set! (.-fills rect) #js [#js {:fillColor "#fabada" :fillOpacity 1}])]
|
||||
["ShapeProxy.strokes" #(set! (.-strokes rect) #js [#js {:strokeColor "#fabada" :strokeOpacity 1 :strokeWidth 2}])]
|
||||
["ShapeProxy.strokeWidthTop" #(do (set! (.-strokes rect) #js [#js {:strokeColor "#fabada" :strokeWidth 2}])
|
||||
(set! (.-strokeWidthTop (aget (.-strokes rect) 0)) 5))]
|
||||
["ShapeProxy.strokeWidthRight" #(do (set! (.-strokes rect) #js [#js {:strokeColor "#fabada" :strokeWidth 2}])
|
||||
(set! (.-strokeWidthRight (aget (.-strokes rect) 0)) 5))]
|
||||
;; relative geometry (shape inside a board)
|
||||
["ShapeProxy.boardX" #(set! (.-boardX child) 10)]
|
||||
["ShapeProxy.boardY" #(set! (.-boardY child) 10)]
|
||||
|
||||
@ -11,7 +11,9 @@
|
||||
[app.common.types.grid :as ctg]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.plugins.format :as format]
|
||||
[app.plugins.parser :as parser]
|
||||
[app.plugins.strokes :as strokes]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
(defn- overlay-action
|
||||
@ -193,3 +195,70 @@
|
||||
|
||||
(t/testing "a malformed id raises, so plugin bugs stay visible"
|
||||
(t/is (thrown? js/Error (parser/parse-id "not-a-uuid")))))
|
||||
|
||||
(t/deftest test-parse-stroke-maps-per-side-widths
|
||||
(let [stroke (parser/parse-stroke
|
||||
#js {:strokeColor "#000000"
|
||||
:strokeWidth 1
|
||||
:strokeWidthTop 2
|
||||
:strokeWidthRight 3
|
||||
:strokeWidthBottom 4
|
||||
:strokeWidthLeft 5})]
|
||||
(t/is (= 2 (:stroke-width-top stroke)))
|
||||
(t/is (= 3 (:stroke-width-right stroke)))
|
||||
(t/is (= 4 (:stroke-width-bottom stroke)))
|
||||
(t/is (= 5 (:stroke-width-left stroke))))
|
||||
|
||||
(t/testing "a uniform literal initializes all sides in simple mode"
|
||||
(let [stroke (parser/parse-stroke #js {:strokeColor "#000000" :strokeWidth 1})]
|
||||
(t/is (= "simple" (aget (format/format-stroke stroke) "strokeWidthType")))
|
||||
(t/is (= 1 (:stroke-width-top stroke)))
|
||||
(t/is (= 1 (:stroke-width-left stroke))))))
|
||||
|
||||
(t/deftest test-parse-stroke-applies-uniform-width-before-sides
|
||||
(let [stroke (parser/parse-stroke
|
||||
#js {:strokeColor "#000000"
|
||||
:strokeWidthLeft 8
|
||||
:strokeWidthBottom 8
|
||||
:strokeWidthRight 8
|
||||
:strokeWidthTop 8
|
||||
:strokeWidth 1})]
|
||||
(t/is (= "simple" (aget (format/format-stroke stroke) "strokeWidthType")))
|
||||
(t/is (= 8 (:stroke-width stroke)))
|
||||
(t/is (= [8 8 8 8]
|
||||
(mapv stroke [:stroke-width-top :stroke-width-right :stroke-width-bottom :stroke-width-left])))))
|
||||
|
||||
(t/deftest test-parse-stroke-top-preserves-other-widths
|
||||
(let [stroke (parser/parse-stroke #js {:strokeColor "#000000" :strokeWidth 5 :strokeWidthTop 9})]
|
||||
(t/is (= [9 5 5 5]
|
||||
(mapv stroke [:stroke-width-top :stroke-width-right :stroke-width-bottom :stroke-width-left])))))
|
||||
|
||||
(t/deftest test-stroke-proxy-mode-transitions
|
||||
(let [^js stroke (strokes/stroke-proxy {:stroke-color "#000000" :stroke-width 1} (fn [_] nil))]
|
||||
(t/is (= "simple" (.-strokeWidthType stroke)))
|
||||
(set! (.-strokeWidthTop stroke) 8)
|
||||
(t/is (= "multiple" (.-strokeWidthType stroke)))
|
||||
(t/is (= 1 (.-strokeWidthRight stroke)))
|
||||
(set! (.-strokeWidthRight stroke) 8)
|
||||
(set! (.-strokeWidthBottom stroke) 8)
|
||||
(set! (.-strokeWidthLeft stroke) 8)
|
||||
(t/is (= "simple" (.-strokeWidthType stroke)))
|
||||
(t/is (= 8 (.-strokeWidth stroke)))
|
||||
(set! (.-strokeColor stroke) "#ff0000")
|
||||
(t/is (not (contains? (parser/parse-stroke stroke) :stroke-width-type)))
|
||||
(set! (.-strokeWidth stroke) 3)
|
||||
(t/is (= "simple" (.-strokeWidthType stroke)))
|
||||
(t/is (= [3 3 3 3]
|
||||
(mapv #(aget stroke %) ["strokeWidthTop" "strokeWidthRight" "strokeWidthBottom" "strokeWidthLeft"])))
|
||||
(set! (.-strokeWidthRight stroke) 9)
|
||||
(t/is (= "multiple" (.-strokeWidthType stroke)))
|
||||
(set! (.-strokeWidth stroke) 3)
|
||||
(t/is (= [3 3 3 3]
|
||||
(mapv #(aget stroke %) ["strokeWidthTop" "strokeWidthRight" "strokeWidthBottom" "strokeWidthLeft"])))
|
||||
(t/is (= "simple" (.-strokeWidthType stroke)))))
|
||||
|
||||
(t/deftest test-stroke-mode-round-trips-through-format
|
||||
(doseq [right [8 12]]
|
||||
(let [stroke (parser/parse-stroke
|
||||
#js {:strokeColor "#000000" :strokeWidth 8 :strokeWidthRight right})]
|
||||
(t/is (= stroke (parser/parse-stroke (format/format-stroke stroke)))))))
|
||||
|
||||
@ -11,17 +11,21 @@
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.tokens :as ctht]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.types.tokens-status :as ctos]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.tokenscript :as ts]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.api :as api]
|
||||
[app.plugins.register :as r]
|
||||
[app.plugins.tokens :as ptok]
|
||||
[app.plugins.utils :as u]
|
||||
[app.util.object :as obj]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.async :as tha]
|
||||
[frontend-tests.helpers.mock :as mock]
|
||||
[frontend-tests.helpers.state :as ths]
|
||||
[frontend-tests.helpers.wasm :as thw]
|
||||
@ -35,6 +39,73 @@
|
||||
|
||||
(def ^:private get-resolved-value @#'ptok/get-resolved-value)
|
||||
|
||||
(t/deftest ^:async stroke-property-edits-only-remove-related-token-bindings
|
||||
(let [tokens {:stroke-color "border.color"
|
||||
:stroke-width-top "border.top"
|
||||
:stroke-width-right "border.right"
|
||||
:stroke-width-bottom "border.bottom"
|
||||
:stroke-width-left "border.left"}
|
||||
cases [[0 "strokeWidthTop" 8 #{:stroke-width-top}]
|
||||
[0 "strokeWidthRight" 8 #{:stroke-width-right}]
|
||||
[0 "strokeWidthBottom" 8 #{:stroke-width-bottom}]
|
||||
[0 "strokeWidthLeft" 8 #{:stroke-width-left}]
|
||||
[0 "strokeWidth" 8 ctt/per-side-stroke-width-keys]
|
||||
[0 "strokeColor" "#ff0000" #{:stroke-color}]
|
||||
[0 "strokeOpacity" 0.5 #{:stroke-color}]
|
||||
[0 "strokeStyle" "dashed" #{}]
|
||||
[0 "strokeAlignment" "center" #{}]
|
||||
[1 "strokeWidthTop" 8 #{}]
|
||||
[1 "strokeWidth" 8 #{}]
|
||||
[1 "strokeColor" "#ff0000" #{}]]]
|
||||
(doseq [[index property value removed] cases]
|
||||
(t/testing (str "stroke " index " property " property)
|
||||
(let [store (ths/setup-store (cthf/sample-file :file1 :page-label :page1))
|
||||
_ (set! st/state store)
|
||||
^js ctx (api/create-context "00000000-0000-0000-0000-000000000000")
|
||||
^js rect (.createRectangle ctx)
|
||||
id (obj/get rect "$id")
|
||||
path [:files (:current-file-id @store) :data :pages-index
|
||||
(:current-page-id @store) :objects id :applied-tokens]]
|
||||
(set! (.-strokes rect)
|
||||
#js [#js {:strokeColor "#000000" :strokeWidth 1 :strokeWidthRight 2}
|
||||
#js {:strokeColor "#000000" :strokeWidth 1}])
|
||||
(ptk/emit! store (dwsh/update-shapes [id] #(assoc % :applied-tokens tokens)))
|
||||
(obj/set! (aget (.-strokes rect) index) property value)
|
||||
(await (tha/settle))
|
||||
(t/is (= (apply dissoc tokens removed) (get-in @store path)))
|
||||
(t/is (= value (obj/get (aget (.-strokes rect) index) property))))))))
|
||||
|
||||
(t/deftest ^:async stroke-gradient-edits-preserve-width-token-bindings
|
||||
(let [store (ths/setup-store (cthf/sample-file :file1 :page-label :page1))
|
||||
_ (set! st/state store)
|
||||
^js ctx (api/create-context "00000000-0000-0000-0000-000000000000")
|
||||
^js rect (.createRectangle ctx)
|
||||
id (obj/get rect "$id")
|
||||
path [:files (:current-file-id @store) :data :pages-index
|
||||
(:current-page-id @store) :objects id :applied-tokens]
|
||||
tokens {:stroke-color "border.color"
|
||||
:stroke-width-top "border.width"
|
||||
:stroke-width-right "border.width"
|
||||
:stroke-width-bottom "border.width"
|
||||
:stroke-width-left "border.width"}]
|
||||
(set! (.-strokes rect)
|
||||
#js [#js {:strokeWidth 1
|
||||
:strokeColorGradient
|
||||
#js {:type "linear" :startX 0 :startY 0 :endX 1 :endY 1 :width 1
|
||||
:stops #js [#js {:color "#000000" :opacity 1 :offset 0}
|
||||
#js {:color "#ffffff" :opacity 1 :offset 1}]}}])
|
||||
(ptk/emit! store (dwsh/update-shapes [id] #(assoc % :applied-tokens tokens)))
|
||||
(let [^js gradient (obj/get (aget (.-strokes rect) 0) "strokeColorGradient")]
|
||||
(set! (.-startX gradient) 0.5)
|
||||
(await (tha/settle))
|
||||
(t/is (= (dissoc tokens :stroke-color) (get-in @store path)))
|
||||
(t/is (= 0.5 (obj/get (obj/get (aget (.-strokes rect) 0) "strokeColorGradient") "startX")))
|
||||
(ptk/emit! store (dwsh/update-shapes [id] #(assoc % :applied-tokens tokens)))
|
||||
(obj/set! (aget (.-stops gradient) 0) "color" "#ff0000")
|
||||
(await (tha/settle))
|
||||
(t/is (= (dissoc tokens :stroke-color) (get-in @store path)))
|
||||
(t/is (= "#ff0000" (obj/get (aget (obj/get (obj/get (aget (.-strokes rect) 0) "strokeColorGradient") "stops") 0) "color"))))))
|
||||
|
||||
;; Regression coverage for issue #9162.
|
||||
;;
|
||||
;; Plugin code calling `shape.applyToken(token, ["fill"])` or
|
||||
@ -122,6 +193,25 @@
|
||||
(t/is (true? (boolean (ptok/token-attr? "r1"))))
|
||||
(t/is (true? (boolean (ptok/token-attr? "m3")))))
|
||||
|
||||
(t/deftest token-attr?-accepts-per-side-stroke-width
|
||||
;; Accepts the uniform width alias and side attributes as keywords or strings.
|
||||
(t/is (true? (boolean (ptok/token-attr? :stroke-width))))
|
||||
(t/is (true? (boolean (ptok/token-attr? "stroke-width"))))
|
||||
(t/is (true? (boolean (ptok/token-attr? :stroke-width-top))))
|
||||
(t/is (true? (boolean (ptok/token-attr? "stroke-width-left")))))
|
||||
|
||||
(t/deftest expand-token-attrs-expands-uniform-stroke-width
|
||||
(t/testing "the uniform alias becomes the four per-side attributes"
|
||||
(t/is (= ctt/per-side-stroke-width-keys
|
||||
(ptok/expand-token-attrs #{:stroke-width})))
|
||||
(t/is (= ctt/per-side-stroke-width-keys
|
||||
(ptok/expand-token-attrs #{"stroke-width"}))))
|
||||
|
||||
(t/testing "per-side and unrelated attributes pass through unchanged"
|
||||
(t/is (= #{:stroke-width-top} (ptok/expand-token-attrs #{:stroke-width-top})))
|
||||
(t/is (= #{:fill} (ptok/expand-token-attrs #{:fill})))
|
||||
(t/is (= #{} (ptok/expand-token-attrs nil)))))
|
||||
|
||||
(t/deftest shape-apply-token-accepts-padding-top
|
||||
(t/async
|
||||
done
|
||||
@ -807,4 +897,3 @@
|
||||
(.addSet catalog #js {"name" "NewSet"})
|
||||
(t/is (= 1 (count @errors)))
|
||||
(t/is (= [plugin-id :addSet "Plugin doesn't have 'content:write' permission"] (first @errors)))))))
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
[frontend-tests.data.event-test]
|
||||
[frontend-tests.data.exports-assets-test]
|
||||
[frontend-tests.data.exports-files-test]
|
||||
[frontend-tests.data.fix-deleted-fonts-test]
|
||||
[frontend-tests.data.imports-test]
|
||||
[frontend-tests.data.jobs-test]
|
||||
[frontend-tests.data.nitrate-test]
|
||||
@ -113,6 +114,7 @@
|
||||
[frontend-tests.ui.comment-input-ime-test]
|
||||
[frontend-tests.ui.comments-clustering-test]
|
||||
[frontend-tests.ui.comments-position-modifier-test]
|
||||
[frontend-tests.ui.drawarea-test]
|
||||
[frontend-tests.ui.ds-controls-numeric-input-test]
|
||||
[frontend-tests.ui.gradient-handlers-test]
|
||||
[frontend-tests.ui.history-test]
|
||||
@ -131,6 +133,7 @@
|
||||
[frontend-tests.ui.sidebar-scroll-test]
|
||||
[frontend-tests.ui.stroke-menu-test]
|
||||
[frontend-tests.ui.text-attrs-multiple-test]
|
||||
[frontend-tests.ui.workspace-libraries-test]
|
||||
[frontend-tests.util-clipboard-test]
|
||||
[frontend-tests.util-object-test]
|
||||
[frontend-tests.util-queue-test]
|
||||
@ -163,6 +166,7 @@
|
||||
'frontend-tests.data.comments-filters-test
|
||||
'frontend-tests.data.dashboard-test
|
||||
'frontend-tests.data.event-test
|
||||
'frontend-tests.data.fix-deleted-fonts-test
|
||||
'frontend-tests.data.nitrate-test
|
||||
'frontend-tests.data.persistence-retry-test
|
||||
'frontend-tests.data.persistence-test
|
||||
@ -263,6 +267,7 @@
|
||||
'frontend-tests.ui.comment-input-ime-test
|
||||
'frontend-tests.ui.comments-clustering-test
|
||||
'frontend-tests.ui.comments-position-modifier-test
|
||||
'frontend-tests.ui.drawarea-test
|
||||
'frontend-tests.ui.ds-controls-numeric-input-test
|
||||
'frontend-tests.ui.gradient-handlers-test
|
||||
'frontend-tests.ui.history-test
|
||||
@ -283,6 +288,7 @@
|
||||
'frontend-tests.ui.sidebar-scroll-test
|
||||
'frontend-tests.ui.stroke-menu-test
|
||||
'frontend-tests.ui.text-attrs-multiple-test
|
||||
'frontend-tests.ui.workspace-libraries-test
|
||||
'frontend-tests.util-clipboard-test
|
||||
'frontend-tests.util-object-test
|
||||
'frontend-tests.util-queue-test
|
||||
|
||||
28
frontend/test/frontend_tests/ui/drawarea_test.cljs
Normal file
28
frontend/test/frontend_tests/ui/drawarea_test.cljs
Normal file
@ -0,0 +1,28 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.ui.drawarea-test
|
||||
(:require
|
||||
[app.common.types.shape :as cts]
|
||||
[app.main.ui.workspace.viewport.drawarea :as drawarea]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
(def ^:private box-types
|
||||
[:rect :frame :circle :text])
|
||||
|
||||
(t/deftest box-drawings-get-the-generic-overlay-for-any-tool
|
||||
(doseq [type box-types
|
||||
tool [:path :curve type nil]]
|
||||
(let [shape (cts/setup-shape {:type type :x 10 :y 10 :width 0.01 :height 0.01})]
|
||||
(t/is (= :generic (drawarea/draw-area-kind tool shape))
|
||||
(str "type " type " with tool " tool)))))
|
||||
|
||||
(t/deftest path-drawings-follow-the-active-tool
|
||||
(let [shape (cts/setup-shape {:type :path})]
|
||||
(t/is (= :path (drawarea/draw-area-kind :path shape)))
|
||||
(t/is (= :curve (drawarea/draw-area-kind :curve shape)))
|
||||
(t/is (nil? (drawarea/draw-area-kind :line shape)))
|
||||
(t/is (nil? (drawarea/draw-area-kind nil shape)))))
|
||||
@ -48,4 +48,11 @@
|
||||
(t/testing "re-renders when width changes"
|
||||
(t/is (false? (check-measures-menu-props
|
||||
(props base-values)
|
||||
(props (assoc base-values :width 150)))))))
|
||||
(props (assoc base-values :width 150))))))
|
||||
|
||||
(t/testing "re-renders when absolute positioning changes but nothing else does"
|
||||
;; Regression test: toggling static <-> absolute on a flex child must
|
||||
;; force a re-render so the X/Y inputs enabled/disabled state updates.
|
||||
(t/is (false? (check-measures-menu-props
|
||||
(props (assoc base-values :layout-item-absolute false))
|
||||
(props (assoc base-values :layout-item-absolute true)))))))
|
||||
|
||||
@ -9,9 +9,11 @@
|
||||
[app.common.time :as ct]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.routes :as routes]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.mock :as mock]))
|
||||
@ -95,3 +97,61 @@
|
||||
(t/is (= 2 (count @events)))
|
||||
(done'))
|
||||
done))))
|
||||
|
||||
;; Template links (`#?template=<url>`) predate query-string routing: the
|
||||
;; params live in a bare `#?…` fragment and there is no `screen` (#12127).
|
||||
|
||||
(def ^:private template-url
|
||||
"https://penpot.github.io/penpot-files/tutorial-for-beginners%20v.2.0.penpot")
|
||||
|
||||
(def ^:private template-param
|
||||
"The query param as the router decodes it."
|
||||
"https://penpot.github.io/penpot-files/tutorial-for-beginners v.2.0.penpot")
|
||||
|
||||
(t/deftest legacy-query-hash-becomes-a-query-token
|
||||
(t/is (= (str "?template=" template-url)
|
||||
(#'routes/legacy-query-token (str "#?template=" template-url))))
|
||||
(t/is (nil? (#'routes/legacy-query-token "#/workspace?team-id=1")))
|
||||
(t/is (nil? (#'routes/legacy-query-token ""))))
|
||||
|
||||
(t/deftest query-without-screen-goes-to-dashboard-with-its-params
|
||||
(t/async done
|
||||
(let [team-id (uuid/next)
|
||||
events (atom [])]
|
||||
(mock/with-mocks
|
||||
{rp/cmd! (mock/stub
|
||||
(fn [command _]
|
||||
(case command
|
||||
:get-profile (rx/of {:id (uuid/next) :default-team-id team-id})
|
||||
:get-teams (rx/of [{:id team-id}]))))
|
||||
dtm/get-last-team-id (mock/stub (fn [] nil))
|
||||
st/emit! (mock/stub (fn [& emitted] (swap! events into emitted)))}
|
||||
(fn [done']
|
||||
(#'routes/on-query-navigate #{:dashboard-recent} (str "?template=" template-url) false)
|
||||
(let [{:keys [id params]} (some-> (first @events) deref)]
|
||||
(t/is (= :dashboard-recent id))
|
||||
(t/is (= template-param (:template params)))
|
||||
(t/is (= team-id (:team-id params))))
|
||||
(done'))
|
||||
done))))
|
||||
|
||||
(t/deftest query-without-screen-keeps-template-for-after-login
|
||||
(t/async done
|
||||
(let [events (atom [])]
|
||||
(mock/with-mocks
|
||||
{rp/cmd! (mock/stub
|
||||
(fn [command _]
|
||||
(case command
|
||||
:get-profile (rx/of {:id uuid/zero})
|
||||
;; The backend requires a session for get-teams.
|
||||
:get-teams (rx/throw (ex-info "authentication required"
|
||||
{:type :authentication})))))
|
||||
st/emit! (mock/stub (fn [& emitted] (swap! events into emitted)))}
|
||||
(fn [done']
|
||||
(#'routes/on-query-navigate #{:auth-login} (str "?template=" template-url) false)
|
||||
(t/is (= template-param (:template @storage/session)))
|
||||
(t/is (= :auth-login (:id (some-> (first @events) deref))))
|
||||
(binding [storage/*sync* true]
|
||||
(swap! storage/session dissoc :template))
|
||||
(done'))
|
||||
done))))
|
||||
|
||||
@ -31,6 +31,14 @@
|
||||
(sc/save-scroll! store [:layers :page-1] #js {})
|
||||
(t/is (= {} @store))))
|
||||
|
||||
(t/deftest forget-scroll-drops-only-that-key
|
||||
(let [store (atom {[:layers :page-1] 250
|
||||
[:layers-search :page-1] 90})]
|
||||
(sc/forget-scroll! store [:layers-search :page-1])
|
||||
(t/is (= {[:layers :page-1] 250} @store))
|
||||
(sc/forget-scroll! store [:layers-search :page-1])
|
||||
(t/is (= {[:layers :page-1] 250} @store))))
|
||||
|
||||
(t/deftest restore-scroll-writes-saved-position
|
||||
(let [node #js {:scrollTop 0}]
|
||||
(sc/restore-scroll! node 320)
|
||||
|
||||
101
frontend/test/frontend_tests/ui/workspace_libraries_test.cljs
Normal file
101
frontend/test/frontend_tests/ui/workspace_libraries_test.cljs
Normal file
@ -0,0 +1,101 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.ui.workspace-libraries-test
|
||||
(:require
|
||||
[app.main.ui.workspace.libraries :as wl]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
(defn- lib
|
||||
"A linked library; `connected-to` lists the ids of the files using it."
|
||||
[id indirect? connected-to]
|
||||
{:id id :name (name id) :is-indirect indirect? :connected-to connected-to})
|
||||
|
||||
(defn- summarize
|
||||
[libraries]
|
||||
(mapv (juxt :id :nested? :parent-name) libraries))
|
||||
|
||||
(t/deftest sort-linked-libraries-cycle-with-direct-root
|
||||
;; file -> App, Foundations; App -> Foundations;
|
||||
;; Foundations <-> Comments (cycle, as left by an import)
|
||||
(let [libraries [(lib :foundations false [:file :app :comments])
|
||||
(lib :app false [:file])
|
||||
(lib :comments true [:foundations])]
|
||||
result (wl/sort-linked-libraries libraries)]
|
||||
(t/is (= [[:app false nil]
|
||||
[:foundations false nil]
|
||||
[:comments true "foundations"]]
|
||||
(summarize result)))
|
||||
(t/is (= (count libraries) (count result)))))
|
||||
|
||||
(t/deftest sort-linked-libraries-cycle-without-root
|
||||
;; The direct flag is still unknown (nil) while the shared files load,
|
||||
;; so both libraries are only reachable through the cycle. `:app` is
|
||||
;; not a linked library, so it is ignored as a parent.
|
||||
(let [libraries [(lib :foundations nil [:app :comments])
|
||||
(lib :comments nil [:foundations])]
|
||||
result (wl/sort-linked-libraries libraries)]
|
||||
(t/is (= [[:comments false nil]
|
||||
[:foundations true "comments"]]
|
||||
(summarize result)))
|
||||
(t/is (= (count libraries) (count result)))))
|
||||
|
||||
(t/deftest sort-linked-libraries-cycle-of-three-without-root
|
||||
;; a -> b -> c -> a, all indirect: no root, nothing is lost
|
||||
(let [libraries [(lib :c true [:b])
|
||||
(lib :b true [:a])
|
||||
(lib :a true [:c])]
|
||||
result (wl/sort-linked-libraries libraries)]
|
||||
(t/is (= [[:a false nil]
|
||||
[:b true "a"]
|
||||
[:c true "b"]]
|
||||
(summarize result)))
|
||||
(t/is (= (count libraries) (count result)))))
|
||||
|
||||
(t/deftest sort-linked-libraries-deep-chain
|
||||
;; file -> a -> b -> c
|
||||
(let [libraries [(lib :c true [:b])
|
||||
(lib :b true [:a])
|
||||
(lib :a false [:file])]]
|
||||
(t/is (= [[:a false nil]
|
||||
[:b true "a"]
|
||||
[:c true "b"]]
|
||||
(summarize (wl/sort-linked-libraries libraries))))))
|
||||
|
||||
(t/deftest sort-linked-libraries-diamond
|
||||
;; a -> b, c; b -> d; c -> d: d is listed once, under its first parent
|
||||
(let [libraries [(lib :d true [:b :c])
|
||||
(lib :c true [:a])
|
||||
(lib :b true [:a])
|
||||
(lib :a false [:file])]
|
||||
result (wl/sort-linked-libraries libraries)]
|
||||
(t/is (= [[:a false nil]
|
||||
[:b true "a"]
|
||||
[:d true "b"]
|
||||
[:c true "a"]]
|
||||
(summarize result)))
|
||||
(t/is (= (count libraries) (count result)))))
|
||||
|
||||
(t/deftest sort-linked-libraries-self-dependency
|
||||
(let [libraries [(lib :a true [:a])]]
|
||||
(t/is (= [[:a false nil]]
|
||||
(summarize (wl/sort-linked-libraries libraries))))))
|
||||
|
||||
(t/deftest sort-linked-libraries-parent-not-linked
|
||||
;; The only user of each library is not a linked library, so both are roots
|
||||
(let [libraries [(lib :b true [:other])
|
||||
(lib :a true [:file])]]
|
||||
(t/is (= [[:a false nil]
|
||||
[:b false nil]]
|
||||
(summarize (wl/sort-linked-libraries libraries))))))
|
||||
|
||||
(t/deftest sort-linked-libraries-without-dependencies
|
||||
;; Before get-team-shared-files answers, all libraries are flat
|
||||
(let [libraries [(lib :b true nil)
|
||||
(lib :a false nil)]]
|
||||
(t/is (= [[:a false nil]
|
||||
[:b false nil]]
|
||||
(summarize (wl/sort-linked-libraries libraries))))))
|
||||
@ -1032,7 +1032,7 @@ msgstr ""
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Restaura tots els projectes i fitxers"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Restaura-ho tot"
|
||||
|
||||
@ -1611,7 +1611,7 @@ msgstr "S'ha produït un problema amb el web worker."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "El color no és vàlid"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Dades no vàlides"
|
||||
|
||||
@ -2946,7 +2946,7 @@ msgstr "Amaga"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Amaga els comentaris resolts"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importa"
|
||||
|
||||
@ -4710,7 +4710,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "Vés a l'inici de sessió"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "Acció"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5355,7 +5355,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Estableix miniatures"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Dreceres de teclat"
|
||||
|
||||
|
||||
@ -1498,7 +1498,7 @@ msgstr "Centrum nápovědy"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Skrýt vyřešené komentáře"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Import"
|
||||
|
||||
@ -3083,7 +3083,7 @@ msgstr "Přiblížení"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Nastavit náhledy"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Klávesové zkratky"
|
||||
|
||||
|
||||
@ -904,7 +904,7 @@ msgstr ""
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Alle Projekte und Dateien wiederherstellen"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Alles wiederherstellen"
|
||||
|
||||
@ -1341,7 +1341,7 @@ msgstr "Mit dem Web Worker ist ein Fehler aufgetreten."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Ungültige Farbe"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Ungültige Daten"
|
||||
|
||||
@ -2238,7 +2238,7 @@ msgstr "Ausblenden"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Erledigte Kommentare ausblenden"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importieren"
|
||||
|
||||
@ -4009,7 +4009,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Miniaturansichten festlegen"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Tastaturkürzel"
|
||||
|
||||
|
||||
@ -1017,7 +1017,7 @@ msgstr "You're going to restore all your projects and files. This may take a whi
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Restore all projects and files"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Restore All"
|
||||
|
||||
@ -1605,7 +1605,7 @@ msgstr "Something wrong has happened with the web worker."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Invalid color"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Invalid data"
|
||||
|
||||
@ -2111,16 +2111,24 @@ msgstr "An error has occurred"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:161
|
||||
msgid "import-shortcuts.apply"
|
||||
msgstr "Apply"
|
||||
msgstr "Import"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:102
|
||||
msgid "import-shortcuts.diff-modal-text"
|
||||
msgstr "The following shortcuts will be changed by the imported configuration."
|
||||
msgstr "These shortcuts will change:"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:98
|
||||
msgid "import-shortcuts.diff-modal-title"
|
||||
msgstr "Import shortcuts"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113
|
||||
msgid "import-shortcuts.imported"
|
||||
msgstr "Imported"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462
|
||||
msgid "import-shortcuts.invalid-file"
|
||||
msgstr "This file is not a valid shortcuts export."
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:148
|
||||
msgid "import-shortcuts.no-changes"
|
||||
msgstr "No differences found between the current and imported shortcuts."
|
||||
@ -2976,7 +2984,7 @@ msgstr "Hide"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Hide resolved comments"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Import"
|
||||
|
||||
@ -4714,20 +4722,20 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "Go to login"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgstr "Acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "Action"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
msgid "restore-shortcuts.modal-text"
|
||||
msgstr "The following shortcuts will be restored to their default values."
|
||||
msgstr "These shortcuts will be reset to default:"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:90
|
||||
msgid "restore-shortcuts.modal-title"
|
||||
msgstr "Restore to default configuration"
|
||||
msgstr "Reset all shortcuts to default"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:170
|
||||
msgid "restore-shortcuts.restore"
|
||||
msgstr "Restore"
|
||||
msgstr "Reset"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:990, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:425
|
||||
msgid "settings.detach"
|
||||
@ -4921,21 +4929,25 @@ msgstr "Text"
|
||||
msgid "shortcuts.duplicate"
|
||||
msgstr "Duplicate"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:589
|
||||
msgid "shortcuts.edit"
|
||||
msgstr "Edit %s"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:645
|
||||
msgid "shortcuts.edit-modal.conflict"
|
||||
msgstr "Combination assigned to \"%s\". Saving will remove it there."
|
||||
msgstr "Already assigned to \"%s\". Saving will move it to this action."
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:636
|
||||
msgid "shortcuts.edit-modal.reset-pending"
|
||||
msgstr "If you save, this shortcut will return to the original value:"
|
||||
msgstr "Saving will reset this shortcut to default:"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:647
|
||||
msgid "shortcuts.edit-modal.success"
|
||||
msgstr "This key combination is available and can be assigned without conflicts."
|
||||
msgstr "Shortcut available."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:157
|
||||
msgid "shortcuts.edit-on-settings"
|
||||
msgstr "Customize on settings"
|
||||
msgstr "Customize shortcuts"
|
||||
|
||||
#: src/app/main/data/workspace/grid_layout/shortcuts.cljs:37, src/app/main/data/workspace/path/shortcuts.cljs:175, src/app/main/data/workspace/shortcuts.cljs:177
|
||||
msgid "shortcuts.escape"
|
||||
@ -4967,11 +4979,11 @@ msgstr "Flip vertically"
|
||||
|
||||
#: src/app/main/data/workspace/text/shortcuts.cljs:263
|
||||
msgid "shortcuts.font-size-dec"
|
||||
msgstr "Decrement font size"
|
||||
msgstr "Decrease font size"
|
||||
|
||||
#: src/app/main/data/workspace/text/shortcuts.cljs:256
|
||||
msgid "shortcuts.font-size-inc"
|
||||
msgstr "Increment font size"
|
||||
msgstr "Increase font size"
|
||||
|
||||
#: src/app/main/data/dashboard/shortcuts.cljs:29
|
||||
msgid "shortcuts.go-to-drafts"
|
||||
@ -5019,7 +5031,7 @@ msgstr "Join nodes"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:624
|
||||
msgid "shortcuts.key-combo"
|
||||
msgstr "Press the key combination"
|
||||
msgstr "Press the new keys"
|
||||
|
||||
#: src/app/main/data/workspace/text/shortcuts.cljs:249
|
||||
msgid "shortcuts.line-through"
|
||||
@ -5087,19 +5099,19 @@ msgstr "Next board"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:510
|
||||
msgid "shortcuts.no-disabled"
|
||||
msgstr "There are not disabled shortcuts"
|
||||
msgstr "No disabled shortcuts."
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:500
|
||||
msgid "shortcuts.no-personalized"
|
||||
msgstr "Head to All to start customizing your shortcuts."
|
||||
msgstr "No custom shortcuts yet. Customize one from the All tab."
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:242, src/app/main/ui/settings/shortcuts.cljs:491
|
||||
msgid "shortcuts.no-shortcuts"
|
||||
msgstr "There are no shortcuts that match your search"
|
||||
msgstr "No shortcuts match your search."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:154
|
||||
msgid "shortcuts.not-found"
|
||||
msgstr "No shortcuts found"
|
||||
msgstr "No shortcuts match your search."
|
||||
|
||||
#: src/app/main/data/workspace/shortcuts.cljs:848
|
||||
msgid "shortcuts.opacity-0"
|
||||
@ -5175,7 +5187,7 @@ msgstr " or "
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:605, src/app/main/ui/shortcuts.cljs:747
|
||||
msgid "shortcuts.overwritten-by"
|
||||
msgstr "This shortcut has been reassigned to %s"
|
||||
msgstr "Reassigned to \"%s\""
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:468
|
||||
msgid "shortcuts.page"
|
||||
@ -5195,7 +5207,7 @@ msgstr "Paste and replace"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:296, src/app/main/ui/settings/shortcuts.cljs:523
|
||||
msgid "shortcuts.personalized"
|
||||
msgstr "Personalized"
|
||||
msgstr "Custom"
|
||||
|
||||
#: src/app/main/data/workspace/shortcuts.cljs:832
|
||||
msgid "shortcuts.plugins"
|
||||
@ -5223,6 +5235,10 @@ msgstr "Rename"
|
||||
msgid "shortcuts.reset"
|
||||
msgstr "Reset to default"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:53
|
||||
msgid "shortcuts.reset-all"
|
||||
msgstr "Reset all"
|
||||
|
||||
#: src/app/main/data/viewer/shortcuts.cljs:48, src/app/main/data/workspace/grid_layout/shortcuts.cljs:69, src/app/main/data/workspace/path/shortcuts.cljs:220, src/app/main/data/workspace/shortcuts.cljs:704
|
||||
msgid "shortcuts.reset-zoom"
|
||||
msgstr "Reset zoom"
|
||||
@ -5231,6 +5247,10 @@ msgstr "Reset zoom"
|
||||
msgid "shortcuts.scale"
|
||||
msgstr "Scale"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
msgid "shortcuts.search"
|
||||
msgstr "Search shortcuts"
|
||||
|
||||
# SECTIONS
|
||||
#: src/app/main/ui/shortcuts.cljs:126, src/app/main/ui/shortcuts.cljs:298
|
||||
msgid "shortcuts.section.basics"
|
||||
@ -5339,7 +5359,7 @@ msgstr "Shapes"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:143
|
||||
msgid "shortcuts.subsection.text-editor"
|
||||
msgstr "Texts"
|
||||
msgstr "Text"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:144
|
||||
msgid "shortcuts.subsection.tools"
|
||||
@ -5357,7 +5377,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Set thumbnails"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Keyboard shortcuts"
|
||||
|
||||
|
||||
@ -1030,7 +1030,7 @@ msgstr "Vas a restaurar todos tus proyectos y archivos. Esto puede tardar un poc
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Restaurar todos los proyectos y archivos"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Restaurar todo"
|
||||
|
||||
@ -1621,7 +1621,7 @@ msgstr "Ha ocurrido un problema con el web worker."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Color no válido"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Datos no válidos"
|
||||
|
||||
@ -2152,16 +2152,24 @@ msgstr "Ha ocurrido un error"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:161
|
||||
msgid "import-shortcuts.apply"
|
||||
msgstr "Aplicar"
|
||||
msgstr "Importar"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:102
|
||||
msgid "import-shortcuts.diff-modal-text"
|
||||
msgstr "Los siguientes atajos se modificarán con la configuración importada."
|
||||
msgstr "Estos atajos cambiarán:"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:98
|
||||
msgid "import-shortcuts.diff-modal-title"
|
||||
msgstr "Importar atajos"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113
|
||||
msgid "import-shortcuts.imported"
|
||||
msgstr "Importado"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462
|
||||
msgid "import-shortcuts.invalid-file"
|
||||
msgstr "Este archivo no es una exportación de atajos válida."
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:148
|
||||
msgid "import-shortcuts.no-changes"
|
||||
msgstr "No se encontraron diferencias entre los atajos actuales y los importados."
|
||||
@ -3019,7 +3027,7 @@ msgstr "Ocultar"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Ocultar comentarios resueltos"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importar"
|
||||
|
||||
@ -4783,22 +4791,20 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "Ir al login"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "Acción"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
msgid "restore-shortcuts.modal-text"
|
||||
msgstr ""
|
||||
"Los siguientes atajos de teclado se restaurarán a sus valores "
|
||||
"predeterminados."
|
||||
msgstr "Estos atajos volverán a su valor por defecto:"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:90
|
||||
msgid "restore-shortcuts.modal-title"
|
||||
msgstr "Restaurar la configuración predeterminada"
|
||||
msgstr "Restablecer todos los atajos por defecto"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:170
|
||||
msgid "restore-shortcuts.restore"
|
||||
msgstr "Restaurar"
|
||||
msgstr "Restablecer"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:990, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:425
|
||||
msgid "settings.detach"
|
||||
@ -4992,21 +4998,25 @@ msgstr "Texto"
|
||||
msgid "shortcuts.duplicate"
|
||||
msgstr "Duplicar"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:589
|
||||
msgid "shortcuts.edit"
|
||||
msgstr "Editar %s"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:645
|
||||
msgid "shortcuts.edit-modal.conflict"
|
||||
msgstr "Combinación usada en \"%s\". Guardar este atajo hará que se elimine allí."
|
||||
msgstr "Ya asignado a \"%s\". Al guardar, pasará a esta acción."
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:636
|
||||
msgid "shortcuts.edit-modal.reset-pending"
|
||||
msgstr "Si guardas, este atajo volvera a su valor original:"
|
||||
msgstr "Al guardar, este atajo volverá a su valor por defecto:"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:647
|
||||
msgid "shortcuts.edit-modal.success"
|
||||
msgstr "Esta combinación de teclas está disponible y puede asignarse sin conflictos."
|
||||
msgstr "Atajo disponible."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:157
|
||||
msgid "shortcuts.edit-on-settings"
|
||||
msgstr "Personalízalos en ajustes"
|
||||
msgstr "Personalízalos"
|
||||
|
||||
#: src/app/main/data/workspace/grid_layout/shortcuts.cljs:37, src/app/main/data/workspace/path/shortcuts.cljs:175, src/app/main/data/workspace/shortcuts.cljs:177
|
||||
msgid "shortcuts.escape"
|
||||
@ -5038,11 +5048,11 @@ msgstr "Voltear verticalmente"
|
||||
|
||||
#: src/app/main/data/workspace/text/shortcuts.cljs:263
|
||||
msgid "shortcuts.font-size-dec"
|
||||
msgstr "Decrementar el tamaño de fuente"
|
||||
msgstr "Reducir el tamaño de fuente"
|
||||
|
||||
#: src/app/main/data/workspace/text/shortcuts.cljs:256
|
||||
msgid "shortcuts.font-size-inc"
|
||||
msgstr "Incrementar el tamaño de fuente"
|
||||
msgstr "Aumentar el tamaño de fuente"
|
||||
|
||||
#: src/app/main/data/dashboard/shortcuts.cljs:29
|
||||
msgid "shortcuts.go-to-drafts"
|
||||
@ -5090,7 +5100,7 @@ msgstr "Unir nodos"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:624
|
||||
msgid "shortcuts.key-combo"
|
||||
msgstr "Pulsa la combinación de teclas deseada"
|
||||
msgstr "Pulsa las nuevas teclas"
|
||||
|
||||
#: src/app/main/data/workspace/text/shortcuts.cljs:249
|
||||
msgid "shortcuts.line-through"
|
||||
@ -5162,7 +5172,7 @@ msgstr "No hay atajos deshabilitados."
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:500
|
||||
msgid "shortcuts.no-personalized"
|
||||
msgstr "Dirígete a TODO para personalizar tus atajos."
|
||||
msgstr "Aún no hay atajos personalizados. Personaliza uno desde la pestaña Todo."
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:242, src/app/main/ui/settings/shortcuts.cljs:491
|
||||
msgid "shortcuts.no-shortcuts"
|
||||
@ -5170,7 +5180,7 @@ msgstr "No hay atajos que coincidan con tu búsqueda."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:154
|
||||
msgid "shortcuts.not-found"
|
||||
msgstr "No hay resultados"
|
||||
msgstr "No hay atajos que coincidan con tu búsqueda."
|
||||
|
||||
#: src/app/main/data/workspace/shortcuts.cljs:848
|
||||
msgid "shortcuts.opacity-0"
|
||||
@ -5246,7 +5256,7 @@ msgstr " o "
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:605, src/app/main/ui/shortcuts.cljs:747
|
||||
msgid "shortcuts.overwritten-by"
|
||||
msgstr "Este atajo ha sido reasignado a %s"
|
||||
msgstr "Reasignado a \"%s\""
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:468
|
||||
msgid "shortcuts.page"
|
||||
@ -5294,6 +5304,10 @@ msgstr "Renombrar"
|
||||
msgid "shortcuts.reset"
|
||||
msgstr "Restablecer valor por defecto"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:53
|
||||
msgid "shortcuts.reset-all"
|
||||
msgstr "Restablecer todo"
|
||||
|
||||
#: src/app/main/data/viewer/shortcuts.cljs:48, src/app/main/data/workspace/grid_layout/shortcuts.cljs:69, src/app/main/data/workspace/path/shortcuts.cljs:220, src/app/main/data/workspace/shortcuts.cljs:704
|
||||
msgid "shortcuts.reset-zoom"
|
||||
msgstr "Reiniciar zoom"
|
||||
@ -5302,6 +5316,10 @@ msgstr "Reiniciar zoom"
|
||||
msgid "shortcuts.scale"
|
||||
msgstr "Escalado"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
msgid "shortcuts.search"
|
||||
msgstr "Buscar atajos"
|
||||
|
||||
# SECTIONS
|
||||
#: src/app/main/ui/shortcuts.cljs:126, src/app/main/ui/shortcuts.cljs:298
|
||||
msgid "shortcuts.section.basics"
|
||||
@ -5410,7 +5428,7 @@ msgstr "Formas"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:143
|
||||
msgid "shortcuts.subsection.text-editor"
|
||||
msgstr "Textos"
|
||||
msgstr "Texto"
|
||||
|
||||
#: src/app/main/ui/shortcuts.cljs:144
|
||||
msgid "shortcuts.subsection.tools"
|
||||
@ -5428,7 +5446,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Activar miniaturas"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Atajos de teclado"
|
||||
|
||||
|
||||
@ -2336,7 +2336,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Aktibatu iruditxoak"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Lasterteklak"
|
||||
|
||||
|
||||
@ -1015,7 +1015,7 @@ msgstr ""
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "بازیابی همهٔ پروژهها و فایلها"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "بازیابی همه"
|
||||
|
||||
@ -1593,7 +1593,7 @@ msgstr "مشکلی در وبورکر رخ داده است."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "رنگ نامعتبر"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "دادهٔ نامعتبر"
|
||||
|
||||
@ -2901,7 +2901,7 @@ msgstr "پنهان کردن"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "پنهان کردن نظرات حل شده"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "درونریزی"
|
||||
|
||||
@ -4643,7 +4643,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "رفتن به ورود"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "اقدام"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5286,7 +5286,7 @@ msgstr "بزرگنمایی"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "تنظیم تصاویر بندانگشتی"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "میانبرهای صفحهکلید"
|
||||
|
||||
|
||||
@ -1040,7 +1040,7 @@ msgstr ""
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Restaurer tous les projets et fichiers"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Tout restaurer"
|
||||
|
||||
@ -1634,7 +1634,7 @@ msgstr "Un problème est survenu avec le web worker."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Couleur non valide"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Données non valides"
|
||||
|
||||
@ -2977,7 +2977,7 @@ msgstr "Cacher"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Masquer les commentaires résolus"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importer"
|
||||
|
||||
@ -4750,7 +4750,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "Aller à la page de connexion"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "Action"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5397,7 +5397,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Définir les vignettes"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Raccourcis clavier"
|
||||
|
||||
|
||||
@ -922,7 +922,7 @@ msgstr "Tu vas restaurer tous tes projets et fichiers. Ça pourrait être long."
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Restaurer tous les projets et fichiers"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Tout restaurer"
|
||||
|
||||
@ -1502,7 +1502,7 @@ msgstr "Il y a eu une erreur avec le travailleur web en arrière-plan (web worke
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Couleur invalide"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Données invalides"
|
||||
|
||||
@ -2758,7 +2758,7 @@ msgstr "Cacher"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Cacher les commentaires résolus"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importer"
|
||||
|
||||
@ -4474,7 +4474,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "Aller à la connexion"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "Action"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5113,7 +5113,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Définir l'imagette"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Raccourcis clavier"
|
||||
|
||||
|
||||
@ -2435,7 +2435,7 @@ msgstr "Zukowa"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "saita babban yatsa"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "allon harufa yanke"
|
||||
|
||||
|
||||
@ -898,7 +898,7 @@ msgstr "הפעולה הזאת תשחזר את כל המיזמים והקבצים
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "שחזור כל המיזמים והקבצים"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "לשחזר הכול"
|
||||
|
||||
@ -1462,7 +1462,7 @@ msgstr "משהו השתבש עם מנגנון הדפדפן."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "צבע שגוי"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "נתונים שגויים"
|
||||
|
||||
@ -2534,7 +2534,7 @@ msgstr "הסתרה"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "הסתרת הערות שנפתרו"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "ייבוא"
|
||||
|
||||
@ -4473,7 +4473,7 @@ msgstr "תקריב"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "הגדרת תמונות ממוזערות"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "קיצורי מקלדת"
|
||||
|
||||
|
||||
@ -1187,7 +1187,7 @@ msgstr "वेब वर्कर के साथ कुछ गलत हो
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "अमान्य रंग"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "अमान्य डेटा"
|
||||
|
||||
@ -2076,7 +2076,7 @@ msgstr "छुपाएँ"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "सुलझाए गए टिप्पणियाँ छुपाएँ"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "आयात"
|
||||
|
||||
@ -3868,7 +3868,7 @@ msgstr "ज़ूम"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "थंबनेल सेट करें"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "कीबोर्ड शॉर्टकट्स"
|
||||
|
||||
|
||||
@ -1491,7 +1491,7 @@ msgstr "Centar za pomoć"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Sakrij riješene komentare"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Uvoz"
|
||||
|
||||
@ -3086,7 +3086,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Postavi sličice"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Prečaci na tipkovnici"
|
||||
|
||||
|
||||
@ -993,7 +993,7 @@ msgstr "Sesuatu yang salah terjadi."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Warna tidak valid"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Data tidak valid"
|
||||
|
||||
@ -1632,7 +1632,7 @@ msgstr "Sembunyikan"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Sembunyikan komentar yang telah diselesaikan"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Impor"
|
||||
|
||||
@ -3307,7 +3307,7 @@ msgstr "Zum"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Tetapkan gambar kecil"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Pintasan papan ketik"
|
||||
|
||||
|
||||
@ -907,7 +907,7 @@ msgstr ""
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Ripristina tutti i progetti e file"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Ripristina tutto"
|
||||
|
||||
@ -1475,7 +1475,7 @@ msgstr "Qualcosa è andato storto con il web worker."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Colore non valido"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Dati non validi"
|
||||
|
||||
@ -2569,7 +2569,7 @@ msgstr "Nascondi"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Non mostrare i commenti risolti"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importa"
|
||||
|
||||
@ -4381,7 +4381,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Imposta miniature"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Scorciatoie da tastiera"
|
||||
|
||||
|
||||
@ -1005,7 +1005,7 @@ msgstr "すべてのプロジェクトとファイルを復元します。しば
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "すべてのプロジェクトとファイルを復元"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "すべて復元"
|
||||
|
||||
@ -1567,7 +1567,7 @@ msgstr "Webワーカーで問題が発生しました。"
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "無効な色"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "無効なデータ"
|
||||
|
||||
@ -2842,7 +2842,7 @@ msgstr "非表示"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "解決済みのコメントを非表示"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "インポート"
|
||||
|
||||
@ -4486,7 +4486,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "ログインへ移動"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "アクション"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5127,7 +5127,7 @@ msgstr "ズーム"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "サムネイルに設定"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "キーボードショートカット"
|
||||
|
||||
|
||||
@ -789,7 +789,7 @@ msgstr "모든 프로젝트와 파일을 복원하려 합니다. 시간이 다
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "모든 프로젝트 및 파일 복원"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "모두 복원"
|
||||
|
||||
@ -1209,7 +1209,7 @@ msgstr "웹 워커에서 문제가 발생했습니다."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "유효하지 않은 컬러"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "유효하지 않은 데이터"
|
||||
|
||||
@ -1973,7 +1973,7 @@ msgstr "숨기기"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "해결된 댓글 숨기기"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "가져오기"
|
||||
|
||||
@ -3706,7 +3706,7 @@ msgstr "확대/축소"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "썸네일 설정"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "키보드 단축키"
|
||||
|
||||
|
||||
@ -1123,7 +1123,7 @@ msgstr "Kaut kas nogāja greizi ar tīmekļa strādni."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Nederīga krāsa"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Nederīgi dati"
|
||||
|
||||
@ -1877,7 +1877,7 @@ msgstr "Paslēpt"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Paslēpt atrisinātos komentārus"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Ievietot"
|
||||
|
||||
@ -3608,7 +3608,7 @@ msgstr "Tālummaiņa"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Iestatīt sīktēlus"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Īsinājumtaustiņi"
|
||||
|
||||
|
||||
@ -1024,7 +1024,7 @@ msgstr "Je gaat al je projecten en bestanden herstellen. Dit kan even duren."
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Alle projecten en bestanden herstellen"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Alles herstellen"
|
||||
|
||||
@ -1613,7 +1613,7 @@ msgstr "Er is iets mis gegaan met de webwerker."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Ongeldige kleur"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Ongeldige gegevens"
|
||||
|
||||
@ -2948,7 +2948,7 @@ msgstr "Verbergen"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Verwerkt commentaar verbergen"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importeren"
|
||||
|
||||
@ -4720,7 +4720,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "Ga naar inlogscherm"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "Actie"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5365,7 +5365,7 @@ msgstr "Zoomen"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Miniaturen instellen"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Sneltoetsen"
|
||||
|
||||
|
||||
@ -2298,7 +2298,7 @@ msgstr "Przybliżenie"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Ustaw miniaturki"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Skróty klawiaturowe"
|
||||
|
||||
|
||||
@ -1095,7 +1095,7 @@ msgstr "Algo de errado aconteceu com o web worker."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Cor inválida"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Data inválida"
|
||||
|
||||
@ -2953,7 +2953,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Definir como miniatura"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Atalhos do teclado"
|
||||
|
||||
|
||||
@ -949,7 +949,7 @@ msgstr "Ocorreu algo inesperado."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Cor inválida"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Dados inválidos"
|
||||
|
||||
@ -1520,7 +1520,7 @@ msgstr "Ocultar"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Ocultar comentários resolvidos"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importar"
|
||||
|
||||
@ -3147,7 +3147,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Definir imagem destaque"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Atalhos do teclado"
|
||||
|
||||
|
||||
@ -1110,7 +1110,7 @@ msgstr "A apărut o problemă cu web worker-ul."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Culoare invalidă"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Date invalide"
|
||||
|
||||
@ -1906,7 +1906,7 @@ msgstr "Ascunde"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Ascunde comentariile rezolvate"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importă"
|
||||
|
||||
@ -3695,7 +3695,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Setează miniaturile"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Comenzi rapide de la tastatură"
|
||||
|
||||
|
||||
@ -827,7 +827,7 @@ msgstr ""
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Восстановить все проекты и файлы"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Восстановить все"
|
||||
|
||||
@ -1259,7 +1259,7 @@ msgstr "Произошла ошибка в работе веб-воркера."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Неверный цвет"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Неверные данные"
|
||||
|
||||
@ -2039,7 +2039,7 @@ msgstr "Скрыть"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Скрыть решённые комментарии"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Импорт"
|
||||
|
||||
@ -3717,7 +3717,7 @@ msgstr "Увеличение"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Установить миниатюры"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Горячие клавиши"
|
||||
|
||||
|
||||
@ -2682,7 +2682,7 @@ msgstr "Увећање"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Подеси сличице"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Пречице на тастатури"
|
||||
|
||||
|
||||
@ -1015,7 +1015,7 @@ msgstr "Du kommer att återställa alla dina projekt och filer. Det kan ta ett t
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Återställ alla projekt och filer"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Återställ allt"
|
||||
|
||||
@ -1600,7 +1600,7 @@ msgstr "Något fel har hänt med webbarbetaren."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Ogiltig färg"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Ogiltig data"
|
||||
|
||||
@ -2921,7 +2921,7 @@ msgstr "Dölj"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Dölj åtgärdade kommentarer"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Importera"
|
||||
|
||||
@ -4671,7 +4671,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "Gå till Logga in"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "Åtgärd"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5316,7 +5316,7 @@ msgstr "Zoom"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Ställ in miniatyrbilder"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Snabbkommandon"
|
||||
|
||||
|
||||
@ -1020,7 +1020,7 @@ msgstr ""
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Tüm projeleri ve dosyaları geri yükle"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Tümünü Geri Yükle"
|
||||
|
||||
@ -1602,7 +1602,7 @@ msgstr "Web çalıştırıcısında bir sorun oluştu."
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Geçersiz renk"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Geçersiz veri"
|
||||
|
||||
@ -2918,7 +2918,7 @@ msgstr "Gizle"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Çözülen yorumları gizle"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "İçe aktar"
|
||||
|
||||
@ -4667,7 +4667,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "Oturum açmaya git"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "Eylem"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5312,7 +5312,7 @@ msgstr "Yakınlaştır"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Küçük resimleri ayarla"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Klavye kısayolları"
|
||||
|
||||
|
||||
@ -876,7 +876,7 @@ msgstr "Ви збираєтесь відновити усі проєкти та
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "Відновити усі проєкти й файли"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "Відновити Все"
|
||||
|
||||
@ -1387,7 +1387,7 @@ msgstr "Під час роботи веб-виконавця сталась по
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "Недійсний колір"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "Помилкові дані"
|
||||
|
||||
@ -2272,7 +2272,7 @@ msgstr "Приховати"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "Приховати вирішені коментарі"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "Імпортувати"
|
||||
|
||||
@ -4087,7 +4087,7 @@ msgstr "Масштабування"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "Вставити мініатюри"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "Клавіатурні скорочення"
|
||||
|
||||
|
||||
@ -828,7 +828,7 @@ msgstr "你将恢复所有项目和文件。这可能需要一些时间。"
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "恢复所有的项目及其文件"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "恢复所有"
|
||||
|
||||
@ -1316,7 +1316,7 @@ msgstr "Web Worker 出错。"
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "无效的颜色"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "无效数据"
|
||||
|
||||
@ -2474,7 +2474,7 @@ msgstr "隐藏"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "隐藏已解决的评论"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "导入"
|
||||
|
||||
@ -4466,7 +4466,7 @@ msgstr "缩放"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "设置缩略图"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "快捷键"
|
||||
|
||||
|
||||
@ -966,7 +966,7 @@ msgstr "即將還原您所有的專案與檔案,這可能需要一些時間。
|
||||
msgid "dashboard.restore-all-confirmation.title"
|
||||
msgstr "還原所有專案與檔案"
|
||||
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317, src/app/main/ui/settings/shortcuts.cljs:53
|
||||
#: src/app/main/ui/dashboard/deleted.cljs:317
|
||||
msgid "dashboard.restore-all-deleted-button"
|
||||
msgstr "全部還原"
|
||||
|
||||
@ -1515,7 +1515,7 @@ msgstr "Web Worker 發生錯誤。"
|
||||
msgid "errors.invalid-color"
|
||||
msgstr "色彩無效"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:460, src/app/main/ui/settings/shortcuts.cljs:462, src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
#: src/app/plugins/utils.cljs:517, ../common/src/app/common/schema/messages.cljc:33, ../common/src/app/common/schema/messages.cljc:87
|
||||
msgid "errors.invalid-data"
|
||||
msgstr "資料無效"
|
||||
|
||||
@ -2774,7 +2774,7 @@ msgstr "隱藏"
|
||||
msgid "labels.hide-resolved-comments"
|
||||
msgstr "隱藏已解決的留言"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:113, src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:544, src/app/main/ui/workspace/tokens/sidebar.cljs:142
|
||||
msgid "labels.import"
|
||||
msgstr "匯入"
|
||||
|
||||
@ -4408,7 +4408,7 @@ msgid "profile.recovery.go-to-login"
|
||||
msgstr "前往登入"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs:109, src/app/main/ui/settings/restore_shortcuts_modal.cljs:100
|
||||
msgid "restore-shortcuts.acction"
|
||||
msgid "restore-shortcuts.action"
|
||||
msgstr "操作"
|
||||
|
||||
#: src/app/main/ui/settings/restore_shortcuts_modal.cljs:94
|
||||
@ -5049,7 +5049,7 @@ msgstr "縮放"
|
||||
msgid "shortcuts.thumbnail-set"
|
||||
msgstr "設定縮圖"
|
||||
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:44, src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132, src/app/main/ui/workspace/sidebar/shortcuts.cljs:139
|
||||
#: src/app/main/ui/settings/shortcuts.cljs:170, src/app/main/ui/workspace/sidebar/shortcuts.cljs:132
|
||||
msgid "shortcuts.title"
|
||||
msgstr "鍵盤快速鍵"
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
"watch": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch library"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^26.1.2",
|
||||
"@types/node": "^26.6.4",
|
||||
"@zip.js/zip.js": "2.8.34",
|
||||
"concurrently": "^10.0.5",
|
||||
"date-fns": "^4.4.0",
|
||||
|
||||
61
library/pnpm-lock.yaml
generated
61
library/pnpm-lock.yaml
generated
@ -162,6 +162,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
shell-quote@>=1.8.4 <1.11.0: ^1.11.0
|
||||
|
||||
patchedDependencies:
|
||||
'@zip.js/zip.js@2.8.34': 7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95
|
||||
|
||||
@ -170,8 +173,8 @@ importers:
|
||||
.:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^26.1.2
|
||||
version: 26.1.2
|
||||
specifier: ^26.6.4
|
||||
version: 26.6.4
|
||||
'@zip.js/zip.js':
|
||||
specifier: 2.8.34
|
||||
version: 2.8.34(patch_hash=7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95)
|
||||
@ -190,15 +193,15 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@types/node@26.1.2':
|
||||
resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==}
|
||||
'@types/node@26.6.4':
|
||||
resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==}
|
||||
|
||||
'@zip.js/zip.js@2.8.34':
|
||||
resolution: {integrity: sha512-+6a3lyqq69rpseLbvDPiVIWsZ/HdTGAAD6afFtug6ECPDGttb2dHnPC6cJgdPofYkzL9OvXizegq+DQVfL2rnA==}
|
||||
engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'}
|
||||
|
||||
ansi-regex@6.2.2:
|
||||
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
|
||||
ansi-regex@6.4.0:
|
||||
resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ansi-styles@6.2.3:
|
||||
@ -217,8 +220,8 @@ packages:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
brace-expansion@5.0.9:
|
||||
resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
|
||||
brace-expansion@5.0.12:
|
||||
resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
braces@3.0.3:
|
||||
@ -277,8 +280,8 @@ packages:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-east-asian-width@1.6.0:
|
||||
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
|
||||
get-east-asian-width@1.7.0:
|
||||
resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
@ -308,8 +311,8 @@ packages:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
minimatch@10.2.6:
|
||||
resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
ms@2.1.3:
|
||||
@ -343,8 +346,8 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
shell-quote@1.9.0:
|
||||
resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==}
|
||||
shell-quote@1.12.0:
|
||||
resolution: {integrity: sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
simple-update-notifier@2.0.0:
|
||||
@ -392,8 +395,8 @@ packages:
|
||||
undefsafe@2.0.5:
|
||||
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
||||
|
||||
undici-types@8.3.0:
|
||||
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
|
||||
undici-types@8.9.0:
|
||||
resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
|
||||
|
||||
wrap-ansi@9.0.2:
|
||||
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
|
||||
@ -413,13 +416,13 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@types/node@26.1.2':
|
||||
'@types/node@26.6.4':
|
||||
dependencies:
|
||||
undici-types: 8.3.0
|
||||
undici-types: 8.9.0
|
||||
|
||||
'@zip.js/zip.js@2.8.34(patch_hash=7b556bbd426f152eb086f0126a53900e369a95cf64357c380b7c8d8e940c3d95)': {}
|
||||
|
||||
ansi-regex@6.2.2: {}
|
||||
ansi-regex@6.4.0: {}
|
||||
|
||||
ansi-styles@6.2.3: {}
|
||||
|
||||
@ -432,7 +435,7 @@ snapshots:
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
brace-expansion@5.0.9:
|
||||
brace-expansion@5.0.12:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
@ -466,7 +469,7 @@ snapshots:
|
||||
dependencies:
|
||||
chalk: 5.6.2
|
||||
rxjs: 7.8.2
|
||||
shell-quote: 1.9.0
|
||||
shell-quote: 1.12.0
|
||||
supports-color: 10.2.2
|
||||
tree-kill: 1.2.2
|
||||
yargs: 18.0.0
|
||||
@ -492,7 +495,7 @@ snapshots:
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-east-asian-width@1.6.0: {}
|
||||
get-east-asian-width@1.7.0: {}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
@ -514,9 +517,9 @@ snapshots:
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
minimatch@10.2.5:
|
||||
minimatch@10.2.6:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.9
|
||||
brace-expansion: 5.0.12
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
@ -525,7 +528,7 @@ snapshots:
|
||||
chokidar: 3.6.0
|
||||
debug: 4.4.3(supports-color@5.5.0)
|
||||
ignore-by-default: 1.0.1
|
||||
minimatch: 10.2.5
|
||||
minimatch: 10.2.6
|
||||
pstree.remy: 1.1.8
|
||||
semver: 7.8.5
|
||||
simple-update-notifier: 2.0.0
|
||||
@ -549,7 +552,7 @@ snapshots:
|
||||
|
||||
semver@7.8.5: {}
|
||||
|
||||
shell-quote@1.9.0: {}
|
||||
shell-quote@1.12.0: {}
|
||||
|
||||
simple-update-notifier@2.0.0:
|
||||
dependencies:
|
||||
@ -565,12 +568,12 @@ snapshots:
|
||||
string-width@7.2.0:
|
||||
dependencies:
|
||||
emoji-regex: 10.6.0
|
||||
get-east-asian-width: 1.6.0
|
||||
get-east-asian-width: 1.7.0
|
||||
strip-ansi: 7.2.0
|
||||
|
||||
strip-ansi@7.2.0:
|
||||
dependencies:
|
||||
ansi-regex: 6.2.2
|
||||
ansi-regex: 6.4.0
|
||||
|
||||
supports-color@10.2.2: {}
|
||||
|
||||
@ -590,7 +593,7 @@ snapshots:
|
||||
|
||||
undefsafe@2.0.5: {}
|
||||
|
||||
undici-types@8.3.0: {}
|
||||
undici-types@8.9.0: {}
|
||||
|
||||
wrap-ansi@9.0.2:
|
||||
dependencies:
|
||||
|
||||
@ -4,3 +4,5 @@ minimumReleaseAgeExclude:
|
||||
- brace-expansion@5.0.8 || 5.0.9
|
||||
patchedDependencies:
|
||||
'@zip.js/zip.js@2.8.34': patches/@zip.js__zip.js@2.8.11.patch
|
||||
overrides:
|
||||
shell-quote@>=1.8.4 <1.11.0: ^1.11.0
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
"clean": "rm -rf dist/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^6.0.3"
|
||||
"typescript": "^7.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
|
||||
@ -18,8 +18,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.1.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.3.1",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "^8.3.2",
|
||||
"vite-live-preview": "^0.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,34 +29,34 @@
|
||||
},
|
||||
"packageManager": "pnpm@12.8.1+sha512.f64ba907507f5ceafe06c8d38e6052d0179444580ec1279ddd5bfc11cb48aa8a2644b66598e07e761da84872a9fc57d5f902b87fa49d024198d558612aabbe45",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/node": "^2.1.0",
|
||||
"@modelcontextprotocol/server": "^2.1.0",
|
||||
"@modelcontextprotocol/node": "^2.1.1",
|
||||
"@modelcontextprotocol/server": "^2.3.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.15.1",
|
||||
"express": "^5.2.1",
|
||||
"ioredis": "^6.0.0",
|
||||
"js-yaml": "^5.4.2",
|
||||
"nrepl-client": "^0.3.0",
|
||||
"pino": "^10.3.1",
|
||||
"pino": "^10.4.0",
|
||||
"pino-loki": "^3.0.0",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"pino-pretty": "^13.2.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"sharp": "^0.35.5",
|
||||
"ws": "^8.22.0",
|
||||
"zod": "^4.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modelcontextprotocol/client": "^2.1.0",
|
||||
"@modelcontextprotocol/client": "^2.3.1",
|
||||
"@penpot/mcp-common": "workspace:../common",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^26.6.3",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@types/node": "^26.6.4",
|
||||
"@types/ws": "^8.18.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"esbuild": "^0.28.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.23.15",
|
||||
"typescript": "^6.0.3"
|
||||
"typescript": "^7.0.2"
|
||||
},
|
||||
"ts-node": {
|
||||
"esm": true
|
||||
|
||||
678
mcp/pnpm-lock.yaml
generated
678
mcp/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user