Merge tag '2.13.0-RC9'

This commit is contained in:
Andrey Antukh 2026-01-26 18:12:16 +01:00
commit abc1773f65
37 changed files with 513 additions and 200 deletions

View File

@ -11,7 +11,7 @@ on:
- "plugins/libs/plugin-types/REAME.md" - "plugins/libs/plugin-types/REAME.md"
- "plugins/tools/typedoc.css" - "plugins/tools/typedoc.css"
- "plugins/CHANGELOG.md" - "plugins/CHANGELOG.md"
- "plugins/wrangler-penpot-plugins-api-doc.toml" - "plugins/wrangle-penpot-plugins-api-doc.toml"
workflow_dispatch: workflow_dispatch:
inputs: inputs:
gh_ref: gh_ref:
@ -98,16 +98,4 @@ jobs:
workingDirectory: plugins workingDirectory: plugins
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangler-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }} command: deploy --config wrangle-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
❌ 🧩📚 *[PENPOT PLUGINS] Error deploying API documentation.*
📄 Triggered from ref: `${{ inputs.gh_ref }}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra

View File

@ -21,7 +21,7 @@ concurrency:
jobs: jobs:
lint: lint:
name: "Linter" name: "Linter"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
steps: steps:
@ -34,7 +34,7 @@ jobs:
test-common: test-common:
name: "Common Tests" name: "Common Tests"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
steps: steps:
@ -53,7 +53,8 @@ jobs:
test-plugins: test-plugins:
name: Plugins Runtime Linter & Tests name: Plugins Runtime Linter & Tests
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -98,7 +99,7 @@ jobs:
test-frontend: test-frontend:
name: "Frontend Tests" name: "Frontend Tests"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
steps: steps:
@ -119,7 +120,7 @@ jobs:
test-render-wasm: test-render-wasm:
name: "Render WASM Tests" name: "Render WASM Tests"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
steps: steps:
@ -143,7 +144,7 @@ jobs:
test-backend: test-backend:
name: "Backend Tests" name: "Backend Tests"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
services: services:
@ -182,7 +183,7 @@ jobs:
test-library: test-library:
name: "Library Tests" name: "Library Tests"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
steps: steps:
@ -196,7 +197,7 @@ jobs:
build-integration: build-integration:
name: "Build Integration Bundle" name: "Build Integration Bundle"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
steps: steps:
@ -217,7 +218,7 @@ jobs:
test-integration-1: test-integration-1:
name: "Integration Tests 1/4" name: "Integration Tests 1/4"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
needs: build-integration needs: build-integration
@ -247,7 +248,7 @@ jobs:
test-integration-2: test-integration-2:
name: "Integration Tests 2/4" name: "Integration Tests 2/4"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
needs: build-integration needs: build-integration
@ -277,7 +278,7 @@ jobs:
test-integration-3: test-integration-3:
name: "Integration Tests 3/4" name: "Integration Tests 3/4"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
needs: build-integration needs: build-integration
@ -307,7 +308,7 @@ jobs:
test-integration-4: test-integration-4:
name: "Integration Tests 4/4" name: "Integration Tests 4/4"
runs-on: ubuntu-24.04 runs-on: penpot-runner-02
container: penpotapp/devenv:latest container: penpotapp/devenv:latest
needs: build-integration needs: build-integration

View File

@ -1,40 +0,0 @@
dist: xenial
language: generic
sudo: required
cache:
directories:
- $HOME/.m2
services:
- docker
branches:
only:
- master
- develop
install:
- curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh
- chmod +x linux-install-1.10.1.447.sh
- sudo ./linux-install-1.10.1.447.sh
before_script:
- env | sort
script:
- ./manage.sh build-devenv
- ./manage.sh run-frontend-tests
- ./manage.sh run-backend-tests
- ./manage.sh build-images
- ./manage.sh run
after_script:
- docker images
notifications:
email: false
env:
- NODE_VERSION=10.16.0

View File

@ -29,6 +29,13 @@
- Fix missing text color token from selected shapes in selected colors list [Taiga #12956](https://tree.taiga.io/project/penpot/issue/12956) - Fix missing text color token from selected shapes in selected colors list [Taiga #12956](https://tree.taiga.io/project/penpot/issue/12956)
- Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959) - Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959)
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865) - Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
- Fix allow negative spread values on shadow token creation [Taiga #13167](https://tree.taiga.io/project/penpot/issue/13167)
- Fix spanish translations on import export token modal [Taiga #13171](https://tree.taiga.io/project/penpot/issue/13171)
- Remove whitespaces from asset export filename [Github #8133](https://github.com/penpot/penpot/pull/8133)
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
## 2.12.1 ## 2.12.1

View File

@ -124,8 +124,6 @@
(throw (IllegalArgumentException. "invalid email body provided"))) (throw (IllegalArgumentException. "invalid email body provided")))
(doseq [[name content] attachments] (doseq [[name content] attachments]
(prn "attachment" name)
(let [attachment-part (MimeBodyPart.)] (let [attachment-part (MimeBodyPart.)]
(.setFileName attachment-part ^String name) (.setFileName attachment-part ^String name)
(.setContent attachment-part ^String content (str "text/plain; charset=" charset)) (.setContent attachment-part ^String content (str "text/plain; charset=" charset))

View File

@ -27,7 +27,17 @@
[app.rpc.helpers :as rph] [app.rpc.helpers :as rph]
[app.rpc.quotes :as quotes] [app.rpc.quotes :as quotes]
[app.storage :as sto] [app.storage :as sto]
[app.util.services :as sv])) [app.storage.tmp :as tmp]
[app.util.services :as sv]
[datoteka.io :as io])
(:import
java.io.InputStream
java.io.OutputStream
java.io.SequenceInputStream
java.util.Collections))
(set! *warn-on-reflection* true)
(def valid-weight #{100 200 300 400 500 600 700 800 900 950}) (def valid-weight #{100 200 300 400 500 600 700 800 900 950})
(def valid-style #{"normal" "italic"}) (def valid-style #{"normal" "italic"})
@ -105,7 +115,7 @@
(defn create-font-variant (defn create-font-variant
[{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}] [{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}]
(letfn [(generate-missing! [data] (letfn [(generate-missing [data]
(let [data (media/run {:cmd :generate-fonts :input data})] (let [data (media/run {:cmd :generate-fonts :input data})]
(when (and (not (contains? data "font/otf")) (when (and (not (contains? data "font/otf"))
(not (contains? data "font/ttf")) (not (contains? data "font/ttf"))
@ -116,8 +126,26 @@
:hint "invalid font upload, unable to generate missing font assets")) :hint "invalid font upload, unable to generate missing font assets"))
data)) data))
(process-chunks [chunks]
(let [tmp (tmp/tempfile :prefix "penpot.tempfont." :suffix "")
streams (map io/input-stream chunks)
streams (Collections/enumeration streams)]
(with-open [^OutputStream output (io/output-stream tmp)
^InputStream input (SequenceInputStream. streams)]
(io/copy input output))
tmp))
(join-chunks [data]
(reduce-kv (fn [data mtype content]
(if (vector? content)
(assoc data mtype (process-chunks content))
data))
data
data))
(prepare-font [data mtype] (prepare-font [data mtype]
(when-let [resource (get data mtype)] (when-let [resource (get data mtype)]
(let [hash (sto/calculate-hash resource) (let [hash (sto/calculate-hash resource)
content (-> (sto/content resource) content (-> (sto/content resource)
(sto/wrap-with-hash hash))] (sto/wrap-with-hash hash))]
@ -156,7 +184,8 @@
:otf-file-id (:id otf) :otf-file-id (:id otf)
:ttf-file-id (:id ttf)}))] :ttf-file-id (:id ttf)}))]
(let [data (generate-missing! data) (let [data (join-chunks data)
data (generate-missing data)
assets (persist-fonts-files! data) assets (persist-fonts-files! data)
result (insert-font-variant! assets)] result (insert-font-variant! assets)]
(vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys)))))) (vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys))))))

View File

@ -36,7 +36,7 @@
{:path path {:path path
:mtype (mime/get type) :mtype (mime/get type)
:name name :name name
:filename (str/concat name (mime/get-extension type)) :filename (str/concat (str/slug name) (mime/get-extension type))
:id task-id})) :id task-id}))
(defn create-zip (defn create-zip

View File

@ -48,13 +48,13 @@
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook", "watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
"clear:shadow-cache": "rm -rf .shadow-cljs", "clear:shadow-cache": "rm -rf .shadow-cljs",
"watch": "exit 0", "watch": "exit 0",
"watch:app": "yarn run clear:shadow-cache && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", "watch:app": "yarn run clear:shadow-cache && yarn run build:wasm && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
"watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"" "watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
}, },
"devDependencies": { "devDependencies": {
"@penpot/draft-js": "portal:./packages/draft-js", "@penpot/draft-js": "portal:./packages/draft-js",
"@penpot/mousetrap": "portal:./packages/mousetrap", "@penpot/mousetrap": "portal:./packages/mousetrap",
"@penpot/plugins-runtime": "1.3.2", "@penpot/plugins-runtime": "1.4.2",
"@penpot/svgo": "penpot/svgo#v3.2", "@penpot/svgo": "penpot/svgo#v3.2",
"@penpot/text-editor": "portal:./text-editor", "@penpot/text-editor": "portal:./text-editor",
"@playwright/test": "1.57.0", "@playwright/test": "1.57.0",

View File

@ -1256,6 +1256,192 @@ test.describe("Tokens: Tokens Tab", () => {
).toBeEnabled(); ).toBeEnabled();
}); });
test("User creates shadow token with negative spread", async ({ page }) => {
const emptyNameError = "Name should be at least 1 character";
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
await setupEmptyTokensFile(page, {flags: ["enable-token-shadow"]});
// Open modal
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const addTokenButton = tokensTabPanel.getByRole("button", {
name: `Add Token: Shadow`,
});
await addTokenButton.click();
await expect(tokensUpdateCreateModal).toBeVisible();
await expect(
tokensUpdateCreateModal.getByPlaceholder(
"Enter a value or alias with {alias}",
),
).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Color",
});
const offsetXField = tokensUpdateCreateModal.getByRole("textbox", {
name: "X",
});
const offsetYField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Y",
});
const blurField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Blur",
});
const spreadField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Spread",
});
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
// 1. Check default values
await expect(offsetXField).toHaveValue("4");
await expect(offsetYField).toHaveValue("4");
await expect(blurField).toHaveValue("4");
await expect(spreadField).toHaveValue("0");
// 2. Name filled + empty value → disabled
await nameField.fill("my-token");
await expect(submitButton).toBeDisabled();
// 3. Invalid color → disabled + error message
await colorField.fill("1");
await expect(
tokensUpdateCreateModal.getByText("Invalid color value: 1"),
).toBeVisible();
await expect(submitButton).toBeDisabled();
await colorField.fill("{missing-reference}");
await expect(
tokensUpdateCreateModal.getByText(
"Missing token references: missing-reference",
),
).toBeVisible();
// 4. Empty name → disabled + error message
await nameField.fill("");
const emptyNameErrorNode =
tokensUpdateCreateModal.getByText(emptyNameError);
await expect(emptyNameErrorNode).toBeVisible();
await expect(submitButton).toBeDisabled();
//
// ------- SUCCESSFUL FIELDS -------
//
// 5. Valid color → resolved
await colorField.fill("red");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: #ff0000"),
).toBeVisible();
const colorSwatch = tokensUpdateCreateModal.getByTestId(
"token-form-color-bullet",
);
await colorSwatch.click();
const rampSelector = tokensUpdateCreateModal.getByTestId(
"value-saturation-selector",
);
await expect(rampSelector).toBeVisible();
await rampSelector.click({ position: { x: 50, y: 50 } });
await expect(
tokensUpdateCreateModal.getByText("Resolved value:"),
).toBeVisible();
const sliderOpacity = tokensUpdateCreateModal.getByTestId("slider-opacity");
await sliderOpacity.click({ position: { x: 50, y: 0 } });
await expect(
tokensUpdateCreateModal.getByRole("textbox", { name: "Color" }),
).toHaveValue(/rgba\s*\([^)]*\)/);
// 6. Valid offset → resolved
await offsetXField.fill("3 + 3");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: 6"),
).toBeVisible();
await offsetYField.fill("3 + 7");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: 10"),
).toBeVisible();
// 7. Valid blur → resolved
await blurField.fill("3 + 1");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: 4"),
).toBeVisible();
// 8. Valid spread → resolved
await spreadField.fill("3 - 3");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: 0"),
).toBeVisible();
await spreadField.fill("1 - 3");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: -2"),
).toBeVisible();
await nameField.fill("my-token");
await expect(submitButton).toBeEnabled();
await submitButton.click();
await expect(
tokensTabPanel.getByRole("button", { name: "my-token" }),
).toBeEnabled();
//
// ------- SECOND TOKEN WITH VALID REFERENCE -------
//
await addTokenButton.click();
await nameField.fill("my-token-2");
const referenceToggle =
tokensUpdateCreateModal.getByTestId("reference-opt");
const compositeToggle =
tokensUpdateCreateModal.getByTestId("composite-opt");
await referenceToggle.click();
const referenceInput = tokensUpdateCreateModal.getByPlaceholder(
"Enter a token shadow alias",
);
await expect(referenceInput).toBeVisible();
await compositeToggle.click();
await expect(colorField).toBeVisible();
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Reference",
});
await referenceField.fill("{my-token}");
await expect(
tokensUpdateCreateModal.getByText(
"Resolved value: - X: 6 - Y: 10 - Blur: 4 - Spread: -2",
),
).toBeVisible();
await expect(submitButton).toBeEnabled();
await submitButton.click();
await expect(
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
).toBeEnabled();
});
test("User creates typography token", async ({ page }) => { test("User creates typography token", async ({ page }) => {
const emptyNameError = "Name should be at least 1 character"; const emptyNameError = "Name should be at least 1 character";
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =

View File

@ -174,6 +174,7 @@ export async function watch(baseDir, predicate, callback) {
const watcher = new Watcher(baseDir, { const watcher = new Watcher(baseDir, {
persistent: true, persistent: true,
recursive: true, recursive: true,
debounce: 500
}); });
watcher.on("change", (path) => { watcher.on("change", (path) => {
@ -181,8 +182,19 @@ export async function watch(baseDir, predicate, callback) {
callback(path); callback(path);
} }
}); });
watcher.on("error", (cause) => {
console.log("WATCHER ERROR", cause);
});
} }
export async function ensureDirectories() {
await fs.mkdir("./resources/public/js/worker/", { recursive: true });
await fs.mkdir("./resources/public/css/", { recursive: true });
}
async function readManifestFile(resource) { async function readManifestFile(resource) {
const manifestPath = "resources/public/" + resource; const manifestPath = "resources/public/" + resource;
let content = await fs.readFile(manifestPath, { encoding: "utf8" }); let content = await fs.readFile(manifestPath, { encoding: "utf8" });
@ -260,6 +272,9 @@ const markedOptions = {
marked.use(markedOptions); marked.use(markedOptions);
export async function compileTranslations() { export async function compileTranslations() {
const outputDir = "resources/public/js/";
await fs.mkdir(outputDir, { recursive: true });
const langs = [ const langs = [
"ar", "ar",
"ca", "ca",
@ -341,7 +356,6 @@ export async function compileTranslations() {
} }
const esm = `export default ${JSON.stringify(result, null, 0)};\n`; const esm = `export default ${JSON.stringify(result, null, 0)};\n`;
const outputDir = "resources/public/js/";
const outputFile = ph.join(outputDir, "translation." + lang + ".js"); const outputFile = ph.join(outputDir, "translation." + lang + ".js");
await fs.writeFile(outputFile, esm); await fs.writeFile(outputFile, esm);
} }
@ -499,17 +513,43 @@ export async function compileStyles() {
export async function compileSvgSprites() { export async function compileSvgSprites() {
const start = process.hrtime(); const start = process.hrtime();
log.info("init: compile svgsprite"); log.info("init: compile svgsprite");
await generateSvgSprites(); let error = false;
try {
await generateSvgSprites();
} catch (cause) {
error = cause;
}
const end = process.hrtime(start); const end = process.hrtime(start);
log.info("done: compile svgsprite", `(${ppt(end)})`);
if (error) {
log.error("error: compile svgsprite", `(${ppt(end)})`);
console.error(error);
} else {
log.info("done: compile svgsprite", `(${ppt(end)})`);
}
} }
export async function compileTemplates() { export async function compileTemplates() {
const start = process.hrtime(); const start = process.hrtime();
let error = false;
log.info("init: compile templates"); log.info("init: compile templates");
await generateTemplates();
try {
await generateTemplates();
} catch (cause) {
error = cause;
}
const end = process.hrtime(start); const end = process.hrtime(start);
log.info("done: compile templates", `(${ppt(end)})`);
if (error) {
log.error("error: compile templates", `(${ppt(end)})`);
console.error(error);
} else {
log.info("done: compile templates", `(${ppt(end)})`);
}
} }
export async function compilePolyfills() { export async function compilePolyfills() {

View File

@ -28,14 +28,12 @@ async function compileFile(path) {
], ],
sourceMap: false, sourceMap: false,
}); });
// console.dir(result);
resolve({ resolve({
inputPath: path, inputPath: path,
outputPath: dest, outputPath: dest,
css: result.css, css: result.css,
}); });
} catch (cause) { } catch (cause) {
console.error(cause);
reject(cause); reject(cause);
} }
}); });

View File

@ -1,5 +1,6 @@
import * as h from "./_helpers.js"; import * as h from "./_helpers.js";
await h.ensureDirectories();
await h.compileStyles(); await h.compileStyles();
await h.copyAssets(); await h.copyAssets();
await h.copyWasmPlayground(); await h.copyWasmPlayground();

View File

@ -12,19 +12,31 @@ let sass = null;
async function compileSassAll() { async function compileSassAll() {
const start = process.hrtime(); const start = process.hrtime();
let error = false;
log.info("init: compile styles"); log.info("init: compile styles");
sass = await h.compileSassAll(worker); try {
let output = await h.concatSass(sass); sass = await h.compileSassAll(worker);
await fs.writeFile("./resources/public/css/main.css", output); let output = await h.concatSass(sass);
await fs.writeFile("./resources/public/css/main.css", output);
if (isDebug) { if (isDebug) {
let debugCSS = await h.compileSassDebug(worker); let debugCSS = await h.compileSassDebug(worker);
await fs.writeFile("./resources/public/css/debug.css", debugCSS); await fs.writeFile("./resources/public/css/debug.css", debugCSS);
}
} catch (cause) {
error = cause;
} }
const end = process.hrtime(start); const end = process.hrtime(start);
log.info("done: compile styles", `(${ppt(end)})`);
if (error) {
log.error("error: compile styles", `(${ppt(end)})`);
console.error(error);
} else {
log.info("done: compile styles", `(${ppt(end)})`);
}
} }
async function compileSass(path) { async function compileSass(path) {
@ -48,7 +60,7 @@ async function compileSass(path) {
} }
} }
await fs.mkdir("./resources/public/css/", { recursive: true }); await h.ensureDirectories();
await compileSassAll(); await compileSassAll();
await h.copyAssets(); await h.copyAssets();
await h.copyWasmPlayground(); await h.copyWasmPlayground();

View File

@ -24,6 +24,20 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
(def ^:const default-chunk-size
(* 1024 1024 4)) ;; 4MiB
(defn- chunk-array
[data chunk-size]
(let [total-size (alength data)]
(loop [offset 0
chunks []]
(if (< offset total-size)
(let [end (min (+ offset chunk-size) total-size)
chunk (.subarray ^js data offset end)]
(recur end (conj chunks chunk)))
chunks))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; General purpose events & IMPL ;; General purpose events & IMPL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -116,9 +130,9 @@
(not= hhea-descender win-descent) (not= hhea-descender win-descent)
(and f-selection (or (and f-selection (or
(not= hhea-ascender os2-ascent) (not= hhea-ascender os2-ascent)
(not= hhea-descender os2-descent))))] (not= hhea-descender os2-descent))))
data (js/Uint8Array. data)]
{:content {:data (js/Uint8Array. data) {:content {:data (chunk-array data default-chunk-size)
:name name :name name
:type type} :type type}
:font-family (or family "") :font-family (or family "")

View File

@ -126,7 +126,7 @@
If the `value` is not parseable and/or has missing references returns a map with `:errors`. If the `value` is not parseable and/or has missing references returns a map with `:errors`.
If the `value` is parseable but is out of range returns a map with `warnings`." If the `value` is parseable but is out of range returns a map with `warnings`."
[value] [value]
(let [missing-references? (seq (seq (cto/find-token-value-references value))) (let [missing-references? (seq (cto/find-token-value-references value))
parsed-value (cft/parse-token-value value) parsed-value (cft/parse-token-value value)
out-of-scope (not (<= 0 (:value parsed-value) 1)) out-of-scope (not (<= 0 (:value parsed-value) 1))
references (seq (cto/find-token-value-references value))] references (seq (cto/find-token-value-references value))]
@ -152,15 +152,14 @@
[value] [value]
(let [missing-references? (seq (cto/find-token-value-references value)) (let [missing-references? (seq (cto/find-token-value-references value))
parsed-value (cft/parse-token-value value) parsed-value (cft/parse-token-value value)
out-of-scope (< (:value parsed-value) 0) out-of-scope (< (:value parsed-value) 0)]
references (seq (cto/find-token-value-references value))]
(cond (cond
(and parsed-value (not out-of-scope)) (and parsed-value (not out-of-scope))
parsed-value parsed-value
references missing-references?
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)] {:errors [(wte/error-with-value :error.style-dictionary/missing-reference missing-references?)]
:references references} :references missing-references?}
(and (not missing-references?) out-of-scope) (and (not missing-references?) out-of-scope)
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-stroke-width value)]} {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-stroke-width value)]}
@ -365,7 +364,7 @@
"Parses shadow spread value (non-negative number)." "Parses shadow spread value (non-negative number)."
[value] [value]
(let [parsed (parse-sd-token-general-value value) (let [parsed (parse-sd-token-general-value value)
valid? (and (:value parsed) (>= (:value parsed) 0))] valid? (:value parsed)]
(cond (cond
valid? valid?
parsed parsed

View File

@ -480,6 +480,9 @@
(def workspace-token-sets-tree (def workspace-token-sets-tree
(l/derived (d/nilf ctob/get-set-tree) tokens-lib)) (l/derived (d/nilf ctob/get-set-tree) tokens-lib))
(def workspace-all-tokens-map
(l/derived (d/nilf ctob/get-all-tokens) tokens-lib))
(def workspace-active-theme-paths (def workspace-active-theme-paths
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))

View File

@ -193,16 +193,15 @@
restore-fn restore-fn
(fn [_] (fn [_]
(st/emit! (dd/restore-files-immediately (st/emit! (dd/restore-files-immediately
(with-meta {:team-id (:id current-team) (with-meta {:team-id current-team-id
:ids (into #{} d/xf:map-id files)} :ids (into #{} d/xf:map-id files)}
{:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file))) {:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file)))
(dd/fetch-projects (:id current-team)) (dd/fetch-projects current-team-id)
(dd/fetch-deleted-files (:id current-team))) (dd/fetch-deleted-files current-team-id))
:on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))})))) :on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))}))))
on-restore-immediately on-restore-immediately
(fn [] (fn []
(prn files)
(st/emit! (st/emit!
(modal/show {:type :confirm (modal/show {:type :confirm
:title (tr "dashboard-restore-file-confirmation.title") :title (tr "dashboard-restore-file-confirmation.title")
@ -214,7 +213,7 @@
on-delete-immediately on-delete-immediately
(fn [] (fn []
(let [accept-fn #(st/emit! (dd/delete-files-immediately (let [accept-fn #(st/emit! (dd/delete-files-immediately
{:team-id (:id current-team) {:team-id current-team-id
:ids (into #{} d/xf:map-id files)}))] :ids (into #{} d/xf:map-id files)}))]
(st/emit! (st/emit!
(modal/show {:type :confirm (modal/show {:type :confirm
@ -244,8 +243,7 @@
(for [project current-projects] (for [project current-projects]
{:name (get-project-name project) {:name (get-project-name project)
:id (get-project-id project) :id (get-project-id project)
:handler (on-move (:id current-team) :handler (on-move current-team-id (:id project))})
(:id project))})
(when (seq other-teams) (when (seq other-teams)
[{:name (tr "dashboard.move-to-other-team") [{:name (tr "dashboard.move-to-other-team")
:id "move-to-other-team" :id "move-to-other-team"

View File

@ -628,6 +628,7 @@
width: $sz-400; width: $sz-400;
padding: var(--sp-xxxl); padding: var(--sp-xxxl);
background-color: var(--color-background-primary); background-color: var(--color-background-primary);
z-index: var(--z-index-set);
&.hero { &.hero {
top: px2rem(216); top: px2rem(216);

View File

@ -23,6 +23,7 @@
touched? (and (contains? (:data @form) input-name) touched? (and (contains? (:data @form) input-name)
(get-in @form [:touched input-name])) (get-in @form [:touched input-name]))
error (get-in @form [:errors input-name]) error (get-in @form [:errors input-name])
value (get-in @form [:data input-name] "") value (get-in @form [:data input-name] "")

View File

@ -375,7 +375,7 @@
(mf/use-fn (mf/use-fn
(mf/deps on-change ids) (mf/deps on-change ids)
(fn [value attr event] (fn [value attr event]
(if (or (string? value) (int? value)) (if (or (string? value) (number? value))
(on-change :simple attr value event) (on-change :simple attr value event)
(do (do
(let [resolved-value (:resolved-value (first value)) (let [resolved-value (:resolved-value (first value))
@ -489,7 +489,7 @@
(mf/use-fn (mf/use-fn
(mf/deps on-change ids) (mf/deps on-change ids)
(fn [value attr event] (fn [value attr event]
(if (or (string? value) (int? value)) (if (or (string? value) (number? value))
(on-change :multiple attr value event) (on-change :multiple attr value event)
(do (do
(let [resolved-value (:resolved-value (first value))] (let [resolved-value (:resolved-value (first value))]
@ -724,7 +724,7 @@
(mf/use-fn (mf/use-fn
(mf/deps on-change wrap-type ids) (mf/deps on-change wrap-type ids)
(fn [value event attr] (fn [value event attr]
(if (or (string? value) (int? value)) (if (or (string? value) (number? value))
(on-change (= "nowrap" wrap-type) attr value event) (on-change (= "nowrap" wrap-type) attr value event)
(do (do
(let [resolved-value (:resolved-value (first value))] (let [resolved-value (:resolved-value (first value))]

View File

@ -53,10 +53,12 @@
(defn- resolve-value (defn- resolve-value
[tokens prev-token value] [tokens prev-token token-name value]
(let [token (let [token
{:value value {:value value
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"} :name (if (str/blank? token-name)
"__PENPOT__TOKEN__NAME__PLACEHOLDER__"
token-name)}
tokens tokens
(-> tokens (-> tokens
@ -131,6 +133,7 @@
(let [form (mf/use-ctx fc/context) (let [form (mf/use-ctx fc/context)
input-name name input-name name
token-name (get-in @form [:data :name] nil)
touched? touched?
@ -140,6 +143,9 @@
error error
(get-in @form [:errors input-name]) (get-in @form [:errors input-name])
extra-error
(get-in @form [:extra-errors input-name])
value value
(get-in @form [:data input-name] "") (get-in @form [:data input-name] "")
@ -247,15 +253,20 @@
:hint-type (:type hint)}) :hint-type (:type hint)})
props props
(if (and error touched?) (cond
(and error touched?)
(mf/spread-props props {:hint-type "error" (mf/spread-props props {:hint-type "error"
:hint-message (:message error)}) :hint-message (:message error)})
(and extra-error touched?)
(mf/spread-props props {:hint-type "error"
:hint-message (:message extra-error)})
:else
props)] props)]
(mf/with-effect [resolve-stream tokens token input-name] (mf/with-effect [resolve-stream tokens token input-name token-name]
(let [subs (->> resolve-stream (let [subs (->> resolve-stream
(rx/debounce 300) (rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token)) (rx/mapcat (partial resolve-value tokens token token-name))
(rx/map (fn [result] (rx/map (fn [result]
(d/update-when result :error (d/update-when result :error
(fn [error] (fn [error]
@ -301,7 +312,7 @@
(let [form (mf/use-ctx fc/context) (let [form (mf/use-ctx fc/context)
input-name name input-name name
token-name (get-in @form [:data :name] nil)
error error
(get-in @form [:errors :value value-subfield index input-name]) (get-in @form [:errors :value value-subfield index input-name])
@ -414,10 +425,10 @@
:hint-message (:message error)}) :hint-message (:message error)})
props)] props)]
(mf/with-effect [resolve-stream tokens token input-name index value-subfield] (mf/with-effect [resolve-stream tokens token input-name index value-subfield token-name]
(let [subs (->> resolve-stream (let [subs (->> resolve-stream
(rx/debounce 300) (rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token)) (rx/mapcat (partial resolve-value tokens token token-name))
(rx/map (fn [result] (rx/map (fn [result]
(d/update-when result :error (d/update-when result :error
(fn [error] (fn [error]

View File

@ -49,10 +49,12 @@
;; validate data within the form state. ;; validate data within the form state.
(defn- resolve-value (defn- resolve-value
[tokens prev-token value] [tokens prev-token token-name value]
(let [token (let [token
{:value (cto/split-font-family value) {:value (cto/split-font-family value)
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"} :name (if (str/blank? token-name)
"__PENPOT__TOKEN__NAME__PLACEHOLDER__"
token-name)}
tokens tokens
(-> tokens (-> tokens
@ -73,6 +75,7 @@
[{:keys [token tokens name] :rest props}] [{:keys [token tokens name] :rest props}]
(let [form (mf/use-ctx fc/context) (let [form (mf/use-ctx fc/context)
input-name name input-name name
token-name (get-in @form [:data :name] nil)
touched? touched?
(and (contains? (:data @form) input-name) (and (contains? (:data @form) input-name)
@ -152,10 +155,10 @@
:hint-message (:message error)}) :hint-message (:message error)})
props)] props)]
(mf/with-effect [resolve-stream tokens token input-name touched?] (mf/with-effect [resolve-stream tokens token input-name touched? token-name]
(let [subs (->> resolve-stream (let [subs (->> resolve-stream
(rx/debounce 300) (rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token)) (rx/mapcat (partial resolve-value tokens token token-name))
(rx/map (fn [result] (rx/map (fn [result]
(d/update-when result :error (d/update-when result :error
(fn [error] (fn [error]
@ -200,7 +203,7 @@
[{:keys [token tokens name] :rest props}] [{:keys [token tokens name] :rest props}]
(let [form (mf/use-ctx fc/context) (let [form (mf/use-ctx fc/context)
input-name name input-name name
token-name (get-in @form [:data :name] nil)
error error
(get-in @form [:errors :value input-name]) (get-in @form [:errors :value input-name])
@ -276,10 +279,10 @@
:hint-message (:message error)}) :hint-message (:message error)})
props)] props)]
(mf/with-effect [resolve-stream tokens token input-name] (mf/with-effect [resolve-stream tokens token input-name token-name]
(let [subs (->> resolve-stream (let [subs (->> resolve-stream
(rx/debounce 300) (rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token)) (rx/mapcat (partial resolve-value tokens token token-name))
(rx/map (fn [result] (rx/map (fn [result]
(d/update-when result :error (d/update-when result :error
(fn [error] (fn [error]

View File

@ -139,10 +139,12 @@
(defn- resolve-value (defn- resolve-value
[tokens prev-token value] [tokens prev-token token-name value]
(let [token (let [token
{:value value {:value value
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"} :name (if (str/blank? token-name)
"__PENPOT__TOKEN__NAME__PLACEHOLDER__"
token-name)}
tokens tokens
(-> tokens (-> tokens
;; Remove previous token when renaming a token ;; Remove previous token when renaming a token
@ -163,6 +165,7 @@
(let [form (mf/use-ctx fc/context) (let [form (mf/use-ctx fc/context)
input-name name input-name name
token-name (get-in @form [:data :name] nil)
touched? touched?
(and (contains? (:data @form) input-name) (and (contains? (:data @form) input-name)
@ -206,11 +209,11 @@
:hint-message (:message error)}) :hint-message (:message error)})
props)] props)]
(mf/with-effect [resolve-stream tokens token input-name] (mf/with-effect [resolve-stream tokens token input-name token-name]
(let [subs (->> resolve-stream (let [subs (->> resolve-stream
(rx/debounce 300) (rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token)) (rx/mapcat (partial resolve-value tokens token token-name))
(rx/map (fn [result] (rx/map (fn [result]
(d/update-when result :error (d/update-when result :error
(fn [error] (fn [error]
@ -236,12 +239,14 @@
(on-composite-input-change form field value false)) (on-composite-input-change form field value false))
([form field value trim?] ([form field value trim?]
(letfn [(clean-errors [errors] (letfn [(clean-errors [errors]
(-> errors (some-> errors
(dissoc field) (update :value #(when (map? %) (dissoc % field)))
(not-empty)))] (update :value #(when (seq %) %))
(not-empty)))]
(swap! form (fn [state] (swap! form (fn [state]
(-> state (-> state
(assoc-in [:data :value field] (if trim? (str/trim value) value)) (assoc-in [:data :value field] (if trim? (str/trim value) value))
(assoc-in [:touched :value field] true)
(update :errors clean-errors) (update :errors clean-errors)
(update :extra-errors clean-errors))))))) (update :extra-errors clean-errors)))))))
@ -250,6 +255,7 @@
(let [form (mf/use-ctx fc/context) (let [form (mf/use-ctx fc/context)
input-name name input-name name
token-name (get-in @form [:data :name] nil)
error error
(get-in @form [:errors :value input-name]) (get-in @form [:errors :value input-name])
@ -257,6 +263,9 @@
value value
(get-in @form [:data :value input-name] "") (get-in @form [:data :value input-name] "")
touched?
(get-in @form [:touched :value input-name])
resolve-stream resolve-stream
(mf/with-memo [token] (mf/with-memo [token]
(if-let [value (get-in token [:value input-name])] (if-let [value (get-in token [:value input-name])]
@ -284,7 +293,7 @@
:hint-message (:message hint) :hint-message (:message hint)
:hint-type (:type hint)}) :hint-type (:type hint)})
props props
(if error (if (and touched? error)
(mf/spread-props props {:hint-type "error" (mf/spread-props props {:hint-type "error"
:hint-message (:message error)}) :hint-message (:message error)})
props) props)
@ -293,10 +302,10 @@
(mf/spread-props props {:hint-formated true}) (mf/spread-props props {:hint-formated true})
props)] props)]
(mf/with-effect [resolve-stream tokens token input-name name] (mf/with-effect [resolve-stream tokens token input-name name token-name]
(let [subs (->> resolve-stream (let [subs (->> resolve-stream
(rx/debounce 300) (rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token)) (rx/mapcat (partial resolve-value tokens token token-name))
(rx/map (fn [result] (rx/map (fn [result]
(d/update-when result :error (d/update-when result :error
(fn [error] (fn [error]
@ -360,7 +369,7 @@
(let [form (mf/use-ctx fc/context) (let [form (mf/use-ctx fc/context)
input-name name input-name name
token-name (get-in @form [:data :name] nil)
error error
(get-in @form [:errors :value value-subfield index input-name]) (get-in @form [:errors :value value-subfield index input-name])
@ -405,10 +414,10 @@
(mf/spread-props props {:hint-formated true}) (mf/spread-props props {:hint-formated true})
props)] props)]
(mf/with-effect [resolve-stream tokens token input-name index value-subfield] (mf/with-effect [resolve-stream tokens token input-name index value-subfield token-name]
(let [subs (->> resolve-stream (let [subs (->> resolve-stream
(rx/debounce 300) (rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token)) (rx/mapcat (partial resolve-value tokens token token-name))
(rx/map (fn [result] (rx/map (fn [result]
(d/update-when result :error (d/update-when result :error
(fn [error] (fn [error]

View File

@ -14,6 +14,7 @@
[app.main.constants :refer [max-input-length]] [app.main.constants :refer [max-input-length]]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.errors :as wte]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.tokens.propagation :as dwtp] [app.main.data.workspace.tokens.propagation :as dwtp]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -110,8 +111,7 @@
token-title (str/lower (:title token-properties)) token-title (str/lower (:title token-properties))
tokens tokens (mf/deref refs/workspace-all-tokens-map)
(mf/deref refs/workspace-active-theme-sets-tokens)
tokens tokens
(mf/with-memo [tokens token] (mf/with-memo [tokens token]
@ -207,7 +207,11 @@
:value (:value valid-token) :value (:value valid-token)
:description description})) :description description}))
(dwtp/propagate-workspace-tokens) (dwtp/propagate-workspace-tokens)
(modal/hide))))))))] (modal/hide)))
(fn [{:keys [errors]}]
(let [error-messages (wte/humanize-errors errors)
error-message (first error-messages)]
(swap! form assoc-in [:extra-errors :value] {:message error-message}))))))))]
[:> fc/form* {:class (stl/css :form-wrapper) [:> fc/form* {:class (stl/css :form-wrapper)
:form form :form form

View File

@ -282,15 +282,11 @@
(let [n (d/parse-double blur)] (let [n (d/parse-double blur)]
(or (nil? n) (not (< n 0)))))]]] (or (nil? n) (not (< n 0)))))]]]
[:spread {:optional true} [:spread {:optional true}
[:and [:maybe :string]]
[:maybe :string]
[:fn {:error/fn #(tr "workspace.tokens.shadow-token-spread-value-error")}
(fn [spread]
(let [n (d/parse-double spread)]
(or (nil? n) (not (< n 0)))))]]]
[:color {:optional true} [:maybe :string]] [:color {:optional true} [:maybe :string]]
[:color-result {:optional true} ::sm/any] [:color-result {:optional true} ::sm/any]
[:inset {:optional true} [:maybe :boolean]]]]] [:inset {:optional true} [:maybe :boolean]]]]]
(if (= active-tab :reference) (if (= active-tab :reference)
[:reference {:optional false} ::sm/text] [:reference {:optional false} ::sm/text]
[:reference {:optional true} [:maybe :string]])]] [:reference {:optional true} [:maybe :string]])]]

View File

@ -802,9 +802,10 @@
([uri name] ([uri name]
(open-new-window uri name "noopener,noreferrer")) (open-new-window uri name "noopener,noreferrer"))
([uri name features] ([uri name features]
(let [new-window (.open js/window (str uri) name features)] (when-let [new-window (.open js/window (str uri) name features)]
(when (not= name "_blank") (when (not= name "_blank")
(.reload (.-location new-window)))))) (when-let [location (.-location new-window)]
(.reload location))))))
(defn browser-back (defn browser-back
[] []

View File

@ -7594,6 +7594,10 @@ msgstr "Error al importar: No se pudo procesar el JSON."
msgid "workspace.tokens.export" msgid "workspace.tokens.export"
msgstr "Exportar" msgstr "Exportar"
#: src/app/main/ui/workspace/tokens/export/modal.cljs:125
msgid "workspace.tokens.export-tokens"
msgstr "Exportar tokens"
#: src/app/main/ui/workspace/tokens/export/modal.cljs:118 #: src/app/main/ui/workspace/tokens/export/modal.cljs:118
msgid "workspace.tokens.export.multiple-files" msgid "workspace.tokens.export.multiple-files"
msgstr "Múltiples ficheros" msgstr "Múltiples ficheros"
@ -7638,10 +7642,26 @@ msgstr "Nombre del grupo"
msgid "workspace.tokens.grouping-set-alert" msgid "workspace.tokens.grouping-set-alert"
msgstr "La agrupación de sets aun no está soportada." msgstr "La agrupación de sets aun no está soportada."
#: src/app/main/ui/workspace/tokens/import/modal.cljs:233
msgid "workspace.tokens.import-button-prefix"
msgstr "Importar %s"
#: src/app/main/data/workspace/tokens/errors.cljs:32, src/app/main/data/workspace/tokens/errors.cljs:37 #: src/app/main/data/workspace/tokens/errors.cljs:32, src/app/main/data/workspace/tokens/errors.cljs:37
msgid "workspace.tokens.import-error" msgid "workspace.tokens.import-error"
msgstr "Error al importar:" msgstr "Error al importar:"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:273
msgid "workspace.tokens.import-menu-folder-option"
msgstr "Carpeta"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:271
msgid "workspace.tokens.import-menu-json-option"
msgstr "Archivo JSON único"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:272
msgid "workspace.tokens.import-menu-zip-option"
msgstr "Archivo ZIP"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:241 #: src/app/main/ui/workspace/tokens/import/modal.cljs:241
msgid "workspace.tokens.import-multiple-files" msgid "workspace.tokens.import-multiple-files"
msgstr "" msgstr ""
@ -7656,7 +7676,7 @@ msgstr ""
#: src/app/main/ui/workspace/tokens/import/modal.cljs:237 #: src/app/main/ui/workspace/tokens/import/modal.cljs:237
msgid "workspace.tokens.import-tokens" msgid "workspace.tokens.import-tokens"
msgstr "Import tokens" msgstr "Importar tokens"
#: src/app/main/ui/workspace/tokens/sidebar.cljs:414, src/app/main/ui/workspace/tokens/sidebar.cljs:415 #: src/app/main/ui/workspace/tokens/sidebar.cljs:414, src/app/main/ui/workspace/tokens/sidebar.cljs:415
#, unused #, unused
@ -7757,7 +7777,7 @@ msgstr "Line height (multiplicador, px o %) o {alias}"
#: src/app/main/data/workspace/tokens/errors.cljs:57 #: src/app/main/data/workspace/tokens/errors.cljs:57
msgid "workspace.tokens.missing-references" msgid "workspace.tokens.missing-references"
msgstr "Referéncias de tokens no encontradas:" msgstr "Referencias de tokens no encontradas: "
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123 #: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
msgid "workspace.tokens.more-options" msgid "workspace.tokens.more-options"

View File

@ -1191,21 +1191,21 @@ __metadata:
languageName: node languageName: node
linkType: soft linkType: soft
"@penpot/plugin-types@npm:^1.3.2": "@penpot/plugin-types@npm:^1.4.2":
version: 1.3.2 version: 1.4.2
resolution: "@penpot/plugin-types@npm:1.3.2" resolution: "@penpot/plugin-types@npm:1.4.2"
checksum: 10c0/3f624472c260721ad89bf8d944e75acf6a9c9577271a757acb77574102213914051d1a32d5ab16e6ba16ae077fff78cf7a0f6d11d18351dfc214426677a67468 checksum: 10c0/b0972fe75c97e697eb1044c89db660393886b3c30676f8436ff4ab56c5bf0397b2c675697ae1b9c5fe40bc95a803aecf6d7ac356dbf6d3535bf8baec5d05eab1
languageName: node languageName: node
linkType: hard linkType: hard
"@penpot/plugins-runtime@npm:1.3.2": "@penpot/plugins-runtime@npm:1.4.2":
version: 1.3.2 version: 1.4.2
resolution: "@penpot/plugins-runtime@npm:1.3.2" resolution: "@penpot/plugins-runtime@npm:1.4.2"
dependencies: dependencies:
"@penpot/plugin-types": "npm:^1.3.2" "@penpot/plugin-types": "npm:^1.4.2"
ses: "npm:^1.1.0" ses: "npm:^1.1.0"
zod: "npm:^3.22.4" zod: "npm:^3.22.4"
checksum: 10c0/b6d2cb3a57bcbe58232db52b8224d1817495e96b34997bfa72421629b5f34a8c9cc71357c315dcab9d52ea036ed632a5efe0ac50f52e730901c02d498dfa1313 checksum: 10c0/af084d906cce9a6dea956fe5420111d7ea37c7620737a1e3d4f12958cb302a8f697c1229c237107c28fbb3b9f37eee308e6d16262b04ad56ae6f76c7a12f12e5
languageName: node languageName: node
linkType: hard linkType: hard
@ -4176,7 +4176,7 @@ __metadata:
dependencies: dependencies:
"@penpot/draft-js": "portal:./packages/draft-js" "@penpot/draft-js": "portal:./packages/draft-js"
"@penpot/mousetrap": "portal:./packages/mousetrap" "@penpot/mousetrap": "portal:./packages/mousetrap"
"@penpot/plugins-runtime": "npm:1.3.2" "@penpot/plugins-runtime": "npm:1.4.2"
"@penpot/svgo": "penpot/svgo#v3.2" "@penpot/svgo": "penpot/svgo#v3.2"
"@penpot/text-editor": "portal:./text-editor" "@penpot/text-editor": "portal:./text-editor"
"@playwright/test": "npm:1.57.0" "@playwright/test": "npm:1.57.0"

View File

@ -1,3 +1,38 @@
## 1.4.2 (2026-01-21)
- **plugin-types:** fix atob/btoa functions
## 1.4.0 (2026-01-21)
### 🚀 Features
- switch component ([7d68450](https://github.com/penpot/penpot-plugins/commit/7d68450))
- Add variants to plugins API ([04f3c26](https://github.com/penpot/penpot-plugins/commit/04f3c26))
- format ci job ([17b5834](https://github.com/penpot/penpot-plugins/commit/17b5834))
- fix problem with ci ([4b3c50f](https://github.com/penpot/penpot-plugins/commit/4b3c50f))
- change in workflow ([3a69f51](https://github.com/penpot/penpot-plugins/commit/3a69f51))
- **plugin-types:** add methods to modify the index for shapes ([4ad50af](https://github.com/penpot/penpot-plugins/commit/4ad50af))
- **plugin-types:** change content type and added new attributes ([dbb68a5](https://github.com/penpot/penpot-plugins/commit/dbb68a5))
- **plugins-runtime:** add data method to image data ([f077481](https://github.com/penpot/penpot-plugins/commit/f077481))
- **plugins-runtime:** fix problem with linter ([30f4984](https://github.com/penpot/penpot-plugins/commit/30f4984))
- **plugins-runtime:** allow openPage() to toggle opening on a new window or not ([da8288b](https://github.com/penpot/penpot-plugins/commit/da8288b))
### 🩹 Fixes
- package-lock.json ([d1d940a](https://github.com/penpot/penpot-plugins/commit/d1d940a))
- fonts gdpr & switch provider ([d63231e](https://github.com/penpot/penpot-plugins/commit/d63231e))
- missing changes ([b8fc936](https://github.com/penpot/penpot-plugins/commit/b8fc936))
- format ci ([e0fab2e](https://github.com/penpot/penpot-plugins/commit/e0fab2e))
- fetch main only in pr ([e48c5d4](https://github.com/penpot/penpot-plugins/commit/e48c5d4))
### ❤️ Thank You
- alonso.torres
- Juanfran @juanfran
- Michał Korczak
- Miguel de Benito Delgado
- Pablo Alba
## 1.3.2 (2025-07-04) ## 1.3.2 (2025-07-04)
### 🩹 Fixes ### 🩹 Fixes

View File

@ -7,6 +7,29 @@ This guide details the process of publishing `plugin-types`,
for plugin development. Below is a walkthrough for publishing these for plugin development. Below is a walkthrough for publishing these
packages and managing releases. packages and managing releases.
**Warning**
Before generating the release, please, check the update the changelog with
the changes that will be released.
## Problem with pnpm
There is an issue with dependencies and release with pnpm. For it to work
you need to add the following into your `.npmrc`
```
link-workspace-packages=true
```
## NPM Authentication
You need to generate a temporary access token in the NPM website.
Once you have the token add the following to the `.npmrc`
```
//registry.npmjs.org/:_authToken=<TOKEN>
```
## Publishing Libraries ## Publishing Libraries
Publishing packages enables the distribution of types and styles Publishing packages enables the distribution of types and styles
@ -35,28 +58,16 @@ pnpm run release -- --dry-run false
This command will: This command will:
- Update the `CHANGELOG.md`
- Update the library's `package.json` version - Update the library's `package.json` version
- Generate a commit - Generate a commit
- Create a new git tag
- Publish to NPM with the `latest` tag - Publish to NPM with the `latest` tag
Ensure everything is correct before proceeding with the git push. Once Ensure everything is correct before proceeding with the git push. Once
verified, execute the following commands: verified, execute the following commands:
```shell ```shell
git commit -m ":arrow_up: Updated plugins release to X.X.X"
git push git push
git push origin vX.X.X
```
Replace `vX.X.X` with the new version number.
> 📘 To update the documentation site, you must also update the `stable` branch:
```shell
git checkout stable
git merge main
git push origin stable
``` ```
For detailed information, refer to the [Nx Release For detailed information, refer to the [Nx Release

View File

@ -1,6 +1,6 @@
{ {
"name": "@penpot/plugin-types", "name": "@penpot/plugin-types",
"version": "1.3.2", "version": "1.4.2",
"typings": "./index.d.ts", "typings": "./index.d.ts",
"type": "module" "type": "module"
} }

View File

@ -1,8 +1,8 @@
{ {
"name": "@penpot/plugins-runtime", "name": "@penpot/plugins-runtime",
"version": "1.3.2", "version": "1.4.2",
"dependencies": { "dependencies": {
"@penpot/plugin-types": "^1.3.2", "@penpot/plugin-types": "^1.4.2",
"ses": "^1.1.0", "ses": "^1.1.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },

View File

@ -118,8 +118,8 @@ export function createSandbox(
// Window properties // Window properties
console: ses.harden(window.console), console: ses.harden(window.console),
devicePixelRatio: ses.harden(window.devicePixelRatio), devicePixelRatio: ses.harden(window.devicePixelRatio),
atob: ses.harden(window.atob), atob: ses.harden(window.atob.bind(null)),
btoa: ses.harden(window.btoa), btoa: ses.harden(window.btoa.bind(null)),
structuredClone: ses.harden(window.structuredClone), structuredClone: ses.harden(window.structuredClone),
}; };

View File

@ -1,5 +1,5 @@
{ {
"name": "@penpot/plugin-styles", "name": "@penpot/plugin-styles",
"version": "1.3.2", "version": "1.4.2",
"dependencies": {} "dependencies": {}
} }

14
plugins/pnpm-lock.yaml generated
View File

@ -230,8 +230,8 @@ importers:
libs/plugins-runtime: libs/plugins-runtime:
dependencies: dependencies:
'@penpot/plugin-types': '@penpot/plugin-types':
specifier: ^1.3.2 specifier: ^1.4.2
version: 1.3.2 version: link:../plugin-types
ses: ses:
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.14.0 version: 1.14.0
@ -4200,12 +4200,6 @@ packages:
} }
engines: { node: '>= 10.0.0' } engines: { node: '>= 10.0.0' }
'@penpot/plugin-types@1.3.2':
resolution:
{
integrity: sha512-f0kmmZaFNs9sGtSmqmSJQYCs5Qt+KYgTD8RneUjL+Dv+zfNQnd5e4L+iHSYFJ4HWvcDvTiK7F/gya7PwMTu7WA==,
}
'@phenomnomnominal/tsquery@5.0.1': '@phenomnomnominal/tsquery@5.0.1':
resolution: resolution:
{ {
@ -13194,6 +13188,7 @@ packages:
integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==, integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==,
} }
engines: { node: '>=10' } engines: { node: '>=10' }
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
tar@7.5.2: tar@7.5.2:
resolution: resolution:
@ -13201,6 +13196,7 @@ packages:
integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==, integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==,
} }
engines: { node: '>=18' } engines: { node: '>=18' }
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
terser-webpack-plugin@5.3.16: terser-webpack-plugin@5.3.16:
resolution: resolution:
@ -18203,8 +18199,6 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.1 '@parcel/watcher-win32-x64': 2.5.1
optional: true optional: true
'@penpot/plugin-types@1.3.2': {}
'@phenomnomnominal/tsquery@5.0.1(typescript@5.6.3)': '@phenomnomnominal/tsquery@5.0.1(typescript@5.6.3)':
dependencies: dependencies:
esquery: 1.6.0 esquery: 1.6.0

View File

@ -69,17 +69,6 @@ const determineArgs = async () => {
}, },
); );
await releaseChangelog({
dryRun: args.dryRun,
versionData: result.projectsVersionData,
version: result.workspaceVersion,
gitCommitMessage: `chore(release): publish ${result.workspaceVersion} [skip ci]`,
gitCommit: true,
gitTag: true,
verbose: args.verbose,
firstRelease: args.firstRelease,
});
if (!args.skipPublish) { if (!args.skipPublish) {
await releasePublish({ await releasePublish({
dryRun: args.dryRun, dryRun: args.dryRun,

View File

@ -0,0 +1,4 @@
name = "penpot-plugins-api-doc"
compatibility_date = "2025-01-01"
assets = { directory = "dist/doc" }