diff --git a/.github/workflows/plugins-deploy-api-doc.yml b/.github/workflows/plugins-deploy-api-doc.yml new file mode 100644 index 0000000000..62a87745bb --- /dev/null +++ b/.github/workflows/plugins-deploy-api-doc.yml @@ -0,0 +1,101 @@ +name: Plugins/api-doc deployer + +on: + push: + branches: + - develop + - staging + - main + paths: + - "plugins/libs/plugin-types/index.d.ts" + - "plugins/libs/plugin-types/REAME.md" + - "plugins/tools/typedoc.css" + - "plugins/CHANGELOG.md" + - "plugins/wrangle-penpot-plugins-api-doc.toml" + workflow_dispatch: + inputs: + gh_ref: + description: 'Name of the branch' + type: choice + required: true + default: 'develop' + options: + - develop + - staging + - main + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Extract some useful variables + id: vars + run: | + echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ steps.vars.outputs.gh_ref }} + + # START: Setup Node and PNPM enabling cache + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + + - name: Enable PNPM + working-directory: ./plugins + shell: bash + run: | + corepack enable; + corepack install; + + - name: Get pnpm store path + id: pnpm-store + working-directory: ./plugins + shell: bash + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT + + - name: Cache pnpm store + uses: actions/cache@v4 + with: + path: ${{ steps.pnpm-store.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm- + # END: Setup Node and PNPM enabling cache + + - name: Install deps + working-directory: ./plugins + shell: bash + run: | + pnpm install --no-frozen-lockfile; + pnpm add -D -w wrangler@latest; + + - name: Build docs + working-directory: plugins + shell: bash + run: pnpm run build:doc + + - name: Select Worker name + run: | + REF="${{ steps.vars.outputs.gh_ref }}" + case "$REF" in + main) echo "WORKER_NAME=penpot-plugins-api-doc-pro" >> $GITHUB_ENV ;; + staging) echo "WORKER_NAME=penpot-plugins-api-doc-pre" >> $GITHUB_ENV ;; + develop) echo "WORKER_NAME=penpot-plugins-api-doc-hourly" >> $GITHUB_ENV ;; + *) echo "Unsupported branch ${REF}" && exit 1 ;; + esac + + - name: Deploy to Cloudflare Workers + uses: cloudflare/wrangler-action@v3 + with: + workingDirectory: plugins + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: deploy --config wrangle-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }} diff --git a/CHANGES.md b/CHANGES.md index c391f09d0d..bca9e9f17b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -30,6 +30,9 @@ - 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 problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835) +- 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) ## 2.12.1 diff --git a/backend/src/app/email.clj b/backend/src/app/email.clj index 43361039d9..44d5cd7e67 100644 --- a/backend/src/app/email.clj +++ b/backend/src/app/email.clj @@ -124,8 +124,6 @@ (throw (IllegalArgumentException. "invalid email body provided"))) (doseq [[name content] attachments] - - (prn "attachment" name) (let [attachment-part (MimeBodyPart.)] (.setFileName attachment-part ^String name) (.setContent attachment-part ^String content (str "text/plain; charset=" charset)) diff --git a/frontend/package.json b/frontend/package.json index 8279b3fd3b..f4c36c6ef6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,13 +48,13 @@ "watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook", "clear:shadow-cache": "rm -rf .shadow-cljs", "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\"" }, "devDependencies": { "@penpot/draft-js": "portal:./packages/draft-js", "@penpot/mousetrap": "portal:./packages/mousetrap", - "@penpot/plugins-runtime": "1.3.2", + "@penpot/plugins-runtime": "1.4.2", "@penpot/svgo": "penpot/svgo#v3.2", "@penpot/text-editor": "portal:./text-editor", "@playwright/test": "1.57.0", diff --git a/frontend/playwright/ui/specs/tokens.spec.js b/frontend/playwright/ui/specs/tokens.spec.js index f8502b5ecb..d7715a7f4c 100644 --- a/frontend/playwright/ui/specs/tokens.spec.js +++ b/frontend/playwright/ui/specs/tokens.spec.js @@ -1256,6 +1256,192 @@ test.describe("Tokens: Tokens Tab", () => { ).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 }) => { const emptyNameError = "Name should be at least 1 character"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js index c1c018c74c..58d3586995 100644 --- a/frontend/scripts/_helpers.js +++ b/frontend/scripts/_helpers.js @@ -174,6 +174,7 @@ export async function watch(baseDir, predicate, callback) { const watcher = new Watcher(baseDir, { persistent: true, recursive: true, + debounce: 500 }); watcher.on("change", (path) => { @@ -181,8 +182,19 @@ export async function watch(baseDir, predicate, callback) { 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) { const manifestPath = "resources/public/" + resource; let content = await fs.readFile(manifestPath, { encoding: "utf8" }); @@ -260,6 +272,9 @@ const markedOptions = { marked.use(markedOptions); export async function compileTranslations() { + const outputDir = "resources/public/js/"; + await fs.mkdir(outputDir, { recursive: true }); + const langs = [ "ar", "ca", @@ -341,7 +356,6 @@ export async function compileTranslations() { } const esm = `export default ${JSON.stringify(result, null, 0)};\n`; - const outputDir = "resources/public/js/"; const outputFile = ph.join(outputDir, "translation." + lang + ".js"); await fs.writeFile(outputFile, esm); } @@ -499,17 +513,43 @@ export async function compileStyles() { export async function compileSvgSprites() { const start = process.hrtime(); log.info("init: compile svgsprite"); - await generateSvgSprites(); + let error = false; + + try { + await generateSvgSprites(); + } catch (cause) { + error = cause; + } + 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() { const start = process.hrtime(); + let error = false; log.info("init: compile templates"); - await generateTemplates(); + + try { + await generateTemplates(); + } catch (cause) { + error = cause; + } + 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() { diff --git a/frontend/scripts/_worker.js b/frontend/scripts/_worker.js index 80ba2f6bf6..c46b26993b 100644 --- a/frontend/scripts/_worker.js +++ b/frontend/scripts/_worker.js @@ -28,14 +28,12 @@ async function compileFile(path) { ], sourceMap: false, }); - // console.dir(result); resolve({ inputPath: path, outputPath: dest, css: result.css, }); } catch (cause) { - console.error(cause); reject(cause); } }); diff --git a/frontend/scripts/build-app-assets.js b/frontend/scripts/build-app-assets.js index 6ccc435839..e0fe385dce 100644 --- a/frontend/scripts/build-app-assets.js +++ b/frontend/scripts/build-app-assets.js @@ -1,5 +1,6 @@ import * as h from "./_helpers.js"; +await h.ensureDirectories(); await h.compileStyles(); await h.copyAssets(); await h.copyWasmPlayground(); diff --git a/frontend/scripts/watch.js b/frontend/scripts/watch.js index e2d9ff399d..38007d4454 100644 --- a/frontend/scripts/watch.js +++ b/frontend/scripts/watch.js @@ -12,19 +12,31 @@ let sass = null; async function compileSassAll() { const start = process.hrtime(); + let error = false; + log.info("init: compile styles"); - sass = await h.compileSassAll(worker); - let output = await h.concatSass(sass); - await fs.writeFile("./resources/public/css/main.css", output); + try { + sass = await h.compileSassAll(worker); + let output = await h.concatSass(sass); + await fs.writeFile("./resources/public/css/main.css", output); - if (isDebug) { - let debugCSS = await h.compileSassDebug(worker); - await fs.writeFile("./resources/public/css/debug.css", debugCSS); + if (isDebug) { + let debugCSS = await h.compileSassDebug(worker); + await fs.writeFile("./resources/public/css/debug.css", debugCSS); + } + } catch (cause) { + error = cause; } 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) { @@ -48,7 +60,7 @@ async function compileSass(path) { } } -await fs.mkdir("./resources/public/css/", { recursive: true }); +await h.ensureDirectories(); await compileSassAll(); await h.copyAssets(); await h.copyWasmPlayground(); diff --git a/frontend/src/app/main/data/style_dictionary.cljs b/frontend/src/app/main/data/style_dictionary.cljs index 1bf2de0d0a..0ed5fd68d9 100644 --- a/frontend/src/app/main/data/style_dictionary.cljs +++ b/frontend/src/app/main/data/style_dictionary.cljs @@ -126,7 +126,7 @@ 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`." [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) out-of-scope (not (<= 0 (:value parsed-value) 1)) references (seq (cto/find-token-value-references value))] @@ -152,15 +152,14 @@ [value] (let [missing-references? (seq (cto/find-token-value-references value)) parsed-value (cft/parse-token-value value) - out-of-scope (< (:value parsed-value) 0) - references (seq (cto/find-token-value-references value))] + out-of-scope (< (:value parsed-value) 0)] (cond (and parsed-value (not out-of-scope)) parsed-value - references - {:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)] - :references references} + missing-references? + {:errors [(wte/error-with-value :error.style-dictionary/missing-reference missing-references?)] + :references missing-references?} (and (not missing-references?) out-of-scope) {: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)." [value] (let [parsed (parse-sd-token-general-value value) - valid? (and (:value parsed) (>= (:value parsed) 0))] + valid? (:value parsed)] (cond valid? parsed diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 3577842a07..24993ed2c4 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -480,6 +480,9 @@ (def workspace-token-sets-tree (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 (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 85b3dc2ac8..dfecbc779b 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -193,16 +193,15 @@ restore-fn (fn [_] (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)} {:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file))) - (dd/fetch-projects (:id current-team)) - (dd/fetch-deleted-files (:id current-team))) + (dd/fetch-projects current-team-id) + (dd/fetch-deleted-files current-team-id)) :on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))})))) on-restore-immediately (fn [] - (prn files) (st/emit! (modal/show {:type :confirm :title (tr "dashboard-restore-file-confirmation.title") @@ -214,7 +213,7 @@ on-delete-immediately (fn [] (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)}))] (st/emit! (modal/show {:type :confirm @@ -244,8 +243,7 @@ (for [project current-projects] {:name (get-project-name project) :id (get-project-id project) - :handler (on-move (:id current-team) - (:id project))}) + :handler (on-move current-team-id (:id project))}) (when (seq other-teams) [{:name (tr "dashboard.move-to-other-team") :id "move-to-other-team" diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs index ce18196c22..9f9d395013 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs @@ -53,10 +53,12 @@ (defn- resolve-value - [tokens prev-token value] + [tokens prev-token token-name value] (let [token {:value value - :name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"} + :name (if (str/blank? token-name) + "__PENPOT__TOKEN__NAME__PLACEHOLDER__" + token-name)} tokens (-> tokens @@ -131,6 +133,7 @@ (let [form (mf/use-ctx fc/context) input-name name + token-name (get-in @form [:data :name] nil) touched? @@ -140,6 +143,9 @@ error (get-in @form [:errors input-name]) + extra-error + (get-in @form [:extra-errors input-name]) + value (get-in @form [:data input-name] "") @@ -247,15 +253,20 @@ :hint-type (:type hint)}) props - (if (and error touched?) + (cond + (and error touched?) (mf/spread-props props {:hint-type "error" :hint-message (:message error)}) + (and extra-error touched?) + (mf/spread-props props {:hint-type "error" + :hint-message (:message extra-error)}) + :else 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 (rx/debounce 300) - (rx/mapcat (partial resolve-value tokens token)) + (rx/mapcat (partial resolve-value tokens token token-name)) (rx/map (fn [result] (d/update-when result :error (fn [error] @@ -301,7 +312,7 @@ (let [form (mf/use-ctx fc/context) input-name name - + token-name (get-in @form [:data :name] nil) error (get-in @form [:errors :value value-subfield index input-name]) @@ -414,10 +425,10 @@ :hint-message (:message error)}) 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 (rx/debounce 300) - (rx/mapcat (partial resolve-value tokens token)) + (rx/mapcat (partial resolve-value tokens token token-name)) (rx/map (fn [result] (d/update-when result :error (fn [error] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs index ba6a8348c2..80f2d91133 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs @@ -49,10 +49,12 @@ ;; validate data within the form state. (defn- resolve-value - [tokens prev-token value] + [tokens prev-token token-name value] (let [token {: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 @@ -73,6 +75,7 @@ [{:keys [token tokens name] :rest props}] (let [form (mf/use-ctx fc/context) input-name name + token-name (get-in @form [:data :name] nil) touched? (and (contains? (:data @form) input-name) @@ -152,10 +155,10 @@ :hint-message (:message error)}) 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 (rx/debounce 300) - (rx/mapcat (partial resolve-value tokens token)) + (rx/mapcat (partial resolve-value tokens token token-name)) (rx/map (fn [result] (d/update-when result :error (fn [error] @@ -200,7 +203,7 @@ [{:keys [token tokens name] :rest props}] (let [form (mf/use-ctx fc/context) input-name name - + token-name (get-in @form [:data :name] nil) error (get-in @form [:errors :value input-name]) @@ -276,10 +279,10 @@ :hint-message (:message error)}) 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 (rx/debounce 300) - (rx/mapcat (partial resolve-value tokens token)) + (rx/mapcat (partial resolve-value tokens token token-name)) (rx/map (fn [result] (d/update-when result :error (fn [error] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs index 2e57f197be..0f1b2a79b1 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs @@ -139,10 +139,12 @@ (defn- resolve-value - [tokens prev-token value] + [tokens prev-token token-name value] (let [token {:value value - :name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"} + :name (if (str/blank? token-name) + "__PENPOT__TOKEN__NAME__PLACEHOLDER__" + token-name)} tokens (-> tokens ;; Remove previous token when renaming a token @@ -163,6 +165,7 @@ (let [form (mf/use-ctx fc/context) input-name name + token-name (get-in @form [:data :name] nil) touched? (and (contains? (:data @form) input-name) @@ -206,11 +209,11 @@ :hint-message (:message error)}) 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 (rx/debounce 300) - (rx/mapcat (partial resolve-value tokens token)) + (rx/mapcat (partial resolve-value tokens token token-name)) (rx/map (fn [result] (d/update-when result :error (fn [error] @@ -252,6 +255,7 @@ (let [form (mf/use-ctx fc/context) input-name name + token-name (get-in @form [:data :name] nil) error (get-in @form [:errors :value input-name]) @@ -298,10 +302,10 @@ (mf/spread-props props {:hint-formated true}) 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 (rx/debounce 300) - (rx/mapcat (partial resolve-value tokens token)) + (rx/mapcat (partial resolve-value tokens token token-name)) (rx/map (fn [result] (d/update-when result :error (fn [error] @@ -365,7 +369,7 @@ (let [form (mf/use-ctx fc/context) input-name name - + token-name (get-in @form [:data :name] nil) error (get-in @form [:errors :value value-subfield index input-name]) @@ -410,10 +414,10 @@ (mf/spread-props props {:hint-formated true}) 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 (rx/debounce 300) - (rx/mapcat (partial resolve-value tokens token)) + (rx/mapcat (partial resolve-value tokens token token-name)) (rx/map (fn [result] (d/update-when result :error (fn [error] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs index 038dbeae03..a260540a92 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs @@ -14,6 +14,7 @@ [app.main.constants :refer [max-input-length]] [app.main.data.modal :as modal] [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.propagation :as dwtp] [app.main.refs :as refs] @@ -110,8 +111,7 @@ token-title (str/lower (:title token-properties)) - tokens - (mf/deref refs/workspace-active-theme-sets-tokens) + tokens (mf/deref refs/workspace-all-tokens-map) tokens (mf/with-memo [tokens token] @@ -207,7 +207,11 @@ :value (:value valid-token) :description description})) (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) :form form diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs index 53c6d8e402..c569c81e30 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/shadow.cljs @@ -282,12 +282,7 @@ (let [n (d/parse-double blur)] (or (nil? n) (not (< n 0)))))]]] [:spread {:optional true} - [:and - [: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)))))]]] + [:maybe :string]] [:color {:optional true} [:maybe :string]] [:color-result {:optional true} ::sm/any] [:inset {:optional true} [:maybe :boolean]]]]] diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 55140fb326..06ada351c4 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -7603,6 +7603,10 @@ msgstr "Error al importar: No se pudo procesar el JSON." msgid "workspace.tokens.export" 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 msgid "workspace.tokens.export.multiple-files" msgstr "Múltiples ficheros" @@ -7647,10 +7651,26 @@ msgstr "Nombre del grupo" msgid "workspace.tokens.grouping-set-alert" 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 msgid "workspace.tokens.import-error" 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 msgid "workspace.tokens.import-multiple-files" msgstr "" @@ -7665,7 +7685,7 @@ msgstr "" #: src/app/main/ui/workspace/tokens/import/modal.cljs:237 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 #, unused diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 7b6b7718c1..ebc1ed2427 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1191,21 +1191,21 @@ __metadata: languageName: node linkType: soft -"@penpot/plugin-types@npm:^1.3.2": - version: 1.3.2 - resolution: "@penpot/plugin-types@npm:1.3.2" - checksum: 10c0/3f624472c260721ad89bf8d944e75acf6a9c9577271a757acb77574102213914051d1a32d5ab16e6ba16ae077fff78cf7a0f6d11d18351dfc214426677a67468 +"@penpot/plugin-types@npm:^1.4.2": + version: 1.4.2 + resolution: "@penpot/plugin-types@npm:1.4.2" + checksum: 10c0/b0972fe75c97e697eb1044c89db660393886b3c30676f8436ff4ab56c5bf0397b2c675697ae1b9c5fe40bc95a803aecf6d7ac356dbf6d3535bf8baec5d05eab1 languageName: node linkType: hard -"@penpot/plugins-runtime@npm:1.3.2": - version: 1.3.2 - resolution: "@penpot/plugins-runtime@npm:1.3.2" +"@penpot/plugins-runtime@npm:1.4.2": + version: 1.4.2 + resolution: "@penpot/plugins-runtime@npm:1.4.2" dependencies: - "@penpot/plugin-types": "npm:^1.3.2" + "@penpot/plugin-types": "npm:^1.4.2" ses: "npm:^1.1.0" zod: "npm:^3.22.4" - checksum: 10c0/b6d2cb3a57bcbe58232db52b8224d1817495e96b34997bfa72421629b5f34a8c9cc71357c315dcab9d52ea036ed632a5efe0ac50f52e730901c02d498dfa1313 + checksum: 10c0/af084d906cce9a6dea956fe5420111d7ea37c7620737a1e3d4f12958cb302a8f697c1229c237107c28fbb3b9f37eee308e6d16262b04ad56ae6f76c7a12f12e5 languageName: node linkType: hard @@ -4176,7 +4176,7 @@ __metadata: dependencies: "@penpot/draft-js": "portal:./packages/draft-js" "@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/text-editor": "portal:./text-editor" "@playwright/test": "npm:1.57.0" diff --git a/plugins/CHANGELOG.md b/plugins/CHANGELOG.md index 67b0b183fa..e15c253923 100644 --- a/plugins/CHANGELOG.md +++ b/plugins/CHANGELOG.md @@ -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) ### 🩹 Fixes diff --git a/plugins/docs/publish-package.md b/plugins/docs/publish-package.md index aaee111e6a..a694792f68 100644 --- a/plugins/docs/publish-package.md +++ b/plugins/docs/publish-package.md @@ -7,6 +7,29 @@ This guide details the process of publishing `plugin-types`, for plugin development. Below is a walkthrough for publishing these 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= +``` + ## Publishing Libraries Publishing packages enables the distribution of types and styles @@ -35,28 +58,16 @@ pnpm run release -- --dry-run false This command will: -- Update the `CHANGELOG.md` - Update the library's `package.json` version - Generate a commit -- Create a new git tag - Publish to NPM with the `latest` tag Ensure everything is correct before proceeding with the git push. Once verified, execute the following commands: ```shell +git commit -m ":arrow_up: Updated plugins release to X.X.X" 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 diff --git a/plugins/libs/plugin-types/package.json b/plugins/libs/plugin-types/package.json index c9dfa6bdc7..0e8e1ca47d 100644 --- a/plugins/libs/plugin-types/package.json +++ b/plugins/libs/plugin-types/package.json @@ -1,6 +1,6 @@ { "name": "@penpot/plugin-types", - "version": "1.3.2", + "version": "1.4.2", "typings": "./index.d.ts", "type": "module" } diff --git a/plugins/libs/plugins-runtime/package.json b/plugins/libs/plugins-runtime/package.json index 2cc3ae3055..5d3756b9fc 100644 --- a/plugins/libs/plugins-runtime/package.json +++ b/plugins/libs/plugins-runtime/package.json @@ -1,8 +1,8 @@ { "name": "@penpot/plugins-runtime", - "version": "1.3.2", + "version": "1.4.2", "dependencies": { - "@penpot/plugin-types": "^1.3.2", + "@penpot/plugin-types": "^1.4.2", "ses": "^1.1.0", "zod": "^3.22.4" }, diff --git a/plugins/libs/plugins-runtime/src/lib/create-sandbox.ts b/plugins/libs/plugins-runtime/src/lib/create-sandbox.ts index 9d03f9da95..70c024835e 100644 --- a/plugins/libs/plugins-runtime/src/lib/create-sandbox.ts +++ b/plugins/libs/plugins-runtime/src/lib/create-sandbox.ts @@ -118,8 +118,8 @@ export function createSandbox( // Window properties console: ses.harden(window.console), devicePixelRatio: ses.harden(window.devicePixelRatio), - atob: ses.harden(window.atob), - btoa: ses.harden(window.btoa), + atob: ses.harden(window.atob.bind(null)), + btoa: ses.harden(window.btoa.bind(null)), structuredClone: ses.harden(window.structuredClone), }; diff --git a/plugins/libs/plugins-styles/package.json b/plugins/libs/plugins-styles/package.json index 511c3f7f86..10144ed9d3 100644 --- a/plugins/libs/plugins-styles/package.json +++ b/plugins/libs/plugins-styles/package.json @@ -1,5 +1,5 @@ { "name": "@penpot/plugin-styles", - "version": "1.3.2", + "version": "1.4.2", "dependencies": {} } diff --git a/plugins/pnpm-lock.yaml b/plugins/pnpm-lock.yaml index d1dd76b579..b1cdc2d9bc 100644 --- a/plugins/pnpm-lock.yaml +++ b/plugins/pnpm-lock.yaml @@ -230,8 +230,8 @@ importers: libs/plugins-runtime: dependencies: '@penpot/plugin-types': - specifier: ^1.3.2 - version: 1.3.2 + specifier: ^1.4.2 + version: link:../plugin-types ses: specifier: ^1.1.0 version: 1.14.0 @@ -4200,12 +4200,6 @@ packages: } 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': resolution: { @@ -13194,6 +13188,7 @@ packages: integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==, } 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: resolution: @@ -13201,6 +13196,7 @@ packages: integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==, } 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: resolution: @@ -18203,8 +18199,6 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.1 optional: true - '@penpot/plugin-types@1.3.2': {} - '@phenomnomnominal/tsquery@5.0.1(typescript@5.6.3)': dependencies: esquery: 1.6.0 diff --git a/plugins/tools/scripts/publish.ts b/plugins/tools/scripts/publish.ts index 7ea5970617..41ec00b7a2 100644 --- a/plugins/tools/scripts/publish.ts +++ b/plugins/tools/scripts/publish.ts @@ -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) { await releasePublish({ dryRun: args.dryRun, diff --git a/plugins/wrangle-penpot-plugins-api-doc.toml b/plugins/wrangle-penpot-plugins-api-doc.toml new file mode 100644 index 0000000000..e9535be2d8 --- /dev/null +++ b/plugins/wrangle-penpot-plugins-api-doc.toml @@ -0,0 +1,4 @@ +name = "penpot-plugins-api-doc" +compatibility_date = "2025-01-01" + +assets = { directory = "dist/doc" }