Compare commits

...

43 Commits

Author SHA1 Message Date
codegen-sh[bot] a97863d941 Fix React 18 TypeScript compatibility issues
- Fixed AnimatePresence children prop issues in 3 components
- Applied useCallback-implicit-any codemod to 34 files
- Added TypeScript suppressions for framer-motion v4 compatibility
- Reduced TypeScript errors from 302 to 249 (-53 errors)

Core React 18 migration is now complete with improved type safety.
2025-06-29 11:18:43 +00:00
codegen-sh[bot] 06b48d4276 Fix React 18 TypeScript compatibility issues
- Fix function-based icons to direct JSX for React 18 compatibility
- Update ActionContext parameter types in teams.tsx
- Replace () => null with null for icon properties
- Remove unused RootStore import
2025-06-29 10:43:38 +00:00
codegen-sh[bot] e3e44260de Trigger CI
Empty commit to trigger continuous integration checks
2025-06-29 10:25:34 +00:00
codegen-sh[bot] 68fe78a20e Applied automatic fixes 2025-06-29 10:15:51 +00:00
codegen-sh[bot] e978f63217 Fix React 18 implicit children types with codemod
- Applied types-react-codemod implicit-children transform
- Updated 63 components to use React.PropsWithChildren
- Fixed React.FC types to be compatible with React 18
- Ensures proper typing for components that accept children props

