diff --git a/.env.development b/.env.development index 387ab6204..bf641c34c 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,5 @@ +MODE="development" + VITE_APP_BACKEND_V2_GET_URL=https://json-dev.excalidraw.com/api/v2/ VITE_APP_BACKEND_V2_POST_URL=https://json-dev.excalidraw.com/api/v2/post/ diff --git a/.env.production b/.env.production index 11e9fd84b..72dd24d6e 100644 --- a/.env.production +++ b/.env.production @@ -1,3 +1,5 @@ +MODE="production" + VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/ VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/ diff --git a/.eslintrc.json b/.eslintrc.json index 8263b08a9..89f822736 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -32,6 +32,12 @@ "name": "jotai", "message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")." } + ], + "react/jsx-no-target-blank": [ + "error", + { + "allowReferrer": true + } ] } } diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..aebac52a0 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,45 @@ +# Project coding standards + +## Generic Communication Guidelines + +- Be succint and be aware that expansive generative AI answers are costly and slow +- Avoid providing explanations, trying to teach unless asked for, your chat partner is an expert +- Stop apologising if corrected, just provide the correct information or code +- Prefer code unless asked for explanation +- Stop summarizing what you've changed after modifications unless asked for + +## TypeScript Guidelines + +- Use TypeScript for all new code +- Where possible, prefer implementations without allocation +- When there is an option, opt for more performant solutions and trade RAM usage for less CPU cycles +- Prefer immutable data (const, readonly) +- Use optional chaining (?.) and nullish coalescing (??) operators + +## React Guidelines + +- Use functional components with hooks +- Follow the React hooks rules (no conditional hooks) +- Keep components small and focused +- Use CSS modules for component styling + +## Naming Conventions + +- Use PascalCase for component names, interfaces, and type aliases +- Use camelCase for variables, functions, and methods +- Use ALL_CAPS for constants + +## Error Handling + +- Use try/catch blocks for async operations +- Implement proper error boundaries in React components +- Always log errors with contextual information + +## Testing + +- Always attempt to fix #problems +- Always offer to run `yarn test:app` in the project root after modifications are complete and attempt fixing the issues reported + +## Types + +- Always include `packages/math/src/types.ts` in the context when your write math related code and always use the Point type instead of { x, y} diff --git a/.github/workflows/autorelease-excalidraw.yml b/.github/workflows/autorelease-excalidraw.yml index 5ff5690eb..6e2c0d00e 100644 --- a/.github/workflows/autorelease-excalidraw.yml +++ b/.github/workflows/autorelease-excalidraw.yml @@ -24,4 +24,4 @@ jobs: - name: Auto release run: | yarn add @actions/core -W - yarn autorelease + yarn release --tag=next --non-interactive diff --git a/.github/workflows/autorelease-preview.yml b/.github/workflows/autorelease-preview.yml deleted file mode 100644 index a40ed3c43..000000000 --- a/.github/workflows/autorelease-preview.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Auto release excalidraw preview -on: - issue_comment: - types: [created, edited] - -jobs: - Auto-release-excalidraw-preview: - name: Auto release preview - if: github.event.comment.body == '@excalibot trigger release' && github.event.issue.pull_request - runs-on: ubuntu-latest - steps: - - name: React to release comment - uses: peter-evans/create-or-update-comment@v1 - with: - token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }} - comment-id: ${{ github.event.comment.id }} - reactions: "+1" - - name: Get PR SHA - id: sha - uses: actions/github-script@v4 - with: - result-encoding: string - script: | - const { owner, repo, number } = context.issue; - const pr = await github.pulls.get({ - owner, - repo, - pull_number: number, - }); - return pr.data.head.sha - - uses: actions/checkout@v2 - with: - ref: ${{ steps.sha.outputs.result }} - fetch-depth: 2 - - name: Setup Node.js 18.x - uses: actions/setup-node@v2 - with: - node-version: 18.x - - name: Set up publish access - run: | - npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Auto release preview - id: "autorelease" - run: | - yarn add @actions/core -W - yarn autorelease preview ${{ github.event.issue.number }} - - name: Post comment post release - if: always() - uses: peter-evans/create-or-update-comment@v1 - with: - token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }} - issue-number: ${{ github.event.issue.number }} - body: "@${{ github.event.comment.user.login }} ${{ steps.autorelease.outputs.result }}" diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index a4a8a4c5f..68eee2775 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -17,9 +17,14 @@ jobs: with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: context: . push: true tags: excalidraw/excalidraw:latest + platforms: linux/amd64, linux/arm64, linux/arm/v7 diff --git a/.gitignore b/.gitignore index 6f9407fad..6f3a62bba 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ packages/excalidraw/types coverage dev-dist html -meta*.json \ No newline at end of file +meta*.json +.claude diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..4faf291ec --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,34 @@ +# CLAUDE.md + +## Project Structure + +Excalidraw is a **monorepo** with a clear separation between the core library and the application: + +- **`packages/excalidraw/`** - Main React component library published to npm as `@excalidraw/excalidraw` +- **`excalidraw-app/`** - Full-featured web application (excalidraw.com) that uses the library +- **`packages/`** - Core packages: `@excalidraw/common`, `@excalidraw/element`, `@excalidraw/math`, `@excalidraw/utils` +- **`examples/`** - Integration examples (NextJS, browser script) + +## Development Workflow + +1. **Package Development**: Work in `packages/*` for editor features +2. **App Development**: Work in `excalidraw-app/` for app-specific features +3. **Testing**: Always run `yarn test:update` before committing +4. **Type Safety**: Use `yarn test:typecheck` to verify TypeScript + +## Development Commands + +```bash +yarn test:typecheck # TypeScript type checking +yarn test:update # Run all tests (with snapshot updates) +yarn fix # Auto-fix formatting and linting issues +``` + +## Architecture Notes + +### Package System + +- Uses Yarn workspaces for monorepo management +- Internal packages use path aliases (see `vitest.config.mts`) +- Build system uses esbuild for packages, Vite for the app +- TypeScript throughout with strict configuration diff --git a/Dockerfile b/Dockerfile index 2716803fa..c08385d65 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18 AS build +FROM --platform=${BUILDPLATFORM} node:18 AS build WORKDIR /opt/node_app @@ -6,13 +6,14 @@ COPY . . # do not ignore optional dependencies: # Error: Cannot find module @rollup/rollup-linux-x64-gnu -RUN yarn --network-timeout 600000 +RUN --mount=type=cache,target=/root/.cache/yarn \ + npm_config_target_arch=${TARGETARCH} yarn --network-timeout 600000 ARG NODE_ENV=production -RUN yarn build:app:docker +RUN npm_config_target_arch=${TARGETARCH} yarn build:app:docker -FROM nginx:1.27-alpine +FROM --platform=${TARGETPLATFORM} nginx:1.27-alpine COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html diff --git a/README.md b/README.md index 9458779ce..f1cf03053 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ Chat on Discord + + Ask DeepWiki + Follow Excalidraw on Twitter @@ -63,7 +66,7 @@ The Excalidraw editor (npm package) supports: - πŸ—οΈ Customizable. - πŸ“· Image support. - πŸ˜€ Shape libraries support. -- πŸ‘… Localization (i18n) support. +- 🌐 Localization (i18n) support. - πŸ–ΌοΈ Export to PNG, SVG & clipboard. - πŸ’Ύ Open format - export drawings as an `.excalidraw` json file. - βš’οΈ Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser... diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx index 3831268f0..e7852cee9 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx @@ -2,7 +2,7 @@ Earlier we were using `renderFooter` prop to render custom footer which was removed in [#5970](https://github.com/excalidraw/excalidraw/pull/5970). Now you can pass a `Footer` component instead to render the custom UI for footer. -You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should a valid React Node. +You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should be a valid React Node. **Usage** @@ -25,7 +25,7 @@ function App() { } ``` -This will only for `Desktop` devices. +This will only work for `Desktop` devices. For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component. @@ -65,4 +65,4 @@ const App = () => ( // Need to render when code is span across multiple components // in Live Code blocks editor render(); -``` \ No newline at end of file +``` diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx index b7a3bab5f..c9580b66b 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx @@ -363,13 +363,7 @@ This API has the below signature. It sets the `tool` passed in param as the acti ```ts ( tool: ( - | ( - | { type: Exclude } - | { - type: Extract; - insertOnCanvasDirectly?: boolean; - } - ) + | { type: ToolType } | { type: "custom"; customType: string } ) & { locked?: boolean }, ) => {}; @@ -377,7 +371,7 @@ This API has the below signature. It sets the `tool` passed in param as the acti | Name | Type | Default | Description | | --- | --- | --- | --- | -| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` | +| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool | | `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface | ## setCursor diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx index 5c2a5501b..607e97182 100644 --- a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx +++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx @@ -31,6 +31,7 @@ All `props` are _optional_. | [`generateIdForFile`](#generateidforfile) | `function` | \_ | Allows you to override `id` generation for files added on canvas | | [`validateEmbeddable`](#validateembeddable) | `string[]` \| `boolean` \| `RegExp` \| `RegExp[]` \| ((link: string) => boolean | undefined) | \_ | use for custom src url validation | | [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `