Repository navigation
Conversation
The deploy scripts printed errors but still exited with 0, so a failed push looked like a success. They now set a non-zero exit code. The release branch commit message reads the version from package.json instead of the npm environment, so it is also right when the script runs with plain node. `npm run deploy` builds once and pushes to the release branch and gh-pages; `deploy:release` and `deploy:demo` each build first, so they work from a fresh clone. gh-pages is updated from 2 to 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wrappers.js becomes adapter.js, which checks for Redactor 5, X, 3 and 2 in turn and exports the code for the one on the page. checkRedactor.js repeated the same checks and is gone: the entry file registers the plugin when an adapter is found and logs "Redactor not found" otherwise. Every module now uses named exports, and each version exports its functions as one object. jQuery, Redactor and RedactorX come from src/globals.js, which reads them from window. Also fixes the lint errors left by the Redactor X and 5 contributions: src/.eslintrc.json set ecmaVersion 5 with ES modules, which ESLint can't parse, so lint had not checked src at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `multiple: true`, only one file ended up in the editor: * Redactor 3: each image insert replaced the image the previous insert left selected, so only the last image stayed. All images now go in one insert. * Redactor X: files uploaded together have no crop info, so reading `fileInfo.crop.width` threw after the first image. The size now falls back to the original image info. Each version now gets all uploaded files in one call, `insertFiles`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The integration string only checked for Redactor 3 and 2, so on Redactor X it reported "Redactor/undefined". It now reports the Redactor X version, for example "Redactor/X1.4.6". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Redactor 2 check read `$.Redactor` without checking that jQuery is on the page, so a page without jQuery threw "Cannot read properties of undefined" and the intended error message never showed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Redactor X shows toolbar buttons as icons only, so without `buttonIconEnabled` the Uploadcare button was an empty square. It now always shows `rx-icon-file`, or `buttonIcon` when set, like on Redactor 5. `buttonIconEnabled` has no effect on Redactor X. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vite 8 builds both files in one run: the plugin placeholders are filled by a small plugin, the minified file keeps the license banner through postBanner, and "use strict" is kept. Rollup, its plugins, rimraf and rollup.config.js are removed; Vite empties dist itself. The output is now ES2015 instead of ES5. Rolldown prints ES2015 shorthand properties and the Oxc minifier uses template literals, and neither can target ES5. The bundles have the same functions as before; the unminified file is 13.5 KB instead of 14.8 KB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.oxlintrc.json keeps the 33 rules of eslint-config-uploadcare that oxlint supports, with the same settings. The 63 it does not have are formatting rules, React rules for JSX this project doesn't have, and two syntax rules strict mode already enforces. ESLint, its config package and all .eslintrc files are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r mode `npm test` builds the plugin and runs it in headless Chromium against the real Redactor builds from demo/ and the real Uploadcare Widget, uploading real files to Uploadcare. Each case is written once in test/suite.js and runs for every version; test/adapters.js holds what differs between them. The cases cover the toolbar button options, the options passed to the widget, the show, done and cancel events, and uploads: images, the crop step, several files, imageTag, uploadCompleteCallback, and a page without Redactor. Two groups skip without extra setup: the Uploadcare demo project only takes images, so the non-image test needs UPLOADCARE_PUBLIC_KEY, and the Redactor 5 tests need a licensed build in test/vendor/redactor5/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
husky 4 only runs hooks set up in a `husky` section of package.json or a .huskyrc file, and there was neither. The `precommit` script is a husky 0.x convention that husky 4 ignores, so nothing ran on commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…please * ci.yml runs lint, the tests and the build on pushes to master and on pull requests. * release.yml runs release-please on every push to master. It keeps a release pull request with the next version and the changelog from Conventional Commits; merging it tags the release, creates the GitHub release, attaches the bundles and calls the deploy. * deploy.yml pushes a release tag's build to the release branch and the demo to gh-pages. It can also run by hand with a tag, to redeploy or roll back. release-please replaces release-it. The first release is pinned to 3.2.0, so the hand-written 3.2.0 changelog entry is removed and package.json goes back to 3.1.0, the last release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three demo pages share one layout and stylesheet: a tab per Redactor version, the editor, and the full code to copy, including the jQuery script the plugin needs. The Redactor X page says the button shows once you click into the text, and shows how to listen to the plugin events there. The footer link used utm_campaign=uploadcare-ckeditor; it now uses uploadcare-redactor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README keeps the overview and links a guide for each version in docs/, with its requirements, script tags, supported options, events and demo. It also says that Uploadcare Widget, which the Uploadcare docs now call jQuery File Uploader, has been deprecated since September 1, 2025, and that support for the new File Uploader is in progress. The heading and the package description say "Uploadcare Widget" instead of "File Uploader". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DEVELOPMENT.md covers the build, lint and test commands, the CI workflows, the Conventional Commits format release-please reads, and how releases and deploys work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Upload ordering, HTML injection, accessibility, licensed deployment, and Redactor 5 coverage remain unresolved.
Review effort: Balanced
Findings: 2
Open (5)
What changed in this PR
Modernizes build, lint, testing, CI, release automation, documentation, and demos while fixing multi-file uploads across supported Redactor versions.
Changes:
- Migrates from Rollup/ESLint/release-it to Vite, oxlint, Vitest, and release-please.
- Introduces version adapters and batched file insertion.
- Adds browser E2E coverage, CI/deployment workflows, documentation, and refreshed demos.
| File | Description |
|---|---|
vitest.config.mjs |
Configures browser E2E tests. |
vite.config.mjs |
Configures Vite bundles and version replacement. |
test/suite.js |
Adds shared integration test suite. |
test/redactorX.test.js |
Runs tests for Redactor X. |
test/redactor5.test.js |
Conditionally runs Redactor 5 tests. |
test/redactor3.test.js |
Runs tests for Redactor 3. |
test/redactor2.test.js |
Runs tests for Redactor 2. |
test/no-redactor.test.js |
Tests missing-editor handling. |
test/helpers.js |
Adds browser and upload test helpers. |
test/adapters.js |
Defines version-specific test adapters. |
src/wrappers.js |
Removes legacy adapter selection. |
src/uploadcare.js |
Registers the selected adapter safely. |
src/redactorX/start.js |
Ensures Redactor X buttons have icons. |
src/redactorX/insertHtml.js |
Removes single-file insertion. |
src/redactorX/insertFiles.js |
Adds multi-file insertion. |
src/redactorX/init.js |
Converts initialization to named exports. |
src/redactorX/index.js |
Exposes the Redactor X adapter. |
src/redactorX/getVersion.js |
Reads the global Redactor X version. |
src/redactorX/getSelection.js |
Converts selection helper export. |
src/redactorX/broadcast.js |
Converts event helper export. |
src/redactorX/addPlugin.js |
Uses the global Redactor X API. |
src/redactor5/plugin.js |
Refactors Redactor 5 plugin exports. |
src/redactor5/loadWidget.js |
Converts widget loader export. |
src/redactor5/index.js |
Exposes the Redactor 5 adapter. |
src/redactor5/getVersion.js |
Reads the global Redactor 5 API. |
src/redactor5/addPlugin.js |
Registers the named plugin class. |
src/redactor3/start.js |
Converts toolbar initialization export. |
src/redactor3/insertHtml.js |
Removes single-image insertion. |
src/redactor3/insertFiles.js |
Adds batched image insertion. |
src/redactor3/init.js |
Converts initialization to named exports. |
src/redactor3/index.js |
Exposes the Redactor 3 adapter. |
src/redactor3/getVersion.js |
Reads the global Redactor version. |
src/redactor3/getSelection.js |
Converts selection helper export. |
src/redactor3/broadcast.js |
Converts event helper export. |
src/redactor3/addPlugin.js |
Uses the global Redactor API. |
src/redactor2/start.js |
Converts toolbar initialization export. |
src/redactor2/insertFiles.js |
Adds multi-file insertion. |
src/redactor2/init.js |
Converts initialization to named exports. |
src/redactor2/index.js |
Exposes the Redactor 2 adapter. |
src/redactor2/getVersion.js |
Safely detects Redactor 2. |
src/redactor2/getSelection.js |
Converts selection helper export. |
src/redactor2/broadcast.js |
Converts callback helper export. |
src/redactor2/addPlugin.js |
Uses global jQuery registration. |
src/globals.js |
Centralizes browser globals. |
src/common/show.js |
Uses the selected adapter for dialogs. |
src/common/normalizeOptions.js |
Converts option normalization export. |
src/common/loadWidget.js |
Converts widget loader export. |
src/common/getFileUrl.js |
Converts URL helper export. |
src/common/done.js |
Inserts resolved files as one batch. |
src/common/applyIntegrationOption.js |
Adds Redactor X integration metadata. |
src/checkRedactor.js |
Removes legacy editor detection. |
src/adapter.js |
Selects the available Redactor adapter. |
src/.eslintrc.json |
Removes source ESLint configuration. |
scripts/deploy-release.js |
Improves release deployment failures. |
scripts/deploy-demo.js |
Propagates demo deployment failures. |
scripts/.eslintrc.json |
Removes script ESLint configuration. |
rollup.config.js |
Removes the Rollup build. |
release-please-config.json |
Configures release-please. |
README.md |
Adds version-specific documentation links. |
package.json |
Updates tooling and scripts. |
index.html |
Refreshes demo navigation. |
docs/redactor-x.md |
Documents Redactor X integration. |
docs/redactor-5.md |
Documents Redactor 5 integration. |
docs/redactor-3.md |
Documents Redactor 3 integration. |
docs/redactor-2.md |
Documents Redactor 2 integration. |
DEVELOPMENT.md |
Documents development and release workflows. |
demo/redactorX/redactorx.min.css |
Adds Redactor X demo styling. |
demo/redactorX/index.html |
Adds the Redactor X demo. |
demo/redactor3/index.html |
Refreshes the Redactor 3 demo. |
demo/redactor2/index.html |
Refreshes the Redactor 2 demo. |
demo/assets/app.css |
Redesigns shared demo styling. |
CHANGELOG.md |
Hands future entries to release-please. |
.release-please-manifest.json |
Records the current release version. |
.release-it.json |
Removes release-it configuration. |
.oxlintrc.json |
Adds oxlint configuration. |
.gitignore |
Ignores test vendor builds and artifacts. |
.github/workflows/release.yml |
Automates releases and assets. |
.github/workflows/deploy.yml |
Adds reusable release deployment. |
.github/workflows/ci.yml |
Adds lint, test, and build CI. |
.eslintrc.json |
Removes root ESLint configuration. |
.eslintignore |
Removes obsolete ESLint ignores. |
Files not reviewed (1)
- demo/redactorX/redactorx.min.css: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| - run: npm run deploy |
There was a problem hiding this comment.
Agreed that this deploy would publish Imperavi's Redactor X build from demo/redactorX/. Whether it can be redistributed is being checked with Imperavi. Until that's settled, the options are to leave the Redactor X demo out of the deploy or to drop the build from the repo; this thread stays open until that's decided.
🤖 Written by Claude Code on behalf of @nd0ut
| plugin.app.insertion.insertHtml( | ||
| '<a href="' + fileUrl + '" data-file="' + fileInfo.uuid + '">' + fileInfo.name + '</a>' | ||
| ) |
There was a problem hiding this comment.
Fixed in 5f55ea8. Redactor 2 and 3 had the same problem: the Redactor 2 image alt and the link text on Redactor 2, 3 and X went into HTML unescaped (Redactor 3's link insert treats the text as HTML too). File names now go through a new escapeHtml helper. A new test uploads an image named a"><img src=x onerror=alert(1)>.png and checks that the name stays text; it fails without the escaping.
🤖 Written by Claude Code on behalf of @nd0ut
| --color-blue: #238de6; | ||
| --color-dark-blue: #187cce; |
| else { | ||
| plugin.app.api('module.link.insert', { | ||
| url: fileUrl, | ||
| text: fileInfo.name, | ||
| id: fileInfo.uuid, | ||
| }) |
There was a problem hiding this comment.
Fixed in b26c8ba. The order was image, image, document: the batched image insert went in before the links. Inserting the files one at a time in order doesn't work on Redactor 3: each image insert replaces whatever is selected, so image, link, image leaves only the last image. Uploads that include images now go in as one block of HTML in upload order, and the images still become Redactor 3 image blocks. Links on their own still use the link insert, so they stay inside the current paragraph. The several-files test now checks the order.
🤖 Written by Claude Code on behalf of @nd0ut
|
|
||
| // __REDACTOR5_BUILD__ is the build path when test/vendor/redactor5/redactor.min.js | ||
| // exists, see vitest.config.mjs | ||
| if (__REDACTOR5_BUILD__) { |
There was a problem hiding this comment.
Right, the Redactor 5 suite always skips in CI. Redactor 5 is licensed, so the build can't be committed or fetched from a public URL, and the public demo build only runs on imperavi.com. Running it in CI would need a licensed build stored somewhere private, such as an encrypted secret or a private package. For now the skip is documented in DEVELOPMENT.md, and the suite runs locally once a licensed build is placed in test/vendor/redactor5/.
🤖 Written by Claude Code on behalf of @nd0ut
File names come from the uploaded files and went into HTML unescaped: the image alt on Redactor 2 and the link text on Redactor 2, 3 and X. A name such as `a"><img src=x onerror=alert(1)>.png` added its own markup to the editor. Names now go through escapeHtml; Redactor 5 already built its link through the DOM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Images were collected into one image insert after the links, so an image, document, image upload came out as image, image, document. Separate inserts in order don't work: each image insert replaces the image the one before it left selected. Files that include images now go in as one block of HTML in upload order; links alone still use the link insert, which puts them inside the current paragraph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#238de6 had 3.49:1 against white, below the 4.5:1 AA needs for normal-size link text and for white text on the active version tab. #1b6fbf has 5.16:1, and the hover color #155a9c has 7.06:1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test Each test file now checks the editor on the page before creating any editor: a wrong build in demo/ stops the file with, for example, "Redactor 3 build on the page: expected 'undefined' to match /^3\./". The show-event test also checks that the plugin reports exactly that editor and the plugin version in the integration option, so the plugin picking the wrong version's code, such as Redactor 5 code on a Redactor 3 page, fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`npm start` runs the Vite dev server on port 3000 instead of browser-sync. Vite is already a dependency, so browser-sync and its 332 packages go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


Summary
Modernizes the tooling, adds end-to-end tests and CI, and fixes the bugs the tests found. Each commit has one purpose, and they're meant to be kept rather than squashed: release-please builds the changelog from their messages.
Behaviour changes, found by the new tests:
Evidence
inserts every file when multiple is onfailed on Redactor 3 (2 images uploaded, 1 in the editor) and on Redactor X (TypeError: Cannot read properties of undefined (reading 'width')).After:
npm test: 4 test files passed, 1 skipped; 43 tests passed, 13 skipped.UPLOADCARE_PUBLIC_KEY); and Redactor 5, which needs a licensed build."use strict"kept, unminified 14.8 KB -> 13.5 KB.npm cifrom the committed lockfile, lint and all three workflows (actionlint) pass. A release-please dry run against this repo finds v3.1.0 as the last release.Merge Danger
Door: two-way
Nothing is released by merging. release-please opens a release pull request for 3.2.0, and only merging that one tags, publishes and deploys.
Blast Radius: consumers
buttonIconEnabledno longer does anything on Redactor X, because the button always has an icon.demo/redactorX/redactorx.js), which will be served fromgh-pages. Imperavi's license restricts redistribution and use in open-source projects without written consent, so check with Imperavi before this is deployed.release-asfromrelease-please-config.jsonafter 3.2.0.🤖 Generated with Claude Code