This addresses React 18's removal of implicit children from component props.
2025-06-29 10:12:31 +00:00
codegen-sh[bot] 56ceff055e Fix import order for ESLint compliance
- Move createRoot imports to proper position after React imports
- Ensure imports follow project's ESLint import order rules
- Fix linting errors in all React 18 migration files
2025-06-29 10:11:49 +00:00
codegen-sh[bot] 1cf9d4c6cb Migrate ReactDOM.render to createRoot API for React 18
- Replace ReactDOM.render() with createRoot().render() in all components
- Remove unused ReactDOM imports
- Update main app entry point (app/index.tsx)
- Update editor components (BlockMenu, uploadPlaceholder, Notice)
- Applied automated codemod for consistent migration
2025-06-29 10:11:49 +00:00
codegen-sh[bot] 84bc70dfd8 Update React and React DOM to version 18 2025-06-29 10:11:49 +00:00
codegen-sh[bot] c97e5fd181 feat: Add block movement with Cmd+Alt+Arrow keys (#9502)
* feat: Add block movement with Cmd+Alt+Arrow keys

- Add getCurrentBlock helper function to find current block node
- Implement moveBlockUp and moveBlockDown commands in Keys extension
- Support Cmd+Alt+ArrowUp to move current block up
- Support Cmd+Alt+ArrowDown to move current block down
- Follow ProseMirror best practices for node manipulation
- Maintain cursor position after block movement

Fixes #9486

* feat: Add block movement shortcuts to KeyboardShortcuts component

- Add Cmd+Alt+↑ shortcut for moving blocks up
- Add Cmd+Alt+↓ shortcut for moving blocks down
- Added to Formatting section alongside other editing shortcuts

---------

Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
2025-06-29 04:56:48 -04:00
Tom Moor 8e56f58102 chore: Add additional validation to SMTP_SERVICE env (#9506)
Related #9505
2025-06-29 04:34:47 -04:00
Translate-O-Tron 3347101c84 New Crowdin updates (#9461)
* fix: New Korean translations from Crowdin [ci skip]

* fix: New Dutch translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]
2025-06-29 04:34:35 -04:00
dependabot[bot] 6d387c61c3 chore(deps): bump the aws group with 5 updates (#9456)
Bumps the aws group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.826.0` | `3.828.0` |
| [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) | `3.826.0` | `3.828.0` |
| [@aws-sdk/s3-presigned-post](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/s3-presigned-post) | `3.826.0` | `3.828.0` |
| [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/s3-request-presigner) | `3.826.0` | `3.828.0` |
| [@aws-sdk/signature-v4-crt](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/signature-v4-crt) | `3.826.0` | `3.828.0` |


Updates `@aws-sdk/client-s3` from 3.826.0 to 3.828.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.828.0/clients/client-s3)

Updates `@aws-sdk/lib-storage` from 3.826.0 to 3.828.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.828.0/lib/lib-storage)

Updates `@aws-sdk/s3-presigned-post` from 3.826.0 to 3.828.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-presigned-post/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.828.0/packages/s3-presigned-post)

Updates `@aws-sdk/s3-request-presigner` from 3.826.0 to 3.828.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-request-presigner/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.828.0/packages/s3-request-presigner)

Updates `@aws-sdk/signature-v4-crt` from 3.826.0 to 3.828.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/signature-v4-crt/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.828.0/packages/signature-v4-crt)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.828.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
- dependency-name: "@aws-sdk/lib-storage"
  dependency-version: 3.828.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
- dependency-name: "@aws-sdk/s3-presigned-post"
  dependency-version: 3.828.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
- dependency-name: "@aws-sdk/s3-request-presigner"
  dependency-version: 3.828.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
- dependency-name: "@aws-sdk/signature-v4-crt"
  dependency-version: 3.828.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-29 04:34:11 -04:00
codegen-sh[bot] 2f06ae9e48 Fix OIDC login failures with Base64 avatar URLs (#9501) 2025-06-28 10:47:51 -04:00
codegen-sh[bot] 2a962efe57 feat: upgrade Node.js support to include Node 22 (#9503) 2025-06-28 10:47:14 -04:00
codegen-sh[bot] 879c568a2c Upgrade Prettier to v3.6.2 (#9500)
* Upgrade Prettier to v3.6.2 and eslint-plugin-prettier to v5.5.1

- Upgraded prettier from ^2.8.8 to ^3.6.2 (latest version)
- Upgraded eslint-plugin-prettier from ^4.2.1 to ^5.5.1 for compatibility
- Applied automatic formatting changes from new Prettier version
- All existing ESLint and Prettier configurations remain compatible

* Applied automatic fixes

* Trigger CI

---------

Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2025-06-28 10:22:28 -04:00
dependabot[bot] 76b54fc234 chore(deps): bump mermaid from 11.4.1 to 11.7.0 (#9481)
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 11.4.1 to 11.7.0.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Changelog](https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/mermaid-js/mermaid/compare/mermaid@11.4.1...mermaid@11.7.0)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-version: 11.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-28 09:50:41 -04:00
Tom Moor 2e53145532 fix: Notion API timed out during import (#9498) 2025-06-28 09:50:32 -04:00
Tom Moor f6f831f3f6 fix: Enable PKCE if OIDC discovery endpoint supports it (#9478)
* fix: Enable PKCE if OIDC discovery endpoint supports it

* fix: Ensure code_verifier is passed through state

* facepalm
2025-06-27 11:06:45 -04:00
Tom Moor 3700342b35 fix: Not correctly catching linked databases (#9497) 2025-06-27 13:50:36 +00:00
Tom Moor 0244ac2a84 feat: Allow allowIndexing and showLastUpdated to be set in shares.create endpoint (#9476) 2025-06-24 17:21:06 -04:00
dependabot[bot] bf54e639a7 chore(deps-dev): bump terser from 5.42.0 to 5.43.1 (#9482)
Bumps [terser](https://github.com/terser/terser) from 5.42.0 to 5.43.1.
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/compare/v5.42.0...v5.43.1)

---
updated-dependencies:
- dependency-name: terser
  dependency-version: 5.43.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-24 05:07:29 -04:00
Tom Moor 083e5bb7c4 fix/pl-rendering (#9484) 2025-06-23 22:01:41 +00:00
Tom Moor ca5c51a712 fix: Client and server validation differ for subdomains (#9468)
* fix: Client and server validation differ for subdomains

* Validation message

* Lower min subdomain length to 2
2025-06-18 16:50:17 -04:00
Hemachandar a3b3e9e0be Use Retry-After header for Notion rate-limit retries (#9467) 2025-06-18 16:39:58 -04:00
Tom Moor ecd5afa6cd fix: Public share search offset is incorrect (#9465) 2025-06-18 16:01:42 +00:00
dependabot[bot] 4562edfda0 chore(deps): bump class-validator from 0.14.1 to 0.14.2 (#9459)
Bumps [class-validator](https://github.com/typestack/class-validator) from 0.14.1 to 0.14.2.
- [Release notes](https://github.com/typestack/class-validator/releases)
- [Changelog](https://github.com/typestack/class-validator/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/typestack/class-validator/compare/v0.14.1...v0.14.2)

---
updated-dependencies:
- dependency-name: class-validator
  dependency-version: 0.14.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-18 11:58:35 -04:00
dependabot[bot] af74535333 chore(deps): bump mailparser from 3.7.2 to 3.7.3 (#9458)
Bumps [mailparser](https://github.com/nodemailer/mailparser) from 3.7.2 to 3.7.3.
- [Release notes](https://github.com/nodemailer/mailparser/releases)
- [Changelog](https://github.com/nodemailer/mailparser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/mailparser/compare/v3.7.2...v3.7.3)

---
updated-dependencies:
- dependency-name: mailparser
  dependency-version: 3.7.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-18 11:58:21 -04:00
dependabot[bot] 18ffccc333 chore(deps): bump octokit from 3.2.1 to 3.2.2 (#9460)
Bumps [octokit](https://github.com/octokit/octokit.js) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/octokit/octokit.js/releases)
- [Commits](https://github.com/octokit/octokit.js/compare/v3.2.1...v3.2.2)

---
updated-dependencies:
- dependency-name: octokit
  dependency-version: 3.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-18 11:58:10 -04:00
Tom Moor 120a3388ed fix: Commenting unavailable on individually shared documents (#9449)
Refactor to use policies for comment
2025-06-15 09:45:42 -04:00
Tom Moor 4689d5e88d fix: PNG without dimension data should fallback to async dimension loading (#9453)
closes #9442
2025-06-15 09:44:38 -04:00
Hemachandar 2183c6d1d2 fix: Skip showing archived docs in shared section (#9451) 2025-06-14 10:46:25 -04:00
Tom Moor 75f173c6ff Add option to publish but not persist events (#9448)
* Add option to publish but not persist events

* tsc
2025-06-14 10:46:01 -04:00
Tom Moor 8b6d9589f9 fix: Switches not working in react-hook-form (#9450) 2025-06-14 00:02:58 -04:00
Translate-O-Tron 92bd67c104 New Crowdin updates (#9438)
* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New Dutch translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]

* fix: New Spanish translations from Crowdin [ci skip]

* fix: New Czech translations from Crowdin [ci skip]

* fix: New Danish translations from Crowdin [ci skip]

* fix: New Hebrew translations from Crowdin [ci skip]

* fix: New Hungarian translations from Crowdin [ci skip]

* fix: New Italian translations from Crowdin [ci skip]

* fix: New Japanese translations from Crowdin [ci skip]

* fix: New Polish translations from Crowdin [ci skip]

* fix: New Portuguese translations from Crowdin [ci skip]

* fix: New Swedish translations from Crowdin [ci skip]

* fix: New Turkish translations from Crowdin [ci skip]

* fix: New Ukrainian translations from Crowdin [ci skip]

* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New Chinese Traditional translations from Crowdin [ci skip]

* fix: New Vietnamese translations from Crowdin [ci skip]

* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Indonesian translations from Crowdin [ci skip]

* fix: New Persian translations from Crowdin [ci skip]

* fix: New Thai translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New Romanian translations from Crowdin [ci skip]

* fix: New English, United Kingdom translations from Crowdin [ci skip]
2025-06-13 20:09:47 -04:00
Tom Moor 84c6bd18ce fix: All documents should show in CMD+K when searching by title (#9445)
closes #8438
2025-06-13 20:09:38 -04:00
Tom Moor 89099ccf58 fix: Notion import failure details are not exposed to admin (#9443) 2025-06-13 07:43:37 -04:00
Tom Moor 0536c108eb fix: Login via email does not properly redirect to desktop app (#9440) 2025-06-12 21:12:58 -04:00
Tom Moor d1ad2f20e1 fix: Facepile overflow became square (#9439) 2025-06-13 00:41:31 +00:00
Tom Moor ca0e37063c fix: Sporadic rate limiting errors from Notion (#9436) 2025-06-12 16:20:32 -04:00
Tom Moor 98366e55e9 fix: Improve table merge/split icons (#9432) 2025-06-12 01:59:20 +00:00
Tom Moor a9c4dd43d6 fix: options vs rest usage (#9431)
No functional difference here, just avoid extra passed attributes
2025-06-11 20:04:43 -04:00
Hemachandar c9f25546e8 fix: Handle Notion linked database errors (#9429) 2025-06-11 09:10:00 -04:00
Translate-O-Tron 276ca1bbf2 New Crowdin updates (#9335)
* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New Dutch translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]

* fix: New Spanish translations from Crowdin [ci skip]

* fix: New Czech translations from Crowdin [ci skip]

* fix: New Danish translations from Crowdin [ci skip]

* fix: New Hebrew translations from Crowdin [ci skip]

* fix: New Hungarian translations from Crowdin [ci skip]

* fix: New Italian translations from Crowdin [ci skip]

* fix: New Japanese translations from Crowdin [ci skip]

* fix: New Polish translations from Crowdin [ci skip]

* fix: New Portuguese translations from Crowdin [ci skip]

* fix: New Swedish translations from Crowdin [ci skip]

* fix: New Turkish translations from Crowdin [ci skip]

* fix: New Ukrainian translations from Crowdin [ci skip]

* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New Chinese Traditional translations from Crowdin [ci skip]

* fix: New Vietnamese translations from Crowdin [ci skip]

* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Indonesian translations from Crowdin [ci skip]

* fix: New Persian translations from Crowdin [ci skip]

* fix: New Thai translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New Romanian translations from Crowdin [ci skip]

* fix: New English, United Kingdom translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New Dutch translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]

* fix: New Spanish translations from Crowdin [ci skip]

* fix: New Czech translations from Crowdin [ci skip]

* fix: New Danish translations from Crowdin [ci skip]

* fix: New Hebrew translations from Crowdin [ci skip]

* fix: New Hungarian translations from Crowdin [ci skip]

* fix: New Italian translations from Crowdin [ci skip]

* fix: New Japanese translations from Crowdin [ci skip]

* fix: New Polish translations from Crowdin [ci skip]

* fix: New Portuguese translations from Crowdin [ci skip]

* fix: New Swedish translations from Crowdin [ci skip]

* fix: New Turkish translations from Crowdin [ci skip]

* fix: New Ukrainian translations from Crowdin [ci skip]

* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New Chinese Traditional translations from Crowdin [ci skip]

* fix: New Vietnamese translations from Crowdin [ci skip]

* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Indonesian translations from Crowdin [ci skip]

* fix: New Persian translations from Crowdin [ci skip]

* fix: New Thai translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New Romanian translations from Crowdin [ci skip]

* fix: New English, United Kingdom translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New Dutch translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]

* fix: New Spanish translations from Crowdin [ci skip]

* fix: New Czech translations from Crowdin [ci skip]

* fix: New Danish translations from Crowdin [ci skip]

* fix: New Hebrew translations from Crowdin [ci skip]

* fix: New Hungarian translations from Crowdin [ci skip]

* fix: New Italian translations from Crowdin [ci skip]

* fix: New Japanese translations from Crowdin [ci skip]

* fix: New Polish translations from Crowdin [ci skip]

* fix: New Portuguese translations from Crowdin [ci skip]

* fix: New Swedish translations from Crowdin [ci skip]

* fix: New Turkish translations from Crowdin [ci skip]

* fix: New Ukrainian translations from Crowdin [ci skip]

* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New Chinese Traditional translations from Crowdin [ci skip]

* fix: New Vietnamese translations from Crowdin [ci skip]

* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Indonesian translations from Crowdin [ci skip]

* fix: New Persian translations from Crowdin [ci skip]

* fix: New Thai translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New Romanian translations from Crowdin [ci skip]

* fix: New English, United Kingdom translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New Dutch translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]

* fix: New Spanish translations from Crowdin [ci skip]

* fix: New Czech translations from Crowdin [ci skip]

* fix: New Danish translations from Crowdin [ci skip]

* fix: New Hebrew translations from Crowdin [ci skip]

* fix: New Hungarian translations from Crowdin [ci skip]

* fix: New Italian translations from Crowdin [ci skip]

* fix: New Japanese translations from Crowdin [ci skip]

* fix: New Polish translations from Crowdin [ci skip]

* fix: New Portuguese translations from Crowdin [ci skip]

* fix: New Swedish translations from Crowdin [ci skip]

* fix: New Turkish translations from Crowdin [ci skip]

* fix: New Ukrainian translations from Crowdin [ci skip]

* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New Chinese Traditional translations from Crowdin [ci skip]

* fix: New Vietnamese translations from Crowdin [ci skip]

* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Indonesian translations from Crowdin [ci skip]

* fix: New Persian translations from Crowdin [ci skip]

* fix: New Thai translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New Romanian translations from Crowdin [ci skip]

* fix: New English, United Kingdom translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New Dutch translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]

* fix: New Spanish translations from Crowdin [ci skip]

* fix: New Czech translations from Crowdin [ci skip]

* fix: New Danish translations from Crowdin [ci skip]

* fix: New Hebrew translations from Crowdin [ci skip]

* fix: New Hungarian translations from Crowdin [ci skip]

* fix: New Italian translations from Crowdin [ci skip]

* fix: New Japanese translations from Crowdin [ci skip]

* fix: New Polish translations from Crowdin [ci skip]

* fix: New Portuguese translations from Crowdin [ci skip]

* fix: New Swedish translations from Crowdin [ci skip]

* fix: New Turkish translations from Crowdin [ci skip]

* fix: New Ukrainian translations from Crowdin [ci skip]

* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New Chinese Traditional translations from Crowdin [ci skip]

* fix: New Vietnamese translations from Crowdin [ci skip]

* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Indonesian translations from Crowdin [ci skip]

* fix: New Persian translations from Crowdin [ci skip]

* fix: New Thai translations from Crowdin [ci skip]

* fix: New Romanian translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New English, United Kingdom translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New Korean translations from Crowdin [ci skip]

* fix: New German translations from Crowdin [ci skip]

* fix: New Dutch translations from Crowdin [ci skip]

* fix: New French translations from Crowdin [ci skip]

* fix: New Spanish translations from Crowdin [ci skip]

* fix: New Czech translations from Crowdin [ci skip]

* fix: New Danish translations from Crowdin [ci skip]

* fix: New Hebrew translations from Crowdin [ci skip]

* fix: New Hungarian translations from Crowdin [ci skip]

* fix: New Italian translations from Crowdin [ci skip]

* fix: New Japanese translations from Crowdin [ci skip]

* fix: New Polish translations from Crowdin [ci skip]

* fix: New Portuguese translations from Crowdin [ci skip]

* fix: New Swedish translations from Crowdin [ci skip]

* fix: New Turkish translations from Crowdin [ci skip]

* fix: New Ukrainian translations from Crowdin [ci skip]

* fix: New Chinese Simplified translations from Crowdin [ci skip]

* fix: New Chinese Traditional translations from Crowdin [ci skip]

* fix: New Vietnamese translations from Crowdin [ci skip]

* fix: New Portuguese, Brazilian translations from Crowdin [ci skip]

* fix: New Indonesian translations from Crowdin [ci skip]

* fix: New Persian translations from Crowdin [ci skip]

* fix: New Thai translations from Crowdin [ci skip]

* fix: New Norwegian Bokmal translations from Crowdin [ci skip]

* fix: New Romanian translations from Crowdin [ci skip]

* fix: New English, United Kingdom translations from Crowdin [ci skip]
2025-06-10 23:32:20 -04:00
242 changed files with 2430 additions and 1481 deletions
+6 -6
View File
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
node-version: [20.x, 22.x]
steps:
- uses: actions/checkout@v4
@@ -42,7 +42,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn lint
@@ -54,7 +54,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn tsc
@@ -92,7 +92,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn test:${{ matrix.test-group }}
@@ -134,7 +134,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn sequelize db:migrate
@@ -151,7 +151,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'
- run: yarn install --frozen-lockfile
- name: Set environment to production
+1
View File
@@ -0,0 +1 @@
22
View File
+1 -1
View File
@@ -6,7 +6,7 @@ ARG APP_PATH
WORKDIR $APP_PATH
# ---
FROM node:20-slim AS runner
FROM node:22-slim AS runner
LABEL org.opencontainers.image.source="https://github.com/outline/outline"
+5 -7
View File
@@ -1,5 +1,6 @@
import copy from "copy-to-clipboard";
import invariant from "invariant";
import uniqBy from "lodash/uniqBy";
import {
DownloadIcon,
DuplicateIcon,
@@ -84,8 +85,9 @@ export const openDocument = createAction({
(acc, node) => [...acc, ...node.children],
[] as NavigationNode[]
);
const documents = stores.documents.orderedData;
return nodes.map((item) => ({
return uniqBy([...documents, ...nodes], "id").map((item) => ({
// Note: using url which includes the slug rather than id here to bust
// cache if the document is renamed
id: item.url,
@@ -1081,17 +1083,13 @@ export const openDocumentComments = createAction({
analyticsName: "Open comments",
section: ActiveDocumentSection,
icon: <CommentIcon />,
visible: ({ activeCollectionId, activeDocumentId, stores }) => {
visible: ({ activeDocumentId, stores }) => {
const can = stores.policies.abilities(activeDocumentId ?? "");
const collection = activeCollectionId
? stores.collections.get(activeCollectionId)
: undefined;
return (
!!activeDocumentId &&
can.comment &&
(collection?.canCreateComment ??
!!stores.auth.team?.getPreference(TeamPreference.Commenting))
!!stores.auth.team?.getPreference(TeamPreference.Commenting)
);
},
perform: ({ activeDocumentId, stores }) => {
+1 -3
View File
@@ -42,9 +42,7 @@ export const changeTheme = createAction({
isContextMenu ? t("Appearance") : t("Change theme"),
analyticsName: "Change theme",
placeholder: ({ t }) => t("Change theme to"),
icon: function _Icon() {
return stores.ui.resolvedTheme === "light" ? <SunIcon /> : <MoonIcon />;
},
icon: stores.ui.resolvedTheme === "light" ? <SunIcon /> : <MoonIcon />,
keywords: "appearance display",
section: SettingsSection,
children: [changeToLightTheme, changeToDarkTheme, changeToSystemTheme],
+13 -16
View File
@@ -1,7 +1,6 @@
import { ArrowIcon, PlusIcon } from "outline-icons";
import styled from "styled-components";
import { stringToColor } from "@shared/utils/color";
import RootStore from "~/stores/RootStore";
import { LoginDialog } from "~/scenes/Login/components/LoginDialog";
import TeamNew from "~/scenes/TeamNew";
import TeamLogo from "~/components/TeamLogo";
@@ -10,27 +9,25 @@ import { ActionContext } from "~/types";
import Desktop from "~/utils/Desktop";
import { TeamSection } from "../sections";
export const switchTeamsList = ({ stores }: { stores: RootStore }) =>
export const switchTeamsList = ({ stores }: ActionContext) =>
stores.auth.availableTeams?.map((session) => ({
id: `switch-${session.id}`,
name: session.name,
analyticsName: "Switch workspace",
section: TeamSection,
keywords: "change switch workspace organization team",
icon: function _Icon() {
return (
<StyledTeamLogo
alt={session.name}
model={{
initial: session.name[0],
avatarUrl: session.avatarUrl,
id: session.id,
color: stringToColor(session.id),
}}
size={24}
/>
);
},
icon: (
<StyledTeamLogo
alt={session.name}
model={{
initial: session.name[0],
avatarUrl: session.avatarUrl,
id: session.id,
color: stringToColor(session.id),
}}
size={24}
/>
),
visible: ({ currentTeamId }: ActionContext) => currentTeamId !== session.id,
perform: () => (window.location.href = session.url),
})) ?? [];
+2 -2
View File
@@ -45,8 +45,8 @@ export const updateUserRoleActionFactory = (user: User, role: UserRole) =>
return UserRoleHelper.isRoleHigher(role, user.role)
? can.promote
: UserRoleHelper.isRoleLower(role, user.role)
? can.demote
: false;
? can.demote
: false;
},
perform: ({ t }) => {
stores.dialogs.openModal({
+3 -3
View File
@@ -27,8 +27,8 @@ export function createAction(definition: Optional<Action, "id">): Action {
context: context.isButton
? "button"
: context.isCommandBar
? "commandbar"
: "contextmenu",
? "commandbar"
: "contextmenu",
});
}
@@ -99,7 +99,7 @@ export function actionToKBar(
const sectionPriority =
typeof action.section !== "string" && "priority" in action.section
? (action.section.priority as number) ?? 0
? ((action.section.priority as number) ?? 0)
: 0;
return [
+3 -1
View File
@@ -10,7 +10,9 @@ type Props = {
};
// TODO: Refactor this component to allow injection from plugins
const Analytics: React.FC = ({ children }: Props) => {
const Analytics: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
// Google Analytics 3
React.useEffect(() => {
if (!env.GOOGLE_ANALYTICS_ID?.startsWith("UA-")) {
+8 -7
View File
@@ -48,8 +48,10 @@ type Props = {
children?: React.ReactNode;
};
const AuthenticatedLayout: React.FC = ({ children }: Props) => {
const { ui, auth, collections } = useStores();
const AuthenticatedLayout: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
const { ui, auth } = useStores();
const location = useLocation();
const layoutRef = React.useRef<HTMLDivElement>(null);
const can = usePolicy(ui.activeDocumentId);
@@ -108,16 +110,15 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
can.comment &&
ui.activeDocumentId &&
ui.commentsExpanded &&
(ui.activeCollectionId
? collections.get(ui.activeCollectionId)?.canCreateComment
: !!team.getPreference(TeamPreference.Commenting));
!!team.getPreference(TeamPreference.Commenting);
const sidebarRight = (
// @ts-expect-error - framer-motion v4 has TypeScript compatibility issues with React 18
<AnimatePresence
initial={false}
key={ui.activeDocumentId ? "active" : "inactive"}
>
{(showHistory || showInsights || showComments) && (
{showHistory || showInsights || showComments ? (
<Route path={`/doc/${slug}`}>
<SidebarRight>
<React.Suspense fallback={null}>
@@ -127,7 +128,7 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
</React.Suspense>
</SidebarRight>
</Route>
)}
) : null}
</AnimatePresence>
);
+2 -2
View File
@@ -10,8 +10,8 @@ const Badge = styled.span<{ yellow?: boolean; primary?: boolean }>`
primary
? theme.accentText
: yellow
? theme.almostBlack
: theme.textTertiary};
? theme.almostBlack
: theme.textTertiary};
border: 1px solid
${({ primary, yellow, theme }) =>
primary || yellow
+1 -1
View File
@@ -176,7 +176,7 @@ const Button = <T extends React.ElementType = "button">(
...rest
} = props;
const hasText = !!children || value !== undefined;
const ic = hideIcon ? undefined : action?.icon ?? icon;
const ic = hideIcon ? undefined : (action?.icon ?? icon);
const hasIcon = ic !== undefined;
return (
+1 -1
View File
@@ -30,7 +30,7 @@ const Content = styled.div<ContentProps>`
`};
`;
const CenteredContent: React.FC<Props> = ({
const CenteredContent: React.FC<React.PropsWithChildren<Props>> = ({
children,
maxWidth,
...rest
+10 -9
View File
@@ -54,9 +54,10 @@ function Collaborators(props: Props) {
const { document } = props;
const { observingUserId } = ui;
const documentPresence = presence.get(document.id);
const documentPresenceArray = documentPresence
? Array.from(documentPresence.values())
: [];
const documentPresenceArray = useMemo(
() => (documentPresence ? Array.from(documentPresence.values()) : []),
[documentPresence]
);
// Use Set for O(1) lookups and stable references
const presentIds = useMemo(
@@ -115,11 +116,11 @@ function Collaborators(props: Props) {
// Memoize onClick handler to avoid inline function creation
const handleAvatarClick = useCallback(
(
collaboratorId: string,
isPresent: boolean,
isObserving: boolean,
isObservable: boolean
) =>
collaboratorId: string,
isPresent: boolean,
isObserving: boolean,
isObservable: boolean
) =>
(ev: React.MouseEvent) => {
if (isObservable && isPresent) {
ev.preventDefault();
@@ -131,7 +132,7 @@ function Collaborators(props: Props) {
);
const renderAvatar = useCallback(
({ model: collaborator, ...rest }) => {
({ model: collaborator, ...rest }: any) => {
const isPresent = presentIds.has(collaborator.id);
const isEditing = editingIds.has(collaborator.id);
const isObserving = observingUserId === collaborator.id;
+27 -14
View File
@@ -22,7 +22,6 @@ import useBoolean from "~/hooks/useBoolean";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import { EmptySelectValue } from "~/types";
import { createSwitchRegister } from "~/utils/forms";
const IconPicker = createLazyComponent(() => import("~/components/IconPicker"));
@@ -182,22 +181,36 @@ export const CollectionForm = observer(function CollectionForm_({
)}
{team.sharing && (
<Switch
id="sharing"
label={t("Public document sharing")}
note={t(
"Allow documents within this collection to be shared publicly on the internet."
<Controller
control={control}
name="sharing"
render={({ field }) => (
<Switch
id="sharing"
label={t("Public document sharing")}
note={t(
"Allow documents within this collection to be shared publicly on the internet."
)}
checked={field.value}
onChange={field.onChange}
/>
)}
{...createSwitchRegister(register, "sharing")}
/>
)}
{team.getPreference(TeamPreference.Commenting) && (
<Switch
id="commenting"
label={t("Commenting")}
note={t("Allow commenting on documents within this collection.")}
{...createSwitchRegister(register, "commenting")}
<Controller
control={control}
name="commenting"
render={({ field }) => (
<Switch
id="commenting"
label={t("Commenting")}
note={t("Allow commenting on documents within this collection.")}
checked={!!field.value}
onChange={field.onChange}
/>
)}
/>
)}
@@ -211,8 +224,8 @@ export const CollectionForm = observer(function CollectionForm_({
? `${t("Saving")}`
: t("Save")
: formState.isSubmitting
? `${t("Creating")}`
: t("Create")}
? `${t("Creating")}`
: t("Create")}
</Button>
</Flex>
</form>
+3 -1
View File
@@ -11,7 +11,9 @@ type Props = {
collection: Collection;
};
export const CollectionBreadcrumb: React.FC<Props> = ({ collection }) => {
export const CollectionBreadcrumb: React.FC<React.PropsWithChildren<Props>> = ({
collection,
}) => {
const { t } = useTranslation();
const items = React.useMemo(() => {
+3 -1
View File
@@ -52,7 +52,9 @@ type Props = {
children?: React.ReactNode;
};
const KBarPortal: React.FC = ({ children }: Props) => {
const KBarPortal: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
const { showing } = useKBar((state) => ({
showing: state.visualState !== "hidden",
}));
+2 -2
View File
@@ -21,7 +21,7 @@ type Props = {
children?: React.ReactNode;
};
const ConfirmationDialog: React.FC<Props> = ({
const ConfirmationDialog: React.FC<React.PropsWithChildren<Props>> = ({
onSubmit,
children,
submitText,
@@ -64,7 +64,7 @@ const ConfirmationDialog: React.FC<Props> = ({
danger={danger}
autoFocus
>
{isSaving && savingText ? savingText : submitText ?? t("Confirm")}
{isSaving && savingText ? savingText : (submitText ?? t("Confirm"))}
</Button>
</Flex>
</Flex>
+2 -2
View File
@@ -21,7 +21,7 @@ type Props = {
to?: LocationDescriptor;
href?: string;
target?: "_blank";
as?: string | React.ComponentType<any>;
as?: string | React.ComponentType<React.PropsWithChildren<any>>;
hide?: () => void;
level?: number;
icon?: React.ReactNode;
@@ -45,7 +45,7 @@ const MenuItem = (
ref: React.Ref<HTMLAnchorElement>
) => {
const content = React.useCallback(
(props) => {
(props: any) => {
// Preventing default mousedown otherwise menu items do not work in Firefox,
// which triggers the hideOnClickOutside handler first via mousedown hiding
// and un-rendering the menu contents.
+1 -1
View File
@@ -56,7 +56,7 @@ type Props = MenuStateReturn & {
children?: React.ReactNode;
};
const ContextMenu: React.FC<Props> = ({
const ContextMenu: React.FC<React.PropsWithChildren<Props>> = ({
menuRef,
children,
onOpen,
+2 -2
View File
@@ -138,8 +138,8 @@ function DocumentBreadcrumb(
? output.slice(-depth)
: output
: depth !== undefined
? output.slice(0, depth)
: output;
? output.slice(0, depth)
: output;
}, [t, path, category, sidebarContext, collectionNode, reverse, depth]);
if (!collections.isLoaded) {
+1 -1
View File
@@ -64,7 +64,7 @@ function DocumentCard(props: Props) {
};
const handleUnpin = useCallback(
async (ev) => {
async (ev: any) => {
ev.preventDefault();
ev.stopPropagation();
await pin?.delete();
+3 -1
View File
@@ -177,7 +177,9 @@ const Actions = styled(EventBoundary)`
color: ${s("textSecondary")};
${NudeButton} {
&: ${hover}, &[aria-expanded= "true"] {
&:
${hover},
&[aria-expanded= "true"] {
background: ${s("sidebarControlHoverBackground")};
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ type Props = {
to?: LocationDescriptor;
};
const DocumentMeta: React.FC<Props> = ({
const DocumentMeta: React.FC<React.PropsWithChildren<Props>> = ({
showPublished,
showCollection,
showLastViewed,
+6 -6
View File
@@ -41,27 +41,27 @@ function EditableTitle(
setValue(title);
}, [title]);
const handleChange = React.useCallback((event) => {
const handleChange = React.useCallback((event: any) => {
setValue(event.target.value);
}, []);
const handleDoubleClick = React.useCallback((event) => {
const handleDoubleClick = React.useCallback((event: any) => {
event.preventDefault();
event.stopPropagation();
setIsEditing(true);
}, []);
const stopPropagation = React.useCallback((event) => {
const stopPropagation = React.useCallback((event: any) => {
event.preventDefault();
event.stopPropagation();
}, []);
const handleFocus = React.useCallback((event) => {
const handleFocus = React.useCallback((event: any) => {
event.target.select();
}, []);
const handleSave = React.useCallback(
async (ev) => {
async (ev: any) => {
ev.preventDefault();
ev.stopPropagation();
@@ -89,7 +89,7 @@ function EditableTitle(
);
const handleKeyDown = React.useCallback(
async (ev) => {
async (ev: any) => {
if (ev.nativeEvent.isComposing) {
return;
}
+1 -1
View File
@@ -169,7 +169,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
}, [onCreateCommentMark, onDeleteCommentMark, comments.orderedData]);
const handleChange = React.useCallback(
(event) => {
(event: any) => {
onChange?.(event);
updateComments();
},
+1 -1
View File
@@ -20,7 +20,7 @@ type Props = WithTranslation & {
/** Whether to show a title heading. */
showTitle?: boolean;
/** The wrapping component to use. */
component?: React.ComponentType | string;
component?: React.ComponentType<React.PropsWithChildren<unknown>> | string;
};
@observer
+16 -11
View File
@@ -1,10 +1,10 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import User from "~/models/User";
import { Avatar, AvatarSize } from "~/components/Avatar";
import Flex from "~/components/Flex";
import Initials from "./Avatar/Initials";
type Props = {
/** The users to display */
@@ -17,9 +17,11 @@ type Props = {
limit?: number;
/** A component to render the avatar, defaults to Avatar. */
renderAvatar?: React.ComponentType<
React.ComponentProps<typeof Avatar> & {
model: User;
}
React.PropsWithChildren<
React.ComponentProps<typeof Avatar> & {
model: User;
}
>
>;
};
@@ -31,19 +33,22 @@ function Facepile({
renderAvatar = Avatar,
...rest
}: Props) {
const { t } = useTranslation();
const filtered = users.filter(Boolean).slice(-limit);
const Component = renderAvatar;
if (overflow > 0) {
filtered.unshift({
id: "overflow",
initial: `${users.length ? "+" : ""}${overflow}`,
name: t(`{{count}} more user`, { count: overflow }),
} as User);
}
return (
<Avatars {...rest}>
{overflow > 0 && (
<Initials size={size} content={String(overflow)}>
{users.length ? "+" : ""}
{overflow}
</Initials>
)}
{filtered.map((model, index) => {
const lastChild = index === 0 && overflow <= 0;
const lastChild = index === 0;
return (
<Component
key={model.id}
+1 -1
View File
@@ -57,7 +57,7 @@ const FilterOptions = ({
: "";
const renderItem = React.useCallback(
(option) => (
(option: any) => (
<MenuItem
key={option.key}
onClick={() => {
+4 -2
View File
@@ -12,7 +12,7 @@ type Props = {
onRequestClose: () => void;
};
const Guide: React.FC<Props> = ({
const Guide: React.FC<React.PropsWithChildren<Props>> = ({
children,
isOpen,
title = "Untitled",
@@ -98,7 +98,9 @@ const Scene = styled.div`
outline: none;
opacity: 0;
transform: translateX(16px);
transition: transform 250ms ease, opacity 250ms ease;
transition:
transform 250ms ease,
opacity 250ms ease;
&[data-enter] {
opacity: 1;
+2 -1
View File
@@ -20,7 +20,8 @@ export const Preview = styled(Link)`
cursor: ${(props: { as?: string }) =>
props.as === "div" ? "default" : "var(--pointer)"};
border-radius: 4px;
box-shadow: 0 30px 90px -20px rgba(0, 0, 0, 0.3),
box-shadow:
0 30px 90px -20px rgba(0, 0, 0, 0.3),
0 0 1px 1px rgba(0, 0, 0, 0.05);
overflow: hidden;
position: absolute;
@@ -115,7 +115,9 @@ const CategoryName = styled(Text)`
`;
const Icon = styled.svg`
transition: color 150ms ease-in-out, fill 150ms ease-in-out;
transition:
color 150ms ease-in-out,
fill 150ms ease-in-out;
transition-delay: var(--delay);
`;
@@ -12,7 +12,8 @@ export const PopoverButton = styled(NudeButton)<{ $borderOnHover?: boolean }>`
$borderOnHover &&
css`
background: ${s("buttonNeutralBackground")};
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px,
box-shadow:
rgba(0, 0, 0, 0.07) 0px 1px 2px,
${s("buttonNeutralBorder")} 0 0 0 1px inset;
`};
}
@@ -27,7 +27,7 @@ const SkinTonePicker = ({
});
const handleSkinClick = useCallback(
(emojiSkin) => {
(emojiSkin: any) => {
menu.hide();
onChange(emojiSkin);
},
+2 -2
View File
@@ -107,8 +107,8 @@ export const Outline = styled(Flex)<{
props.hasError
? props.theme.danger
: props.focused
? props.theme.inputBorderFocused
: props.theme.inputBorder};
? props.theme.inputBorderFocused
: props.theme.inputBorder};
border-radius: 4px;
font-weight: normal;
align-items: center;
+5 -1
View File
@@ -17,7 +17,11 @@ type Props = Omit<InputProps, "onChange"> & {
onChange: (value: string) => void;
};
const InputColor: React.FC<Props> = ({ value, onChange, ...rest }: Props) => {
const InputColor: React.FC<React.PropsWithChildren<Props>> = ({
value,
onChange,
...rest
}: Props) => {
const { t } = useTranslation();
const menu = useMenuState({
modal: true,
@@ -35,4 +35,4 @@ const Select = styled(InputSelect)`
select {
margin: 0;
}
` as React.ComponentType<SelectProps>;
` as React.ComponentType<React.PropsWithChildren<SelectProps>>;
+2 -2
View File
@@ -29,7 +29,7 @@ function InputSearch(
} = props;
const handleFocus = React.useCallback(
(event) => {
(event: any) => {
setIsFocused(true);
onFocus?.(event);
},
@@ -37,7 +37,7 @@ function InputSearch(
);
const handleBlur = React.useCallback(
(event) => {
(event: any) => {
setIsFocused(false);
onBlur?.(event);
},
+6 -2
View File
@@ -1,7 +1,9 @@
import * as React from "react";
import lazyWithRetry from "~/utils/lazyWithRetry";
export interface LazyComponent<T extends React.ComponentType<any>> {
export interface LazyComponent<
T extends React.ComponentType<React.PropsWithChildren<any>>,
> {
Component: React.LazyExoticComponent<T>;
preload: () => Promise<{ default: T }>;
}
@@ -34,7 +36,9 @@ interface LazyLoadOptions {
* MyComponent.preload();
* ```
*/
export function createLazyComponent<T extends React.ComponentType<any>>(
export function createLazyComponent<
T extends React.ComponentType<React.PropsWithChildren<any>>,
>(
factory: () => Promise<{ default: T }>,
options: LazyLoadOptions = {}
): LazyComponent<T> {
+3 -1
View File
@@ -9,7 +9,9 @@ type Props = {
/**
* Asyncronously load required polyfills. Should wrap the React tree.
*/
export const LazyPolyfill: React.FC = ({ children }: Props) => {
export const LazyPolyfill: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
const [isLoaded, setIsLoaded] = React.useState(false);
React.useEffect(() => {
+4 -1
View File
@@ -12,7 +12,10 @@ export type Props = {
format?: Partial<Record<keyof typeof locales, string>>;
};
const LocaleTime: React.FC<Props> = ({ children, ...rest }: Props) => {
const LocaleTime: React.FC<React.PropsWithChildren<Props>> = ({
children,
...rest
}: Props) => {
const { tooltipContent, content } = useLocaleTime(rest);
return (
+1 -1
View File
@@ -29,7 +29,7 @@ type Props = {
onRequestClose: () => void;
};
const Modal: React.FC<Props> = ({
const Modal: React.FC<React.PropsWithChildren<Props>> = ({
children,
isOpen,
fullscreen = true,
+5 -1
View File
@@ -10,7 +10,11 @@ type Props = {
description?: JSX.Element;
};
const Notice: React.FC<Props> = ({ children, icon, description }: Props) => (
const Notice: React.FC<React.PropsWithChildren<Props>> = ({
children,
icon,
description,
}: Props) => (
<Container as="div">
<Flex as="span" gap={8}>
{icon}
@@ -12,7 +12,9 @@ type Props = {
children?: React.ReactNode;
};
const NotificationsPopover: React.FC = ({ children }: Props) => {
const NotificationsPopover: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
const { t } = useTranslation();
const scrollableRef = React.useRef<HTMLDivElement>(null);
const closeRef = React.useRef<HTMLButtonElement>(null);
+13 -7
View File
@@ -8,7 +8,6 @@ import ImageInput from "~/scenes/Settings/components/ImageInput";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input, { LabelText } from "~/components/Input";
import { createSwitchRegister } from "~/utils/forms";
import isCloudHosted from "~/utils/isCloudHosted";
import Switch from "../Switch";
@@ -116,10 +115,17 @@ export const OAuthClientForm = observer(function OAuthClientForm_({
)}
/>
{isCloudHosted && (
<Switch
{...createSwitchRegister(register, "published")}
label={t("Published")}
note={t("Allow this app to be installed by other workspaces")}
<Controller
control={control}
name="published"
render={({ field }) => (
<Switch
label={t("Published")}
note={t("Allow this app to be installed by other workspaces")}
checked={field.value}
onChange={field.onChange}
/>
)}
/>
)}
</>
@@ -134,8 +140,8 @@ export const OAuthClientForm = observer(function OAuthClientForm_({
? `${t("Saving")}`
: t("Save")
: formState.isSubmitting
? `${t("Creating")}`
: t("Create")}
? `${t("Creating")}`
: t("Create")}
</Button>
</Flex>
</form>
+10 -4
View File
@@ -150,12 +150,15 @@ const PaginatedList = <T extends PaginatedItem>({
offset,
...options,
});
if (!results) {
return;
}
if (offset !== 0) {
setRenderCount((prevCount) => prevCount + limit);
}
if (results && (results.length === 0 || results.length < limit)) {
if (results.length === 0 || results.length < limit) {
setAllowLoadMore(false);
} else {
setOffset((prevOffset) => prevOffset + limit);
@@ -275,8 +278,8 @@ const PaginatedList = <T extends PaginatedItem>({
"updatedAt" in item && item.updatedAt
? item.updatedAt
: "createdAt" in item && item.createdAt
? item.createdAt
: previousHeading;
? item.createdAt
: previousHeading;
const currentHeading = dateToHeading(
currentDate,
t,
@@ -305,7 +308,10 @@ const PaginatedList = <T extends PaginatedItem>({
</ArrowKeyNavigation>
{allowLoadMore && (
<div style={{ height: "1px" }}>
<Waypoint key={renderCount} onEnter={loadMoreResults} />
<Waypoint
key={items?.length + renderCount}
onEnter={loadMoreResults}
/>
</div>
)}
</React.Fragment>
+2 -2
View File
@@ -86,8 +86,8 @@ function PinnedDocuments({
overPos === 0
? fractionalIndex(null, overIndex)
: activePos > overPos
? fractionalIndex(prevIndex, overIndex)
: fractionalIndex(overIndex, nextIndex),
? fractionalIndex(prevIndex, overIndex)
: fractionalIndex(overIndex, nextIndex),
})
.catch(() => setItems(existing));
+1 -1
View File
@@ -86,7 +86,7 @@ const useTooltipContent = ({
}
};
const Reaction: React.FC<Props> = ({
const Reaction: React.FC<React.PropsWithChildren<Props>> = ({
reaction,
reactedUsers,
disabled,
+1 -1
View File
@@ -22,7 +22,7 @@ type Props = {
picker?: React.ReactElement;
};
const ReactionList: React.FC<Props> = ({
const ReactionList: React.FC<React.PropsWithChildren<Props>> = ({
model,
onAddReaction,
onRemoveReaction,
+1 -1
View File
@@ -29,7 +29,7 @@ type Props = {
size?: number;
};
const ReactionPicker: React.FC<Props> = ({
const ReactionPicker: React.FC<React.PropsWithChildren<Props>> = ({
onSelect,
onOpen,
onClose,
@@ -19,7 +19,9 @@ type Props = {
model: Comment;
};
const ViewReactionsDialog: React.FC<Props> = ({ model }) => {
const ViewReactionsDialog: React.FC<React.PropsWithChildren<Props>> = ({
model,
}) => {
const { t } = useTranslation();
const { users } = useStores();
const tab = useTabState();
+1 -1
View File
@@ -23,7 +23,7 @@ type Props = {
children?: React.ReactNode;
};
const Scene: React.FC<Props> = ({
const Scene: React.FC<React.PropsWithChildren<Props>> = ({
title,
icon,
textTitle,
+31 -3
View File
@@ -1,13 +1,24 @@
import { useKBar } from "kbar";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { Minute } from "@shared/utils/time";
import { searchDocumentsForQuery } from "~/actions/definitions/documents";
import { navigateToRecentSearchQuery } from "~/actions/definitions/navigation";
import useCommandBarActions from "~/hooks/useCommandBarActions";
import useStores from "~/hooks/useStores";
// Type for cache entries
interface CacheEntry {
timestamp: number;
}
// Cache configuration
const cacheTTL = Minute.ms * 5;
export default function SearchActions() {
const { searches } = useStores();
const { searches, documents } = useStores();
// Cache structure: Map of search queries to timestamp of last search
const searchCache = useRef<Map<string, CacheEntry>>(new Map());
useEffect(() => {
if (!searches.isLoaded && !searches.isFetching) {
@@ -21,6 +32,23 @@ export default function SearchActions() {
searchQuery: state.searchQuery,
}));
// Search for matching documents
useEffect(() => {
if (searchQuery) {
const now = Date.now();
const cachedEntry = searchCache.current.get(searchQuery);
const isExpired = cachedEntry
? now - cachedEntry.timestamp > cacheTTL
: true;
if (!cachedEntry || isExpired) {
void documents.searchTitles({ query: searchQuery }).then(() => {
searchCache.current.set(searchQuery, { timestamp: now });
});
}
}
}, [documents, searchQuery]);
useCommandBarActions(
searchQuery ? [searchDocumentsForQuery(searchQuery)] : [],
[searchQuery]
+1 -1
View File
@@ -53,7 +53,7 @@ function SearchPopover({ shareId, className }: Props) {
}, [searchResults, query, show]);
const performSearch = React.useCallback(
async ({ query: searchQuery, ...options }) => {
async ({ query: searchQuery, ...options }: any) => {
if (searchQuery?.length > 0) {
const response = await documents.search({
query: searchQuery,
@@ -111,7 +111,7 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
}, [pendingIds, prevPendingIds]);
const handleQuery = React.useCallback(
(event) => {
(event: any) => {
showPicker();
setQuery(event.target.value);
},
@@ -38,7 +38,7 @@ function DocumentMembersList({ document, invitedInSession }: Props) {
const theme = useTheme();
const handleRemoveUser = React.useCallback(
async (item) => {
async (item: any) => {
try {
await userMemberships.delete({
documentId: document.id,
@@ -62,7 +62,7 @@ function DocumentMembersList({ document, invitedInSession }: Props) {
);
const handleUpdateUser = React.useCallback(
async (userToUpdate, permission) => {
async (userToUpdate: any, permission: any) => {
try {
await userMemberships.create({
documentId: document.id,
@@ -129,7 +129,7 @@ function PublicAccess({ document, share, sharedParent }: Props) {
const shareUrl = sharedParent?.url
? `${sharedParent.url}${document.url}`
: share?.url ?? "";
: (share?.url ?? "");
const copyButton = (
<Tooltip content={t("Copy public link")} placement="top">
@@ -230,7 +230,7 @@ function SharePopover({ document, onRequestClose, visible }: Props) {
);
const handleQuery = React.useCallback(
(event) => {
(event: any) => {
showPicker();
setQuery(event.target.value);
},
@@ -26,7 +26,7 @@ export const SearchInput = React.forwardRef(function _SearchInput(
const isMobile = useMobile();
const focusInput = React.useCallback(
(event) => {
(event: any) => {
if (event.target.closest("button")) {
return;
}
@@ -97,8 +97,8 @@ export const Suggestions = observer(
.notInDocument(document.id, query)
.filter((u) => u.id !== user.id)
: collection
? users.notInCollection(collection.id, query)
: users.activeOrInvited
? users.notInCollection(collection.id, query)
: users.activeOrInvited
).filter((u) => !u.isSuspended);
if (isEmail(query)) {
@@ -109,8 +109,8 @@ export const Suggestions = observer(
...(document
? groups.notInDocument(document.id, query)
: collection
? groups.notInCollection(collection.id, query)
: []),
? groups.notInCollection(collection.id, query)
: []),
...filtered,
];
}, [
@@ -133,7 +133,7 @@ export const Suggestions = observer(
.map((id) =>
isEmail(id)
? getSuggestionForEmail(id)
: users.get(id) ?? groups.get(id)
: (users.get(id) ?? groups.get(id))
)
.filter(Boolean) as User[],
[users, groups, getSuggestionForEmail, pendingIds]
@@ -158,8 +158,8 @@ export const Suggestions = observer(
subtitle: suggestion.email
? suggestion.email
: suggestion.isViewer
? t("Viewer")
: t("Editor"),
? t("Viewer")
: t("Editor"),
image: <Avatar model={suggestion} size={AvatarSize.Medium} />,
};
}
+1 -1
View File
@@ -47,7 +47,7 @@ function AppSidebar() {
}, [documents, collections, user.isViewer]);
const [dndArea, setDndArea] = useState();
const handleSidebarRef = useCallback((node) => setDndArea(node), []);
const handleSidebarRef = useCallback((node: any) => setDndArea(node), []);
const html5Options = useMemo(
() => ({
rootElement: dndArea,
+1 -1
View File
@@ -50,7 +50,7 @@ function Right({ children, border, className }: Props) {
}
}, []);
const handleMouseDown = React.useCallback((event) => {
const handleMouseDown = React.useCallback((event: any) => {
event.preventDefault();
setResizing(true);
}, []);
+3 -1
View File
@@ -121,7 +121,9 @@ const ToggleWrapper = styled.div`
right: 0;
opacity: 0;
transform: translateX(10px);
transition: opacity 100ms ease-out, transform 100ms ease-out;
transition:
opacity 100ms ease-out,
transform 100ms ease-out;
`;
const StyledSidebar = styled(Sidebar)<{ $hoverTransition: boolean }>`
+7 -5
View File
@@ -101,7 +101,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
}, []);
const handleMouseDown = React.useCallback(
(event) => {
(event: any) => {
event.preventDefault();
if (!document.hasFocus()) {
return;
@@ -126,7 +126,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
}, [ui.sidebarIsClosed]);
const handlePointerLeave = React.useCallback(
(ev) => {
(ev: any) => {
if (hasPointerMoved) {
// clear any previous timeout
if (hoverTimeoutRef.current) {
@@ -294,8 +294,8 @@ const hoverStyles = (props: ContainerProps) => `
props.$collapsed
? "rgba(0, 0, 0, 0.2) 1px 0 4px"
: props.$isSmallerThanMinimum
? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
: "none"
? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
: "none"
};
${ToggleButton} {
@@ -309,7 +309,9 @@ const Container = styled(Flex)<ContainerProps>`
bottom: 0;
width: 100%;
background: ${s("sidebarBackground")};
transition: box-shadow 150ms ease-in-out, transform 150ms ease-out,
transition:
box-shadow 150ms ease-in-out,
transform 150ms ease-out,
${(props: ContainerProps) =>
props.$isAnimating ? `,width ${ANIMATION_MS}ms ease-out` : ""};
transform: translateX(
@@ -51,7 +51,7 @@ function ArchiveLink() {
}
}, [expanded, request]);
const handleDisclosureClick = useCallback((ev) => {
const handleDisclosureClick = useCallback((ev: any) => {
ev.preventDefault();
ev.stopPropagation();
setExpanded((e) => !e);
@@ -15,7 +15,7 @@ export function ArchivedCollectionLink({ collection, depth }: Props) {
const [expanded, setExpanded] = useState(false);
const handleDisclosureClick = useCallback((ev) => {
const handleDisclosureClick = useCallback((ev: any) => {
ev.preventDefault();
ev.stopPropagation();
setExpanded((e) => !e);
@@ -36,7 +36,7 @@ type Props = {
onClick?: () => void;
};
const CollectionLink: React.FC<Props> = ({
const CollectionLink: React.FC<React.PropsWithChildren<Props>> = ({
collection,
expanded,
onDisclosureClick,
@@ -88,7 +88,7 @@ const CollectionLink: React.FC<Props> = ({
useBoolean();
const handleNewDoc = React.useCallback(
async (input) => {
async (input: any) => {
const newDocument = await documents.create(
{
collectionId: collection.id,
@@ -54,7 +54,10 @@ const Button = styled(NudeButton)<{ $root?: boolean }>`
const StyledCollapsedIcon = styled(CollapsedIcon)<{
expanded?: boolean;
}>`
transition: opacity 100ms ease, transform 100ms ease, fill 50ms !important;
transition:
opacity 100ms ease,
transform 100ms ease,
fill 50ms !important;
${(props) => !props.expanded && "transform: rotate(-90deg);"};
`;
@@ -118,7 +118,7 @@ function InnerDocumentLink(
}, [setCollapsed, expanded, hasChildDocuments]);
const handleDisclosureClick = React.useCallback(
(ev) => {
(ev: any) => {
ev?.preventDefault();
if (expanded) {
setCollapsed();
@@ -233,7 +233,7 @@ function InnerDocumentLink(
useBoolean();
const handleNewDoc = React.useCallback(
async (input) => {
async (input: any) => {
const newDocument = await documents.create(
{
collectionId: collection?.id,
@@ -93,7 +93,7 @@ function DraggableCollectionLink({
locationSidebarContext,
]);
const handleDisclosureClick = useCallback((ev) => {
const handleDisclosureClick = useCallback((ev: any) => {
ev?.preventDefault();
setExpanded((e) => !e);
}, []);
+4 -1
View File
@@ -6,7 +6,10 @@ type Props = {
children?: React.ReactNode;
};
const Folder: React.FC<Props> = ({ expanded, children }: Props) => {
const Folder: React.FC<React.PropsWithChildren<Props>> = ({
expanded,
children,
}: Props) => {
const [openedOnce, setOpenedOnce] = React.useState(expanded);
// allows us to avoid rendering all children when the folder hasn't been opened
@@ -14,14 +14,14 @@ type Props = {
group: Group;
};
const GroupLink: React.FC<Props> = ({ group }) => {
const GroupLink: React.FC<React.PropsWithChildren<Props>> = ({ group }) => {
const locationSidebarContext = useLocationSidebarContext();
const sidebarContext = groupSidebarContext(group.id);
const [expanded, setExpanded] = React.useState(
locationSidebarContext === sidebarContext
);
const handleDisclosureClick = React.useCallback((ev) => {
const handleDisclosureClick = React.useCallback((ev: any) => {
ev?.preventDefault();
setExpanded((e) => !e);
}, []);
+9 -2
View File
@@ -19,7 +19,11 @@ export function getHeaderExpandedKey(id: string) {
/**
* Toggleable sidebar header
*/
export const Header: React.FC<Props> = ({ id, title, children }: Props) => {
export const Header: React.FC<React.PropsWithChildren<Props>> = ({
id,
title,
children,
}: Props) => {
const [firstRender, setFirstRender] = React.useState(true);
const [expanded, setExpanded] = usePersistedState<boolean>(
getHeaderExpandedKey(id ?? ""),
@@ -91,7 +95,10 @@ const Button = styled.button`
`;
const Disclosure = styled(CollapsedIcon)<{ expanded?: boolean }>`
transition: opacity 100ms ease, transform 100ms ease, fill 50ms !important;
transition:
opacity 100ms ease,
transform 100ms ease,
fill 50ms !important;
${({ expanded }) => !expanded && "transform: rotate(-90deg);"};
opacity: 0;
`;
@@ -39,7 +39,7 @@ export interface Props extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
location?: Location;
strict?: boolean;
to: LocationDescriptor;
component?: React.ComponentType;
component?: React.ComponentType<React.PropsWithChildren<unknown>>;
onBeforeClick?: () => void;
}
@@ -189,7 +189,7 @@ export function useDragDocument(
depth,
icon: icon ? <Icon value={icon} color={color} /> : undefined,
collectionId: document?.collectionId || "",
} as DragObject),
}) as DragObject,
canDrag: () => !!document?.isActive && !isEditing,
collect: (monitor) => ({
isDragging: monitor.isDragging(),
@@ -505,7 +505,7 @@ export function useDragMembership(
id,
title,
icon,
} as DragObject),
}) as DragObject,
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
+4 -4
View File
@@ -52,10 +52,10 @@ function Star({ size, document, collection, color, ...rest }: Props) {
? unstarCollection
: starCollection
: document
? document.isStarred
? unstarDocument
: starDocument
: undefined
? document.isStarred
? unstarDocument
: starDocument
: undefined
}
size={size}
{...rest}
+5 -1
View File
@@ -34,7 +34,11 @@ const Background = styled.div<{ sticky?: boolean }>`
z-index: 1;
`;
const Subheading: React.FC<Props> = ({ children, sticky, ...rest }: Props) => (
const Subheading: React.FC<React.PropsWithChildren<Props>> = ({
children,
sticky,
...rest
}: Props) => (
<Background sticky={sticky}>
<H3 {...rest}>
<Underline>{children}</Underline>
+1 -1
View File
@@ -58,7 +58,7 @@ const transition = {
damping: 30,
};
const Tab: React.FC<Props> = ({
const Tab: React.FC<React.PropsWithChildren<Props>> = ({
children,
exact,
exactQueryString,
+3 -1
View File
@@ -60,7 +60,9 @@ type Props = {
children?: React.ReactNode;
};
const Tabs: React.FC = ({ children }: Props) => {
const Tabs: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
const ref = React.useRef<any>();
const [shadowVisible, setShadow] = React.useState(false);
const { width } = useWindowSize();
+3 -1
View File
@@ -11,7 +11,9 @@ type Props = {
children?: React.ReactNode;
};
const Theme: React.FC = ({ children }: Props) => {
const Theme: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
const { auth, ui } = useStores();
const theme = useBuildTheme(
auth.team?.getPreference(TeamPreference.CustomTheme) ||
+5 -3
View File
@@ -6,12 +6,14 @@ import useStores from "~/hooks/useStores";
type StoreProps = keyof RootStore;
function withStores<
P extends React.ComponentType<ResolvedProps & RootStore>,
P extends React.ComponentType<
React.PropsWithChildren<ResolvedProps & RootStore>
>,
ResolvedProps = JSX.LibraryManagedAttributes<
P,
Omit<React.ComponentProps<P>, StoreProps>
>
>(WrappedComponent: P): React.FC<ResolvedProps> {
>,
>(WrappedComponent: P): React.FC<React.PropsWithChildren<ResolvedProps>> {
const ComponentWithStore = (props: ResolvedProps) => {
const stores = useStores();
return <WrappedComponent {...(props as any)} {...stores} />;
+2 -2
View File
@@ -23,7 +23,7 @@ type ComponentViewConstructor = {
export default class ComponentView {
/** The React component to render. */
component: FunctionComponent<ComponentProps>;
component: FunctionComponent<React.PropsWithChildren<ComponentProps>>;
/** The editor instance. */
editor: Editor;
/** The extension the view belongs to. */
@@ -45,7 +45,7 @@ export default class ComponentView {
// See https://prosemirror.net/docs/ref/#view.NodeView
constructor(
component: FunctionComponent<ComponentProps>,
component: FunctionComponent<React.PropsWithChildren<ComponentProps>>,
{
editor,
extension,
+2 -2
View File
@@ -249,7 +249,7 @@ export default function FindAndReplace({
);
const handleReplace = React.useCallback(
(ev) => {
(ev: any) => {
if (readOnly) {
return;
}
@@ -260,7 +260,7 @@ export default function FindAndReplace({
);
const handleReplaceAll = React.useCallback(
(ev) => {
(ev: any) => {
if (readOnly) {
return;
}
+4 -3
View File
@@ -87,8 +87,8 @@ function usePosition({
const position = codeBlock
? codeBlock.pos
: noticeBlock
? noticeBlock.pos
: null;
? noticeBlock.pos
: null;
if (position !== null) {
const element = view.nodeDOM(position);
@@ -345,7 +345,8 @@ const Wrapper = styled.div<WrapperProps>`
box-shadow: ${s("menuShadow")};
border-radius: 4px;
transform: scale(0.95);
transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
transition:
opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition-delay: 150ms;
line-height: 0;
+1 -1
View File
@@ -42,7 +42,7 @@ type Props = {
view: EditorView;
};
const LinkEditor: React.FC<Props> = ({
const LinkEditor: React.FC<React.PropsWithChildren<Props>> = ({
mark,
from,
to,
+3 -3
View File
@@ -98,7 +98,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
actorId,
label: user.name,
},
} as MentionItem)
}) as MentionItem
)
.concat(
documents
@@ -130,7 +130,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
actorId,
label: doc.title,
},
} as MentionItem)
}) as MentionItem
)
)
.concat(
@@ -158,7 +158,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
actorId,
label: collection.name,
},
} as MentionItem)
}) as MentionItem
)
)
.concat([
+1 -1
View File
@@ -8,7 +8,7 @@ export class NodeViewRenderer<T extends object> {
public constructor(
public element: HTMLElement,
private Component: FunctionComponent,
private Component: FunctionComponent<React.PropsWithChildren<unknown>>,
props: T
) {
this.props = props;
+10 -7
View File
@@ -251,7 +251,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
);
const handleClickItem = React.useCallback(
(item) => {
(item: any) => {
props.onSelect?.(item);
switch (item.name) {
@@ -470,7 +470,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
item,
section:
"section" in item && item.section && "priority" in item.section
? (item.section.priority as number) ?? 0
? ((item.section.priority as number) ?? 0)
: 0,
priority: "priority" in item ? item.priority : 0,
score:
@@ -596,8 +596,8 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
"placeholder" in insertItem
? insertItem.placeholder
: insertItem.title
? dictionary.pasteLinkWithTitle(insertItem.title)
: dictionary.pasteLink
? dictionary.pasteLinkWithTitle(insertItem.title)
: dictionary.pasteLink
}
onKeyDown={handleLinkInputKeydown}
onPaste={handleLinkInputPaste}
@@ -740,11 +740,14 @@ export const Wrapper = styled(Scrollable)<{
left: ${(props) => props.left}px;
background: ${s("menuBackground")};
border-radius: 6px;
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px,
rgba(0, 0, 0, 0.08) 0px 4px 8px, rgba(0, 0, 0, 0.08) 0px 2px 4px;
box-shadow:
rgba(0, 0, 0, 0.05) 0px 0px 0px 1px,
rgba(0, 0, 0, 0.08) 0px 4px 8px,
rgba(0, 0, 0, 0.08) 0px 2px 4px;
opacity: 0;
transform: scale(0.95);
transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
transition:
opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition-delay: 150ms;
line-height: 0;
@@ -36,7 +36,7 @@ function SuggestionsMenuItem({
}: Props) {
const portal = usePortalContext();
const ref = React.useCallback(
(node) => {
(node: any) => {
if (selected && node) {
scrollIntoView(node, {
scrollMode: "if-needed",
+1 -1
View File
@@ -2,7 +2,7 @@ import * as React from "react";
import styled from "styled-components";
import Tooltip, { Props } from "~/components/Tooltip";
const WrappedTooltip: React.FC<Props> = ({
const WrappedTooltip: React.FC<React.PropsWithChildren<Props>> = ({
children,
content,
...rest
+3 -2
View File
@@ -2,7 +2,7 @@ import { action } from "mobx";
import { PlusIcon } from "outline-icons";
import { Plugin } from "prosemirror-state";
import { Decoration, DecorationSet } from "prosemirror-view";
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";
import { WidgetProps } from "@shared/editor/lib/Extension";
import { PlaceholderPlugin } from "@shared/editor/plugins/PlaceholderPlugin";
import { findParentNode } from "@shared/editor/queries/findParentNode";
@@ -27,7 +27,8 @@ export default class BlockMenuExtension extends Suggestion {
const button = document.createElement("button");
button.className = "block-menu-trigger";
button.type = "button";
ReactDOM.render(<PlusIcon />, button);
const root = createRoot(button);
root.render(<PlusIcon />);
return [
...super.plugins,
+92
View File
@@ -6,8 +6,10 @@ import {
TextSelection,
EditorState,
Command,
Transaction,
} from "prosemirror-state";
import Extension from "@shared/editor/lib/Extension";
import { getCurrentBlock } from "@shared/editor/queries/getCurrentBlock";
import { isInCode } from "@shared/editor/queries/isInCode";
export default class Keys extends Extension {
@@ -24,6 +26,93 @@ export default class Keys extends Extension {
return false;
};
const moveBlockUp = (
state: EditorState,
dispatch?: (tr: Transaction) => void
) => {
if (!state.selection.empty) {
return false;
}
const result = getCurrentBlock(state);
if (!result) {
return false;
}
const [currentBlock, currentPos] = result;
const $pos = state.doc.resolve(currentPos);
// Check if there's a previous sibling block
if (!$pos.nodeBefore || !$pos.nodeBefore.isBlock) {
return false;
}
const prevBlock = $pos.nodeBefore;
const prevBlockPos = currentPos - prevBlock.nodeSize;
if (!dispatch) {
return true;
}
const { tr } = state;
// Move current block before the previous block
dispatch(
tr
.delete(currentPos, currentPos + currentBlock.nodeSize)
.insert(prevBlockPos, currentBlock)
.setSelection(TextSelection.near(tr.doc.resolve(prevBlockPos + 1)))
);
return true;
};
const moveBlockDown = (
state: EditorState,
dispatch?: (tr: Transaction) => void
) => {
if (!state.selection.empty) {
return false;
}
const result = getCurrentBlock(state);
if (!result) {
return false;
}
const [currentBlock, currentPos] = result;
const $pos = state.doc.resolve(currentPos + currentBlock.nodeSize);
// Check if there's a next sibling block
if (!$pos.nodeAfter || !$pos.nodeAfter.isBlock) {
return false;
}
const nextBlock = $pos.nodeAfter;
const nextBlockEndPos =
currentPos + currentBlock.nodeSize + nextBlock.nodeSize;
if (!dispatch) {
return true;
}
const { tr } = state;
// Move current block after the next block
dispatch(
tr
.insert(nextBlockEndPos, currentBlock)
.delete(currentPos, currentPos + currentBlock.nodeSize)
.setSelection(
TextSelection.near(
tr.doc.resolve(nextBlockEndPos - currentBlock.nodeSize + 1)
)
)
);
return true;
};
return {
// Shortcuts for when editor has separate edit mode
"Mod-Escape": onCancel,
@@ -46,6 +135,9 @@ export default class Keys extends Extension {
(this.editor.view.dom as HTMLElement).blur();
return true;
},
// Block movement shortcuts
"Mod-Alt-ArrowUp": moveBlockUp,
"Mod-Alt-ArrowDown": moveBlockDown,
};
}
+6 -6
View File
@@ -32,12 +32,12 @@ export default function useBuildTheme(
isPrinting
? buildLightTheme(customTheme)
: isMobile
? resolvedTheme === "dark"
? buildPitchBlackTheme(customTheme)
: buildLightTheme(customTheme)
: resolvedTheme === "dark"
? buildDarkTheme(customTheme)
: buildLightTheme(customTheme),
? resolvedTheme === "dark"
? buildPitchBlackTheme(customTheme)
: buildLightTheme(customTheme)
: resolvedTheme === "dark"
? buildDarkTheme(customTheme)
: buildLightTheme(customTheme),
[customTheme, isMobile, isPrinting, resolvedTheme]
);
+4 -4
View File
@@ -28,10 +28,10 @@ const createKeyPredicate = (keyFilter: KeyFilter) =>
typeof keyFilter === "function"
? keyFilter
: typeof keyFilter === "string"
? (event: KeyboardEvent) => event.key === keyFilter
: keyFilter
? (_event: KeyboardEvent) => true
: (_event: KeyboardEvent) => false;
? (event: KeyboardEvent) => event.key === keyFilter
: keyFilter
? (_event: KeyboardEvent) => true
: (_event: KeyboardEvent) => false;
export default function useKeyDown(
key: KeyFilter,
+3 -1
View File
@@ -18,7 +18,9 @@ type Props = {
children?: React.ReactNode;
};
export const MenuProvider: React.FC = ({ children }: Props) => {
export const MenuProvider: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const registerMenu = React.useCallback(
+2 -2
View File
@@ -17,8 +17,8 @@ export function usePinnedDocuments(urlId: UrlId, collectionId?: string) {
return urlId === "home"
? pins.home
: collectionId
? pins.inCollection(collectionId)
: [];
? pins.inCollection(collectionId)
: [];
}
useEffect(() => {
+2 -2
View File
@@ -49,8 +49,8 @@ const Templates = lazy(() => import("~/scenes/Settings/Templates"));
export type ConfigItem = {
name: string;
path: string;
icon: React.FC<ComponentProps<typeof Icon>>;
component: React.ComponentType;
icon: React.FC<React.PropsWithChildren<ComponentProps<typeof Icon>>>;
component: React.ComponentType<React.PropsWithChildren<unknown>>;
description?: string;
preload?: () => void;
enabled: boolean;
+3 -2
View File
@@ -4,7 +4,7 @@ import { LazyMotion } from "framer-motion";
import { KBarProvider } from "kbar";
import { Provider } from "mobx-react";
import { StrictMode } from "react";
import { render } from "react-dom";
import { createRoot } from "react-dom/client";
import { HelmetProvider } from "react-helmet-async";
import { Router } from "react-router-dom";
import stores from "~/stores";
@@ -79,7 +79,8 @@ if (element) {
</StrictMode>
);
render(<App />, element);
const root = createRoot(element);
root.render(<App />);
}
window.addEventListener("load", async () => {
+3 -1
View File
@@ -25,7 +25,9 @@ type Props = {
children?: React.ReactNode;
};
const AccountMenu: React.FC = ({ children }: Props) => {
const AccountMenu: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}: Props) => {
const menu = useMenuState({
placement: "bottom-end",
modal: true,

Some files were not shown because too many files have changed in this diff Show More