Compare commits

...

762 Commits

Author SHA1 Message Date
Tom Moor 71ace74005 Clarify invite 2024-01-28 21:38:51 -05:00
Tom Moor 2f527cbecd Clarify invite 2024-01-28 20:20:22 -05:00
Tom Moor 8502345b65 revoke -> removeAll 2024-01-28 13:26:22 -05:00
Tom Moor f888d6b2d9 tweaks 2024-01-28 13:22:45 -05:00
Tom Moor c27d2fe5b9 wip 2024-01-27 15:04:00 -05:00
Tom Moor e2ff8c47e2 Popover rebuild 2024-01-27 11:02:45 -05:00
Tom Moor 94d814290c fix: previous/next methods should only apply to memberships from the same user 2024-01-25 23:33:46 -05:00
Tom Moor dccf83ab4d Merge branch 'main' of github.com:outline/outline into feat/2359/individual-document-sharing 2024-01-25 23:03:19 -05:00
Tom Moor e62c734c41 Duplicative method cleanup (#6431) 2024-01-25 20:02:17 -08:00
Tom Moor db97a0b592 More permission -> membership replacement 2024-01-25 08:06:22 -05:00
Tom Moor 0efd67a88b Simplify websockets, remove document- channels and dynamic subscriptions 2024-01-25 00:21:04 -05:00
Tom Moor 83c93846d8 Merge branch 'main' of github.com:outline/outline into feat/2359/individual-document-sharing 2024-01-24 22:50:40 -05:00
Tom Moor cab9a1ec96 Misc fixes ported from #5814 2024-01-24 22:43:10 -05:00
Tom Moor ae03cfdc85 fix: Payload on webhook task 2024-01-24 22:02:58 -05:00
Tom Moor 122ca35fb4 Merge main 2024-01-24 21:50:40 -05:00
Tom Moor be48523b86 Refactor SearchHelper to not use raw queries (#6426)
* Removing user.documentIds usage #1

* refactor

* Refactor all search queries to orm

* Remove user.documentIds()

* Filter deleted documents

* Remove unneccessary clause, add test for draft with permission
2024-01-24 18:39:39 -08:00
dependabot[bot] df816c200f chore(deps): bump @dnd-kit/sortable from 7.0.1 to 7.0.2 (#6419)
Bumps [@dnd-kit/sortable](https://github.com/clauderic/dnd-kit/tree/HEAD/packages/sortable) from 7.0.1 to 7.0.2.
- [Release notes](https://github.com/clauderic/dnd-kit/releases)
- [Changelog](https://github.com/clauderic/dnd-kit/blob/master/packages/sortable/CHANGELOG.md)
- [Commits](https://github.com/clauderic/dnd-kit/commits/@dnd-kit/sortable@7.0.2/packages/sortable)

---
updated-dependencies:
- dependency-name: "@dnd-kit/sortable"
  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>
2024-01-24 15:58:18 -08:00
dependabot[bot] 5f655535ab chore(deps): bump aws-sdk from 2.1510.0 to 2.1540.0 (#6418)
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.1510.0 to 2.1540.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Commits](https://github.com/aws/aws-sdk-js/compare/v2.1510.0...v2.1540.0)

---
updated-dependencies:
- dependency-name: aws-sdk
  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>
2024-01-23 20:12:23 -08:00
dependabot[bot] eb6d30483d chore(deps): bump react-virtualized-auto-sizer and @types/react-virtualized-auto-sizer (#6415)
Bumps [react-virtualized-auto-sizer](https://github.com/bvaughn/react-virtualized-auto-sizer) and [@types/react-virtualized-auto-sizer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-virtualized-auto-sizer). These dependencies needed to be updated together.

Updates `react-virtualized-auto-sizer` from 1.0.20 to 1.0.21
- [Release notes](https://github.com/bvaughn/react-virtualized-auto-sizer/releases)
- [Changelog](https://github.com/bvaughn/react-virtualized-auto-sizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bvaughn/react-virtualized-auto-sizer/compare/1.0.20...1.0.21)

Updates `@types/react-virtualized-auto-sizer` from 1.0.2 to 1.0.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-virtualized-auto-sizer)

---
updated-dependencies:
- dependency-name: react-virtualized-auto-sizer
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/react-virtualized-auto-sizer"
  dependency-type: direct:development
  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>
2024-01-23 20:11:56 -08:00
Tom Moor 26167bb8e3 Allow access to child documents of shared in sidebar 2024-01-23 23:03:29 -05:00
Tom Moor 9dcee083dd fix: Filter sourced permissions from sidebar 2024-01-23 20:25:46 -05:00
Tom Moor 4fd00f7b97 fix: Remove disabled public sharing control for those without access 2024-01-23 20:15:54 -05:00
Tom Moor 5812bdfe65 test 2024-01-23 20:01:20 -05:00
Tom Moor 99bfe94e17 tsc 2024-01-23 20:01:20 -05:00
Tom Moor 5c343843e1 chore: Upgrade typescript/eslint, fix warnings 2024-01-23 20:01:20 -05:00
Tom Moor b6ab7cc8d5 fix: Toggle shortcut for TOC not working 2024-01-23 20:01:20 -05:00
Tom Moor 4cab695047 fix: Mismatch between route registered vs checked for custom rate limiters 2024-01-23 20:01:20 -05:00
Tom Moor a6812c7d25 fix: signupQueryParams default to true, closes OLN-206 2024-01-23 20:01:20 -05:00
Translate-O-Tron 5f82e1df40 New Crowdin updates (#6388)
* fix: New Danish translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

* fix: New Polish 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 Czech translations from Crowdin [ci skip]

* fix: New German 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 Korean 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 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 Japanese translations from Crowdin [ci skip]

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

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

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

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

* fix: New Polish 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 Czech translations from Crowdin [ci skip]

* fix: New German 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 Korean 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 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]
2024-01-23 20:01:20 -05:00
Tom Moor e0995bfcb4 chore: Add missing constraints to comments table 2024-01-23 20:01:20 -05:00
Tom Moor bc8697acdf feat: Add option to replace existing file attachment in editor 2024-01-23 20:01:20 -05:00
Tom Moor fe0cb5ef12 fix: documents.search API does not work with custom search slug 2024-01-23 20:01:20 -05:00
Tom Moor 8dc77cc819 fix: Can't un-publish docs with archived children. closes #6408 2024-01-23 20:01:20 -05:00
Tom Moor 79c6b9a28d fix: Allow user account deletion without SMTP setup, closes #6107 2024-01-23 20:01:20 -05:00
Tom Moor 0a0c7367dd fix: Do not offer invite user functionality without permission 2024-01-23 20:01:20 -05:00
dependabot[bot] 0df23b4725 chore(deps): bump vite from 5.0.5 to 5.0.12 (#6409)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.5 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 20:01:20 -05:00
Tom Moor ae2acb0ed9 fix: Incorret collapsing of mermaid diagram margins, closes #6373 2024-01-23 20:01:20 -05:00
Tom Moor cdbeaf402e fix: TOC above emoji in title, closes #6400 2024-01-23 20:01:20 -05:00
dependabot[bot] d9a37798a1 chore(deps): bump @sentry/node from 7.85.0 to 7.93.0 (#6391)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.85.0 to 7.93.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.85.0...7.93.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  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>
2024-01-23 20:01:20 -05:00
dependabot[bot] aa1dc5c54c chore(deps): bump @dnd-kit/modifiers from 6.0.0 to 6.0.1 (#6390)
Bumps [@dnd-kit/modifiers](https://github.com/clauderic/dnd-kit/tree/HEAD/packages/modifiers) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/clauderic/dnd-kit/releases)
- [Changelog](https://github.com/clauderic/dnd-kit/blob/master/packages/modifiers/CHANGELOG.md)
- [Commits](https://github.com/clauderic/dnd-kit/commits/@dnd-kit/modifiers@6.0.1/packages/modifiers)

---
updated-dependencies:
- dependency-name: "@dnd-kit/modifiers"
  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>
2024-01-23 20:01:20 -05:00
dependabot[bot] 110312e8bf chore(deps): bump rfc6902 from 5.0.1 to 5.1.1 (#6392)
Bumps [rfc6902](https://github.com/chbrown/rfc6902) from 5.0.1 to 5.1.1.
- [Commits](https://github.com/chbrown/rfc6902/compare/v5.0.1...v5.1.1)

---
updated-dependencies:
- dependency-name: rfc6902
  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>
2024-01-23 20:01:20 -05:00
dependabot[bot] ff72c8b170 chore(deps-dev): bump eslint-import-resolver-typescript from 3.5.4 to 3.6.1 (#6393)
Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 3.5.4 to 3.6.1.
- [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-import-resolver-typescript/compare/v3.5.4...v3.6.1)

---
updated-dependencies:
- dependency-name: eslint-import-resolver-typescript
  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>
2024-01-23 20:01:20 -05:00
dependabot[bot] d3f3ac349d chore(deps-dev): bump typescript from 5.1.6 to 5.3.3 (#6394)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.1.6 to 5.3.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.1.6...v5.3.3)

---
updated-dependencies:
- dependency-name: typescript
  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>
2024-01-23 20:01:20 -05:00
Apoorv Mishra 0a7f5764d5 Zod schemas for routes under /plugins (#6378)
* fix: schema for slack routes

* fix: slack.post

* fix: email
2024-01-23 20:01:20 -05:00
Apoorv Mishra feec3c130d Type server models (#6326)
* fix: type server models

* fix: make ParanoidModel generic

* fix: ApiKey

* fix: Attachment

* fix: AuthenticationProvider

* fix: Backlink

* fix: Collection

* fix: Comment

* fix: Document

* fix: FileOperation

* fix: Group

* fix: GroupPermission

* fix: GroupUser

* fix: Integration

* fix: IntegrationAuthentication

* fix: Notification

* fix: Pin

* fix: Revision

* fix: SearchQuery

* fix: Share

* fix: Star

* fix: Subscription

* fix: TypeError

* fix: Imports

* fix: Team

* fix: TeamDomain

* fix: User

* fix: UserAuthentication

* fix: UserPermission

* fix: View

* fix: WebhookDelivery

* fix: WebhookSubscription

* Remove type duplication

---------

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2024-01-23 20:01:20 -05:00
Tom Moor ace6eec131 Update DesktopEventHandler.tsx (#6376) 2024-01-23 20:00:52 -05:00
Tom Moor 0a781b7d99 chore: Upgrade typescript/eslint, fix warnings 2024-01-23 09:07:52 -05:00
Tom Moor db2a66cb8a fix: Toggle shortcut for TOC not working 2024-01-23 09:00:09 -05:00
Tom Moor aadd916336 fix: Mismatch between route registered vs checked for custom rate limiters 2024-01-22 22:40:17 -05:00
Tom Moor 0d797d49e3 fix: signupQueryParams default to true, closes OLN-206 2024-01-22 21:30:45 -05:00
Translate-O-Tron 7329b10846 New Crowdin updates (#6388)
* fix: New Danish translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

* fix: New Polish 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 Czech translations from Crowdin [ci skip]

* fix: New German 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 Korean 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 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 Japanese translations from Crowdin [ci skip]

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

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

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

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

* fix: New Polish 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 Czech translations from Crowdin [ci skip]

* fix: New German 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 Korean 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 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]
2024-01-22 06:46:05 -08:00
Tom Moor 4f74fe03dd chore: Add missing constraints to comments table 2024-01-21 12:35:57 -05:00
Tom Moor 4ddb5c3eed feat: Add option to replace existing file attachment in editor 2024-01-21 11:52:20 -05:00
Tom Moor cbb00c4871 fix: documents.search API does not work with custom search slug 2024-01-20 22:58:37 -05:00
Tom Moor 4e8fe75368 fix: Can't un-publish docs with archived children. closes #6408 2024-01-20 21:34:48 -05:00
Tom Moor 2f2113adb8 fix: Allow user account deletion without SMTP setup, closes #6107 2024-01-20 10:12:10 -05:00
Tom Moor b482654c66 fix: Do not offer invite user functionality without permission 2024-01-20 10:01:56 -05:00
dependabot[bot] c903b174b9 chore(deps): bump vite from 5.0.5 to 5.0.12 (#6409)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.5 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 06:53:27 -08:00
Tom Moor 8df77fe478 fix: Incorret collapsing of mermaid diagram margins, closes #6373 2024-01-18 21:39:37 -05:00
Tom Moor ff8b3cc0f4 fix: TOC above emoji in title, closes #6400 2024-01-18 21:21:44 -05:00
dependabot[bot] e0d4e9bc0f chore(deps): bump @sentry/node from 7.85.0 to 7.93.0 (#6391)
Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.85.0 to 7.93.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.85.0...7.93.0)

---
updated-dependencies:
- dependency-name: "@sentry/node"
  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>
2024-01-15 10:00:33 -08:00
dependabot[bot] 330691a8d9 chore(deps): bump @dnd-kit/modifiers from 6.0.0 to 6.0.1 (#6390)
Bumps [@dnd-kit/modifiers](https://github.com/clauderic/dnd-kit/tree/HEAD/packages/modifiers) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/clauderic/dnd-kit/releases)
- [Changelog](https://github.com/clauderic/dnd-kit/blob/master/packages/modifiers/CHANGELOG.md)
- [Commits](https://github.com/clauderic/dnd-kit/commits/@dnd-kit/modifiers@6.0.1/packages/modifiers)

---
updated-dependencies:
- dependency-name: "@dnd-kit/modifiers"
  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>
2024-01-15 09:59:58 -08:00
dependabot[bot] 96bee22951 chore(deps): bump rfc6902 from 5.0.1 to 5.1.1 (#6392)
Bumps [rfc6902](https://github.com/chbrown/rfc6902) from 5.0.1 to 5.1.1.
- [Commits](https://github.com/chbrown/rfc6902/compare/v5.0.1...v5.1.1)

---
updated-dependencies:
- dependency-name: rfc6902
  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>
2024-01-15 09:59:47 -08:00
dependabot[bot] e72850fa1a chore(deps-dev): bump eslint-import-resolver-typescript from 3.5.4 to 3.6.1 (#6393)
Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 3.5.4 to 3.6.1.
- [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-import-resolver-typescript/compare/v3.5.4...v3.6.1)

---
updated-dependencies:
- dependency-name: eslint-import-resolver-typescript
  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>
2024-01-15 09:59:38 -08:00
dependabot[bot] d3f5b6cbae chore(deps-dev): bump typescript from 5.1.6 to 5.3.3 (#6394)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.1.6 to 5.3.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.1.6...v5.3.3)

---
updated-dependencies:
- dependency-name: typescript
  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>
2024-01-15 09:59:26 -08:00
Tom Moor 2a5f19c8ff Cover unknown access scenario in UI 2024-01-14 21:12:21 -05:00
Tom Moor 81e2e03142 fix: Incorrectly defined relationship 2024-01-14 19:47:03 -05:00
Tom Moor 0e43ebd413 Remove presentDocumentMembership 2024-01-14 19:47:03 -05:00
Tom Moor 5f1cc1bf9d Unsource on edit, change to cascade delete permissions 2024-01-14 19:47:03 -05:00
Tom Moor 4a39d52738 Add UserPermission sourceId 2024-01-14 19:47:03 -05:00
Apoorv Mishra e6d8ca9b3d fix: reload policies upon collection permission change 2024-01-14 22:48:00 +05:30
Apoorv Mishra a398cabe68 fix: tsc 2024-01-13 23:08:26 +05:30
Apoorv Mishra 80ee36722a fix: sync re-ordering of user memberships across tabs 2024-01-13 22:03:01 +05:30
Apoorv Mishra 3561b79d65 Zod schemas for routes under /plugins (#6378)
* fix: schema for slack routes

* fix: slack.post

* fix: email
2024-01-13 10:55:30 +05:30
Tom Moor 632299ac50 tsc 2024-01-12 23:58:44 -05:00
Tom Moor e847c2f48b Serialize creator on membership response 2024-01-12 23:39:03 -05:00
Tom Moor e6924e3b73 Do not show document permissions for collections we already have access to
Remove JIT ordering
2024-01-12 23:19:17 -05:00
Tom Moor d95eb87a18 fix: Remove unneccessary where in scopes, remove unused scope 2024-01-12 20:09:25 -05:00
Apoorv Mishra 7e61a519f1 Type server models (#6326)
* fix: type server models

* fix: make ParanoidModel generic

* fix: ApiKey

* fix: Attachment

* fix: AuthenticationProvider

* fix: Backlink

* fix: Collection

* fix: Comment

* fix: Document

* fix: FileOperation

* fix: Group

* fix: GroupPermission

* fix: GroupUser

* fix: Integration

* fix: IntegrationAuthentication

* fix: Notification

* fix: Pin

* fix: Revision

* fix: SearchQuery

* fix: Share

* fix: Star

* fix: Subscription

* fix: TypeError

* fix: Imports

* fix: Team

* fix: TeamDomain

* fix: User

* fix: UserAuthentication

* fix: UserPermission

* fix: View

* fix: WebhookDelivery

* fix: WebhookSubscription

* Remove type duplication

---------

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2024-01-12 22:33:05 +05:30
Tom Moor 8360c2dec9 Update DesktopEventHandler.tsx (#6376) 2024-01-12 06:18:12 -08:00
Tom Moor 7bc899c0fb fix: Document memberships should not see UI for adding new members 2024-01-11 23:02:03 -05:00
Tom Moor bf0866f1ce Allow user to leave document they've been given access to 2024-01-11 23:00:07 -05:00
Tom Moor 94a6ae75d4 Merge main 2024-01-11 22:37:29 -05:00
Tom Moor 2505fea103 fix: Prevent duplicate documents in collection structure 2024-01-11 22:18:50 -05:00
Tom Moor 89931ca2f0 fix: Improve reliability of inter-linking documents through importer. closes OLN-156 2024-01-10 21:19:39 -05:00
Tom Moor 22c52f84c5 fix: Remove try/catch statements without error argument (#6370) 2024-01-10 08:02:44 -08:00
Tom Moor 870c623601 chore: Update checksums (#6369) 2024-01-10 06:02:03 -08:00
Tom Moor 6e1347c2a7 Add 'Find and replace' option to menu on mobile (#6368) 2024-01-10 05:07:05 -08:00
Tom Moor 7d7d0fd9ca fix: Improve logic for word import (#6361)
* Refactor DocumentConverter

* Support parsing images from Confluence exported .doc files

* fix: Bring across 2 fixes from enterprise codebase

* Bust dependency cache
2024-01-09 20:29:47 -08:00
Tom Moor a032f2e7e5 fix: Revert removal of protocol on pasted links (turns out folks didn't like this change) 2024-01-09 22:04:11 -05:00
Tom Moor 5a0c8e41cb Update minimum build target to match package.json 2024-01-09 18:05:46 -08:00
Translate-O-Tron a9f5d65591 New Crowdin updates (#6319)
* fix: New Czech translations from Crowdin [ci skip]

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

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

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

* fix: New Spanish 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 Korean translations from Crowdin [ci skip]

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

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

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

* fix: New Portuguese 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 Spanish translations from Crowdin [ci skip]

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

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

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

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

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

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

* fix: New Spanish 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 Korean translations from Crowdin [ci skip]

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

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

* fix: New Portuguese 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 Swedish translations from Crowdin [ci skip]

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

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

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

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

* fix: New Spanish 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 Korean translations from Crowdin [ci skip]

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

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

* fix: New Portuguese 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 German translations from Crowdin [ci skip]

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

* fix: New French translations from Crowdin [ci skip]
2024-01-09 17:43:29 -08:00
dependabot[bot] 58d280b84f chore(deps): bump validator from 13.9.0 to 13.11.0 (#6356)
Bumps [validator](https://github.com/validatorjs/validator.js) from 13.9.0 to 13.11.0.
- [Release notes](https://github.com/validatorjs/validator.js/releases)
- [Changelog](https://github.com/validatorjs/validator.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/validatorjs/validator.js/compare/13.9.0...13.11.0)

---
updated-dependencies:
- dependency-name: validator
  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>
2024-01-09 08:15:38 -08:00
Tom Moor eaf60cd891 fix: @shared path in shared directory, added linting to prevent in the future 2024-01-08 21:35:24 -05:00
Tom Moor 0986276d7e test 2024-01-08 21:02:12 -05:00
Tom Moor f08c426715 fix: Heading hash on link dropped when pasting 2024-01-08 20:23:37 -05:00
dependabot[bot] 7f06ea044a chore(deps): bump @dnd-kit/core from 6.0.5 to 6.1.0 (#6357)
Bumps [@dnd-kit/core](https://github.com/clauderic/dnd-kit/tree/HEAD/packages/core) from 6.0.5 to 6.1.0.
- [Release notes](https://github.com/clauderic/dnd-kit/releases)
- [Changelog](https://github.com/clauderic/dnd-kit/blob/master/packages/core/CHANGELOG.md)
- [Commits](https://github.com/clauderic/dnd-kit/commits/@dnd-kit/core@6.1.0/packages/core)

---
updated-dependencies:
- dependency-name: "@dnd-kit/core"
  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>
2024-01-08 14:05:38 -08:00
dependabot[bot] 21a4176b36 chore(deps-dev): bump @types/react-portal from 4.0.6 to 4.0.7 (#6358)
Bumps [@types/react-portal](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-portal) from 4.0.6 to 4.0.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-portal)

---
updated-dependencies:
- dependency-name: "@types/react-portal"
  dependency-type: direct:development
  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>
2024-01-08 14:05:05 -08:00
dependabot[bot] 413b39c473 chore(deps): bump @babel/core from 7.22.5 to 7.23.7 (#6359)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.22.5 to 7.23.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.7/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  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>
2024-01-08 14:04:31 -08:00
dependabot[bot] a0ef71a0c1 chore(deps): bump socket.io from 4.7.2 to 4.7.3 (#6360)
Bumps [socket.io](https://github.com/socketio/socket.io) from 4.7.2 to 4.7.3.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/4.7.2...4.7.3)

---
updated-dependencies:
- dependency-name: socket.io
  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>
2024-01-08 14:04:19 -08:00
Tom Moor c13c4e9412 various fixes 2024-01-07 23:11:25 -05:00
Tom Moor 3fd838582a More share dialog design 2024-01-07 22:20:35 -05:00
Tom Moor f3d761be54 Move help tooltip 2024-01-07 19:44:04 -05:00
Tom Moor 6b853351e4 Hide inapplicable sharing options 2024-01-07 17:53:47 -05:00
Tom Moor 4a5ec3856a fix: Show 'Copy link' button when team sharing disabled 2024-01-07 17:27:46 -05:00
Tom Moor 3c73782eeb Merge branch 'main' into feat/2359/individual-document-sharing 2024-01-07 13:46:11 -05:00
Tom Moor f511540770 fix: Disable 'dark reader' chrome extension on Outline.
- We have native dark mode
- With extension enabled it mutates the document causing unrecoverable render loops

closes #6353
2024-01-07 09:19:52 -05:00
Tom Moor c0aa904eaf chore: cherry-pick change from enterprise fork 2024-01-07 08:57:03 -05:00
Tom Moor 92cbceb6c7 Insert document title when pasting internal doc url (#6352)
* refactor

* DRY
2024-01-06 13:44:11 -08:00
Tom Moor 08b1755f8e fix: Loading indicator position to match 2024-01-06 12:30:23 -05:00
Tom Moor 956a2be8fb tsc 2024-01-06 11:59:01 -05:00
Tom Moor fc76918932 Kind of hacky for now, need to re-do all of the heading components. closes OLN-166 2024-01-06 11:33:30 -05:00
Tom Moor 3ea1f72bc3 fix: www. should not be counted as an internal URL (#6351) 2024-01-06 08:15:31 -08:00
Tom Moor 140526af06 chore: Removing some any 2024-01-05 23:25:05 -05:00
Tom Moor 89d905ebb7 fix: Size of inserted retina images (#6350)
* Fix pasted size of retina images

* lint

* lint
2024-01-05 19:17:39 -08:00
Tom Moor 47c13c9916 Show comment context in thread 2024-01-04 22:30:22 -05:00
Tom Moor 63eae352ee Auto-redirect single auth provider OIDC installations to login
closes #6167
2024-01-04 20:12:28 -05:00
Tom Moor 2270340c76 fix: Direct links to comments do not always scroll to show visible mark, closes #6231 2024-01-04 18:52:57 -05:00
Tom Moor e82815e1d6 fix: Link to share link in document should be treated as external. closes #6347 2024-01-04 17:51:35 -05:00
Tom Moor d2ef174a77 fix: Incorrect team usage in local development 2024-01-03 23:47:52 -05:00
Tom Moor 3c3ec45dc0 fix: Show count of days rather than relative time on notice, closes #6330 2024-01-03 22:44:14 -05:00
Tom Moor 67a6b3fe43 fix: Cleanup relationships when user is deleted (#6343)
* fix: Cleanup relationships when user is deleted

* Update tests

* Update User.test.ts
2024-01-03 06:14:10 -08:00
Apoorv Mishra 7606a3af41 Reconfigure document type filter for search results (#6335)
* fix: include drafts in search results

* fix: default to Active

* fix: names
2024-01-02 15:34:31 -08:00
Tom Moor 1112254a8d fix: API should allow removing avatarUrl by passing null, closes #6329 2024-01-02 18:14:52 -05:00
Tom Moor 42563eee16 fix: Cannot save Google Analytics integration. closes #6342 2024-01-02 18:07:50 -05:00
dependabot[bot] f2885a899b chore(deps): bump vite-plugin-pwa from 0.17.0 to 0.17.4 (#6338)
Bumps [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) from 0.17.0 to 0.17.4.
- [Release notes](https://github.com/vite-pwa/vite-plugin-pwa/releases)
- [Commits](https://github.com/vite-pwa/vite-plugin-pwa/compare/v0.17.0...v0.17.4)

---
updated-dependencies:
- dependency-name: vite-plugin-pwa
  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>
2024-01-02 06:22:26 -08:00
Tom Moor 3ab779bfa4 Restore close dialog on share copied (feels nicer) 2024-01-02 08:52:31 -05:00
Tom Moor d38cc5db68 fix: Popover states collection members have access to drafts 2024-01-02 08:47:56 -05:00
Tom Moor 812270deed Custom slug input alignment 2024-01-02 08:38:19 -05:00
dependabot[bot] 95d9dda64d chore(deps): bump @babel/plugin-transform-destructuring from 7.22.5 to 7.23.3 (#6337)
Bumps [@babel/plugin-transform-destructuring](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-destructuring) from 7.22.5 to 7.23.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.3/packages/babel-plugin-transform-destructuring)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-destructuring"
  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>
2024-01-02 05:17:54 -08:00
dependabot[bot] 9e6339696d chore(deps): bump react-window from 1.8.9 to 1.8.10 (#6340)
Bumps [react-window](https://github.com/bvaughn/react-window) from 1.8.9 to 1.8.10.
- [Release notes](https://github.com/bvaughn/react-window/releases)
- [Changelog](https://github.com/bvaughn/react-window/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bvaughn/react-window/compare/1.8.9...1.8.10)

---
updated-dependencies:
- dependency-name: react-window
  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>
2024-01-02 05:17:38 -08:00
dependabot[bot] 923ed24843 chore(deps): bump katex from 0.16.8 to 0.16.9 (#6339)
Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.16.8 to 0.16.9.
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.16.8...v0.16.9)

---
updated-dependencies:
- dependency-name: katex
  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>
2024-01-02 05:17:27 -08:00
dependabot[bot] 6f249630c2 chore(deps-dev): bump @types/fuzzy-search from 2.1.2 to 2.1.5 (#6341)
Bumps [@types/fuzzy-search](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/fuzzy-search) from 2.1.2 to 2.1.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/fuzzy-search)

---
updated-dependencies:
- dependency-name: "@types/fuzzy-search"
  dependency-type: direct:development
  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>
2024-01-02 05:17:14 -08:00
Tom Moor a4c581e9ad Split SharePopover component 2024-01-01 23:52:36 -05:00
Tom Moor 6fd9cea27d Refactor, redesign of share dialog 2024-01-01 22:53:31 -05:00
Tom Moor 4998e1f6b9 Merge branch 'main' of github.com:outline/outline into feat/2359/individual-document-sharing 2024-01-01 20:01:49 -05:00
Tom Moor a88fc392de stash 2024-01-01 20:01:45 -05:00
Tom Moor 00205e9eb5 fix ordering and reactivity of user popover 2024-01-01 11:18:53 -05:00
Tom Moor 446fe1f3eb Add 'No access' option to document permissions 2023-12-31 22:53:14 -05:00
Tom Moor 2e17d0c185 test 2023-12-31 12:45:47 -05:00
Tom Moor ea3f0b12c8 Disable drafts access without specific document membership 2023-12-31 11:37:11 -05:00
Tom Moor 572c0ef848 fix: Missing runInAction 2023-12-30 14:53:58 -05:00
Tom Moor 6f5f1025e5 perf: Avoid db queries for isCollectionDeleted where possible 2023-12-30 14:45:55 -05:00
Tom Moor f528523c1a Refactor drag and drop to reduce duplication 2023-12-30 14:36:09 -05:00
Tom Moor 480eaedb17 fix: New documents shared with user should be placed at the top 2023-12-30 13:39:48 -05:00
Tom Moor b106de5e76 Add missing transactions, improve performance in membership api endpoints 2023-12-30 09:21:33 -05:00
Tom Moor a2ece7d935 Move error message to hook 2023-12-30 09:05:22 -05:00
Apoorv Mishra 284ce18001 fix: include in documents.search_titles 2023-12-30 19:07:41 +05:30
Apoorv Mishra 6cb0118747 fix: dropdown on mobile 2023-12-30 11:42:17 +05:30
Tom Moor 95fe6b10d7 fix: Comments are not received in realtime on drafts 2023-12-29 23:10:10 -05:00
Tom Moor bc6cbade1a fix: Document events are not received 2023-12-29 21:08:05 -05:00
Tom Moor 6abf2cd4c7 Allow sharing collection-less drafts 2023-12-29 20:17:02 -05:00
Tom Moor 044eb758e9 Add user avatars to select dropdown 2023-12-29 18:59:39 -05:00
Tom Moor f864bb2dbd Improved insights icon 2023-12-29 18:21:40 -05:00
Tom Moor ce88e0ea8d Move display option switches to the other side (It looks better, setting up for share dialog improvements) 2023-12-29 17:34:26 -05:00
Tom Moor cb40e285f4 chore: Remove RPCAction.Count as default valid action on frontend (Only available for users) 2023-12-29 10:18:34 -05:00
Tom Moor 5d2a75c8e9 feat: Add missing comments.info endpoint, fix misnamed types 2023-12-29 10:13:22 -05:00
Tom Moor 08a787082f chore: Automatically remove policy from memory when associated model is deleted 2023-12-29 09:22:23 -05:00
Tom Moor 8d74028f93 chore: Remove unused fetchDocumentComments method 2023-12-29 09:15:16 -05:00
Apoorv Mishra 2e9ada5a87 Merge branch 'main' into feat/2359/individual-document-sharing 2023-12-29 10:17:39 +05:30
Tom Moor 01c806d6ea fix: Comment form should not collapse with draft 2023-12-28 21:31:40 -05:00
Tom Moor 0419e7dc52 fix: usePolicy attempting to fetch policies for unsaved entity 2023-12-28 19:59:15 -05:00
Tom Moor 6f989ec327 chore: Missing runInAction in DocumentsStore 2023-12-28 19:34:11 -05:00
Tom Moor 55a55376c6 chore: Improve typings around model methods (#6324) 2023-12-28 16:11:27 -08:00
dependabot[bot] ed1f345326 chore(deps): bump msgpackr from 1.6.2 to 1.10.1 (#6323)
Bumps [msgpackr](https://github.com/kriszyp/msgpackr) from 1.6.2 to 1.10.1.
- [Release notes](https://github.com/kriszyp/msgpackr/releases)
- [Commits](https://github.com/kriszyp/msgpackr/commits/v1.10.1)

---
updated-dependencies:
- dependency-name: msgpackr
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-28 13:53:41 -08:00
Tom Moor 428b3c9553 chore: Ensure comment data is validated before persisting (#6322)
Fix flash on render of comment create
2023-12-28 10:46:50 -08:00
Apoorv Mishra 5ee728b955 fix: include drafts in search results 2023-12-28 23:59:21 +05:30
Apoorv Mishra c8e153a5fa fix: search 2023-12-28 23:39:24 +05:30
Tom Moor 79764b1e64 chore: Improve relationship loading, include policies (#6321)
Use model where available in usePolicy
2023-12-28 08:51:33 -08:00
Tom Moor bd7d5c338d feat: Add option to 'Create new child doc' from link editor 2023-12-27 22:40:21 -05:00
Tom Moor 7be71fda61 fix: Flash in sidebar when publishing document (regression in 5fc68db5da) 2023-12-27 22:05:37 -05:00
Translate-O-Tron 60de93bc48 New Crowdin updates (#6170)
* fix: New Czech translations from Crowdin [ci skip]

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

* fix: New German 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 Korean translations from Crowdin [ci skip]

* fix: New Dutch 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 French translations from Crowdin [ci skip]

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

* fix: New Chinese Simplified 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 German 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 Korean translations from Crowdin [ci skip]

* fix: New Dutch 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 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 German 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 Korean translations from Crowdin [ci skip]

* fix: New Dutch 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 French translations from Crowdin [ci skip]

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

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

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

* fix: New Spanish 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 German 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 Korean translations from Crowdin [ci skip]

* fix: New Dutch 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 French translations from Crowdin [ci skip]

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

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

* fix: New Ukrainian 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 German 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 Korean translations from Crowdin [ci skip]

* fix: New Dutch 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 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 German 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 Korean translations from Crowdin [ci skip]

* fix: New Dutch 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 French translations from Crowdin [ci skip]

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

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

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

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

* fix: New German 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 German 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 Korean translations from Crowdin [ci skip]

* fix: New Dutch 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 French translations from Crowdin [ci skip]

* fix: New Czech 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 German 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 Korean translations from Crowdin [ci skip]

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

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

* fix: New French 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 Swedish translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

* fix: New Spanish 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 Korean translations from Crowdin [ci skip]

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

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

* fix: New Portuguese 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 Swedish translations from Crowdin [ci skip]

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

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

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

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

* fix: New Spanish 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 Korean translations from Crowdin [ci skip]

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

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

* fix: New Portuguese 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]
2023-12-27 14:26:18 -08:00
Tom Moor 551f569896 feat: Allow filtering searches by 'source'
fix: Do not show API searches in recent list in app
2023-12-27 16:56:27 -05:00
Tom Moor 820e4839d5 feat: Allow plugins to provide Email templates 2023-12-27 16:21:44 -05:00
Apoorv Mishra b86f8559d6 Merge branch 'main' into feat/2359/individual-document-sharing 2023-12-27 23:34:54 +05:30
Apoorv Mishra 831321b556 fix: default permissions 2023-12-27 23:27:25 +05:30
Tom Moor e7fbec91fc fix: Missing permission on selector in permissions dialog 2023-12-27 12:41:53 -05:00
Apoorv Mishra 97a6084bf0 fix: docs 2023-12-27 22:57:35 +05:30
Apoorv Mishra f27041bb67 fix: sidebarLimit 2023-12-27 22:47:16 +05:30
Apoorv Mishra 565c606e02 fix: document deletion should update memberships via websockets 2023-12-27 22:47:16 +05:30
Apoorv Mishra 1c773b9212 fix: webhook subscriptions 2023-12-27 22:47:16 +05:30
Apoorv Mishra ba15bf41bf Handle websocket events 2023-12-27 22:47:16 +05:30
Apoorv Mishra b1c2d79a12 fix: check for deleted collection 2023-12-27 22:47:16 +05:30
Apoorv Mishra d640359ab5 fix: disable react/prop-types 2023-12-27 22:47:16 +05:30
Apoorv Mishra e89da99d3e trigger ci 2023-12-27 22:47:16 +05:30
Apoorv Mishra dcc90e14fb fix: if user is viewer at workspace level, they should have only read perm by default on a doc 2023-12-27 22:47:16 +05:30
Apoorv Mishra 33f0e1c2af fix: allow download permission 2023-12-27 22:47:16 +05:30
Apoorv Mishra 848374c177 Revert "fix: share permission for read_write access"
This reverts commit 788f62e1eb29d07285af9d4852580d62c38f7d4f.
2023-12-27 22:47:16 +05:30
Apoorv Mishra db6c21a92b fix: share permission for read_write access 2023-12-27 22:47:16 +05:30
Apoorv Mishra ada5195c9f fix: duplicate 2023-12-27 22:47:16 +05:30
Apoorv Mishra 70a62d395f fix: publish button in document menu 2023-12-27 22:47:16 +05:30
Apoorv Mishra f09ad46891 fix: disallow publish 2023-12-27 22:47:16 +05:30
Apoorv Mishra 6726ee0616 fix: star, comment, subscribe permissions 2023-12-27 22:47:16 +05:30
Apoorv Mishra 206db09c94 fix: gap between drafts and starred section 2023-12-27 22:47:16 +05:30
Apoorv Mishra d7652849ba fix: show shared nested docs setting 2023-12-27 22:47:16 +05:30
Apoorv Mishra 946860a808 fix: Selecting a user after searching should clear input and close the search dropdown 2023-12-27 22:47:15 +05:30
Apoorv Mishra 99d91b55de fix: Select input should overflow outside popover, not cause it to scroll 2023-12-27 22:47:15 +05:30
Apoorv Mishra 5334076e63 fix: remove heading 2023-12-27 22:47:15 +05:30
Apoorv Mishra 55ab65e188 fix: trans text 2023-12-27 22:47:15 +05:30
Apoorv Mishra b654ea60b2 fix: move loading team members and document members into the invite component 2023-12-27 22:47:15 +05:30
Apoorv Mishra 03b3bff9ec fix: pullAllWidth mutates team members arr, use differenceWith instead 2023-12-27 22:47:15 +05:30
Apoorv Mishra e119f353d4 fix: users.notInDocument 2023-12-27 22:47:15 +05:30
Apoorv Mishra b816f3644a fix: removed user should appear in suggestions 2023-12-27 22:47:15 +05:30
Apoorv Mishra a6ff88d9c2 fix: move out of the collapsed section 2023-12-27 22:47:15 +05:30
Apoorv Mishra 270278b29c fix: get rid of document.nonMembers 2023-12-27 22:47:15 +05:30
Apoorv Mishra e93ba4f447 fix: combobox 2023-12-27 22:47:15 +05:30
Apoorv Mishra 2993415425 fix: move fetching into SharePopover 2023-12-27 22:47:15 +05:30
Apoorv Mishra 9f68e90e1a fix: tests 2023-12-27 22:47:15 +05:30
Apoorv Mishra fb76910495 fix: documents.add_user with tests 2023-12-27 22:47:15 +05:30
Apoorv Mishra 2ed9c6ddcc feat: functionally working first pass 2023-12-27 22:47:15 +05:30
Apoorv Mishra 3cc4bc8217 fix: test 2023-12-27 22:47:15 +05:30
Apoorv Mishra 0b4a3ae90b feat: Shared with me section 2023-12-27 22:47:15 +05:30
Apoorv Mishra 03efcae48e fix: add index column to user_permissions 2023-12-27 22:47:15 +05:30
Apoorv Mishra 9ba8b197ce fix: apply withMembership scope in documents.drafts 2023-12-27 22:47:15 +05:30
Apoorv Mishra 20b4998da8 fix: reuse UserPermission in favor of DocumentUser 2023-12-27 22:47:15 +05:30
Apoorv Mishra ced8befffa fix: reload document 2023-12-27 22:47:15 +05:30
Apoorv Mishra 0dcf919920 fix: documents.info should not return 403 for individually shared docs 2023-12-27 22:47:15 +05:30
Apoorv Mishra 41c81be07a fix: test for individually shared draft 2023-12-27 22:47:15 +05:30
Apoorv Mishra 1d6e57be68 fix: modify documents.search to include individually shared docs 2023-12-27 22:47:15 +05:30
Apoorv Mishra d84236f1f0 feat: documents.shared_with_me 2023-12-27 22:47:15 +05:30
Apoorv Mishra f490f65330 fix: documents.add_user event 2023-12-27 22:47:15 +05:30
Apoorv Mishra ffd256cd90 feat: documents.remove_user 2023-12-27 22:47:15 +05:30
Apoorv Mishra 257b453cc4 fix(tests): reload membership 2023-12-27 22:47:15 +05:30
Apoorv Mishra 19278bc612 fix: modify document update permissions to account for document memberships 2023-12-27 22:47:15 +05:30
Apoorv Mishra faca39becb fix: userId required for preloading collection memberships 2023-12-27 22:47:15 +05:30
Apoorv Mishra da431802bd fix: test 2023-12-27 22:47:15 +05:30
Apoorv Mishra dcc85a3ba8 fix: primary keys 2023-12-27 22:47:15 +05:30
Apoorv Mishra 1abd141e3c feat: documents.add_user 2023-12-27 22:47:15 +05:30
Apoorv Mishra 548a56e058 Accomodate membership id (#6221)
* fix: accomodate membership id

* fix: remove only

* fix: event handling

* fix: tests

* fix: use transaction

* Remove useless test

---------

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-12-27 07:12:39 -08:00
Tom Moor 027357acad test 2023-12-27 10:00:15 -05:00
Tom Moor b357fe78ee Tweak search weights 2023-12-26 23:12:09 -05:00
Tom Moor 75b0cd380a Remove error on storeFromUrl failure 2023-12-26 22:50:33 -05:00
Apoorv Mishra 08aacdb302 Handle users.demote event (#6315)
* fix: Handle users.demote event

* fix: fetchAll

* fix: fetch based on total
2023-12-27 08:33:44 +05:30
Tom Moor 4fd0e99909 fix: Allow embedding editable Grist
closes #6013
2023-12-26 21:41:07 -05:00
Tom Moor 083c32cb10 Merge branch 'main' of github.com:outline/outline 2023-12-26 20:56:15 -05:00
dependabot[bot] 61522c0a5f chore(deps): bump prosemirror-inputrules from 1.2.1 to 1.3.0 (#6309)
Bumps [prosemirror-inputrules](https://github.com/prosemirror/prosemirror-inputrules) from 1.2.1 to 1.3.0.
- [Changelog](https://github.com/ProseMirror/prosemirror-inputrules/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prosemirror/prosemirror-inputrules/compare/1.2.1...1.3.0)

---
updated-dependencies:
- dependency-name: prosemirror-inputrules
  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>
2023-12-25 08:59:52 -08:00
dependabot[bot] e1fdfa5f9b chore(deps-dev): bump @types/enzyme from 3.10.13 to 3.10.18 (#6313)
Bumps [@types/enzyme](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/enzyme) from 3.10.13 to 3.10.18.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/enzyme)

---
updated-dependencies:
- dependency-name: "@types/enzyme"
  dependency-type: direct:development
  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>
2023-12-25 08:58:17 -08:00
dependabot[bot] 7be3b833ed chore(deps): bump tiny-cookie from 2.4.1 to 2.5.1 (#6312)
Bumps [tiny-cookie](https://github.com/Alex1990/tiny-cookie) from 2.4.1 to 2.5.1.
- [Changelog](https://github.com/Alex1990/tiny-cookie/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Alex1990/tiny-cookie/compare/v2.4.1...v2.5.1)

---
updated-dependencies:
- dependency-name: tiny-cookie
  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>
2023-12-25 08:49:20 -08:00
dependabot[bot] 35371e0bbf chore(deps): bump fs-extra and @types/fs-extra (#6310)
Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) and [@types/fs-extra](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/fs-extra). These dependencies needed to be updated together.

Updates `fs-extra` from 11.1.1 to 11.2.0
- [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jprichardson/node-fs-extra/compare/11.1.1...11.2.0)

Updates `@types/fs-extra` from 11.0.3 to 11.0.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/fs-extra)

---
updated-dependencies:
- dependency-name: fs-extra
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: "@types/fs-extra"
  dependency-type: direct:development
  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>
2023-12-25 08:48:43 -08:00
dependabot[bot] fe1b15e976 chore(deps): bump css-inline from 0.11.0 to 0.11.2 (#6311)
Bumps [css-inline](https://github.com/Stranger6667/css-inline) from 0.11.0 to 0.11.2.
- [Release notes](https://github.com/Stranger6667/css-inline/releases)
- [Changelog](https://github.com/Stranger6667/css-inline/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/css-inline/compare/ruby-v0.11.0...ruby-v0.11.2)

---
updated-dependencies:
- dependency-name: css-inline
  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>
2023-12-25 08:48:28 -08:00
Tom Moor 1d0b2db972 fix: Cannot read properties of undefined (reading 'getAttribute') with some pasted content 2023-12-25 09:56:18 -05:00
Davy 20d25a086a Render theme of Mermaid diagram properly (#6307) 2023-12-21 16:30:52 -08:00
Tom Moor 3ca8dc775d test 2023-12-20 19:14:52 -04:00
Tom Moor df65575776 fix: Further restrict viewer permissions 2023-12-20 19:07:34 -04:00
Tom Moor 9b5a7394b8 Add debugging for missing invite 2023-12-20 18:59:39 -04:00
Tom Moor 9fde70b924 fix: Incorrect error message when attempting to join team without error message 2023-12-20 18:47:43 -04:00
Tom Moor 56e6b5211a fix: Confirmation dialog call to action should be on the right 2023-12-19 11:21:30 -05:00
Tom Moor 1c0e396cd1 fix: Positioning on wide settings header 2023-12-19 10:55:19 -05:00
Tom Moor c3429bdbbd chore: Drag and drop refactor 2023-12-19 10:35:19 -05:00
Tom Moor 6616276e4b feat: Drag collection into starred section to star 2023-12-19 10:27:31 -05:00
Tom Moor c1b2d3c4a7 feat: Drag document into starred section to star 2023-12-19 09:33:36 -05:00
Tom Moor d8c6257429 fix: Extra entry on end of breadcrumb 2023-12-18 22:47:03 -05:00
Davy d09a4f7d20 Bring Mermaid diagram priority higher than heading's anchor (#6299) 2023-12-18 18:57:09 -08:00
dependabot[bot] 662b42d92b chore(deps): bump i18next-fs-backend from 2.1.5 to 2.3.1 (#6303)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 15:35:08 -08:00
dependabot[bot] 9566f4cf04 chore(deps-dev): bump @types/readable-stream from 4.0.9 to 4.0.10 (#6300)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 08:43:58 -08:00
dependabot[bot] db0b16a216 chore(deps): bump @babel/plugin-transform-regenerator from 7.22.10 to 7.23.3 (#6301)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 08:43:46 -08:00
dependabot[bot] 032c8006a8 chore(deps): bump sequelize from 6.33.0 to 6.35.2 (#6302)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 08:43:34 -08:00
Tom Moor fc761244a9 fix: Do not apply WEB_CONCURRENCY to worker processes 2023-12-17 13:07:03 -05:00
Tom Moor 1840370e6f Adds content column to documents and revisions as JSON snapshot (#6179) 2023-12-17 07:51:11 -08:00
Ray (Jui-Tse) Hung 78b9322a28 Fix Suspended User Login Error Redirect URL (#6297) 2023-12-16 16:34:55 -08:00
Tom Moor 0213221a7c JSdoc 2023-12-16 13:43:21 -05:00
Tom Moor 557fb94642 Add collection access hint to invite screen 2023-12-16 13:24:53 -05:00
Tom Moor 00ecf84dbd fix: Finicky selection around headings in FF 2023-12-16 12:56:21 -05:00
Tom Moor 87d288cdfc Add 'Copy as link' option to menu
closes OLN-141
2023-12-16 11:30:29 -05:00
Tom Moor fcec796130 Quality of life improvements on 'Invite' screen 2023-12-16 11:11:57 -05:00
Tom Moor 7df0f63ce6 fix: Load relationships on search page load (#6295) 2023-12-16 07:36:19 -08:00
Tom Moor ab7515b0e1 fix: Race condition updating document breadcrumb 2023-12-15 23:30:14 -05:00
Tom Moor 1d0d4e4048 fix: Disabled embeds regression 2023-12-15 09:34:59 -05:00
Tom Moor bd65a4f151 fix: Enable embeds within HTML and PDF exports (#6290) 2023-12-14 18:52:51 -08:00
Tom Moor c40ab288fa fix: Pasted links should not by default have underline mark, closes #6292 2023-12-14 21:49:47 -05:00
Tom Moor feaadc8276 fix: Unable to use CMD+K link toolbar without selection 2023-12-14 00:07:52 -05:00
Tom Moor b002310ce5 chore: yarn.lock 2023-12-13 21:17:45 -05:00
Tom Moor a53f304a9e fix: Server error when search term contains double single quotes 2023-12-13 21:17:16 -05:00
Tom Moor d6c357d909 fix: Airtable embed, undefined in url 2023-12-13 20:21:28 -05:00
Tom Moor 792b8062dc fix: 'Replace all' button overflows container on Firefox 2023-12-13 19:42:56 -05:00
Tom Moor ac75521c6c fix: Firefox swallows mouse up when dragging to resize video 2023-12-13 19:40:51 -05:00
Tom Moor 7dbdfcc823 fix: Allow parenthesis before mention.
closes #6280
2023-12-13 19:29:48 -05:00
Tom Moor 53ff144f00 fix: Positioning of editing toolbar on mobile devices (#6279) 2023-12-13 16:22:06 -08:00
Tom Moor 04d4cb6d52 fix: New Airtable url format does not embed correctly.
closes #6095
2023-12-13 19:21:19 -05:00
Tom Moor d586bdf28a closes #6273 2023-12-13 00:20:50 -05:00
Tom Moor e315ba9dff String -> Text 2023-12-11 17:28:39 -08:00
Tom Moor 43e538dba7 Add answer column to SearchQuery model 2023-12-11 20:15:33 -05:00
dependabot[bot] f9fb57abf4 chore(deps-dev): bump eslint-plugin-react from 7.21.5 to 7.33.2 (#6226)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-12-11 16:55:37 -08:00
dependabot[bot] 7b7f9c4dea chore(deps): bump reflect-metadata from 0.1.13 to 0.1.14 (#6278)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 16:33:07 -08:00
dependabot[bot] 9853b74d8a chore(deps-dev): bump jest-cli from 29.6.4 to 29.7.0 (#6277)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 16:32:44 -08:00
dependabot[bot] 689a606969 chore(deps): bump prosemirror-transform from 1.7.5 to 1.8.0 (#6275)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 16:32:31 -08:00
dependabot[bot] 8e0b604dfa chore(deps-dev): bump @types/addressparser from 1.0.2 to 1.0.3 (#6276)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 16:32:14 -08:00
Tom Moor 9e9fedaff1 fix: Missing unencoding of search terms in url, related #6268 2023-12-10 21:30:30 -05:00
Tom Moor 0a2b559bbb yarn.lock 2023-12-10 19:44:23 -05:00
Tom Moor 9930a676ba fix: Prevent duplicate of comment marks through copy/paste. closes #6263 2023-12-10 18:21:32 -05:00
Tom Moor 5dfa6a6011 Convert Search page to functional component (#6268) 2023-12-09 18:54:39 -08:00
Tom Moor 3f3d7b4978 Add 'Copy as Markdown' action
Remove smart quotes from Markdown export, closes #5303
2023-12-09 15:00:33 -05:00
Tom Moor f9c3b0e193 Use table header for triggering markdown detection 2023-12-09 13:29:49 -05:00
Tom Moor 84ef72d846 fix: Deeply nested command menu actions have incorrect parent 2023-12-09 13:26:27 -05:00
Tom Moor 8a6b8404e4 Add 'Copy ID' developer actions 2023-12-09 13:24:23 -05:00
Tom Moor ff284db3f9 fix: Cannot read properties of undefined on old Android 2023-12-09 10:54:37 -05:00
Tom Moor 17e55832ac Add resource tracing to route span 2023-12-09 10:48:27 -05:00
Tom Moor aab5697b21 Improved API tracing 2023-12-09 10:23:53 -05:00
Tom Moor cb0d84a803 Add extra trace tagging 2023-12-08 20:15:07 -05:00
Tom Moor fb790c0688 0.74.0 2023-12-06 18:42:45 -05:00
Tom Moor cf64da1050 Add score column to search_queries (#6253)
* Add score column to search_queries

* Allow user to record search score
2023-12-06 05:37:46 -08:00
Tom Moor f494e28ce9 chore: Refactor editor click handler for reusability 2023-12-05 22:48:33 -05:00
Tom Moor 8d65b13b3d fix: FindAndReplace does not update to reflect changing between readOnly/editable 2023-12-05 21:38:54 -05:00
dependabot[bot] 9fbe256807 chore(deps): bump emoji-regex from 10.2.1 to 10.3.0 (#6225)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-05 17:40:43 -08:00
Tom Moor 5558d5af95 chore: Move from inline-css -> css-inline (#6252) 2023-12-05 17:40:27 -08:00
dependabot[bot] b6f0d44dee chore(deps): bump vite from 5.0.2 to 5.0.5 (#6251)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-05 17:16:15 -08:00
Tom Moor 6506ff7291 chore: Bump Sentry deps 2023-12-04 20:12:22 -05:00
dependabot[bot] da4abfde3e chore(deps-dev): bump @types/inline-css from 3.0.1 to 3.0.3 (#6246)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 13:10:42 -08:00
dependabot[bot] 1fe15dc8f6 chore(deps): bump jsdom from 22.0.0 to 22.1.0 (#6247)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 13:10:23 -08:00
dependabot[bot] 659b31750c chore(deps): bump core-js from 3.33.2 to 3.33.3 (#6248)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 13:09:58 -08:00
Tom Moor d13a518a8a chore: Dependency updates (#6240) 2023-12-02 10:25:43 -08:00
Ray Hong 31021172e7 Fix some minor typos. (#6238) 2023-12-02 06:29:11 -08:00
Tom Moor 25be2fee40 fix: Display of videos in revision history
closes #6176
2023-12-01 23:15:34 -05:00
Tom Moor 2bbc384b5a fix: Incorrect template permission check disallows member template creation
closes #6203
2023-12-01 21:18:13 -05:00
Tom Moor 889070012a Select entire link when clicking and editing 2023-12-01 20:38:11 -05:00
dependabot[bot] 8999e76961 chore(deps): bump sequelize-typescript from 2.1.5 to 2.1.6 (#6236)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 17:45:58 -05:00
dependabot[bot] 8ecc3361a6 chore(deps-dev): bump @babel/cli from 7.21.5 to 7.23.4 (#6222)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 14:45:07 -08:00
Tom Moor 86cc7e461b sp 2023-11-29 23:50:00 -05:00
Tom Moor 01c6952ec9 fix: Cannot read properties of undefined 2023-11-29 19:42:00 -05:00
Tom Moor b8f5d669fe Improve styling of search results 2023-11-28 23:04:20 -05:00
Tom Moor 5092694951 fix: shares.info request from client incorrectly capitalized 2023-11-28 20:20:24 -05:00
Tom Moor a08adc930d chore: Webhook subscription creator delete cascade 2023-11-28 20:13:27 -05:00
Tom Moor ed175c84ff fix: Invalid token to websocket server should not log an error 2023-11-28 20:08:13 -05:00
Tom Moor fdd774fca7 fix: HealthMonitor attached to wrong queue 2023-11-28 19:54:58 -05:00
Tom Moor 9b335b5342 fix: Do not load manifest from CDN 2023-11-28 08:29:41 -05:00
Pranav Joglekar a4341b0d89 feat: add support for tldraw snapshot links (#6210) 2023-11-28 05:22:42 -08:00
Tom Moor 654e4c9ce6 fix: Update diff emails too short in some email clients 2023-11-27 23:21:14 -05:00
Tom Moor 07cd13f17a fix: Queue health monitor should only run on worker processes (#6228) 2023-11-27 20:55:00 -05:00
github-actions[bot] 2db7776533 chore: Auto Compress Images (#6227)
Co-authored-by: tommoor <tommoor@users.noreply.github.com>
2023-11-27 16:26:03 -08:00
dependabot[bot] 3726b11c14 chore(deps): bump mammoth from 1.5.1 to 1.6.0 (#6223)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-27 16:25:29 -08:00
dependabot[bot] 4810b470e4 chore(deps-dev): bump @babel/preset-typescript from 7.21.4 to 7.23.3 (#6224)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-27 16:25:08 -08:00
Tom Moor e01a0a1b64 fix: PWA installability broke on recent Chrome updates 2023-11-27 19:11:11 -05:00
github-actions[bot] bbca133455 chore: Auto Compress Images (#6219)
Co-authored-by: tommoor <tommoor@users.noreply.github.com>
2023-11-27 05:36:47 -08:00
Tom Moor 5d70129c04 Add health check for background queue (#6218) 2023-11-27 05:36:07 -08:00
Andrew Smith 8f53f3b28c Allow embedding of GitLab snippets (#6217) 2023-11-27 05:35:37 -08:00
Tom Moor ca737ab641 fix: Slight misalignment of embed logos in block menu 2023-11-26 22:21:15 -05:00
Tom Moor 483ede8a01 fix: bind Logger.sanitize 2023-11-26 20:22:16 -05:00
Tom Moor a21e1d9fea chore: Expose createDatabaseInstance, createMigrationRunner methods 2023-11-25 13:18:51 -05:00
Tom Moor b903be6804 chore: Bump Bull 2023-11-25 13:15:15 -05:00
Tom Moor a6fdb63da4 chore: updated types 2023-11-24 16:56:18 -05:00
Tom Moor 4d19168ed2 fix: Incorrect lowercase in #6212 2023-11-24 14:15:23 -05:00
Tom Moor b9767a9fdc fix: Do not rely on class names in production bundle (#6212) 2023-11-24 09:59:57 -08:00
Tom Moor 13a6f89640 fix: Deleted unpublished drafts in trash 2023-11-23 11:28:11 -05:00
Tom Moor 0e4dbbef1f fix: Vite 5 manifest does not always contain imports key 2023-11-23 10:09:30 -05:00
Tom Moor 86494461cf fix: Location of generated manifest changed in Vite 5, see:
https://vitejs.dev/guide/migration\#manifest-files-are-now-generated-in-vite-directory-by-default
2023-11-23 09:54:47 -05:00
Tom Moor 72c485e0c8 fix: HTML exports have extra empty page, closes #6205 2023-11-23 09:50:58 -05:00
Tom Moor b18740c989 Always inject ready script 2023-11-23 09:10:16 -05:00
Tom Moor 8b68ee404a fix: Render Mermaid diagrams in HTML export, towards #6205 2023-11-23 09:06:07 -05:00
Apoorv Mishra ea8ebc3b2a fix: Document.findByPk() with and (#6208) 2023-11-23 18:59:56 +05:30
Tom Moor 5c55b1367b Vite 5 upgrade (#6206) 2023-11-23 04:34:47 -08:00
Tom Moor 1f40b640ac test 2023-11-22 22:05:15 -05:00
Tom Moor 3b01368677 Add suspendedAt column to teams 2023-11-22 21:35:04 -05:00
Tom Moor d3e0b19202 Update tests and docker to Node 20 LTS (#6204) 2023-11-22 17:51:56 -08:00
Tom Moor 396d886ecb Bump max node version to 20, tested. closes #6198 2023-11-22 20:27:20 -05:00
Apoorv Mishra 3749b2fa79 fix: set noErrorTruncation compiler flag (#6196) 2023-11-22 19:32:21 +05:30
Tom Moor c75d769d9e feat: Add 'share' option for documents on mobile 2023-11-21 22:27:54 -05:00
dependabot[bot] e109c8a265 chore(deps-dev): bump @types/stoppable from 1.1.1 to 1.1.3 (#6183)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-21 04:54:46 -08:00
Apoorv Mishra a891b6b604 Add column id to user_permissions and group_permissions (#6181)
* fix: add column id to user_permissions and group_permissions

* fix: don't drop ext

* fix: put back default value
2023-11-21 13:16:57 +05:30
dependabot[bot] e2ad3f9e73 chore(deps): bump datadog-metrics from 0.11.0 to 0.11.1 (#6182)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 18:21:34 -08:00
dependabot[bot] 7308020a3d chore(deps-dev): bump rollup-plugin-webpack-stats from 0.2.0 to 0.2.1 (#6184)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 18:21:17 -08:00
dependabot[bot] 2d27c37d78 chore(deps-dev): bump @types/throng from 5.0.4 to 5.0.7 (#6186)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 18:21:00 -08:00
dependabot[bot] 1a72902b68 chore(deps-dev): bump @types/crypto-js from 4.1.2 to 4.2.1 (#6185)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 18:20:47 -08:00
Tom Moor 95e8f3ae73 chore: Normalize pagination on comments.list 2023-11-17 19:55:06 -05:00
Pranav Joglekar 750c324b1a Feat/5870 list all comments (#6081) 2023-11-17 16:24:26 -08:00
Tom Moor 5c03908529 fix: Filtering on selection toolbar 2023-11-17 13:24:39 -05:00
Tom Moor 50ae815389 Add notice on errored file operations in self-hosted 2023-11-17 09:24:50 -05:00
Tom Moor f75bd7145b fix: Apply emoji from template, closes #6169 2023-11-17 09:21:47 -05:00
Translate-O-Tron 7790231464 New Crowdin updates (#6090) 2023-11-16 19:12:04 -08:00
Tom Moor 7b4bedae59 fix: Handle public attachments in Markdown export, closes #6161 2023-11-16 22:09:51 -05:00
Tom Moor 6177d6f3cb fix: Comment should not appear in selection toolbar with view-only permissions. closes #6011 2023-11-16 20:36:34 -05:00
Tom Moor 1ba8e756d9 fix: Cannot comment in code blocks, closes #6154 2023-11-16 19:48:22 -05:00
Tom Moor 67a1033ded fix: Allow for zip files with '/' path in central directory 2023-11-16 19:25:43 -05:00
Tom Moor b08a430131 docs 2023-11-15 20:31:21 -08:00
Tom Moor e76dcc0baf test 2023-11-15 20:50:44 -05:00
Tom Moor a0b51b8c71 Error loading attachment should not fail entire export. closes #6158 2023-11-15 20:38:54 -05:00
Tom Moor cf6a946c9c chore: Normalize fs-extra usage 2023-11-15 19:43:17 -05:00
Tom Moor 726613bf1d fix: Remove unzipper as it cannot handle zip within zip (#6162) 2023-11-15 16:32:17 -08:00
Nam Vu 68a3d327f6 Fix optional authentication (#6134) 2023-11-15 16:32:01 -08:00
Tom Moor 2c870f0dbb 0.73.1 2023-11-14 07:56:56 -05:00
Tom Moor cd359f0e76 fix: Migration script fails on fresh installation
closes #6153
2023-11-14 07:43:15 -05:00
Apoorv Mishra ff7f9d68d5 fix: restore working of babel-plugin-styled-components (#6140) 2023-11-14 09:56:24 +05:30
Tom Moor 8c85029d55 Improve reliability of team deletion 2023-11-13 22:06:24 -05:00
Tom Moor 530b8a1989 0.73.0 2023-11-13 20:47:36 -05:00
Tom Moor b3047e2599 yarn.lock 2023-11-13 20:47:32 -05:00
Tom Moor 1898a34418 Rearchitect import (#6141) 2023-11-13 17:15:38 -08:00
dependabot[bot] 2143269bcd chore(deps-dev): bump @types/enzyme-adapter-react-16 from 1.0.6 to 1.0.9 (#6147)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 17:01:50 -08:00
dependabot[bot] 14987f1e6d chore(deps-dev): bump @types/react-avatar-editor from 13.0.0 to 13.0.2 (#6148)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 17:01:36 -08:00
dependabot[bot] 482dbec901 chore(deps-dev): bump @types/koa-logger from 3.1.2 to 3.1.5 (#6145)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 17:01:25 -08:00
Tom Moor 48d688c0a5 Store source metadata for imported documents (#6136) 2023-11-11 07:52:29 -08:00
Tom Moor 90605e110a fix: Include maximum import size 2023-11-10 08:28:17 -05:00
dependabot[bot] 854802e137 chore(deps): bump @babel/plugin-proposal-decorators from 7.21.0 to 7.23.2 (#6022)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-09 18:31:20 -08:00
Tom Moor 6684c420d3 chore: Upgrade @types/node 2023-11-09 19:36:48 -05:00
Tom Moor 1c6f8dda5e fix: Cleanup empty folders when using local storage provider, closes #5978 2023-11-09 19:24:16 -05:00
Tom Moor 1a556b6ff2 fix: Internal server error during import with nested documents 2023-11-09 19:24:16 -05:00
Tom Moor 0964d03a17 More use of isProduction/isDevelopment getters 2023-11-09 19:24:16 -05:00
Tom Moor 1ace76eb44 fix: Show correct favicon for team on login screen if public branding is enabled 2023-11-09 19:24:16 -05:00
Tom Moor a1b52e18dd chore: Centralize environment detection 2023-11-09 19:24:16 -05:00
Tom Moor 096a65b0f9 fix: Improve error handling on env boolean parsing 2023-11-09 19:24:16 -05:00
Tom Moor d8d49f6950 fix: Internal server error during import with nested documents 2023-11-09 19:24:16 -05:00
Apoorv Mishra a7dd5c6798 fix: allow script injection from react dev tools in dev and stage envs (#6120) 2023-11-09 10:40:04 +05:30
dependabot[bot] 71c4c88bbe chore(deps-dev): bump babel-jest from 29.6.4 to 29.7.0 (#6123)
Bumps [babel-jest](https://github.com/jestjs/jest/tree/HEAD/packages/babel-jest) from 29.6.4 to 29.7.0.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v29.7.0/packages/babel-jest)

---
updated-dependencies:
- dependency-name: babel-jest
  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>
2023-11-06 18:07:32 -05:00
dependabot[bot] 9ddf31632f chore(deps-dev): bump @types/addressparser from 1.0.1 to 1.0.2 (#6124)
Bumps [@types/addressparser](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/addressparser) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/addressparser)

---
updated-dependencies:
- dependency-name: "@types/addressparser"
  dependency-type: direct:development
  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>
2023-11-06 18:06:51 -05:00
dependabot[bot] 067fd11663 chore(deps): bump core-js from 3.33.0 to 3.33.2 (#6125)
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.33.0 to 3.33.2.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.33.2/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  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>
2023-11-06 18:06:16 -05:00
Tom Moor 446fffd367 fix: Mention attributed incorrectly 2023-11-06 08:19:23 -05:00
Tom Moor 03bdc4e00d Display domain on share button 2023-11-05 13:43:36 -05:00
Tom Moor 7b88547051 Add more fields to shared links management screen 2023-11-05 13:39:34 -05:00
Tom Moor 9be180d44d fix: Incorrect cursor on sortable table header cells 2023-11-05 12:44:53 -05:00
Tom Moor 733bd39ae4 fix: Sort nodes correctly in useCollectionTrees. closes #6102 2023-11-05 12:38:39 -05:00
Tom Moor 7c319c17c6 fix: Correct user on documents in deleted collection (#6116) 2023-11-05 06:43:38 -08:00
Tom Moor c76aa845f4 fix: Protect against view updates after destroyed in async uploads 2023-11-04 21:57:13 -04:00
Tom Moor ec79cab8b8 fix: Uncaught error in JSZip file reading crashes worker process. closes #6109 2023-11-04 21:51:09 -04:00
Tom Moor c769a95f65 API: Add endpoint to check custom domain resolution (#6110) 2023-11-04 12:21:47 -07:00
Tom Moor b2ad6ca9bc Refactor to middleware, support old routes 2023-11-01 23:52:18 -04:00
Tom Moor a48d8fac88 Return correct canonical url for share with domain 2023-11-01 23:45:41 -04:00
Tom Moor 1b73339800 fix: Link on 'Not Found' page for root share leads to custom domain landing 2023-11-01 23:20:41 -04:00
Tom Moor f0bf60eb40 Add graceful redirect from old share paths 2023-11-01 23:17:54 -04:00
Tom Moor 0f072acfd9 fix: Guard undefined ctx.state 2023-11-01 23:14:45 -04:00
Tom Moor 2838503273 Backend of public sharing at root (#6103) 2023-11-01 19:10:00 -07:00
Tom Moor 1d6ef2e1b3 perf: Remove unneeded query before custom domain redirect 2023-10-31 22:32:29 -04:00
Tom Moor 0700e2f5ef fix: Incorrect import – really need a lint rule for this 2023-10-31 22:20:31 -04:00
Tom Moor a9ff0c245d Toggle current todo item with Mod-Enter 2023-10-31 22:10:55 -04:00
Tom Moor 4af45c68cc Remove unused InputRich component 2023-10-31 21:58:30 -04:00
Tom Moor df6d8c12cc Refactor Editor components to be injected by associated extension (#6093) 2023-10-31 18:55:55 -07:00
dependabot[bot] 44198732d3 chore(deps-dev): bump @types/fs-extra from 11.0.1 to 11.0.3 (#6098)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 18:51:20 -07:00
dependabot[bot] 667e42e814 chore(deps): bump fetch-retry from 5.0.5 to 5.0.6 (#6097)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 18:51:09 -07:00
dependabot[bot] 5027ae9def chore(deps-dev): bump @types/natural-sort from 0.0.22 to 0.0.23 (#6096)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 18:50:54 -07:00
dependabot[bot] 07ce213232 chore(deps): bump slugify from 1.6.5 to 1.6.6 (#6099)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 18:50:47 -07:00
Tom Moor ed447f5811 fix: Eroneous toast on export 2023-10-30 08:41:09 -04:00
Tom Moor d593976b4d Add a button to upload images into comments (#6092) 2023-10-29 17:42:49 -07:00
Tom Moor 44cbf4359f Add success message on import completion 2023-10-29 20:42:23 -04:00
Tom Moor f0825b4cd9 fix: Remove broken comment ability on templates 2023-10-29 20:27:06 -04:00
Tom Moor 9f6c1f8b67 Hide scrollbars on search filters bar 2023-10-29 19:20:15 -04:00
Tom Moor 6b13a32234 fix: Refactor hover previews to reduce false positives (#6091) 2023-10-29 15:31:12 -07:00
Tom Moor 90bc60d4cf Move pinned collection documents above description 2023-10-29 12:12:23 -04:00
Apoorv Mishra 3fd429baa9 usePaginatedRequest hook for simpler handling of pagination on FE (#6060)
* feat: usePaginatedRequest hook

* fix: spread params

* fix: handle limit zero

* fix: handle case when stars.fetchPage returns empty array

* fix: use stars.orderedData for reactivity
2023-10-29 20:01:47 +05:30
Tom Moor 4d3655bc6c Remove usage of .at() for browser compat 2023-10-28 22:22:10 -04:00
Tom Moor 7b98ce3514 fix: Background transition on home screen 2023-10-28 20:42:57 -04:00
Tom Moor e6196ae79e fix: Find and replace dialog should be fixed when scrolling 2023-10-28 19:51:13 -04:00
Tom Moor 89f3d47327 Port HTML import improvements from enterprise codebase 2023-10-28 19:09:53 -04:00
Tom Moor 846fb122cd fix: Misalignment of code block line numbers when font-size is increased in Edge
closes #5612
2023-10-28 13:40:47 -04:00
Tom Moor 884f3c5896 fix: Emoji position when document is full-width 2023-10-28 13:12:30 -04:00
Tom Moor f2df25d115 Persist full-width as user preference when toggled
closes #5562
2023-10-28 13:03:38 -04:00
Tom Moor 92ba095124 fix: Guard against empty items in Facepile users, closes #6087 2023-10-28 12:45:02 -04:00
Tom Moor 1e847dc1cf Cleanup and refactor AuthStore (#6086) 2023-10-28 09:43:50 -07:00
Tom Moor 3cd90f3e74 Remove duplicative test 2023-10-28 11:38:35 -04:00
Tom Moor 964d2b6bb3 Allow use of useCurrentUser/useCurrentTeam hooks in unauthenticated components 2023-10-28 11:38:35 -04:00
Translate-O-Tron 56f9755cd9 New Crowdin updates (#6036) 2023-10-28 08:38:28 -07:00
Agnès Haasser 057d8a7f3b API - allow search of a group using its name (#6066) 2023-10-28 08:36:16 -07:00
Tom Moor 7380f6d5ae fix: Maximum number of connections reached, closes #5446
The opposite of onDisconnect is connected, not onConnect. smh.
2023-10-28 10:32:58 -04:00
Tom Moor 08d89fb57a fix: Enforce emoji flags on macOS 2023-10-26 23:58:02 -04:00
Tom Moor b53c595e1b fix: FILE_STORAGE_UPLOAD_MAX_SIZE not considered for direct uploads. closes #6078 2023-10-26 21:57:48 -04:00
Tom Moor f23a7bd685 fix: Development cannot start s3 2023-10-26 21:50:57 -04:00
Tom Moor 60941dc285 fix: Title is duplicated on imported collections 2023-10-26 21:27:06 -04:00
Tom Moor 33576b794a fix: Misalignment of menu item icon when text overflows 2023-10-26 20:37:36 -04:00
Tom Moor a6f8872baa fix: Selecting flag inserts a different flag
closes #6079
2023-10-26 20:37:36 -04:00
github-actions[bot] fb56b00e81 chore: Auto Compress Images (#6080)
Co-authored-by: tommoor <tommoor@users.noreply.github.com>
2023-10-26 17:35:35 -07:00
Prikshit Singh d6fcf44bf4 Add instagram embed (#6075) 2023-10-26 17:34:25 -07:00
Tom Moor afb5ccbf74 fix: Improve quality of search results from : emoji search
closes #6073
2023-10-25 22:13:03 -04:00
Tom Moor d7dacd0cd3 test 2023-10-25 21:29:42 -04:00
Tom Moor 5402731ec3 fix: Do not prevent local IP connections to OIDC server
ref #6064
2023-10-25 20:54:26 -04:00
Tom Moor bf6bd3f8d0 Allow share record creation when disabled at team level for private sharing 2023-10-25 20:50:09 -04:00
Tom Moor 35fd1227e7 docs: Remove reference to huntr.dev 2023-10-25 20:32:28 -04:00
Tom Moor 2fcf9149b5 fix: Templatize no longer works, closes #6067 2023-10-25 20:29:36 -04:00
Tom Moor f063bef968 Merge branch 'main' of github.com:outline/outline 2023-10-25 19:46:03 -04:00
dependabot[bot] 3e1287064b chore(deps): bump crypto-js from 4.1.1 to 4.2.0 (#6072)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-25 16:10:28 -07:00
Pranav Joglekar d4b598570d fix: remember prev selected lang when creating new code block (#6062) 2023-10-24 19:35:55 -07:00
dependabot[bot] 04ac417bef chore(deps-dev): bump @types/node from 18.0.6 to 18.18.6 (#6056)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 20:08:30 -07:00
dependabot[bot] 1b11b9e31b chore(deps): bump oy-vey from 0.12.0 to 0.12.1 (#6057)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 20:08:12 -07:00
dependabot[bot] 6adf80d4e7 chore(deps): bump react-virtualized-auto-sizer and @types/react-virtualized-auto-sizer (#6058)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 20:07:53 -07:00
dependabot[bot] 7bc8f1fc72 chore(deps-dev): bump @types/react-color from 3.0.6 to 3.0.9 (#6055)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 20:07:40 -07:00
Tom Moor 5fc68db5da Improve reliability of document operations with websocket disconnected 2023-10-22 23:21:33 -04:00
Tom Moor 1abe4964e8 fix: Can't perform a React state update on an unmounted component 2023-10-22 22:08:36 -04:00
Tom Moor b0095e6fe1 Remove fakes3, no longer neccessary 2023-10-22 21:42:10 -04:00
Tom Moor e7f3e500cd fix: Menu height on mobile prevents closing 2023-10-22 21:30:39 -04:00
Tom Moor ef76405bd6 Move toasts to sonner (#6053) 2023-10-22 14:30:24 -07:00
Tom Moor 389297a337 fix: Include notice on where collection exports end up 2023-10-22 15:32:08 -04:00
Tom Moor 1a3b2dc307 Improve Redis error handling 2023-10-22 14:02:36 -04:00
Tom Moor fb74494108 Do not expose insightsEnabled, templateId on public shares 2023-10-22 13:49:50 -04:00
Tom Moor 764dc84da9 test 2023-10-21 22:20:17 -04:00
Tom Moor 3bf35affb1 fix: Enable management of notifications without SMTP configured
closes #6046
2023-10-21 21:36:11 -04:00
Tom Moor 9f6c90c86a fix: Improve performance of base64 detection regex 2023-10-21 21:29:06 -04:00
jannschu 0518cdc6d9 Use transaction from middleware for more database queries (#6051) 2023-10-21 14:31:14 -07:00
dependabot[bot] 5df48b3204 chore(deps-dev): bump @relative-ci/agent from 4.1.8 to 4.1.10 (#6018)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 19:11:31 -07:00
Translate-O-Tron c32aee8372 New Crowdin updates (#6028) 2023-10-18 15:50:30 -07:00
Agnès Haasser 3589980864 API : allow filter user list via emails (#6031) 2023-10-18 05:36:16 -07:00
Tom Moor d536fa9939 translation: team -> workspace 2023-10-18 08:32:43 -04:00
Tom Moor 89694a561f translation: Pinned to team home -> Pinned to home 2023-10-17 23:30:26 -04:00
Tom Moor ac7668b5f7 Merge branch 'main' of github.com:outline/outline 2023-10-17 23:29:54 -04:00
github-actions[bot] 76b12cbad5 chore: Auto Compress Images (#6027)
Co-authored-by: tommoor <tommoor@users.noreply.github.com>
2023-10-17 20:14:29 -07:00
Prikshit Singh bb8fd93628 Add LinkedIn embed (#5933)
Co-authored-by: Prikshit singh <prikshit.singh@aimedlabs.com>
2023-10-17 20:12:20 -07:00
Translate-O-Tron 12f7e3d1da New Crowdin updates (#5897) 2023-10-17 20:11:52 -07:00
Tom Moor ba612a557f translation: knowledgebase -> workspace 2023-10-17 22:34:01 -04:00
Tom Moor 608e1eeaa0 Remove unused translations 2023-10-17 21:51:26 -04:00
Tom Moor 297536bfe5 Remove duplicate translation. ref: https://crowdin.com/translate/outline/44/en-es\?filter\=basic\&value\=0\#3202 2023-10-17 21:28:55 -04:00
Tom Moor caafdb2fe7 fix: Ensure initials on avatars are always displayed uppercase 2023-10-17 18:49:33 -04:00
Tom Moor ea97963feb fix: RangeError: Position -1 out of range 2023-10-17 18:20:57 -04:00
Tom Moor 03869784be fix: permanantly -> permanently 2023-10-17 18:16:41 -04:00
Tom Moor 43ee487e91 Add 'Rename' option to document sidebar menu 2023-10-17 18:15:35 -04:00
Tom Moor 955705dd64 fix: deletedAt property not reactive 2023-10-16 22:59:12 -04:00
Tom Moor d8b7d14419 fix: Unarchiving a deleted draft adds to collection structure 2023-10-16 22:54:26 -04:00
Tom Moor d89ce1ea4d fix: Remove comment open in selection toolbar on deleted documents 2023-10-16 22:30:24 -04:00
Tom Moor 5bc5759f42 Add userId to views response, closes #6014 2023-10-16 21:26:35 -04:00
Tom Moor 03c739032d fix: views.list should not include deleted users 2023-10-16 21:21:03 -04:00
Tom Moor 0bec781695 fix: Improve validation of edge cases with documents.move endpoint. closes #6015 2023-10-16 21:04:39 -04:00
Tom Moor a357cbaf8d perf: Improve performance of Placeholder extension 2023-10-16 19:38:11 -04:00
Tom Moor 0b7253bb0c fix: Tighten valiation around URLs in database fields
closes #6012
2023-10-16 19:38:11 -04:00
Tom Moor 31cb9c865f fix: Assumption that url passed to storeFromUrl will send content-length header 2023-10-16 19:38:11 -04:00
Tom Moor 787b893cd2 Increase max collection description 2023-10-16 19:38:11 -04:00
Tom Moor faf97401e6 feat: add "Copy document" dialog (#6009) 2023-10-16 16:13:57 -07:00
Tom Moor 1ce0d3470e Make code block Enter behavior the same as quote block (#6010) 2023-10-16 16:13:23 -07:00
dependabot[bot] bedad9d802 chore(deps): bump @babel/traverse from 7.22.5 to 7.23.2 (#6023)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 16:13:01 -07:00
dependabot[bot] e2c5daefac chore(deps-dev): bump @types/natural-sort from 0.0.21 to 0.0.22 (#6017)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 16:05:48 -07:00
dependabot[bot] 3feb104288 chore(deps-dev): bump @types/crypto-js from 4.1.1 to 4.1.2 (#6019)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 16:04:51 -07:00
Tom Moor 9e3b2c043c fix: Port escape overrides from enterprise fork 2023-10-15 10:54:27 -04:00
Tom Moor d5bac6cbca fix: Paragraphs in table cells skipped in import
Port HTML importer rules from enterprise fork
2023-10-15 10:54:27 -04:00
Tom Moor 00ee8729ec fix: Import of breaks from HTML files 2023-10-14 17:44:25 -07:00
Tom Moor 1305e3746b fix: Warning setState after unmounted 2023-10-14 17:27:02 -04:00
Tom Moor 02731e73c5 fix: Hover cards appearing on embeds, change to allow-list approach 2023-10-14 09:29:08 -04:00
Tom Moor b3f9707ffb Add 'open' link to Grist embeds 2023-10-14 09:22:57 -04:00
Tom Moor c32ac1a265 fix: Table of contents slightly covers emoji picker
closes #6006
2023-10-14 09:07:17 -04:00
Tom Moor 6a74fdf6cf fix: Allow browser translation of documents in read-only, closes #5998 2023-10-12 23:33:57 -04:00
Tom Moor a84008085f Merge branch 'main' of github.com:outline/outline 2023-10-12 23:17:09 -04:00
dependabot[bot] 3212d37ca5 chore(deps): bump vite from 4.4.9 to 4.4.11 (#5972)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-12 05:44:41 -07:00
dependabot[bot] 47837e315a chore(deps): bump zod from 3.21.4 to 3.22.4 (#5974)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-12 05:38:45 -07:00
Tom Moor a652386329 fix: Use consistent active state on recent search items 2023-10-11 23:39:11 -04:00
ktmouk c5c323690b Add the keyboard operation on recent search items (#5987)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-10-11 20:37:57 -07:00
Tom Moor 8bfd17c8d4 Remove creator name in template menu, closes #5976 2023-10-11 22:30:20 -04:00
Tom Moor e8646acd21 fix: Mermaid diagrams are not hidden under a collapsed heading. closes #5979 2023-10-11 22:27:38 -04:00
Tom Moor ffbe4c1b80 fix: Prevent potential page scroll on context menu open 2023-10-11 22:17:18 -04:00
Tom Moor b63cd67c24 fix: Expensive recursive regex when using French language, closes #5939 2023-10-11 21:32:44 -04:00
Tom Moor 0d319d50b8 Shakes fist at autocomplete 2023-10-11 09:24:37 -04:00
Tom Moor a579ecd512 fix: Commenting on code blocks does not work in read-only view 2023-10-10 19:35:45 -04:00
Tom Moor 547b6c0ac9 fix: Allow context menus to use more available viewport height 2023-10-10 19:32:24 -04:00
Tom Moor bf53ac4f4b fix: Ensure video src passed to player is sanitized 2023-10-10 19:22:05 -04:00
Tom Moor 09938c2649 perf: Avoid recreating transaction when no upload placeholders exist 2023-10-10 19:19:44 -04:00
Tom Moor 8354a5bc37 fix: Use of view after destroyed, closes #5982 2023-10-10 19:00:12 -04:00
Tom Moor fec1a72780 fix: Remove zapier from CSP on self-hosted 2023-10-09 21:11:05 -04:00
dependabot[bot] 4181aa0f3c chore(deps-dev): bump @types/react-helmet from 6.1.6 to 6.1.7 (#5973)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 11:46:34 -07:00
dependabot[bot] 5305c142a2 chore(deps): bump prosemirror-tables from 1.3.2 to 1.3.4 (#5971)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 11:07:35 -07:00
Tom Moor 594898affc fix: Sidebar should collapse after control click (again)
closes #5958
2023-10-07 21:14:48 -04:00
Tom Moor 6402f0bfcf fix: Enable backtick inline code shortcut on keyboard layouts that use composition for character
Add Cmd+e shortcut to toggle inline code on macOS

closes #5955
2023-10-07 21:11:50 -04:00
Tom Moor 2f3247b500 fix: Cannot read properties of undefined (reading 'width') 2023-10-07 20:59:18 -04:00
Tom Moor b6706efe6f Add validation to require protocol on urls in env
Related #5961
2023-10-07 17:49:24 -04:00
Tom Moor 63263eee82 Tweak floating toolbar style 2023-10-06 12:31:03 -04:00
Tom Moor 9924fa6621 feat: Allow commenting in code (#5953)
* Allow commenting in code marks

* Allow commenting in code blocks

* Floating comment toolbar in code block
2023-10-06 06:56:59 -07:00
dependabot[bot] ac319de1df chore(deps): bump postcss from 8.4.27 to 8.4.31 (#5952)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-05 20:07:13 -07:00
Tom Moor 2108ca29df fix: Sidebar toggle icon should always appear when hovering over sidebar 2023-10-05 22:55:49 -04:00
Tom Moor ea4de0dfb5 chore: Docs, define additional client-side relations 2023-10-05 22:32:58 -04:00
Tom Moor 773c35ebc3 fix: Correctly clear accessToken when logging out.
Token is correctly rotated on the server, it just isnt correctly removed on the client.
closes #5940
2023-10-05 22:10:07 -04:00
Tom Moor 0ae4c7d6bd Merge branch 'main' of github.com:outline/outline 2023-10-05 21:52:13 -04:00
Tom Moor 50faefbc45 fix: Reserve space for unread dot on notifications flyover 2023-10-05 21:47:34 -04:00
Tom Moor eb71a8f933 fix: Various compounding memory leaks in editor (#5950) 2023-10-05 17:01:27 -07:00
Tom Moor a2f037531a Add registering of client-side relationship properties (#5936) 2023-10-05 16:50:59 -07:00
Tom Moor e70d4e60fd fix: Microsoft logo does not inheret accent color
closes #5944
2023-10-05 19:50:22 -04:00
Tom Moor 5e0b812083 fix: 'Untitled' is not translated
closes #5949
2023-10-05 19:44:54 -04:00
Tom Moor 1359f44814 fix: Links to attachments do not work in emailed notifications (#5935) 2023-10-04 18:25:07 -07:00
Tom Moor e1c90d3938 fix: Correctly invalidate translations cache on change
closes #5924
2023-10-04 21:12:58 -04:00
Tom Moor e967641bb6 fix: Clicking collapse sidebar does not hide sidebar until clicked elsewhere
closes #5928
2023-10-03 20:52:29 -04:00
Tom Moor 4d2a5ae748 fix: 'latest' tagging in build process. Incorrect check assumed image built on main instead of non-prerelease tag 2023-10-03 08:14:27 -04:00
Tom Moor 56cae8a545 0.72.0 2023-10-02 20:24:17 -04:00
dependabot[bot] e5e049a671 chore(deps): bump react-dropzone from 11.3.2 to 11.7.1 (#5913)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 16:26:12 -07:00
dependabot[bot] 48438eea2d chore(deps): bump i18next-http-backend from 2.2.0 to 2.2.2 (#5914)
Bumps [i18next-http-backend](https://github.com/i18next/i18next-http-backend) from 2.2.0 to 2.2.2.
- [Changelog](https://github.com/i18next/i18next-http-backend/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-http-backend/compare/v2.2.0...v2.2.2)

---
updated-dependencies:
- dependency-name: i18next-http-backend
  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>
2023-10-02 15:44:47 -07:00
dependabot[bot] 8e7dfdb6a0 chore(deps-dev): bump @types/react-window from 1.8.5 to 1.8.6 (#5915)
Bumps [@types/react-window](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-window) from 1.8.5 to 1.8.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-window)

---
updated-dependencies:
- dependency-name: "@types/react-window"
  dependency-type: direct:development
  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>
2023-10-02 15:44:31 -07:00
dependabot[bot] c8acf96790 chore(deps-dev): bump @types/sequelize from 4.28.10 to 4.28.16 (#5917)
Bumps [@types/sequelize](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sequelize) from 4.28.10 to 4.28.16.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/sequelize)

---
updated-dependencies:
- dependency-name: "@types/sequelize"
  dependency-type: direct:development
  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>
2023-10-02 15:44:21 -07:00
Tom Moor e7b7032284 feat: Allow deletion of imports (#5907) 2023-10-01 18:24:50 -07:00
Tom Moor 16cd82a732 fix: JSON import fails if the same import has been imported and deleted previously 2023-10-01 15:16:55 -04:00
Tom Moor 41a6f77998 fix: Types on overridden findByPk methods (#5908) 2023-10-01 12:02:56 -07:00
Tom Moor e2a6d828a9 Allow creating published share record, closes #5902 2023-09-30 12:38:16 -04:00
Tom Moor 2868ab2d00 fix: Floating toolbar overflow 2023-09-29 22:32:43 -04:00
Tom Moor aa79bc85f1 fix: Mobile toolbar appears on Windows touchscreen
fix: Improve mobile toolbar
2023-09-29 00:18:22 -04:00
Tom Moor 5397907599 chore: Refactor upload placeholder (#5898) 2023-09-28 20:13:40 -07:00
Tom Moor f4fd9dae5f feat: Native video display (#5866) 2023-09-28 17:28:09 -07:00
Tom Moor bd06e03b1e Added more debugging logs for #5564 2023-09-28 17:36:10 -04:00
Tom Moor 5b2bb41ead fix: Remember previous path when logging out due to auth expired
closes #5893
2023-09-28 17:22:52 -04:00
Translate-O-Tron 2e759e4e81 New Crowdin updates (#5857) 2023-09-28 06:01:38 -07:00
Tom Moor 5a89edbcb2 fix: withCollection scopes on UserPermission, GroupPermission 2023-09-27 23:08:06 -04:00
Tom Moor 6eab716779 fix: Mention menu hanging after backspace 2023-09-27 22:41:30 -04:00
Tom Moor 6de96b1d9d perf: Don't render SuggestionMenu contents until active 2023-09-27 21:19:55 -04:00
dependabot[bot] 318a1120d4 chore(deps): bump aws-sdk from 2.1369.0 to 2.1464.0 (#5888)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 06:26:01 -07:00
Tom Moor 86cb861ca7 Improve clarity of error message when database SSL disabled and file storage cannot be written 2023-09-27 09:07:49 -04:00
Tom Moor 2261514138 Improve debugging when OIDC userinfo endpoint does not return JSON 2023-09-26 23:46:56 -04:00
Tom Moor 402695c2e3 fix: Remove zero count on drafts 2023-09-25 23:10:16 -04:00
Tom Moor 9e810387c0 Add cache-control headers to attachments.redirect response 2023-09-25 21:30:57 -04:00
Tom Moor b1ddf417be fix: Capture errors recreating transform and log, these are non-critical 2023-09-25 20:45:34 -04:00
Tom Moor 0014bcf22d chore: Remove unused dep 2023-09-25 20:42:10 -04:00
Tom Moor 606a4e0772 Add small cache to mermaid diagrams, follow on to #5852 2023-09-25 20:41:18 -04:00
Tom Moor 4807c60042 fix: Do not modify internet server error before pushing to Sentry 2023-09-25 18:11:53 -04:00
dependabot[bot] dd02bd9c03 chore(deps): bump bull from 4.10.4 to 4.11.3 (#5886)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 15:04:40 -07:00
antran22 1639c657c8 feat: update mermaid rendering flow (#5852)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-09-25 14:03:25 -07:00
dependabot[bot] 25b961b3b8 chore(deps): bump katex from 0.16.7 to 0.16.8 (#5884)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 14:03:04 -07:00
dependabot[bot] 144ba0ced9 chore(deps): bump sequelize from 6.32.1 to 6.33.0 (#5885)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 14:02:55 -07:00
dependabot[bot] d340f8977d chore(deps-dev): bump lint-staged from 13.2.3 to 13.3.0 (#5887)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 14:02:43 -07:00
Apoorv Mishra 7145f7ef51 UserPermission and GroupPermission models (#5860)
* fix: rename to group_permissions

* fix: delete null collectionId records before setting non null constraint

* fix: use scope with collectionId not null

* fix: update model with documentId

* fix: rename to GroupPermission

* fix: rename collection_users to user_permissions

* fix: teamPermanentDeleter test

* fix: use scope with collectionId not null

* fix: update model with documentId

* fix: rename to UserPermission

* fix: create views upon table rename for zero downtime

* fix: remove comments
2023-09-25 10:51:29 +05:30
Tom Moor 43bdb97639 fix: Aggressive embed conversion on server Markdown -> Ydoc 2023-09-24 23:12:38 -04:00
Tom Moor 136ee0ad1d fix: 500 server error when files.create request is closed by client (#5878) 2023-09-24 13:30:52 -07:00
Tom Moor 517f2634e3 chore: Create data directory inside Dockerfile (#5862) 2023-09-24 12:03:00 -07:00
Tom Moor 42cc991317 fix: files.create permissions (#5877)
* fix: files.create permissions

* test

* new
2023-09-24 12:02:49 -07:00
Tom Moor e50e0bba53 Allow file access not in Attachments table (#5876)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-09-24 06:43:37 -07:00
Tom Moor d0bb6c6a41 fix: Size of export does not update dynamically on screen 2023-09-23 18:11:17 -04:00
Tom Moor 6aec085942 fix: Error handling on streams missing error handler on read streams.
Related https://github.com/outline/outline/discussions/5855
2023-09-23 18:08:36 -04:00
Tom Moor 65d3c8309e fix: Unable to store/read in avatars bucket with local file system storage
closes #5873
2023-09-23 15:00:48 -04:00
Tom Moor 5c7c9ceeb1 JSDoc, closes #5874 2023-09-23 14:31:55 -04:00
Tom Moor 3f11b014c5 fix: --watch mode no longer working
closes #5867
2023-09-22 09:01:51 -04:00
Tom Moor 76862b626b Allow setting createdAt, emoji properties through documents.create (#5864) 2023-09-21 19:37:27 -07:00
Tom Moor 8833e578f1 fix: JS error hitting up or down with entire document selection
closes #5863
2023-09-21 21:16:59 -04:00
Tom Moor 8c661345f0 fix: Incorrect translation of != null 2023-09-21 10:24:18 -04:00
Tom Moor 89537aabc3 Vendorize prosemirror-recreate-transform (#5861) 2023-09-21 05:44:23 -07:00
Tom Moor 6672536cde fix: Retain image and video placeholders when document remotely edited 2023-09-21 00:15:54 -04:00
Tom Moor 34d4209dd5 fix: Race condition with setting awareness field 2023-09-20 23:48:14 -04:00
Translate-O-Tron 27befbf3f7 New Crowdin updates (#5767) 2023-09-20 18:04:03 -07:00
Tom Moor 5aa7b42f8b fix: Cannot read properties of undefined (reading 'id') 2023-09-20 20:27:50 -04:00
Apoorv Mishra 67b1fe5514 Local file storage (#5763)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-09-20 15:12:03 -07:00
dependabot[bot] fea50feb0d chore(deps): bump @babel/preset-react from 7.22.5 to 7.22.15 (#5838)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-19 05:42:57 -07:00
dependabot[bot] 1b1b95d673 chore(deps-dev): bump @types/semver from 7.5.0 to 7.5.2 (#5839)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-19 05:41:52 -07:00
dependabot[bot] 1137d45f92 chore(deps): bump node-fetch and @types/node-fetch (#5840)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-19 05:41:37 -07:00
Tom Moor 091ef340f4 fix: Mermaid syntax error diagram sometimes displayed at side of document
ref https://github.com/outline/outline/discussions/5834
2023-09-16 19:33:28 -04:00
Tom Moor 432fa970e5 fix: Pasting single line of code should use a mark, not a block by default. 2023-09-16 17:59:12 -04:00
Tom Moor 59734f2bf7 fix: Check commenting feature is enabled on all comments endpoints 2023-09-16 17:46:02 -04:00
Tom Moor 4fa3270f4e Port changes from enterprise codebase 2023-09-16 08:36:22 -04:00
Tom Moor 3582a6a0a2 Bump thickness of LetterIcon to better match other icons 2023-09-15 21:19:12 -04:00
Tom Moor 8c2a47db9d fix: Serve CORS header for fonts 2023-09-15 21:10:03 -04:00
Tom Moor 266a2f4485 fix: integrations.delete event not handled in DeliverWebhookTask 2023-09-15 20:48:24 -04:00
Tom Moor c20eac0b03 fix: Letter icon at all sizes 2023-09-15 08:58:40 -04:00
Tom Moor 6b4feb51e0 Add letter icon option for collections 2023-09-15 08:54:22 -04:00
Tom Moor b79f86d347 Enter at beginning of first table column should insert row above 2023-09-14 23:34:25 -04:00
Tom Moor 411ab6b785 fix: Backspace emoji as first character in heading converts to paragraph 2023-09-14 22:19:27 -04:00
Tom Moor 924ab156f3 fix: Allow mention and emoji menus in headings 2023-09-14 09:11:18 -04:00
Tom Moor 7e17e82ac8 fix: Handle emoji field in imported documents
closes #5810
2023-09-13 22:05:21 -04:00
Tom Moor ef22a5dc52 fix: Escape to exit popover in Safari fullscreen exits fullscreen instead.
closes #5812
2023-09-13 21:23:17 -04:00
Tom Moor 56a526e930 fix: Respect fullWidth setting when creating document from template 2023-09-13 20:54:37 -04:00
Tom Moor a32857c715 fix: Cannot edit templates in settings with separate editing mode 2023-09-13 20:27:45 -04:00
Tom Moor 882408bc0e Add actions to create document from template in command bar 2023-09-12 22:09:38 -04:00
Tom Moor b80ee89588 Correctly resize full width images when table of contents is opened/closed (#5826)
* stash

* restore

* Self review
2023-09-12 18:33:25 -07:00
Apoorv Mishra d81db7e4f6 fix: typo in includesMembership function (#5823) 2023-09-12 16:56:34 +05:30
Apoorv Mishra 401d1ba871 multipart middleware (#5809)
* fix: multipart middleware

* fix: reviews
2023-09-12 10:21:58 +05:30
dependabot[bot] 99e3a305d3 chore(deps-dev): bump @relative-ci/agent from 4.1.3 to 4.1.8 (#5815)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 18:58:45 -07:00
dependabot[bot] 5e9151f02a chore(deps-dev): bump eslint-plugin-jsx-a11y from 6.4.1 to 6.7.1 (#5817)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 18:44:43 -07:00
dependabot[bot] 9e218bd4f3 chore(deps): bump socket.io from 4.6.1 to 4.7.2 (#5816)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 18:44:33 -07:00
Tom Moor d43f1b529d fix: Flipped template logic (regressed in 0856f5f) 2023-09-11 09:41:42 -04:00
Tom Moor 0856f5f6ae Move template management to settings (#5811) 2023-09-10 12:46:12 -07:00
Tom Moor ac068c0c07 fix observability regression, from 5c839998 2023-09-09 23:41:48 -04:00
Tom Moor 9602d09964 fix: Add locks to user mutations (#5805) 2023-09-09 20:26:22 -07:00
Tom Moor c22ed0c82e BaseModel -> Model 2023-09-09 22:39:08 -04:00
Tom Moor 6159973df9 fix: Only update views in collaborative server on data change (#5804) 2023-09-09 19:16:02 -07:00
Tom Moor 5c839998c1 fix: Initials do not display on notification avatars (#5803) 2023-09-09 18:01:14 -07:00
Tom Moor 80ef0a38d6 chore: More flakey test improvements (#5801) 2023-09-09 15:30:19 -07:00
Tom Moor 7270e65f0c fix: Remapping gemoji thumbs_up/down 2023-09-08 17:58:19 -04:00
Tom Moor 76845a3308 fix: Cannot leave thumbs up through emoji menu 2023-09-08 10:57:03 -04:00
Tom Moor 5c8bcc11b4 fix: App switches back to default installation language when navigating to root 2023-09-07 22:48:19 -04:00
Tom Moor d8bfb0fe5d cleanup 2023-09-07 22:36:10 -04:00
Tom Moor bb555de1ba fix: In-app document mention notifications do not link to correct doc 2023-09-07 22:17:55 -04:00
Tom Moor 127115272a feat: Return attachments when exporting an individual file (#5778) 2023-09-06 17:53:30 -07:00
Tom Moor d1de5871de fix: Flaky groups test (#5789) 2023-09-06 15:29:30 -07:00
Tom Moor ec0564eb32 chore: Test performance (#5786) 2023-09-06 14:19:21 -07:00
Tom Moor 3eb947e9a5 chore: Improve perf of server tests (#5785) 2023-09-06 04:14:49 -07:00
Tom Moor a724a21c21 fix: Error viewing revisions 2023-09-05 23:55:06 -04:00
Tom Moor c4aad4d4bf fix: Document unfurling on custom domains
closes #5781
2023-09-05 23:19:26 -04:00
Tom Moor 795fe37bd6 fix: Emoji picker should be on right hand side for RTL languages 2023-09-05 07:53:07 -04:00
Tom Moor 262590e507 perf: Improve performance of rendering context menus 2023-09-04 23:29:19 -04:00
Tom Moor 5f788012db Hide document UI while typing 2023-09-04 22:10:27 -04:00
Tom Moor 2358c3d13d refactor 2023-09-04 21:12:28 -04:00
github-actions[bot] a03b95221a chore: Auto Compress Images (#5779)
Co-authored-by: tommoor <tommoor@users.noreply.github.com>
2023-09-04 17:49:54 -07:00
Tom Moor 3223341062 feat: Add Valtown embed support 2023-09-04 20:48:24 -04:00
Tom Moor ce645b158b fix: Allow pasting Pitch embed links 2023-09-04 20:48:24 -04:00
Tom Moor 74860ed961 feat: Allow users to override team setting for seamless editing (#5772) 2023-09-04 16:19:43 -07:00
Tom Moor c376dc1011 Increase hit area on sidebar buttons 2023-09-04 16:24:58 -04:00
Tom Moor a956f54b5a Hover preview tweak 2023-09-04 16:11:06 -04:00
Tom Moor 1c99e8519a fix: URLs to internal resources should not be sent to Iframely 2023-09-04 14:46:26 -04:00
dependabot[bot] 6079b71d3c chore(deps): bump passport-slack-oauth2 from 1.1.1 to 1.2.0 (#5776)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-04 11:19:45 -07:00
dependabot[bot] 749c8dc335 chore(deps): bump react-i18next from 12.1.5 to 12.3.1 (#5775)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-04 11:19:06 -07:00
dependabot[bot] 57d1643d77 chore(deps): bump koa-compress from 5.1.0 to 5.1.1 (#5773)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-04 11:18:48 -07:00
Tom Moor 1df7a42868 Update language on /outline help text
Update Slack hooks to use zod validation

closes #5768
2023-09-04 10:40:46 -04:00
Tom Moor 02cced078f Update .env.sample 2023-09-03 19:14:29 -07:00
Tom Moor d7c331532d Add document unsubscribe link in email footer (#5762) 2023-09-03 16:04:28 -07:00
Jack Woodgate 0261e0712c fix: Safari sidebar animation #5765 (#5766) 2023-09-03 15:42:47 -07:00
Tom Moor f7111991dc Rename Tldraw (beta) -> Tldraw 2023-09-03 17:23:59 -04:00
Tom Moor 10a190cd80 fix: Add support for main and old tldraw domains
closes #5769
2023-09-03 17:23:08 -04:00
Tom Moor 3721ea2333 fix: Allow use of validations middleware in plugins 2023-09-03 16:52:46 -04:00
Tom Moor 1048ea8771 fix: Background error building plugins with no server content 2023-09-03 16:20:54 -04:00
Tom Moor a3cfef09f3 Lockfile 2023-09-03 09:12:20 -04:00
Tom Moor ef71a54120 Merge branch 'main' of github.com:outline/outline 2023-09-03 09:11:33 -04:00
Apoorv Mishra 1c7bb65c7a Document emoji picker (#4338)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-09-03 06:11:14 -07:00
Tom Moor 093ee74a90 fix: Protect against exports larger than memory/max 2023-09-02 22:11:53 -04:00
Tom Moor 0054b7152e Update LICENSE 2023-08-31 18:37:26 -07:00
Tom Moor 8b4b2ca741 fix: S3Storage incorrectly setting hostname 2023-08-31 20:44:34 -04:00
Tom Moor 911bb1f492 Prefer SF Pro on Mac 2023-08-31 20:28:00 -04:00
Tom Moor c9f0c86719 Small improvement to messages posted to Slack
Related #5295
2023-08-31 20:18:25 -04:00
Translate-O-Tron d0fe6ad93f New Crowdin updates (#5697) 2023-08-31 15:12:44 -07:00
Tom Moor 4e53029377 Use "Inter" as default typeface (#5741)
* Inter

* tweaks

* tweaks
2023-08-31 15:07:45 -07:00
Tom Moor 7abb4f9ad6 Improve validation on api/users endpoints (#5752) 2023-08-31 15:06:18 -07:00
Tom Moor dec03b9d84 fix: Remove cloud hosted check before running migrations 2023-08-30 23:03:49 -04:00
Tom Moor d591158c4d Restore sidebar toggle in settings
Cleanup some unused props
2023-08-30 20:38:09 -04:00
Tom Moor fa03f9c08d Add additional rate limits on documents API endpoints 2023-08-30 20:28:22 -04:00
Tom Moor b7055ef853 Move sidebar toggle into the sidebar itself instead of overlaying document content (#5749) 2023-08-29 18:45:03 -07:00
Tom Moor 864ddbd438 fix: Do not attempt to download non-valid urls in document create/import 2023-08-28 21:02:10 -04:00
Tom Moor 30a4303a8e chore: Remove DEPLOYMENT and SUBDOMAINS_ENABLED (#5742) 2023-08-28 17:39:58 -07:00
Tom Moor 7725f29dc7 Merge branch 'main' of github.com:outline/outline 2023-08-28 19:28:46 -04:00
dependabot[bot] 08825c7d97 chore(deps): bump i18next-fs-backend from 2.1.1 to 2.1.5 (#5745)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 16:06:40 -07:00
dependabot[bot] 448258746c chore(deps-dev): bump eslint-plugin-import from 2.26.0 to 2.28.1 (#5746)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-08-28 16:06:31 -07:00
Tom Moor b002d51ace Add support for iframes in imported HTML 2023-08-28 18:37:39 -04:00
dependabot[bot] 3e6a22e369 chore(deps): bump @babel/plugin-transform-regenerator from 7.20.5 to 7.22.10 (#5747)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 15:36:24 -07:00
Tom Moor 412f3ed9a4 Update README.md 2023-08-27 17:16:42 -07:00
Tom Moor 78ad1b867a fix: Handle base64 and remote images when creating a file (#5740) 2023-08-26 06:15:14 -07:00
Tom Moor c643f62d96 fix: Show more header options in edit mode 2023-08-24 21:51:42 -04:00
Tom Moor 79ff9309fd fix: Write revision when leaving editing mode 2023-08-24 21:18:38 -04:00
Tom Moor 9256c59e60 Add C++ to code language options, closes #5736 2023-08-24 19:52:35 -04:00
Tom Moor 1d90f98a29 fix: Remove overly aggressive AWS_ env variable validation prevents use with IAM roles 2023-08-24 18:16:52 -04:00
Tom Moor 10ec8a59b4 fix: Disable previews in notification items 2023-08-23 22:34:21 -04:00
Tom Moor dfbd89ad53 fix: Improve error message when an individual document in a large import is too large 2023-08-23 21:49:35 -04:00
Tom Moor da9a8af543 fix: Prevent rendering of undefined SVG placeholder 2023-08-23 21:35:05 -04:00
Tom Moor aada5c20cd fix: Clarify separate billing on workspaces 2023-08-23 19:44:42 -04:00
Apoorv Mishra 8f86eadc5d fix: tsc (#5732) 2023-08-23 19:23:41 +05:30
Apoorv Mishra 53c6c5599a Go-To Actions with transactions emails (#5728)
* feat: go-to actions for emails

* fix: comment

* fix: tsc without previewText

* fix: goToAction

* fix: link to original template

* fix: final comments
2023-08-23 18:43:52 +05:30
dependabot[bot] e3ba87dcb0 chore(deps-dev): bump eslint from 8.45.0 to 8.47.0 (#5722)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-22 07:07:37 -07:00
dependabot[bot] 3c5753621c chore(deps-dev): bump babel-jest from 29.6.1 to 29.6.3 (#5723)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-22 07:07:24 -07:00
Tom Moor 3366fb46cd fix: Copy Mermaid toolbar incorrectly positioned in read-only
closes #5714
2023-08-21 22:20:42 -04:00
Tom Moor 89bf5373aa chore: Add pointer to troubleshooting when nonce prevents script execution
closes #5718
2023-08-21 21:38:30 -04:00
dependabot[bot] e6b0e434ea chore(deps): bump rate-limiter-flexible from 2.4.1 to 2.4.2 (#5721)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-21 17:25:33 -07:00
dependabot[bot] 225f0dbf11 chore(deps): bump dd-trace from 3.32.1 to 3.33.0 (#5725)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-21 17:25:00 -07:00
Tom Moor 418d3305b2 feat: Add team deletion flow for cloud-hosted (#5717) 2023-08-21 17:24:46 -07:00
Tom Moor 5c07694f6b Refactor 'uploadFromUrl' to base storage implementation
Add safety around using fetch implementation
2023-08-20 13:13:17 -04:00
Tom Moor 74722b80f2 chore: Refactor file storage (#5711) 2023-08-20 07:04:34 -07:00
Apoorv Mishra 4354e1055e Keep nested docs in shared sidebar collapsed by default (#5208)
Co-authored-by: Tom Moor <tom@getoutline.com>
2023-08-20 07:04:04 -07:00
Apoorv Mishra c3a8858c6b fix: re-position hover preview correctly to prevent going out of page bounds (#5702) 2023-08-20 16:42:05 +05:30
Tom Moor 546022e5d6 fix: Allow webhooks to connct to private IPs when self-hosting, restore proxy compatability closes #5709 2023-08-20 07:03:33 -04:00
Tom Moor 33e532847e feat: Add Ukranian language support 2023-08-20 06:52:07 -04:00
Apoorv Mishra c9d62420c8 feat: send header (#5707) 2023-08-20 10:55:04 +05:30
Tom Moor cc2a1865c5 perf: Do not render KeyboardShortcuts guide unless clicked 2023-08-18 19:00:59 +02:00
Tom Moor 1ec87da8a9 fix: Add additional check for mobile device to bottom toolbar
closes #5703
2023-08-18 18:51:39 +02:00
Tom Moor d820b2a617 fix: Scrolling in desktop app sidebar is finicky 2023-08-18 18:45:38 +02:00
Tom Moor 5e7ea165b4 0.71.0 2023-08-18 11:11:32 +02:00
Tom Moor c68d55f49b fix: Inopperable image toolbar appears in read-only mode 2023-08-17 23:30:42 +02:00
Tom Moor 7e349c9db1 perf: Do not load state to calculate navigation node 2023-08-17 23:14:44 +02:00
Tom Moor 13b067fb3f fix: Document importer only replaces first base64 encoded image when there are multiple identical images in a document
closes #5653
2023-08-17 22:46:40 +02:00
Tom Moor 41c346d105 Revert "chore: Update browserslist"
This reverts commit fce90df3aa.
2023-08-17 10:41:36 +02:00
Tom Moor 4788ab3bd6 fix: Add support for Airtable share links with app ID 2023-08-16 22:20:55 +02:00
Tom Moor 5f00b4f744 fix: Incorrect error shown to user when connection limit is reached (#5695) 2023-08-16 12:39:56 -07:00
Tom Moor fd600ced09 fix: Flash of 'not found' screen when deleting a collection 2023-08-15 21:39:01 +02:00
Tom Moor 0047384d70 fix: Code blocks nested in list do not get line numbers 2023-08-15 19:52:16 +02:00
Tom Moor 8bff566c30 fix: Sidebar item misalignment 2023-08-15 11:32:19 +02:00
Tom Moor fce90df3aa chore: Update browserslist 2023-08-15 11:26:48 +02:00
Tom Moor 28ae1af2a3 fix: ctrl+a does not work on Windows in code block (#5692) 2023-08-14 13:16:12 -07:00
Tom Moor 9f0534d544 chore: Bump vite
Reduces full page reloads in dev, increase perf
2023-08-14 20:51:08 +02:00
Tom Moor 4edfab20fe fix: Bug with local dynamic reloading since moving to SSL 2023-08-14 20:48:49 +02:00
Philip Standt c38e045df2 feat: support self hosted grist (#5655)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-08-14 11:46:24 -07:00
Tom Moor b7bfc4bb1a chore: Remove optimize imports to allow vite upgrade (#5691) 2023-08-14 11:44:58 -07:00
dependabot[bot] a71ad43c31 chore(deps): bump nodemailer and @types/nodemailer (#5689)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 10:15:32 -07:00
dependabot[bot] 199fa5844e chore(deps): bump react-window from 1.8.7 to 1.8.9 (#5685)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 10:15:19 -07:00
dependabot[bot] b466f1c8bb chore(deps): bump ws from 7.5.6 to 7.5.9 (#5686)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 10:14:56 -07:00
dependabot[bot] 503e4e1f71 chore(deps-dev): bump @typescript-eslint/eslint-plugin from 5.61.0 to 5.62.0 (#5688)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 10:14:40 -07:00
dependabot[bot] 2bc52be2cf chore(deps): bump email-providers from 1.13.1 to 1.14.0 (#5687)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 09:28:58 -07:00
github-actions[bot] 3ba730943c chore: Auto Compress Images (#5682)
Co-authored-by: tommoor <tommoor@users.noreply.github.com>
2023-08-12 02:47:12 -07:00
Jack Woodgate 6828718cf0 feat: Add Google Maps Embed (#5667) (#5673)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-08-12 02:45:21 -07:00
Tom Moor 9749a53558 feat: Handle pasting iframe for supported embed 2023-08-12 11:44:16 +02:00
Tom Moor f4e4992508 fix: Remove fetch-with-proxy from DeliverWebhookTask 2023-08-11 22:35:38 +02:00
Tom Moor cf2f0b1b5c fix: Flash of empty state in sidebar when creating a new collection 2023-08-11 22:34:14 +02:00
Tom Moor 4a4ea0e531 fix: Text alignment on collection empty state 2023-08-11 22:30:48 +02:00
Tom Moor 8830773acb fix: Mobile styling bugs 2023-08-11 22:26:40 +02:00
Tom Moor f5d2c7890a fix: Unable to create collection with no access permission 2023-08-10 15:13:40 +02:00
Apoorv Mishra 434812dbe3 Allow vite to serve files from workspace's parent directory (#5675)
* fix: allow vite to serve files from workspace parent dir

* trigger ci

* trigger ci
2023-08-09 21:52:44 +05:30
Tom Moor ed5671209a New Crowdin updates (#5647) 2023-08-09 04:23:00 -07:00
Tom Moor c32cec7bff Add support for SSL in development (#5668) 2023-08-09 04:21:41 -07:00
1034 changed files with 38206 additions and 23410 deletions
+21 -14
View File
@@ -3,7 +3,7 @@ version: 2.1
defaults: &defaults
working_directory: ~/outline
docker:
- image: cimg/node:18.12
- image: cimg/node:20.10
- image: cimg/redis:5.0
- image: cimg/postgres:14.2
environment:
@@ -36,12 +36,12 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: install-deps
command: yarn install --frozen-lockfile
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
paths:
- ./node_modules
lint:
@@ -49,7 +49,7 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: lint
command: yarn lint
@@ -58,7 +58,7 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: typescript
command: yarn tsc
@@ -67,7 +67,7 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: test
command: yarn test:app
@@ -76,22 +76,25 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: test
command: yarn test:shared
test-server:
<<: *defaults
parallelism: 3
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: migrate
command: ./node_modules/.bin/sequelize db:migrate --url $DATABASE_URL_TEST
- run:
name: test
command: yarn test:server --forceExit
command: |
TESTFILES=$(circleci tests glob "server/**/*.test.ts" | circleci tests split)
yarn test --maxWorkers=2 $TESTFILES
bundle-size:
<<: *defaults
environment:
@@ -99,7 +102,7 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: build-vite
command: yarn vite:build
@@ -142,7 +145,12 @@ jobs:
command: docker push $BASE_IMAGE_NAME:latest
- run:
name: Build and push Docker image
command: docker buildx build -t $IMAGE_NAME:latest -t $IMAGE_NAME:${CIRCLE_TAG/v/''} --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --push .
command: |
if [[ "$CIRCLE_TAG" == *"-"* ]]; then
docker buildx build -t $IMAGE_NAME:${CIRCLE_TAG/v/''} --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --push .
else
docker buildx build -t $IMAGE_NAME:latest -t $IMAGE_NAME:${CIRCLE_TAG/v/''} --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --push .
fi
workflows:
version: 2
@@ -166,9 +174,8 @@ workflows:
- build
- bundle-size:
requires:
- test-app
- test-shared
- test-server
- build
- types
build-docker:
jobs:
-1
View File
@@ -13,5 +13,4 @@ app.json
crowdin.yml
build
docker-compose.yml
fakes3
node_modules
+19 -4
View File
@@ -30,7 +30,7 @@ REDIS_URL=redis://localhost:6379
# URL should point to the fully qualified, publicly accessible URL. If using a
# proxy the port in URL and PORT may be different.
URL=http://localhost:3000
URL=https://app.outline.dev:3000
PORT=3000
# See [documentation](docs/SERVICES.md) on running a separate collaboration
@@ -51,10 +51,20 @@ AWS_REGION=xx-xxxx-x
AWS_S3_ACCELERATE_URL=
AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569
AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here
AWS_S3_UPLOAD_MAX_SIZE=26214400
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private
# Specify what storage system to use. Possible value is one of "s3" or "local".
# For "local", the avatar images and document attachments will be saved on local disk.
FILE_STORAGE=local
# If "local" is configured for FILE_STORAGE above, then this sets the parent directory under
# which all attachments/images go. Make sure that the process has permissions to create
# this path and also to write files to it.
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
# Maximum allowed size for the uploaded attachment.
FILE_STORAGE_UPLOAD_MAX_SIZE=26214400
# –––––––––––––– AUTHENTICATION ––––––––––––––
@@ -183,5 +193,10 @@ RATE_LIMITER_REQUESTS=1000
RATE_LIMITER_DURATION_WINDOW=60
# Iframely API config
IFRAMELY_URL=
IFRAMELY_API_KEY=
# IFRAMELY_URL=
# IFRAMELY_API_KEY=
# Enable unsafe-inline in script-src CSP directive
# Setting it to true allows React dev tools add-on in
# Firefox to successfully detect the project
DEVELOPMENT_UNSAFE_INLINE_CSP=false
+3 -1
View File
@@ -21,7 +21,7 @@
"eslint-plugin-import",
"eslint-plugin-node",
"eslint-plugin-react",
"import"
"eslint-plugin-lodash"
],
"rules": {
"eqeqeq": 2,
@@ -37,6 +37,7 @@
"component": true,
"html": true
}],
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-misused-promises": [
@@ -55,6 +56,7 @@
],
"padding-line-between-statements": ["error", { "blankLine": "always", "prev": "*", "next": "export" }],
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
"lodash/import-scope": ["warn", "method"],
"import/no-named-as-default": "off",
"import/no-named-as-default-member": "off",
"import/newline-after-import": 2,
+1 -1
View File
@@ -7,7 +7,7 @@ node_modules/*
npm-debug.log
stats.json
.DS_Store
fakes3/*
data/*
.idea
*.pem
*.key
+5 -6
View File
@@ -1,5 +1,6 @@
{
"workerIdleMemoryLimit": "0.75",
"maxWorkers": "50%",
"projects": [
{
"displayName": "server",
@@ -8,13 +9,11 @@
"^@server/(.*)$": "<rootDir>/server/$1",
"^@shared/(.*)$": "<rootDir>/shared/$1"
},
"setupFiles": [
"<rootDir>/__mocks__/console.js",
"<rootDir>/server/test/env.ts"
],
"setupFiles": ["<rootDir>/__mocks__/console.js", "<rootDir>/server/test/env.ts"],
"setupFilesAfterEnv": ["<rootDir>/server/test/setup.ts"],
"testEnvironment": "node",
"runner": "@getoutline/jest-runner-serial"
"globalSetup": "<rootDir>/server/test/globalSetup.js",
"globalTeardown": "<rootDir>/server/test/globalTeardown.js",
"testEnvironment": "node"
},
{
"displayName": "app",
+11 -2
View File
@@ -5,7 +5,7 @@ ARG APP_PATH
WORKDIR $APP_PATH
# ---
FROM node:18-alpine AS runner
FROM node:20-alpine AS runner
RUN apk update && apk add --no-cache curl && apk add --no-cache ca-certificates
@@ -24,7 +24,16 @@ COPY --from=base $APP_PATH/package.json ./package.json
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs $APP_PATH/build
chown -R nodejs:nodejs $APP_PATH/build && \
mkdir -p /var/lib/outline && \
chown -R nodejs:nodejs /var/lib/outline
ENV FILE_STORAGE_LOCAL_ROOT_DIR /var/lib/outline/data
RUN mkdir -p "$FILE_STORAGE_LOCAL_ROOT_DIR" && \
chown -R nodejs:nodejs "$FILE_STORAGE_LOCAL_ROOT_DIR" && \
chmod 1777 "$FILE_STORAGE_LOCAL_ROOT_DIR"
VOLUME /var/lib/outline/data
USER nodejs
+1 -1
View File
@@ -1,5 +1,5 @@
ARG APP_PATH=/opt/outline
FROM node:18-alpine AS deps
FROM node:20-alpine AS deps
ARG APP_PATH
WORKDIR $APP_PATH
+2 -2
View File
@@ -3,7 +3,7 @@ Business Source License 1.1
Parameters
Licensor: General Outline, Inc.
Licensed Work: Outline 0.64.0
Licensed Work: Outline 0.71.0
The Licensed Work is (c) 2020 General Outline, Inc.
Additional Use Grant: You may make use of the Licensed Work, provided that
you may not use the Licensed Work for a Document
@@ -15,7 +15,7 @@ Additional Use Grant: You may make use of the Licensed Work, provided that
Licensed Work by creating teams and documents
controlled by such third parties.
Change Date: 2026-05-23
Change Date: 2027-08-18
Change License: Apache License, Version 2.0
+6 -5
View File
@@ -1,5 +1,6 @@
up:
docker-compose up -d redis postgres s3
docker-compose up -d redis postgres
yarn install-local-ssl
yarn install --pure-lockfile
yarn dev:watch
@@ -7,17 +8,17 @@ build:
docker-compose build --pull outline
test:
docker-compose up -d redis postgres s3
docker-compose up -d redis postgres
yarn sequelize db:drop --env=test
yarn sequelize db:create --env=test
yarn sequelize db:migrate --env=test
NODE_ENV=test yarn sequelize db:migrate --env=test
yarn test
watch:
docker-compose up -d redis postgres s3
docker-compose up -d redis postgres
yarn sequelize db:drop --env=test
yarn sequelize db:create --env=test
yarn sequelize db:migrate --env=test
NODE_ENV=test yarn sequelize db:migrate --env=test
yarn test:watch
destroy:
+5 -1
View File
@@ -96,6 +96,10 @@ Or to run migrations on test database:
yarn sequelize db:migrate --env test
```
## License
# Activity
![Alt](https://repobeats.axiom.co/api/embed/ff2e4e6918afff1acf9deb72d1ba6b071d586178.svg "Repobeats analytics image")
# License
Outline is [BSL 1.1 licensed](LICENSE).
+5 -5
View File
@@ -128,11 +128,6 @@
"description": "Live web link to your bucket. For CNAMEs, https://yourbucket.example.com",
"required": false
},
"AWS_S3_UPLOAD_MAX_SIZE": {
"description": "Maximum file upload size in bytes",
"value": "26214400",
"required": false
},
"AWS_S3_FORCE_PATH_STYLE": {
"description": "Use path-style URL's for connecting to S3 instead of subdomain. This is useful for S3-compatible storage.",
"value": "true",
@@ -148,6 +143,11 @@
"description": "S3 canned ACL for document attachments",
"required": false
},
"FILE_STORAGE_UPLOAD_MAX_SIZE": {
"description": "Maximum file upload size in bytes",
"value": "26214400",
"required": false
},
"SMTP_HOST": {
"description": "smtp.example.com (optional)",
"required": false
+2 -2
View File
@@ -2,10 +2,10 @@
"extends": [
"../.eslintrc",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:react-hooks/recommended"
],
"plugins": [
"eslint-plugin-react-hooks",
"eslint-plugin-react-hooks"
],
"env": {
"jest": true,
+90 -7
View File
@@ -1,6 +1,7 @@
import { ToolsIcon, TrashIcon, UserIcon } from "outline-icons";
import copy from "copy-to-clipboard";
import { CopyIcon, ToolsIcon, TrashIcon, UserIcon } from "outline-icons";
import * as React from "react";
import stores from "~/stores";
import { toast } from "sonner";
import { createAction } from "~/actions";
import { DeveloperSection } from "~/actions/sections";
import env from "~/env";
@@ -8,6 +9,71 @@ import { client } from "~/utils/ApiClient";
import Logger from "~/utils/Logger";
import { deleteAllDatabases } from "~/utils/developer";
export const copyId = createAction({
name: ({ t }) => t("Copy ID"),
icon: <CopyIcon />,
keywords: "uuid",
section: DeveloperSection,
children: ({
currentTeamId,
currentUserId,
activeCollectionId,
activeDocumentId,
}) => {
function copyAndToast(text: string | null | undefined) {
if (text) {
copy(text);
toast.success("Copied to clipboard");
}
}
return [
createAction({
name: "Copy User ID",
section: DeveloperSection,
icon: <CopyIcon />,
visible: () => !!currentUserId,
perform: () => copyAndToast(currentUserId),
}),
createAction({
name: "Copy Team ID",
section: DeveloperSection,
icon: <CopyIcon />,
visible: () => !!currentTeamId,
perform: () => copyAndToast(currentTeamId),
}),
createAction({
name: "Copy Collection ID",
icon: <CopyIcon />,
section: DeveloperSection,
visible: () => !!activeCollectionId,
perform: () => copyAndToast(activeCollectionId),
}),
createAction({
name: "Copy Document ID",
icon: <CopyIcon />,
section: DeveloperSection,
visible: () => !!activeDocumentId,
perform: () => copyAndToast(activeDocumentId),
}),
createAction({
name: "Copy Team ID",
icon: <CopyIcon />,
section: DeveloperSection,
visible: () => !!currentTeamId,
perform: () => copyAndToast(currentTeamId),
}),
createAction({
name: "Copy Release ID",
icon: <CopyIcon />,
section: DeveloperSection,
visible: () => !!env.RELEASE,
perform: () => copyAndToast(env.RELEASE),
}),
];
},
});
export const clearIndexedDB = createAction({
name: ({ t }) => t("Delete IndexedDB cache"),
icon: <TrashIcon />,
@@ -15,7 +81,7 @@ export const clearIndexedDB = createAction({
section: DeveloperSection,
perform: async ({ t }) => {
await deleteAllDatabases();
stores.toasts.showToast(t("IndexedDB cache deleted"));
toast.message(t("IndexedDB cache deleted"));
},
});
@@ -29,20 +95,31 @@ export const createTestUsers = createAction({
try {
await client.post("/developer.create_test_users", { count });
stores.toasts.showToast(`${count} test users created`);
toast.message(`${count} test users created`);
} catch (err) {
stores.toasts.showToast(err.message, { type: "error" });
toast.error(err.message);
}
},
});
export const createToast = createAction({
name: "Create toast",
section: DeveloperSection,
visible: () => env.ENVIRONMENT === "development",
perform: async () => {
toast.message("Hello world", {
duration: 30000,
});
},
});
export const toggleDebugLogging = createAction({
name: ({ t }) => t("Toggle debug logging"),
icon: <ToolsIcon />,
section: DeveloperSection,
perform: async ({ t }) => {
Logger.debugLoggingEnabled = !Logger.debugLoggingEnabled;
stores.toasts.showToast(
toast.message(
Logger.debugLoggingEnabled
? t("Debug logging enabled")
: t("Debug logging disabled")
@@ -56,7 +133,13 @@ export const developer = createAction({
icon: <ToolsIcon />,
iconInContextMenu: false,
section: DeveloperSection,
children: [clearIndexedDB, toggleDebugLogging, createTestUsers],
children: [
copyId,
clearIndexedDB,
toggleDebugLogging,
createToast,
createTestUsers,
],
});
export const rootDeveloperActions = [developer];
+186 -47
View File
@@ -1,3 +1,4 @@
import copy from "copy-to-clipboard";
import invariant from "invariant";
import {
DownloadIcon,
@@ -19,19 +20,25 @@ import {
ArchiveIcon,
ShuffleIcon,
HistoryIcon,
LightBulbIcon,
GraphIcon,
UnpublishIcon,
PublishIcon,
CommentIcon,
GlobeIcon,
CopyIcon,
} from "outline-icons";
import * as React from "react";
import { toast } from "sonner";
import { ExportContentType, TeamPreference } from "@shared/types";
import MarkdownHelper from "@shared/utils/MarkdownHelper";
import { getEventFiles } from "@shared/utils/files";
import DocumentDelete from "~/scenes/DocumentDelete";
import DocumentMove from "~/scenes/DocumentMove";
import DocumentPermanentDelete from "~/scenes/DocumentPermanentDelete";
import DocumentPublish from "~/scenes/DocumentPublish";
import DocumentTemplatizeDialog from "~/components/DocumentTemplatizeDialog";
import DuplicateDialog from "~/components/DuplicateDialog";
import SharePopover from "~/components/Sharing";
import { createAction } from "~/actions";
import { DocumentSection } from "~/actions/sections";
import env from "~/env";
@@ -42,6 +49,8 @@ import {
homePath,
newDocumentPath,
searchPath,
documentPath,
urlify,
} from "~/utils/routeHelpers";
export const openDocument = createAction({
@@ -86,6 +95,48 @@ export const createDocument = createAction({
}),
});
export const createDocumentFromTemplate = createAction({
name: ({ t }) => t("New from template"),
analyticsName: "New document",
section: DocumentSection,
icon: <NewDocumentIcon />,
keywords: "create",
visible: ({ currentTeamId, activeDocumentId, stores }) =>
!!currentTeamId &&
!!activeDocumentId &&
!!stores.documents.get(activeDocumentId)?.template &&
stores.policies.abilities(currentTeamId).createDocument,
perform: ({ activeCollectionId, activeDocumentId, inStarredSection }) =>
history.push(
newDocumentPath(activeCollectionId, { templateId: activeDocumentId }),
{
starred: inStarredSection,
}
),
});
export const createNestedDocument = createAction({
name: ({ t }) => t("New nested document"),
analyticsName: "New document",
section: DocumentSection,
icon: <NewDocumentIcon />,
keywords: "create",
visible: ({ currentTeamId, activeDocumentId, stores }) =>
!!currentTeamId &&
!!activeDocumentId &&
stores.policies.abilities(currentTeamId).createDocument &&
stores.policies.abilities(activeDocumentId).createChildDocument,
perform: ({ activeCollectionId, activeDocumentId, inStarredSection }) =>
history.push(
newDocumentPath(activeCollectionId, {
parentDocumentId: activeDocumentId,
}),
{
starred: inStarredSection,
}
),
});
export const starDocument = createAction({
name: ({ t }) => t("Star"),
analyticsName: "Star document",
@@ -148,7 +199,7 @@ export const publishDocument = createAction({
}
const document = stores.documents.get(activeDocumentId);
return (
!!document?.isDraft && stores.policies.abilities(activeDocumentId).update
!!document?.isDraft && stores.policies.abilities(activeDocumentId).publish
);
},
perform: async ({ activeDocumentId, stores, t }) => {
@@ -165,9 +216,11 @@ export const publishDocument = createAction({
await document.save(undefined, {
publish: true,
});
stores.toasts.showToast(t("Document published"), {
type: "success",
});
toast.success(
t("Published {{ documentName }}", {
documentName: document.noun,
})
);
} else if (document) {
stores.dialogs.openModal({
title: t("Publish document"),
@@ -195,12 +248,21 @@ export const unpublishDocument = createAction({
}
const document = stores.documents.get(activeDocumentId);
if (!document) {
return;
}
await document?.unpublish();
try {
await document.unpublish();
stores.toasts.showToast(t("Document unpublished"), {
type: "success",
});
toast.success(
t("Unpublished {{ documentName }}", {
documentName: document.noun,
})
);
} catch (err) {
toast.error(err.message);
}
},
});
@@ -230,9 +292,7 @@ export const subscribeDocument = createAction({
await document?.subscribe();
stores.toasts.showToast(t("Subscribed to document notifications"), {
type: "success",
});
toast.success(t("Subscribed to document notifications"));
},
});
@@ -262,8 +322,39 @@ export const unsubscribeDocument = createAction({
await document?.unsubscribe(currentUserId);
stores.toasts.showToast(t("Unsubscribed from document notifications"), {
type: "success",
toast.success(t("Unsubscribed from document notifications"));
},
});
export const shareDocument = createAction({
name: ({ t }) => t("Share"),
analyticsName: "Share document",
section: DocumentSection,
icon: <GlobeIcon />,
perform: async ({ activeDocumentId, stores, currentUserId, t }) => {
if (!activeDocumentId || !currentUserId) {
return;
}
const document = stores.documents.get(activeDocumentId);
const share = stores.shares.getByDocumentId(activeDocumentId);
const sharedParent = stores.shares.getByDocumentParents(activeDocumentId);
if (!document) {
return;
}
stores.dialogs.openModal({
title: t("Share this document"),
isCentered: true,
content: (
<SharePopover
document={document}
share={share}
sharedParent={sharedParent}
onRequestClose={stores.dialogs.closeAllModals}
visible
/>
),
});
},
});
@@ -303,15 +394,11 @@ export const downloadDocumentAsPDF = createAction({
return;
}
const id = stores.toasts.showToast(`${t("Exporting")}`, {
type: "loading",
timeout: 30 * 1000,
});
const id = toast.loading(`${t("Exporting")}`);
const document = stores.documents.get(activeDocumentId);
document
return document
?.download(ExportContentType.Pdf)
.finally(() => id && stores.toasts.hideToast(id));
.finally(() => id && toast.dismiss(id));
},
});
@@ -348,6 +435,47 @@ export const downloadDocument = createAction({
],
});
export const copyDocumentAsMarkdown = createAction({
name: ({ t }) => t("Copy as Markdown"),
section: DocumentSection,
keywords: "clipboard",
visible: ({ activeDocumentId }) => !!activeDocumentId,
perform: ({ stores, activeDocumentId, t }) => {
const document = activeDocumentId
? stores.documents.get(activeDocumentId)
: undefined;
if (document) {
copy(MarkdownHelper.toMarkdown(document));
toast.success(t("Markdown copied to clipboard"));
}
},
});
export const copyDocumentLink = createAction({
name: ({ t }) => t("Copy link"),
section: DocumentSection,
keywords: "clipboard",
visible: ({ activeDocumentId }) => !!activeDocumentId,
perform: ({ stores, activeDocumentId, t }) => {
const document = activeDocumentId
? stores.documents.get(activeDocumentId)
: undefined;
if (document) {
copy(urlify(documentPath(document)));
toast.success(t("Link copied to clipboard"));
}
},
});
export const copyDocument = createAction({
name: ({ t }) => t("Copy"),
analyticsName: "Copy document",
section: DocumentSection,
icon: <CopyIcon />,
keywords: "clipboard",
children: [copyDocumentLink, copyDocumentAsMarkdown],
});
export const duplicateDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Duplicate") : t("Duplicate document"),
@@ -356,7 +484,7 @@ export const duplicateDocument = createAction({
icon: <DuplicateIcon />,
keywords: "copy",
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).update,
!!activeDocumentId && stores.policies.abilities(activeDocumentId).duplicate,
perform: async ({ activeDocumentId, t, stores }) => {
if (!activeDocumentId) {
return;
@@ -364,11 +492,19 @@ export const duplicateDocument = createAction({
const document = stores.documents.get(activeDocumentId);
invariant(document, "Document must exist");
const duped = await document.duplicate();
// when duplicating, go straight to the duplicated document content
history.push(duped.url);
stores.toasts.showToast(t("Document duplicated"), {
type: "success",
stores.dialogs.openModal({
title: t("Copy document"),
isCentered: true,
content: (
<DuplicateDialog
document={document}
onSubmit={(response) => {
stores.dialogs.closeAllModals();
history.push(documentPath(response[0]));
}}
/>
),
});
},
});
@@ -414,12 +550,10 @@ export const pinDocumentToCollection = createAction({
const collection = stores.collections.get(activeCollectionId);
if (!collection || !location.pathname.startsWith(collection?.url)) {
stores.toasts.showToast(t("Pinned to collection"));
toast.success(t("Pinned to collection"));
}
} catch (err) {
stores.toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
});
@@ -456,12 +590,10 @@ export const pinDocumentToHome = createAction({
await document?.pin();
if (location.pathname !== homePath()) {
stores.toasts.showToast(t("Pinned to team home"));
toast.success(t("Pinned to home"));
}
} catch (err) {
stores.toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
});
@@ -504,7 +636,7 @@ export const importDocument = createAction({
return false;
},
perform: ({ activeCollectionId, activeDocumentId, stores }) => {
const { documents, toasts } = stores;
const { documents } = stores;
const input = document.createElement("input");
input.type = "file";
input.accept = documents.importFileTypes.join(", ");
@@ -524,9 +656,7 @@ export const importDocument = createAction({
);
history.push(document.url);
} catch (err) {
toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
throw err;
}
};
@@ -647,15 +777,13 @@ export const archiveDocument = createAction({
}
await document.archive();
stores.toasts.showToast(t("Document archived"), {
type: "success",
});
toast.success(t("Document archived"));
}
},
});
export const deleteDocument = createAction({
name: ({ t }) => t("Delete"),
name: ({ t }) => `${t("Delete")}`,
analyticsName: "Delete document",
section: DocumentSection,
icon: <TrashIcon />,
@@ -733,8 +861,7 @@ export const openDocumentComments = createAction({
const can = stores.policies.abilities(activeDocumentId ?? "");
return (
!!activeDocumentId &&
can.read &&
!can.restore &&
can.comment &&
!!stores.auth.team?.getPreference(TeamPreference.Commenting)
);
},
@@ -772,10 +899,19 @@ export const openDocumentInsights = createAction({
name: ({ t }) => t("Insights"),
analyticsName: "Open document insights",
section: DocumentSection,
icon: <LightBulbIcon />,
icon: <GraphIcon />,
visible: ({ activeDocumentId, stores }) => {
const can = stores.policies.abilities(activeDocumentId ?? "");
return !!activeDocumentId && can.read;
const document = activeDocumentId
? stores.documents.get(activeDocumentId)
: undefined;
return (
!!activeDocumentId &&
can.read &&
!document?.isTemplate &&
!document?.isDeleted
);
},
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
@@ -797,6 +933,8 @@ export const rootDocumentActions = [
deleteDocument,
importDocument,
downloadDocument,
copyDocumentLink,
copyDocumentAsMarkdown,
starDocument,
unstarDocument,
publishDocument,
@@ -813,4 +951,5 @@ export const rootDocumentActions = [
openDocumentComments,
openDocumentHistory,
openDocumentInsights,
shareDocument,
];
+12 -14
View File
@@ -6,14 +6,15 @@ import {
EditIcon,
OpenIcon,
SettingsIcon,
ShapesIcon,
KeyboardIcon,
EmailIcon,
LogoutIcon,
ProfileIcon,
BrowserIcon,
ShapesIcon,
} from "outline-icons";
import * as React from "react";
import { isMac } from "@shared/utils/browser";
import {
developersUrl,
changelogUrl,
@@ -26,14 +27,12 @@ import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { createAction } from "~/actions";
import { NavigationSection, RecentSearchesSection } from "~/actions/sections";
import Desktop from "~/utils/Desktop";
import { isMac } from "~/utils/browser";
import history from "~/utils/history";
import isCloudHosted from "~/utils/isCloudHosted";
import {
homePath,
searchPath,
draftsPath,
templatesPath,
archivePath,
trashPath,
settingsPath,
@@ -67,15 +66,6 @@ export const navigateToDrafts = createAction({
visible: ({ location }) => location.pathname !== draftsPath(),
});
export const navigateToTemplates = createAction({
name: ({ t }) => t("Templates"),
analyticsName: "Navigate to templates",
section: NavigationSection,
icon: <ShapesIcon />,
perform: () => history.push(templatesPath()),
visible: ({ location }) => location.pathname !== templatesPath(),
});
export const navigateToArchive = createAction({
name: ({ t }) => t("Archive"),
analyticsName: "Navigate to archive",
@@ -103,7 +93,7 @@ export const navigateToSettings = createAction({
icon: <SettingsIcon />,
visible: ({ stores }) =>
stores.policies.abilities(stores.auth.team?.id || "").update,
perform: () => history.push(settingsPath("details")),
perform: () => history.push(settingsPath()),
});
export const navigateToProfileSettings = createAction({
@@ -115,6 +105,15 @@ export const navigateToProfileSettings = createAction({
perform: () => history.push(settingsPath()),
});
export const navigateToTemplateSettings = createAction({
name: ({ t }) => t("Templates"),
analyticsName: "Navigate to template settings",
section: NavigationSection,
iconInContextMenu: false,
icon: <ShapesIcon />,
perform: () => history.push(settingsPath("templates")),
});
export const navigateToNotificationSettings = createAction({
name: ({ t }) => t("Notifications"),
analyticsName: "Navigate to notification settings",
@@ -216,7 +215,6 @@ export const logout = createAction({
export const rootNavigationActions = [
navigateToHome,
navigateToDrafts,
navigateToTemplates,
navigateToArchive,
navigateToTrash,
downloadApp,
+2 -3
View File
@@ -2,6 +2,7 @@ import copy from "copy-to-clipboard";
import { LinkIcon, RestoreIcon } from "outline-icons";
import * as React from "react";
import { matchPath } from "react-router-dom";
import { toast } from "sonner";
import stores from "~/stores";
import { createAction } from "~/actions";
import { RevisionSection } from "~/actions/sections";
@@ -68,9 +69,7 @@ export const copyLinkToRevision = createAction({
copy(url, {
format: "text/plain",
onCopy: () => {
stores.toasts.showToast(t("Link copied"), {
type: "info",
});
toast.message(t("Link copied"));
},
});
},
+6 -5
View File
@@ -1,5 +1,6 @@
import { flattenDeep } from "lodash";
import flattenDeep from "lodash/flattenDeep";
import * as React from "react";
import { toast } from "sonner";
import { Optional } from "utility-types";
import { v4 as uuidv4 } from "uuid";
import {
@@ -77,9 +78,7 @@ export function actionToMenuItem(
try {
action.perform?.(context);
} catch (err) {
context.stores.toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
selected: action.selected?.(context),
@@ -117,6 +116,8 @@ export function actionToKBar(
icon: resolvedIcon,
perform: action.perform ? () => action.perform?.(context) : undefined,
},
].concat(
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
].concat(children.map((child) => ({ ...child, parent: action.id })));
children.map((child) => ({ ...child, parent: child.parent ?? action.id }))
);
}
+1 -1
View File
@@ -1,6 +1,6 @@
/* eslint-disable prefer-rest-params */
/* global ga */
import { escape } from "lodash";
import escape from "lodash/escape";
import * as React from "react";
import { IntegrationService } from "@shared/types";
import env from "~/env";
-41
View File
@@ -1,41 +0,0 @@
import * as React from "react";
type Props = {
size?: number;
fill?: string;
className?: string;
};
function SlackLogo({ size = 34, fill = "#FFF", className }: Props) {
return (
<svg
fill={fill}
width={size}
height={size}
viewBox="0 0 34 34"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g stroke="none" strokeWidth="1" fillRule="evenodd">
<g transform="translate(0.000000, 17.822581)">
<path d="M7.23870968,3.61935484 C7.23870968,5.56612903 5.6483871,7.15645161 3.7016129,7.15645161 C1.75483871,7.15645161 0.164516129,5.56612903 0.164516129,3.61935484 C0.164516129,1.67258065 1.75483871,0.0822580645 3.7016129,0.0822580645 L7.23870968,0.0822580645 L7.23870968,3.61935484 Z" />
<path d="M9.02096774,3.61935484 C9.02096774,1.67258065 10.6112903,0.0822580645 12.5580645,0.0822580645 C14.5048387,0.0822580645 16.0951613,1.67258065 16.0951613,3.61935484 L16.0951613,12.4758065 C16.0951613,14.4225806 14.5048387,16.0129032 12.5580645,16.0129032 C10.6112903,16.0129032 9.02096774,14.4225806 9.02096774,12.4758065 C9.02096774,12.4758065 9.02096774,3.61935484 9.02096774,3.61935484 Z" />
</g>
<g>
<path d="M12.5580645,7.23870968 C10.6112903,7.23870968 9.02096774,5.6483871 9.02096774,3.7016129 C9.02096774,1.75483871 10.6112903,0.164516129 12.5580645,0.164516129 C14.5048387,0.164516129 16.0951613,1.75483871 16.0951613,3.7016129 L16.0951613,7.23870968 L12.5580645,7.23870968 Z" />
<path d="M12.5580645,9.02096774 C14.5048387,9.02096774 16.0951613,10.6112903 16.0951613,12.5580645 C16.0951613,14.5048387 14.5048387,16.0951613 12.5580645,16.0951613 L3.7016129,16.0951613 C1.75483871,16.0951613 0.164516129,14.5048387 0.164516129,12.5580645 C0.164516129,10.6112903 1.75483871,9.02096774 3.7016129,9.02096774 C3.7016129,9.02096774 12.5580645,9.02096774 12.5580645,9.02096774 Z" />
</g>
<g transform="translate(17.822581, 0.000000)">
<path d="M8.93870968,12.5580645 C8.93870968,10.6112903 10.5290323,9.02096774 12.4758065,9.02096774 C14.4225806,9.02096774 16.0129032,10.6112903 16.0129032,12.5580645 C16.0129032,14.5048387 14.4225806,16.0951613 12.4758065,16.0951613 L8.93870968,16.0951613 L8.93870968,12.5580645 Z" />
<path d="M7.15645161,12.5580645 C7.15645161,14.5048387 5.56612903,16.0951613 3.61935484,16.0951613 C1.67258065,16.0951613 0.0822580645,14.5048387 0.0822580645,12.5580645 L0.0822580645,3.7016129 C0.0822580645,1.75483871 1.67258065,0.164516129 3.61935484,0.164516129 C5.56612903,0.164516129 7.15645161,1.75483871 7.15645161,3.7016129 L7.15645161,12.5580645 Z" />
</g>
<g transform="translate(17.822581, 17.822581)">
<path d="M3.61935484,8.93870968 C5.56612903,8.93870968 7.15645161,10.5290323 7.15645161,12.4758065 C7.15645161,14.4225806 5.56612903,16.0129032 3.61935484,16.0129032 C1.67258065,16.0129032 0.0822580645,14.4225806 0.0822580645,12.4758065 L0.0822580645,8.93870968 L3.61935484,8.93870968 Z" />
<path d="M3.61935484,7.15645161 C1.67258065,7.15645161 0.0822580645,5.56612903 0.0822580645,3.61935484 C0.0822580645,1.67258065 1.67258065,0.0822580645 3.61935484,0.0822580645 L12.4758065,0.0822580645 C14.4225806,0.0822580645 16.0129032,1.67258065 16.0129032,3.61935484 C16.0129032,5.56612903 14.4225806,7.15645161 12.4758065,7.15645161 L3.61935484,7.15645161 Z" />
</g>
</g>
</svg>
);
}
export default SlackLogo;
+5 -3
View File
@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Redirect } from "react-router-dom";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import { changeLanguage } from "~/utils/language";
import LoadingIndicator from "./LoadingIndicator";
@@ -13,10 +14,11 @@ type Props = {
const Authenticated = ({ children }: Props) => {
const { auth } = useStores();
const { i18n } = useTranslation();
const language = auth.user?.language;
const user = useCurrentUser({ rejectOnEmpty: false });
const language = user?.language;
// Watching for language changes here as this is the earliest point we have
// the user available and means we can start loading translations faster
// Watching for language changes here as this is the earliest point we might have the user
// available and means we can start loading translations faster
React.useEffect(() => {
void changeLanguage(language, i18n);
}, [i18n, language]);
+23 -15
View File
@@ -12,6 +12,7 @@ import Sidebar from "~/components/Sidebar";
import SidebarRight from "~/components/Sidebar/Right";
import SettingsSidebar from "~/components/Sidebar/Settings";
import type { Editor as TEditor } from "~/editor";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import history from "~/utils/history";
@@ -25,6 +26,7 @@ import {
matchDocumentInsights,
} from "~/utils/routeHelpers";
import Fade from "./Fade";
import { PortalContext } from "./Portal";
const DocumentComments = lazyWithRetry(
() => import("~/scenes/Document/components/Comments")
@@ -44,8 +46,9 @@ type Props = {
const AuthenticatedLayout: React.FC = ({ children }: Props) => {
const { ui, auth } = useStores();
const location = useLocation();
const layoutRef = React.useRef<HTMLDivElement>(null);
const can = usePolicy(ui.activeCollectionId);
const { user, team } = auth;
const team = useCurrentTeam();
const documentContext = useLocalStore<DocumentContextValue>(() => ({
editor: null,
setEditor: (editor: TEditor) => {
@@ -76,16 +79,14 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
return <ErrorSuspended />;
}
const showSidebar = auth.authenticated && user && team;
const sidebar = showSidebar ? (
const sidebar = (
<Fade>
<Switch>
<Route path={settingsPath()} component={SettingsSidebar} />
<Route component={Sidebar} />
</Switch>
</Fade>
) : undefined;
);
const showHistory = !!matchPath(location.pathname, {
path: matchDocumentHistory,
@@ -98,7 +99,7 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
!showHistory &&
ui.activeDocumentId &&
ui.commentsExpanded.includes(ui.activeDocumentId) &&
team?.getPreference(TeamPreference.Commenting);
team.getPreference(TeamPreference.Commenting);
const sidebarRight = (
<AnimatePresence
@@ -121,15 +122,22 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
return (
<DocumentContext.Provider value={documentContext}>
<Layout title={team?.name} sidebar={sidebar} sidebarRight={sidebarRight}>
<RegisterKeyDown trigger="n" handler={goToNewDocument} />
<RegisterKeyDown trigger="t" handler={goToSearch} />
<RegisterKeyDown trigger="/" handler={goToSearch} />
{children}
<React.Suspense fallback={null}>
<CommandBar />
</React.Suspense>
</Layout>
<PortalContext.Provider value={layoutRef.current}>
<Layout
title={team.name}
sidebar={sidebar}
sidebarRight={sidebarRight}
ref={layoutRef}
>
<RegisterKeyDown trigger="n" handler={goToNewDocument} />
<RegisterKeyDown trigger="t" handler={goToSearch} />
<RegisterKeyDown trigger="/" handler={goToSearch} />
{children}
<React.Suspense fallback={null}>
<CommandBar />
</React.Suspense>
</Layout>
</PortalContext.Provider>
</DocumentContext.Provider>
);
};
+8 -2
View File
@@ -5,6 +5,7 @@ import Initials from "./Initials";
export enum AvatarSize {
Small = 16,
Toast = 18,
Medium = 24,
Large = 32,
XLarge = 48,
@@ -23,6 +24,7 @@ type Props = {
src?: string;
model?: IAvatar;
alt?: string;
shape?: "square";
showBorder?: boolean;
onClick?: React.MouseEventHandler<HTMLImageElement>;
className?: string;
@@ -64,11 +66,15 @@ const Relative = styled.div`
flex-shrink: 0;
`;
const CircleImg = styled.img<{ size: number; $showBorder?: boolean }>`
const CircleImg = styled.img<{
size: number;
shape?: "square";
$showBorder?: boolean;
}>`
display: block;
width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
border-radius: 50%;
border-radius: ${(props) => (props.shape === "square" ? 4 : props.size)}px;
border: ${(props) =>
props.$showBorder === false
? "none"
+2 -4
View File
@@ -3,19 +3,17 @@ import Flex from "~/components/Flex";
const Initials = styled(Flex)<{
color?: string;
shape?: "square";
size: number;
$showBorder?: boolean;
}>`
align-items: center;
justify-content: center;
border-radius: 50%;
width: 100%;
height: 100%;
border-radius: ${(props) => (props.shape === "square" ? 4 : props.size)}px;
color: #fff;
background-color: ${(props) => props.color};
width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
border-radius: 50%;
border: 2px solid
${(props) =>
props.$showBorder === false ? "transparent" : props.theme.background};
+1 -1
View File
@@ -171,7 +171,7 @@ const Button = <T extends React.ElementType = "button">(
danger,
...rest
} = props;
const hasText = children !== undefined || value !== undefined;
const hasText = !!children || value !== undefined;
const ic = hideIcon ? undefined : action?.icon ?? icon;
const hasIcon = ic !== undefined;
+12 -5
View File
@@ -4,6 +4,7 @@ import breakpoint from "styled-components-breakpoint";
type Props = {
children?: React.ReactNode;
maxWidth?: string;
withStickyHeader?: boolean;
};
@@ -18,18 +19,24 @@ const Container = styled.div<Props>`
`};
`;
const Content = styled.div`
max-width: 46em;
type ContentProps = { $maxWidth?: string };
const Content = styled.div<ContentProps>`
max-width: ${(props) => props.$maxWidth ?? "46em"};
margin: 0 auto;
${breakpoint("desktopLarge")`
max-width: 52em;
max-width: ${(props: ContentProps) => props.$maxWidth ?? "52em"};
`};
`;
const CenteredContent: React.FC<Props> = ({ children, ...rest }: Props) => (
const CenteredContent: React.FC<Props> = ({
children,
maxWidth,
...rest
}: Props) => (
<Container {...rest}>
<Content>{children}</Content>
<Content $maxWidth={maxWidth}>{children}</Content>
</Container>
);
+17
View File
@@ -0,0 +1,17 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { changeLanguage } from "~/utils/language";
type Props = {
locale: string;
};
export default function ChangeLanguage({ locale }: Props) {
const { i18n } = useTranslation();
React.useEffect(() => {
void changeLanguage(locale, i18n);
}, [locale, i18n]);
return null;
}
+4 -1
View File
@@ -1,4 +1,7 @@
import { sortBy, filter, uniq, isEqual } from "lodash";
import filter from "lodash/filter";
import isEqual from "lodash/isEqual";
import sortBy from "lodash/sortBy";
import uniq from "lodash/uniq";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
+6 -2
View File
@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import Collection from "~/models/Collection";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Text from "~/components/Text";
@@ -22,11 +23,14 @@ function CollectionDeleteDialog({ collection, onSubmit }: Props) {
const handleSubmit = async () => {
const redirect = collection.id === ui.activeCollectionId;
await collection.delete();
onSubmit();
if (redirect) {
history.push(homePath());
}
await collection.delete();
onSubmit();
toast.success(t("Collection deleted"));
};
return (
+16 -9
View File
@@ -3,7 +3,9 @@ import { observer } from "mobx-react";
import { transparentize } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { richExtensions } from "@shared/editor/nodes";
import { s } from "@shared/styles";
import Collection from "~/models/Collection";
import Arrow from "~/components/Arrow";
@@ -11,9 +13,18 @@ import ButtonLink from "~/components/ButtonLink";
import Editor from "~/components/Editor";
import LoadingIndicator from "~/components/LoadingIndicator";
import NudeButton from "~/components/NudeButton";
import BlockMenuExtension from "~/editor/extensions/BlockMenu";
import EmojiMenuExtension from "~/editor/extensions/EmojiMenu";
import HoverPreviewsExtension from "~/editor/extensions/HoverPreviews";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
const extensions = [
...richExtensions,
BlockMenuExtension,
EmojiMenuExtension,
HoverPreviewsExtension,
];
type Props = {
collection: Collection;
@@ -21,7 +32,6 @@ type Props = {
function CollectionDescription({ collection }: Props) {
const { collections } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const [isExpanded, setExpanded] = React.useState(false);
const [isEditing, setEditing] = React.useState(false);
@@ -59,15 +69,11 @@ function CollectionDescription({ collection }: Props) {
});
setDirty(false);
} catch (err) {
showToast(
t("Sorry, an error occurred saving the collection", {
type: "error",
})
);
toast.error(t("Sorry, an error occurred saving the collection"));
throw err;
}
}, 1000),
[collection, showToast, t]
[collection, t]
);
const handleChange = React.useCallback(
@@ -109,6 +115,7 @@ function CollectionDescription({ collection }: Props) {
readOnly={!isEditing}
autoFocus={isEditing}
onBlur={handleStopEditing}
extensions={extensions}
maxLength={1000}
embedsDisabled
canUpdate
@@ -170,7 +177,7 @@ const MaxHeight = styled.div`
position: relative;
max-height: 25vh;
overflow: hidden;
margin: -12px -8px -8px;
margin: 8px -8px -8px;
padding: 8px;
&[data-editing="true"],
+6 -18
View File
@@ -11,39 +11,26 @@ import SearchActions from "~/components/SearchActions";
import rootActions from "~/actions/root";
import useCommandBarActions from "~/hooks/useCommandBarActions";
import useSettingsActions from "~/hooks/useSettingsActions";
import { CommandBarAction } from "~/types";
import useTemplateActions from "~/hooks/useTemplateActions";
function CommandBar() {
const { t } = useTranslation();
const settingsActions = useSettingsActions();
const templateActions = useTemplateActions();
const commandBarActions = React.useMemo(
() => [...rootActions, settingsActions],
[settingsActions]
() => [...rootActions, templateActions, settingsActions],
[settingsActions, templateActions]
);
useCommandBarActions(commandBarActions);
const { rootAction } = useKBar((state) => ({
rootAction: state.currentRootActionId
? (state.actions[
state.currentRootActionId
] as unknown as CommandBarAction)
: undefined,
}));
return (
<>
<KBarPortal>
<Positioner>
<Animator>
<SearchActions />
<SearchInput
placeholder={`${
rootAction?.placeholder ||
rootAction?.name ||
t("Type a command or search")
}`}
/>
<SearchInput defaultPlaceholder={t("Type a command or search")} />
<CommandBarResults />
</Animator>
</Positioner>
@@ -83,6 +70,7 @@ const SearchInput = styled(KBarSearch)`
&:disabled,
&::placeholder {
color: ${s("placeholder")};
opacity: 1;
}
`;
+2 -3
View File
@@ -1,11 +1,11 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import Comment from "~/models/Comment";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
comment: Comment;
@@ -14,7 +14,6 @@ type Props = {
function CommentDeleteDialog({ comment, onSubmit }: Props) {
const { comments } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const hasChildComments = comments.inThread(comment.id).length > 1;
@@ -23,7 +22,7 @@ function CommentDeleteDialog({ comment, onSubmit }: Props) {
await comment.delete();
onSubmit?.();
} catch (err) {
showToast(err.message, { type: "error" });
toast.error(err.message);
}
};
+12 -12
View File
@@ -1,10 +1,11 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
/** Callback when the dialog is submitted */
@@ -29,8 +30,8 @@ const ConfirmationDialog: React.FC<Props> = ({
disabled = false,
}: Props) => {
const [isSaving, setIsSaving] = React.useState(false);
const { t } = useTranslation();
const { dialogs } = useStores();
const { showToast } = useToasts();
const handleSubmit = React.useCallback(
async (ev: React.SyntheticEvent) => {
@@ -40,30 +41,29 @@ const ConfirmationDialog: React.FC<Props> = ({
await onSubmit();
dialogs.closeAllModals();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}
},
[onSubmit, dialogs, showToast]
[onSubmit, dialogs]
);
return (
<Flex column>
<form onSubmit={handleSubmit}>
<Text type="secondary">{children}</Text>
<form onSubmit={handleSubmit}>
<Text type="secondary">{children}</Text>
<Flex justify="flex-end">
<Button
type="submit"
disabled={isSaving || disabled}
danger={danger}
autoFocus
>
{isSaving && savingText ? savingText : submitText}
{isSaving && savingText ? savingText : submitText ?? t("Confirm")}
</Button>
</form>
</Flex>
</Flex>
</form>
);
};
+38 -5
View File
@@ -14,15 +14,48 @@ function ConnectionStatus() {
const theme = useTheme();
const { t } = useTranslation();
const codeToMessage = {
1009: {
title: t("Document is too large"),
body: t(
"This document has reached the maximum size and can no longer be edited"
),
},
4401: {
title: t("Authentication failed"),
body: t("Please try logging out and back in again"),
},
4403: {
title: t("Authorization failed"),
body: t("You may have lost access to this document, try reloading"),
},
4503: {
title: t("Too many users connected to document"),
body: t("Your edits will sync once other users leave the document"),
},
};
const message = ui.multiplayerErrorCode
? codeToMessage[ui.multiplayerErrorCode]
: undefined;
return ui.multiplayerStatus === "connecting" ||
ui.multiplayerStatus === "disconnected" ? (
<Tooltip
tooltip={
<Centered>
<strong>{t("Server connection lost")}</strong>
<br />
{t("Edits you make will sync once youre online")}
</Centered>
message ? (
<Centered>
<strong>{message.title}</strong>
<br />
{message.body}
</Centered>
) : (
<Centered>
<strong>{t("Server connection lost")}</strong>
<br />
{t("Edits you make will sync once youre online")}
</Centered>
)
}
placement="bottom"
>
+5 -2
View File
@@ -9,6 +9,7 @@ type Props = Omit<React.HTMLAttributes<HTMLSpanElement>, "ref" | "onChange"> & {
readOnly?: boolean;
onClick?: React.MouseEventHandler<HTMLDivElement>;
onChange?: (text: string) => void;
onFocus?: React.FocusEventHandler<HTMLSpanElement> | undefined;
onBlur?: React.FocusEventHandler<HTMLSpanElement> | undefined;
onInput?: React.FormEventHandler<HTMLSpanElement> | undefined;
onKeyDown?: React.KeyboardEventHandler<HTMLSpanElement> | undefined;
@@ -35,6 +36,7 @@ const ContentEditable = React.forwardRef(function _ContentEditable(
disabled,
onChange,
onInput,
onFocus,
onBlur,
onKeyDown,
value,
@@ -143,11 +145,13 @@ const ContentEditable = React.forwardRef(function _ContentEditable(
);
return (
<div className={className} dir={dir} onClick={onClick}>
<div className={className} dir={dir} onClick={onClick} tabIndex={-1}>
{children}
<Content
ref={contentRef}
contentEditable={!disabled && !readOnly}
onInput={wrappedEvent(onInput)}
onFocus={wrappedEvent(onFocus)}
onBlur={wrappedEvent(onBlur)}
onKeyDown={wrappedEvent(onKeyDown)}
onPaste={handlePaste}
@@ -158,7 +162,6 @@ const ContentEditable = React.forwardRef(function _ContentEditable(
>
{innerValue}
</Content>
{children}
</div>
);
});
@@ -1,11 +1,13 @@
import styled from "styled-components";
import { s } from "@shared/styles";
const MenuIconWrapper = styled.span`
width: 24px;
height: 24px;
margin-right: 6px;
margin-left: -4px;
color: ${({ theme }) => theme.textSecondary};
color: ${s("textSecondary")};
flex-shrink: 0;
`;
export default MenuIconWrapper;
+1 -1
View File
@@ -5,7 +5,7 @@ import { mergeRefs } from "react-merge-refs";
import { MenuItem as BaseMenuItem } from "reakit/Menu";
import styled, { css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import MenuIconWrapper from "../MenuIconWrapper";
import MenuIconWrapper from "./MenuIconWrapper";
type Props = {
id?: string;
+6 -6
View File
@@ -2,17 +2,17 @@ import * as React from "react";
import { useMousePosition } from "~/hooks/useMousePosition";
type Positions = {
/* Sub-menu x */
/** Sub-menu x */
x: number;
/* Sub-menu y */
/** Sub-menu y */
y: number;
/* Sub-menu height */
/** Sub-menu height */
h: number;
/* Sub-menu width */
/** Sub-menu width */
w: number;
/* Mouse x */
/** Mouse x */
mouseX: number;
/* Mouse y */
/** Mouse y */
mouseY: number;
};
+2 -2
View File
@@ -9,8 +9,8 @@ import {
MenuStateReturn,
} from "reakit/Menu";
import styled, { useTheme } from "styled-components";
import MenuIconWrapper from "~/components/ContextMenu/MenuIconWrapper";
import Flex from "~/components/Flex";
import MenuIconWrapper from "~/components/MenuIconWrapper";
import { actionToMenuItem } from "~/actions";
import useActionContext from "~/hooks/useActionContext";
import {
@@ -201,7 +201,7 @@ function Template({ items, actions, context, ...menu }: Props) {
}
if (item.type === "heading") {
return <Header>{item.title}</Header>;
return <Header key={index}>{item.title}</Header>;
}
const _exhaustiveCheck: never = item;
+92 -61
View File
@@ -46,6 +46,8 @@ type Props = MenuStateReturn & {
onClose?: () => void;
/** Called when the context menu is clicked. */
onClick?: (ev: React.MouseEvent) => void;
/** The maximum width of the context menu. */
maxWidth?: number;
children?: React.ReactNode;
};
@@ -57,11 +59,6 @@ const ContextMenu: React.FC<Props> = ({
...rest
}: Props) => {
const previousVisible = usePrevious(rest.visible);
const maxHeight = useMenuHeight({
visible: rest.visible,
elementRef: rest.unstable_disclosureRef,
});
const backgroundRef = React.useRef<HTMLDivElement>(null);
const { ui } = useStores();
const { t } = useTranslation();
const { setIsMenuOpen } = useMenuContext();
@@ -99,21 +96,6 @@ const ContextMenu: React.FC<Props> = ({
t,
]);
// We must manually manage scroll lock for iOS support so that the scrollable
// element can be passed into body-scroll-lock. See:
// https://github.com/ariakit/ariakit/issues/469
React.useEffect(() => {
const scrollElement = backgroundRef.current;
if (rest.visible && scrollElement && !isSubMenu) {
disableBodyScroll(scrollElement, {
reserveScrollBarGap: true,
});
}
return () => {
scrollElement && !isSubMenu && enableBodyScroll(scrollElement);
};
}, [isSubMenu, rest.visible]);
// Perf win don't render anything until the menu has been opened
if (!rest.visible && !previousVisible) {
return null;
@@ -124,51 +106,98 @@ const ContextMenu: React.FC<Props> = ({
return (
<>
<Menu hideOnClickOutside={!isMobile} preventBodyScroll={false} {...rest}>
{(props) => {
// kind of hacky, but this is an effective way of telling which way
// the menu will _actually_ be placed when taking into account screen
// positioning.
const topAnchor = props.style?.top === "0";
// @ts-expect-error ts-migrate(2339) FIXME: Property 'placement' does not exist on type 'Extra... Remove this comment to see the full error message
const rightAnchor = props.placement === "bottom-end";
return (
<>
{isMobile && (
<Backdrop
onClick={(ev) => {
ev.preventDefault();
ev.stopPropagation();
rest.hide?.();
}}
/>
)}
<Position {...props}>
<Background
dir="auto"
topAnchor={topAnchor}
rightAnchor={rightAnchor}
ref={backgroundRef}
hiddenScrollbars
style={
topAnchor
? {
maxHeight,
}
: undefined
}
>
{rest.visible || rest.animating ? children : null}
</Background>
</Position>
</>
);
}}
{(props) => (
<InnerContextMenu
// eslint-disable-next-line @typescript-eslint/no-explicit-any
menuProps={props as any}
{...rest}
isSubMenu={isSubMenu}
>
{children}
</InnerContextMenu>
)}
</Menu>
</>
);
};
type InnerContextMenuProps = MenuStateReturn & {
isSubMenu: boolean;
menuProps: { style?: React.CSSProperties; placement: string };
children: React.ReactNode;
maxWidth?: number;
};
/**
* Inner context menu allows deferring expensive window measurement hooks etc
* until the menu is actually opened.
*/
const InnerContextMenu = (props: InnerContextMenuProps) => {
const { menuProps } = props;
// kind of hacky, but this is an effective way of telling which way
// the menu will _actually_ be placed when taking into account screen
// positioning.
const topAnchor =
menuProps.style?.top === "0" || menuProps.style?.position === "fixed";
const rightAnchor = menuProps.placement === "bottom-end";
const backgroundRef = React.useRef<HTMLDivElement>(null);
const isMobile = useMobile();
const maxHeight = useMenuHeight({
visible: props.visible,
elementRef: props.unstable_disclosureRef,
});
// We must manually manage scroll lock for iOS support so that the scrollable
// element can be passed into body-scroll-lock. See:
// https://github.com/ariakit/ariakit/issues/469
React.useEffect(() => {
const scrollElement = backgroundRef.current;
if (props.visible && scrollElement && !props.isSubMenu) {
disableBodyScroll(scrollElement, {
reserveScrollBarGap: true,
});
}
return () => {
scrollElement && !props.isSubMenu && enableBodyScroll(scrollElement);
};
}, [props.isSubMenu, props.visible]);
const style =
topAnchor && !isMobile
? {
maxHeight,
}
: undefined;
return (
<>
{isMobile && (
<Backdrop
onClick={(ev) => {
ev.preventDefault();
ev.stopPropagation();
props.hide?.();
}}
/>
)}
<Position {...menuProps}>
<Background
dir="auto"
maxWidth={props.maxWidth}
topAnchor={topAnchor}
rightAnchor={rightAnchor}
ref={backgroundRef}
hiddenScrollbars
style={style}
>
{props.visible || props.animating ? props.children : null}
</Background>
</Position>
</>
);
};
export default ContextMenu;
export const Backdrop = styled.div`
@@ -203,6 +232,7 @@ export const Position = styled.div`
type BackgroundProps = {
topAnchor?: boolean;
rightAnchor?: boolean;
maxWidth?: number;
theme: DefaultTheme;
};
@@ -228,7 +258,8 @@ export const Background = styled(Scrollable)<BackgroundProps>`
props.topAnchor ? fadeAndSlideDown : fadeAndSlideUp} 200ms ease;
transform-origin: ${(props: BackgroundProps) =>
props.rightAnchor ? "75%" : "25%"} 0;
max-width: 276px;
max-width: ${(props: BackgroundProps) => props.maxWidth ?? 276}px;
max-height: 100vh;
background: ${(props: BackgroundProps) => props.theme.menuBackground};
box-shadow: ${(props: BackgroundProps) => props.theme.menuShadow};
`};
+30 -21
View File
@@ -1,5 +1,6 @@
import copy from "copy-to-clipboard";
import * as React from "react";
import { mergeRefs } from "react-merge-refs";
import env from "~/env";
type Props = {
@@ -9,32 +10,40 @@ type Props = {
onCopy?: () => void;
};
class CopyToClipboard extends React.PureComponent<Props> {
onClick = (ev: React.SyntheticEvent) => {
const { text, onCopy, children } = this.props;
const elem = React.Children.only(children);
function CopyToClipboard(props: Props, ref: React.Ref<HTMLElement>) {
const { text, onCopy, children, ...rest } = props;
copy(text, {
debug: env.ENVIRONMENT !== "production",
format: "text/plain",
});
const onClick = React.useCallback(
(ev: React.MouseEvent<HTMLElement>) => {
const elem = React.Children.only(children);
onCopy?.();
copy(text, {
debug: env.ENVIRONMENT !== "production",
format: "text/plain",
});
if (elem && elem.props && typeof elem.props.onClick === "function") {
elem.props.onClick(ev);
}
};
onCopy?.();
render() {
const { text, onCopy, children, ...rest } = this.props;
const elem = React.Children.only(children);
if (!elem) {
return null;
}
if (elem && elem.props && typeof elem.props.onClick === "function") {
elem.props.onClick(ev);
}
},
[children, onCopy, text]
);
return React.cloneElement(elem, { ...rest, onClick: this.onClick });
const elem = React.Children.only(children);
if (!elem) {
return null;
}
return React.cloneElement(elem, {
...rest,
ref:
"ref" in elem
? mergeRefs([elem.ref as React.MutableRefObject<HTMLElement>, ref])
: ref,
onClick,
});
}
export default CopyToClipboard;
export default React.forwardRef(CopyToClipboard);
@@ -1,13 +1,13 @@
import { HomeIcon } from "outline-icons";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Optional } from "utility-types";
import Flex from "~/components/Flex";
import CollectionIcon from "~/components/Icons/CollectionIcon";
import InputSelect from "~/components/InputSelect";
import { IconWrapper } from "~/components/Sidebar/components/SidebarLink";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type DefaultCollectionInputSelectProps = Optional<
React.ComponentProps<typeof InputSelect>
@@ -25,7 +25,6 @@ const DefaultCollectionInputSelect = ({
const { collections } = useStores();
const [fetching, setFetching] = useState(false);
const [fetchError, setFetchError] = useState();
const { showToast } = useToasts();
React.useEffect(() => {
async function fetchData() {
@@ -36,11 +35,8 @@ const DefaultCollectionInputSelect = ({
limit: 100,
});
} catch (error) {
showToast(
t("Collections could not be loaded, please reload the app"),
{
type: "error",
}
toast.error(
t("Collections could not be loaded, please reload the app")
);
setFetchError(error);
} finally {
@@ -49,7 +45,7 @@ const DefaultCollectionInputSelect = ({
}
}
void fetchData();
}, [showToast, fetchError, t, fetching, collections]);
}, [fetchError, t, fetching, collections]);
const options = React.useMemo(
() =>
+12 -7
View File
@@ -1,10 +1,10 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { useDesktopTitlebar } from "~/hooks/useDesktopTitlebar";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import Desktop from "~/utils/Desktop";
export default function DesktopEventHandler() {
@@ -12,7 +12,7 @@ export default function DesktopEventHandler() {
const { t } = useTranslation();
const history = useHistory();
const { dialogs } = useStores();
const { showToast } = useToasts();
const hasDisabledUpdateMessage = React.useRef(false);
React.useEffect(() => {
Desktop.bridge?.redirect((path: string, replace = false) => {
@@ -24,11 +24,16 @@ export default function DesktopEventHandler() {
});
Desktop.bridge?.updateDownloaded(() => {
showToast("An update is ready to install.", {
type: "info",
timeout: Infinity,
if (hasDisabledUpdateMessage.current) {
return;
}
hasDisabledUpdateMessage.current = true;
toast.message("An update is ready to install.", {
duration: Infinity,
dismissible: true,
action: {
text: "Install now",
label: t("Install now"),
onClick: () => {
void Desktop.bridge?.restartAndInstall();
},
@@ -50,7 +55,7 @@ export default function DesktopEventHandler() {
content: <KeyboardShortcuts />,
});
});
}, [t, history, dialogs, showToast]);
}, [t, history, dialogs]);
return null;
}
+20 -13
View File
@@ -12,9 +12,10 @@ import { MenuInternalLink } from "~/types";
import {
archivePath,
collectionPath,
templatesPath,
settingsPath,
trashPath,
} from "~/utils/routeHelpers";
import EmojiIcon from "./Icons/EmojiIcon";
type Props = {
children?: React.ReactNode;
@@ -43,12 +44,12 @@ function useCategory(document: Document): MenuInternalLink | null {
};
}
if (document.isTemplate) {
if (document.template) {
return {
type: "route",
icon: <ShapesIcon />,
title: t("Templates"),
to: templatesPath(),
to: settingsPath("templates"),
};
}
@@ -67,6 +68,10 @@ const DocumentBreadcrumb: React.FC<Props> = ({
? collections.get(document.collectionId)
: undefined;
React.useEffect(() => {
void document.loadRelations();
}, [document]);
let collectionNode: MenuInternalLink | undefined;
if (collection) {
@@ -76,20 +81,16 @@ const DocumentBreadcrumb: React.FC<Props> = ({
icon: <CollectionIcon collection={collection} expanded />,
to: collectionPath(collection.url),
};
} else if (document.collectionId && !collection) {
} else if (document.isCollectionDeleted) {
collectionNode = {
type: "route",
title: t("Deleted Collection"),
icon: undefined,
to: collectionPath("deleted-collection"),
to: "",
};
}
const path = React.useMemo(
() => collection?.pathToDocument(document.id).slice(0, -1) || [],
// eslint-disable-next-line react-hooks/exhaustive-deps
[collection, document, document.collectionId, document.parentDocumentId]
);
const path = document.pathTo;
const items = React.useMemo(() => {
const output = [];
@@ -102,10 +103,16 @@ const DocumentBreadcrumb: React.FC<Props> = ({
output.push(collectionNode);
}
path.forEach((node: NavigationNode) => {
path.slice(0, -1).forEach((node: NavigationNode) => {
output.push({
type: "route",
title: node.title,
title: node.emoji ? (
<>
<EmojiIcon emoji={node.emoji} /> {node.title}
</>
) : (
node.title
),
to: node.url,
});
});
@@ -120,7 +127,7 @@ const DocumentBreadcrumb: React.FC<Props> = ({
return (
<>
{collection?.name}
{path.map((node: NavigationNode) => (
{path.slice(0, -1).map((node: NavigationNode) => (
<React.Fragment key={node.id}>
<SmallSlash />
{node.title}
+4 -3
View File
@@ -111,11 +111,12 @@ function DocumentCard(props: Props) {
{document.emoji ? (
<Squircle color={theme.slateLight}>
<EmojiIcon emoji={document.emoji} size={26} />
<EmojiIcon emoji={document.emoji} size={24} />
</Squircle>
) : (
<Squircle color={collection?.color}>
{collection?.icon &&
collection?.icon !== "letter" &&
collection?.icon !== "collection" &&
!pin?.collectionId ? (
<CollectionIcon collection={collection} color="white" />
@@ -279,8 +280,8 @@ const Heading = styled.h3`
overflow: hidden;
color: ${s("text")};
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-family: ${s("fontFamily")};
font-weight: 500;
`;
export default observer(DocumentCard);
+18
View File
@@ -1,5 +1,6 @@
import * as React from "react";
import { Editor } from "~/editor";
import useIdle from "~/hooks/useIdle";
export type DocumentContextValue = {
/** The current editor instance for this document. */
@@ -16,4 +17,21 @@ const DocumentContext = React.createContext<DocumentContextValue>({
export const useDocumentContext = () => React.useContext(DocumentContext);
const activityEvents = [
"click",
"mousemove",
"DOMMouseScroll",
"mousewheel",
"mousedown",
"touchstart",
"touchmove",
"focus",
];
export const useEditingFocus = () => {
const { editor } = useDocumentContext();
const isIdle = useIdle(3000, activityEvents);
return isIdle && !!editor?.view.hasFocus();
};
export default DocumentContext;
+81 -75
View File
@@ -1,5 +1,10 @@
import FuzzySearch from "fuzzy-search";
import { includes, difference, concat, filter, map, fill } from "lodash";
import concat from "lodash/concat";
import difference from "lodash/difference";
import fill from "lodash/fill";
import filter from "lodash/filter";
import includes from "lodash/includes";
import map from "lodash/map";
import { observer } from "mobx-react";
import { StarredIcon, DocumentIcon } from "outline-icons";
import * as React from "react";
@@ -10,7 +15,6 @@ import scrollIntoView from "smooth-scroll-into-view-if-needed";
import styled, { useTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { NavigationNode } from "@shared/types";
import parseTitle from "@shared/utils/parseTitle";
import DocumentExplorerNode from "~/components/DocumentExplorerNode";
import DocumentExplorerSearchResult from "~/components/DocumentExplorerSearchResult";
import Flex from "~/components/Flex";
@@ -200,84 +204,86 @@ function DocumentExplorer({ onSubmit, onSelect, items }: Props) {
}
};
const ListItem = ({
index,
data,
style,
}: {
index: number;
data: NavigationNode[];
style: React.CSSProperties;
}) => {
const node = data[index];
const isCollection = node.type === "collection";
let icon, title, path;
const ListItem = observer(
({
index,
data,
style,
}: {
index: number;
data: NavigationNode[];
style: React.CSSProperties;
}) => {
const node = data[index];
const isCollection = node.type === "collection";
let icon, title: string, emoji: string | undefined, path;
if (isCollection) {
const col = collections.get(node.collectionId as string);
icon = col && (
<CollectionIcon collection={col} expanded={isExpanded(index)} />
);
title = node.title;
} else {
const doc = documents.get(node.id);
const { strippedTitle, emoji } = parseTitle(node.title);
title = strippedTitle;
if (emoji) {
icon = <EmojiIcon emoji={emoji} />;
} else if (doc?.isStarred) {
icon = <StarredIcon color={theme.yellow} />;
if (isCollection) {
const col = collections.get(node.collectionId as string);
icon = col && (
<CollectionIcon collection={col} expanded={isExpanded(index)} />
);
title = node.title;
} else {
icon = <DocumentIcon color={theme.textSecondary} />;
const doc = documents.get(node.id);
emoji = doc?.emoji ?? node.emoji;
title = doc?.title ?? node.title;
if (emoji) {
icon = <EmojiIcon emoji={emoji} />;
} else if (doc?.isStarred) {
icon = <StarredIcon color={theme.yellow} />;
} else {
icon = <DocumentIcon color={theme.textSecondary} />;
}
path = ancestors(node)
.map((a) => a.title)
.join(" / ");
}
path = ancestors(node)
.map((a) => parseTitle(a.title).strippedTitle)
.join(" / ");
return searchTerm ? (
<DocumentExplorerSearchResult
selected={isSelected(index)}
active={activeNode === index}
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
icon={icon}
title={title}
path={path}
/>
) : (
<DocumentExplorerNode
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
onDisclosureClick={(ev) => {
ev.stopPropagation();
toggleCollapse(index);
}}
selected={isSelected(index)}
active={activeNode === index}
expanded={isExpanded(index)}
icon={icon}
title={title}
depth={node.depth as number}
hasChildren={hasChildren(index)}
ref={itemRefs[index]}
/>
);
}
return searchTerm ? (
<DocumentExplorerSearchResult
selected={isSelected(index)}
active={activeNode === index}
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
icon={icon}
title={title}
path={path}
/>
) : (
<DocumentExplorerNode
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
onDisclosureClick={(ev) => {
ev.stopPropagation();
toggleCollapse(index);
}}
selected={isSelected(index)}
active={activeNode === index}
expanded={isExpanded(index)}
icon={icon}
title={title}
depth={node.depth as number}
hasChildren={hasChildren(index)}
ref={itemRefs[index]}
/>
);
};
);
const focusSearchInput = () => {
inputSearchRef.current?.focus();
+13 -32
View File
@@ -1,5 +1,4 @@
import { observer } from "mobx-react";
import { PlusIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
@@ -9,7 +8,6 @@ import breakpoint from "styled-components-breakpoint";
import { s } from "@shared/styles";
import Document from "~/models/Document";
import Badge from "~/components/Badge";
import Button from "~/components/Button";
import DocumentMeta from "~/components/DocumentMeta";
import EventBoundary from "~/components/EventBoundary";
import Flex from "~/components/Flex";
@@ -18,12 +16,11 @@ import NudeButton from "~/components/NudeButton";
import StarButton, { AnimatedStar } from "~/components/Star";
import Tooltip from "~/components/Tooltip";
import useBoolean from "~/hooks/useBoolean";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useCurrentUser from "~/hooks/useCurrentUser";
import usePolicy from "~/hooks/usePolicy";
import DocumentMenu from "~/menus/DocumentMenu";
import { hover } from "~/styles";
import { newDocumentPath } from "~/utils/routeHelpers";
import { documentPath } from "~/utils/routeHelpers";
import EmojiIcon from "./Icons/EmojiIcon";
type Props = {
document: Document;
@@ -51,7 +48,6 @@ function DocumentListItem(
) {
const { t } = useTranslation();
const user = useCurrentUser();
const team = useCurrentTeam();
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
const {
@@ -71,8 +67,6 @@ function DocumentListItem(
!!document.title.toLowerCase().includes(highlight.toLowerCase());
const canStar =
!document.isDraft && !document.isArchived && !document.isTemplate;
const can = usePolicy(team);
const canCollection = usePolicy(document.collectionId);
return (
<CompositeItem
@@ -83,7 +77,7 @@ function DocumentListItem(
$isStarred={document.isStarred}
$menuOpen={menuOpen}
to={{
pathname: document.url,
pathname: documentPath(document),
state: {
title: document.titleWithDefault,
},
@@ -92,6 +86,12 @@ function DocumentListItem(
>
<Content>
<Heading dir={document.dir}>
{document.emoji && (
<>
<EmojiIcon emoji={document.emoji} size={24} />
&nbsp;
</>
)}
<Title
text={document.titleWithDefault}
highlight={highlight}
@@ -135,25 +135,6 @@ function DocumentListItem(
/>
</Content>
<Actions>
{document.isTemplate &&
!document.isArchived &&
!document.isDeleted &&
can.createDocument &&
canCollection.update && (
<>
<Button
as={Link}
to={newDocumentPath(document.collectionId, {
templateId: document.id,
})}
icon={<PlusIcon />}
neutral
>
{t("New doc")}
</Button>
&nbsp;
</>
)}
<DocumentMenu
document={document}
showPin={showPin}
@@ -262,8 +243,8 @@ const Heading = styled.h3<{ rtl?: boolean }>`
margin-bottom: 0.25em;
white-space: nowrap;
color: ${s("text")};
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-family: ${s("fontFamily")};
font-weight: 500;
`;
const StarPositioner = styled(Flex)`
@@ -279,8 +260,8 @@ const Title = styled(Highlight)`
const ResultContext = styled(Highlight)`
display: block;
color: ${s("textTertiary")};
font-size: 14px;
color: ${s("textSecondary")};
font-size: 15px;
margin-top: -0.25em;
margin-bottom: 0.25em;
`;
+3 -6
View File
@@ -3,9 +3,9 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { documentPath } from "~/utils/routeHelpers";
type Props = {
@@ -14,7 +14,6 @@ type Props = {
function DocumentTemplatizeDialog({ documentId }: Props) {
const history = useHistory();
const { showToast } = useToasts();
const { t } = useTranslation();
const { documents } = useStores();
const document = documents.get(documentId);
@@ -24,11 +23,9 @@ function DocumentTemplatizeDialog({ documentId }: Props) {
const template = await document?.templatize();
if (template) {
history.push(documentPath(template));
showToast(t("Template created, go ahead and customize it"), {
type: "info",
});
toast.success(t("Template created, go ahead and customize it"));
}
}, [document, showToast, history, t]);
}, [document, history, t]);
return (
<ConfirmationDialog
+16 -7
View File
@@ -1,13 +1,15 @@
import { sortBy } from "lodash";
import compact from "lodash/compact";
import sortBy from "lodash/sortBy";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { dateToRelative } from "@shared/utils/date";
import { dateLocale, dateToRelative } from "@shared/utils/date";
import Document from "~/models/Document";
import User from "~/models/User";
import Avatar from "~/components/Avatar";
import ListItem from "~/components/List/Item";
import PaginatedList from "~/components/PaginatedList";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
type Props = {
@@ -18,6 +20,9 @@ type Props = {
function DocumentViews({ document, isOpen }: Props) {
const { t } = useTranslation();
const { views, presence } = useStores();
const user = useCurrentUser();
const locale = dateLocale(user.language);
const documentPresence = presence.get(document.id);
const documentPresenceArray = documentPresence
? Array.from(documentPresence.values())
@@ -31,10 +36,10 @@ function DocumentViews({ document, isOpen }: Props) {
const documentViews = views.inDocument(document.id);
const sortedViews = sortBy(
documentViews,
(view) => !presentIds.includes(view.user.id)
(view) => !presentIds.includes(view.userId)
);
const users = React.useMemo(
() => sortedViews.map((v) => v.user),
() => compact(sortedViews.map((v) => v.user)),
[sortedViews]
);
@@ -45,16 +50,20 @@ function DocumentViews({ document, isOpen }: Props) {
aria-label={t("Viewers")}
items={users}
renderItem={(model: User) => {
const view = documentViews.find((v) => v.user.id === model.id);
const view = documentViews.find((v) => v.userId === model.id);
const isPresent = presentIds.includes(model.id);
const isEditing = editingIds.includes(model.id);
const subtitle = isPresent
? isEditing
? t("Currently editing")
: t("Currently viewing")
: t("Viewed {{ timeAgo }} ago", {
: t("Viewed {{ timeAgo }}", {
timeAgo: dateToRelative(
view ? Date.parse(view.lastViewedAt) : new Date()
view ? Date.parse(view.lastViewedAt) : new Date(),
{
addSuffix: true,
locale,
}
),
});
return (
+74
View File
@@ -0,0 +1,74 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { DocumentValidation } from "@shared/validations";
import Document from "~/models/Document";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Input from "./Input";
import Text from "./Text";
type Props = {
/** The original document to duplicate */
document: Document;
onSubmit: (documents: Document[]) => void;
};
function DuplicateDialog({ document, onSubmit }: Props) {
const { t } = useTranslation();
const defaultTitle = t(`Copy of {{ documentName }}`, {
documentName: document.title,
});
const [recursive, setRecursive] = React.useState<boolean>(true);
const [title, setTitle] = React.useState<string>(defaultTitle);
const handleRecursiveChange = React.useCallback(
(ev: React.ChangeEvent<HTMLInputElement>) => {
setRecursive(ev.target.checked);
},
[]
);
const handleTitleChange = React.useCallback(
(ev: React.ChangeEvent<HTMLInputElement>) => {
setTitle(ev.target.value);
},
[]
);
const handleSubmit = async () => {
const result = await document.duplicate({
recursive,
title,
});
onSubmit(result);
};
return (
<ConfirmationDialog onSubmit={handleSubmit} submitText={t("Duplicate")}>
<Input
autoFocus
autoSelect
name="title"
label={t("Title")}
onChange={handleTitleChange}
maxLength={DocumentValidation.maxTitleLength}
defaultValue={defaultTitle}
/>
{document.publishedAt && !document.isTemplate && (
<label>
<Text size="small">
<input
type="checkbox"
name="recursive"
checked={recursive}
onChange={handleRecursiveChange}
/>{" "}
{t("Include nested documents")}
</Text>
</label>
)}
</ConfirmationDialog>
);
}
export default observer(DuplicateDialog);
+12 -79
View File
@@ -1,10 +1,11 @@
import { deburr, difference, sortBy } from "lodash";
import deburr from "lodash/deburr";
import difference from "lodash/difference";
import sortBy from "lodash/sortBy";
import { observer } from "mobx-react";
import { DOMParser as ProsemirrorDOMParser } from "prosemirror-model";
import { TextSelection } from "prosemirror-state";
import * as React from "react";
import { mergeRefs } from "react-merge-refs";
import { useHistory } from "react-router-dom";
import { Optional } from "utility-types";
import insertFiles from "@shared/editor/commands/insertFiles";
import { AttachmentPreset } from "@shared/types";
@@ -14,22 +15,18 @@ import { getDataTransferFiles } from "@shared/utils/files";
import parseDocumentSlug from "@shared/utils/parseDocumentSlug";
import { isInternalUrl } from "@shared/utils/urls";
import { AttachmentValidation } from "@shared/validations";
import Document from "~/models/Document";
import ClickablePadding from "~/components/ClickablePadding";
import ErrorBoundary from "~/components/ErrorBoundary";
import HoverPreview from "~/components/HoverPreview";
import type { Props as EditorProps, Editor as SharedEditor } from "~/editor";
import useCurrentUser from "~/hooks/useCurrentUser";
import useDictionary from "~/hooks/useDictionary";
import useEditorClickHandlers from "~/hooks/useEditorClickHandlers";
import useEmbeds from "~/hooks/useEmbeds";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import useUserLocale from "~/hooks/useUserLocale";
import { NotFoundError } from "~/utils/errors";
import { uploadFile } from "~/utils/files";
import { isModKey } from "~/utils/keyboard";
import lazyWithRetry from "~/utils/lazyWithRetry";
import { sharedDocumentPath } from "~/utils/routeHelpers";
import { isHash } from "~/utils/urls";
import DocumentBreadcrumb from "./DocumentBreadcrumb";
const LazyLoadedEditor = lazyWithRetry(() => import("~/editor"));
@@ -41,14 +38,13 @@ export type Props = Optional<
| "onClickLink"
| "embeds"
| "dictionary"
| "onShowToast"
| "extensions"
> & {
shareId?: string | undefined;
embedsDisabled?: boolean;
onHeadingsChange?: (headings: Heading[]) => void;
onSynced?: () => Promise<void>;
onPublish?: (event: React.MouseEvent) => any;
onPublish?: (event: React.MouseEvent) => void;
editorStyle?: React.CSSProperties;
};
@@ -63,27 +59,14 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
} = props;
const userLocale = useUserLocale();
const locale = dateLocale(userLocale);
const { auth, comments, documents } = useStores();
const { showToast } = useToasts();
const { comments, documents } = useStores();
const dictionary = useDictionary();
const embeds = useEmbeds(!shareId);
const history = useHistory();
const localRef = React.useRef<SharedEditor>();
const preferences = auth.user?.preferences;
const preferences = useCurrentUser({ rejectOnEmpty: false })?.preferences;
const previousHeadings = React.useRef<Heading[] | null>(null);
const [activeLinkElement, setActiveLink] =
React.useState<HTMLAnchorElement | null>(null);
const previousCommentIds = React.useRef<string[]>();
const handleLinkActive = React.useCallback((element: HTMLAnchorElement) => {
setActiveLink(element);
return false;
}, []);
const handleLinkInactive = React.useCallback(() => {
setActiveLink(null);
}, []);
const handleSearchLink = React.useCallback(
async (term: string) => {
if (isInternalUrl(term)) {
@@ -120,7 +103,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
const results = await documents.searchTitles(term);
return sortBy(
results.map((document: Document) => ({
results.map(({ document }) => ({
title: document.title,
subtitle: <DocumentBreadcrumb document={document} onlyText />,
url: document.url,
@@ -133,7 +116,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
: 1
);
},
[documents]
[locale, documents]
);
const handleUploadFile = React.useCallback(
@@ -147,47 +130,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
[id]
);
const handleClickLink = React.useCallback(
(href: string, event: MouseEvent) => {
// on page hash
if (isHash(href)) {
window.location.href = href;
return;
}
if (isInternalUrl(href) && !isModKey(event) && !event.shiftKey) {
// relative
let navigateTo = href;
// probably absolute
if (href[0] !== "/") {
try {
const url = new URL(href);
navigateTo = url.pathname + url.hash;
} catch (err) {
navigateTo = href;
}
}
// Link to our own API should be opened in a new tab, not in the app
if (navigateTo.startsWith("/api/")) {
window.open(href, "_blank");
return;
}
// If we're navigating to an internal document link then prepend the
// share route to the URL so that the document is loaded in context
if (shareId && navigateTo.includes("/doc/")) {
navigateTo = sharedDocumentPath(shareId, navigateTo);
}
history.push(navigateTo);
} else if (href) {
window.open(href, "_blank");
}
},
[history, shareId]
);
const { handleClickLink } = useEditorClickHandlers({ shareId });
const focusAtEnd = React.useCallback(() => {
localRef?.current?.focusAtEnd();
@@ -233,11 +176,10 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
(file) => !AttachmentValidation.imageContentTypes.includes(file.type)
);
insertFiles(view, event, pos, files, {
return insertFiles(view, event, pos, files, {
uploadFile: handleUploadFile,
onFileUploadStart: props.onFileUploadStart,
onFileUploadStop: props.onFileUploadStop,
onShowToast: showToast,
dictionary,
isAttachment,
});
@@ -248,7 +190,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
props.onFileUploadStop,
dictionary,
handleUploadFile,
showToast,
]
);
@@ -332,12 +273,10 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
<LazyLoadedEditor
ref={mergeRefs([ref, localRef, handleRefChanged])}
uploadFile={handleUploadFile}
onShowToast={showToast}
embeds={embeds}
userPreferences={preferences}
dictionary={dictionary}
{...props}
onHoverLink={handleLinkActive}
onClickLink={handleClickLink}
onSearchLink={handleSearchLink}
onChange={handleChange}
@@ -352,12 +291,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
minHeight={props.editorStyle.paddingBottom}
/>
)}
{activeLinkElement && !shareId && (
<HoverPreview
element={activeLinkElement}
onClose={handleLinkInactive}
/>
)}
</>
</ErrorBoundary>
);
+23
View File
@@ -0,0 +1,23 @@
import styled from "styled-components";
import Button from "~/components/Button";
import { hover } from "~/styles";
import Flex from "../Flex";
export const EmojiButton = styled(Button)`
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
&: ${hover},
&:active,
&[aria-expanded= "true"] {
opacity: 1 !important;
}
`;
export const Emoji = styled(Flex)<{ size?: number }>`
line-height: 1.6;
${(props) => (props.size ? `font-size: ${props.size}px` : "")}
`;
+269
View File
@@ -0,0 +1,269 @@
import data from "@emoji-mart/data";
import Picker from "@emoji-mart/react";
import { SmileyIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { usePopoverState, PopoverDisclosure } from "reakit/Popover";
import styled, { useTheme } from "styled-components";
import { depths, s } from "@shared/styles";
import { toRGB } from "@shared/utils/color";
import Button from "~/components/Button";
import Popover from "~/components/Popover";
import useStores from "~/hooks/useStores";
import useUserLocale from "~/hooks/useUserLocale";
import { Emoji, EmojiButton } from "./components";
/* Locales supported by emoji-mart */
const supportedLocales = [
"en",
"ar",
"be",
"cs",
"de",
"es",
"fa",
"fi",
"fr",
"hi",
"it",
"ja",
"kr",
"nl",
"pl",
"pt",
"ru",
"sa",
"tr",
"uk",
"vi",
"zh",
];
/**
* React hook to derive emoji picker's theme from UI theme
*
* @returns {string} Theme to use for emoji picker
*/
function usePickerTheme(): string {
const { ui } = useStores();
const { theme } = ui;
if (theme === "system") {
return "auto";
}
return theme;
}
type Props = {
/** The selected emoji, if any */
value?: string | null;
/** Callback when an emoji is selected */
onChange: (emoji: string | null) => void | Promise<void>;
/** Callback when the picker is opened */
onOpen?: () => void;
/** Callback when the picker is closed */
onClose?: () => void;
/** Callback when the picker is clicked outside of */
onClickOutside: () => void;
/** Whether to auto focus the search input on open */
autoFocus?: boolean;
/** Class name to apply to the trigger button */
className?: string;
};
function EmojiPicker({
value,
onOpen,
onClose,
onChange,
onClickOutside,
autoFocus,
className,
}: Props) {
const { t } = useTranslation();
const pickerTheme = usePickerTheme();
const theme = useTheme();
const locale = useUserLocale(true) ?? "en";
const popover = usePopoverState({
placement: "bottom-start",
modal: true,
unstable_offset: [0, 0],
});
const [emojisPerLine, setEmojisPerLine] = React.useState(9);
const pickerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (popover.visible) {
onOpen?.();
} else {
onClose?.();
}
}, [popover.visible, onOpen, onClose]);
React.useEffect(() => {
if (popover.visible && pickerRef.current) {
// 28 is picker's observed width when perLine is set to 0
// and 36 is the default emojiButtonSize
// Ref: https://github.com/missive/emoji-mart#options--props
setEmojisPerLine(Math.floor((pickerRef.current.clientWidth - 28) / 36));
}
}, [popover.visible]);
const handleEmojiChange = React.useCallback(
async (emoji) => {
popover.hide();
await onChange(emoji ? emoji.native : null);
},
[popover, onChange]
);
const handleClick = React.useCallback(
(ev: React.MouseEvent) => {
ev.stopPropagation();
if (popover.visible) {
popover.hide();
} else {
popover.show();
}
},
[popover]
);
const handleClickOutside = React.useCallback(() => {
// It was observed that onClickOutside got triggered
// even when the picker wasn't open or opened at all.
// Hence, this guard here...
if (popover.visible) {
onClickOutside();
}
}, [popover.visible, onClickOutside]);
// Auto focus search input when picker is opened
React.useLayoutEffect(() => {
if (autoFocus && popover.visible) {
requestAnimationFrame(() => {
const searchInput = pickerRef.current
?.querySelector("em-emoji-picker")
?.shadowRoot?.querySelector(
"input[type=search]"
) as HTMLInputElement | null;
searchInput?.focus();
});
}
}, [autoFocus, popover.visible]);
return (
<>
<PopoverDisclosure {...popover}>
{(props) => (
<EmojiButton
{...props}
className={className}
onClick={handleClick}
icon={
value ? (
<Emoji size={32} align="center" justify="center">
{value}
</Emoji>
) : (
<StyledSmileyIcon size={32} color={theme.textTertiary} />
)
}
neutral
borderOnHover
/>
)}
</PopoverDisclosure>
<PickerPopover
{...popover}
tabIndex={0}
// This prevents picker from closing when any of its
// children are focused, e.g, clicking on search bar or
// a click on skin tone button
onClick={(e) => e.stopPropagation()}
width={352}
aria-label={t("Emoji Picker")}
>
{popover.visible && (
<>
{value && (
<RemoveButton neutral onClick={() => handleEmojiChange(null)}>
{t("Remove")}
</RemoveButton>
)}
<PickerStyles ref={pickerRef}>
<Picker
// https://github.com/missive/emoji-mart/issues/800
locale={
locale === "ko"
? "kr"
: supportedLocales.includes(locale)
? locale
: "en"
}
data={data}
onEmojiSelect={handleEmojiChange}
theme={pickerTheme}
previewPosition="none"
perLine={emojisPerLine}
onClickOutside={handleClickOutside}
/>
</PickerStyles>
</>
)}
</PickerPopover>
</>
);
}
const StyledSmileyIcon = styled(SmileyIcon)`
flex-shrink: 0;
@media print {
display: none;
}
`;
const RemoveButton = styled(Button)`
margin-left: -12px;
margin-bottom: 8px;
border-radius: 6px;
height: 24px;
font-size: 13px;
> :first-child {
min-height: unset;
line-height: unset;
}
`;
const PickerPopover = styled(Popover)`
z-index: ${depths.popover};
> :first-child {
padding-top: 8px;
padding-bottom: 0;
max-height: 488px;
overflow: unset;
}
`;
const PickerStyles = styled.div`
margin-left: -24px;
margin-right: -24px;
em-emoji-picker {
--shadow: none;
--font-family: ${s("fontFamily")};
--rgb-background: ${(props) => toRGB(props.theme.menuBackground)};
--rgb-accent: ${(props) => toRGB(props.theme.accent)};
--border-radius: 6px;
margin-left: auto;
margin-right: auto;
min-height: 443px;
}
`;
export default EmojiPicker;
+2 -2
View File
@@ -121,11 +121,11 @@ class ErrorBoundary extends React.Component<Props> {
<Button onClick={this.handleReload}>{t("Reload")}</Button>{" "}
{this.showDetails ? (
<Button onClick={this.handleReportBug} neutral>
<Trans>Report a Bug</Trans>
<Trans>Report a bug</Trans>
</Button>
) : (
<Button onClick={this.handleShowDetails} neutral>
<Trans>Show Detail</Trans>
<Trans>Show detail</Trans>
</Button>
)}
</p>
+15 -3
View File
@@ -1,6 +1,7 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { FileOperationFormat, NotificationEventType } from "@shared/types";
import Collection from "~/models/Collection";
@@ -10,7 +11,8 @@ import Text from "~/components/Text";
import env from "~/env";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import history from "~/utils/history";
import { settingsPath } from "~/utils/routeHelpers";
type Props = {
collection?: Collection;
@@ -24,7 +26,6 @@ function ExportDialog({ collection, onSubmit }: Props) {
const [includeAttachments, setIncludeAttachments] =
React.useState<boolean>(true);
const user = useCurrentUser();
const { showToast } = useToasts();
const { collections } = useStores();
const { t } = useTranslation();
const appName = env.APP_NAME;
@@ -46,11 +47,22 @@ function ExportDialog({ collection, onSubmit }: Props) {
const handleSubmit = async () => {
if (collection) {
await collection.export(format, includeAttachments);
toast.success(t("Export started"), {
description: t(`Your file will be available in {{ location }} soon`, {
location: `"${t("Settings")} > ${t("Export")}"`,
}),
action: {
label: t("View"),
onClick: () => {
history.push(settingsPath("export"));
},
},
});
} else {
await collections.export(format, includeAttachments);
toast.success(t("Export started"));
}
onSubmit();
showToast(t("Export started"), { type: "success" });
};
const items = [
+6 -3
View File
@@ -32,9 +32,12 @@ function Facepile({
</span>
</More>
)}
{users.slice(0, limit).map((user) => (
<AvatarWrapper key={user.id}>{renderAvatar(user)}</AvatarWrapper>
))}
{users
.filter(Boolean)
.slice(0, limit)
.map((user) => (
<AvatarWrapper key={user.id}>{renderAvatar(user)}</AvatarWrapper>
))}
</Avatars>
);
}
+1 -1
View File
@@ -80,7 +80,7 @@ const Note = styled(Text)`
margin-bottom: 0;
line-height: 1.2em;
font-size: 14px;
font-weight: 400;
font-weight: 500;
color: ${s("textTertiary")};
`;
+5 -3
View File
@@ -1,4 +1,4 @@
import { throttle } from "lodash";
import throttle from "lodash/throttle";
import { observer } from "mobx-react";
import { MenuIcon } from "outline-icons";
import { transparentize } from "polished";
@@ -6,6 +6,7 @@ import * as React from "react";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { depths, s } from "@shared/styles";
import { supportsPassiveListener } from "@shared/utils/browser";
import Button from "~/components/Button";
import Fade from "~/components/Fade";
import Flex from "~/components/Flex";
@@ -14,16 +15,16 @@ import useMobile from "~/hooks/useMobile";
import useStores from "~/hooks/useStores";
import { draggableOnDesktop, fadeOnDesktopBackgrounded } from "~/styles";
import Desktop from "~/utils/Desktop";
import { supportsPassiveListener } from "~/utils/browser";
type Props = {
left?: React.ReactNode;
title: React.ReactNode;
actions?: React.ReactNode;
hasSidebar?: boolean;
className?: string;
};
function Header({ left, title, actions, hasSidebar }: Props) {
function Header({ left, title, actions, hasSidebar, className }: Props) {
const { ui } = useStores();
const isMobile = useMobile();
const hasMobileSidebar = hasSidebar && isMobile;
@@ -54,6 +55,7 @@ function Header({ left, title, actions, hasSidebar }: Props) {
<Wrapper
align="center"
shrink={false}
className={className}
$passThrough={passThrough}
$insetTitleAdjust={ui.sidebarIsClosed && Desktop.hasInsetTitlebar()}
>
+2 -1
View File
@@ -1,9 +1,10 @@
import styled from "styled-components";
const Heading = styled.h1<{ centered?: boolean }>`
const Heading = styled.h1<{ as?: string; centered?: boolean }>`
display: flex;
align-items: center;
user-select: none;
${(props) => (props.as ? "" : "margin-top: 6vh; font-weight: 600;")}
${(props) => (props.centered ? "text-align: center;" : "")}
`;
+4 -5
View File
@@ -1,8 +1,7 @@
import { escapeRegExp } from "lodash";
import escapeRegExp from "lodash/escapeRegExp";
import * as React from "react";
import replace from "string-replace-to-array";
import styled from "styled-components";
import { s } from "@shared/styles";
type Props = React.HTMLAttributes<HTMLSpanElement> & {
highlight: (string | null | undefined) | RegExp;
@@ -44,9 +43,9 @@ function Highlight({
}
export const Mark = styled.mark`
background: ${s("searchHighlight")};
border-radius: 2px;
padding: 0 2px;
color: inherit;
background: transparent;
font-weight: 600;
`;
export default Highlight;
+2 -2
View File
@@ -4,7 +4,7 @@ import styled, { css } from "styled-components";
import { s } from "@shared/styles";
import Text from "~/components/Text";
export const CARD_MARGIN = 16;
export const CARD_MARGIN = 10;
const NUMBER_OF_LINES = 10;
@@ -17,7 +17,7 @@ const StyledText = styled(Text)`
`;
export const Preview = styled(Link)`
cursor: ${(props: any) =>
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),
+195 -149
View File
@@ -2,13 +2,14 @@ import { m } from "framer-motion";
import * as React from "react";
import { Portal } from "react-portal";
import styled from "styled-components";
import { depths, s } from "@shared/styles";
import { depths } from "@shared/styles";
import { UnfurlType } from "@shared/types";
import LoadingIndicator from "~/components/LoadingIndicator";
import useEventListener from "~/hooks/useEventListener";
import useKeyDown from "~/hooks/useKeyDown";
import useMobile from "~/hooks/useMobile";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import usePrevious from "~/hooks/usePrevious";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import { client } from "~/utils/ApiClient";
@@ -17,131 +18,77 @@ import HoverPreviewDocument from "./HoverPreviewDocument";
import HoverPreviewLink from "./HoverPreviewLink";
import HoverPreviewMention from "./HoverPreviewMention";
const DELAY_OPEN = 300;
const DELAY_CLOSE = 600;
const POINTER_HEIGHT = 22;
const POINTER_WIDTH = 22;
type Props = {
/* The HTML element that is being hovered over */
element: HTMLAnchorElement;
/* A callback on close of the hover preview */
/** The HTML element that is being hovered over, or null if none. */
element: HTMLElement | null;
/** A callback on close of the hover preview. */
onClose: () => void;
};
function HoverPreviewInternal({ element, onClose }: Props) {
const url = element.href || element.dataset.url;
enum Direction {
UP,
DOWN,
}
function HoverPreviewDesktop({ element, onClose }: Props) {
const url = element?.getAttribute("href") || element?.dataset.url;
const previousUrl = usePrevious(url, true);
const [isVisible, setVisible] = React.useState(false);
const timerClose = React.useRef<ReturnType<typeof setTimeout>>();
const timerOpen = React.useRef<ReturnType<typeof setTimeout>>();
const cardRef = React.useRef<HTMLDivElement>(null);
const stores = useStores();
const [cardLeft, setCardLeft] = React.useState(0);
const [cardTop, setCardTop] = React.useState(0);
const [pointerOffset, setPointerOffset] = React.useState(0);
React.useLayoutEffect(() => {
if (isVisible && cardRef.current) {
const elem = element.getBoundingClientRect();
const card = cardRef.current.getBoundingClientRect();
const top = elem.bottom + window.scrollY;
setCardTop(top);
let left = elem.left;
let pointerOffset = elem.width / 2;
if (left + card.width > window.innerWidth) {
// shift card leftwards by the amount it went out of screen
let shiftBy = left + card.width - window.innerWidth;
// shift a littler further to leave some margin between card and window boundary
shiftBy += CARD_MARGIN;
left -= shiftBy;
// shift pointer rightwards by same amount so as to position it back correctly
pointerOffset += shiftBy;
}
setCardLeft(left);
setPointerOffset(pointerOffset);
}
}, [isVisible, element]);
const { data, request, loading } = useRequest(
React.useCallback(
() =>
client.post("/urls.unfurl", {
url,
documentId: stores.ui.activeDocumentId,
}),
[url, stores.ui.activeDocumentId]
)
);
React.useEffect(() => {
if (url) {
stopOpenTimer();
setVisible(false);
void request();
}
}, [url, request]);
const stopOpenTimer = () => {
if (timerOpen.current) {
clearTimeout(timerOpen.current);
timerOpen.current = undefined;
}
};
const { cardLeft, cardTop, pointerLeft, pointerTop, pointerDir } =
useHoverPosition({
cardRef,
element,
isVisible,
});
const closePreview = React.useCallback(() => {
if (isVisible) {
stopOpenTimer();
setVisible(false);
onClose();
}
}, [isVisible, onClose]);
setVisible(false);
onClose();
}, [onClose]);
useOnClickOutside(cardRef, closePreview);
useKeyDown("Escape", closePreview);
useEventListener("scroll", closePreview, window, { capture: true });
const stopCloseTimer = () => {
const stopCloseTimer = React.useCallback(() => {
if (timerClose.current) {
clearTimeout(timerClose.current);
timerClose.current = undefined;
}
};
const startOpenTimer = () => {
if (!timerOpen.current) {
timerOpen.current = setTimeout(() => setVisible(true), DELAY_OPEN);
}
};
}, []);
const startCloseTimer = React.useCallback(() => {
stopOpenTimer();
timerClose.current = setTimeout(closePreview, DELAY_CLOSE);
}, [closePreview]);
// Open and close the preview when the element changes.
React.useEffect(() => {
if (element) {
setVisible(true);
} else {
startCloseTimer();
}
}, [startCloseTimer, element]);
// Close the preview on Escape, scroll, or click outside.
useOnClickOutside(cardRef, closePreview);
useKeyDown("Escape", closePreview);
useEventListener("scroll", closePreview, window, { capture: true });
// Ensure that the preview stays open while the user is hovering over the card.
React.useEffect(() => {
const card = cardRef.current;
if (data) {
startOpenTimer();
if (isVisible) {
if (card) {
card.addEventListener("mouseenter", stopCloseTimer);
card.addEventListener("mouseleave", startCloseTimer);
}
element.addEventListener("mouseout", startCloseTimer);
element.addEventListener("mouseover", stopCloseTimer);
element.addEventListener("mouseover", startOpenTimer);
}
return () => {
element.removeEventListener("mouseout", startCloseTimer);
element.removeEventListener("mouseover", stopCloseTimer);
element.removeEventListener("mouseover", startOpenTimer);
if (card) {
card.removeEventListener("mouseenter", stopCloseTimer);
card.removeEventListener("mouseleave", startCloseTimer);
@@ -149,7 +96,83 @@ function HoverPreviewInternal({ element, onClose }: Props) {
stopCloseTimer();
};
}, [element, startCloseTimer, data]);
}, [element, startCloseTimer, isVisible, stopCloseTimer]);
const displayUrl = url ?? previousUrl;
if (!isVisible || !displayUrl) {
return null;
}
return (
<Portal>
<Position top={cardTop} left={cardLeft} ref={cardRef} aria-hidden>
<DataLoader url={displayUrl}>
{(data) => (
<Animate
initial={{ opacity: 0, y: -20, pointerEvents: "none" }}
animate={{ opacity: 1, y: 0, pointerEvents: "auto" }}
>
{data.type === UnfurlType.Mention ? (
<HoverPreviewMention
url={data.thumbnailUrl}
title={data.title}
info={data.meta.info}
color={data.meta.color}
/>
) : data.type === UnfurlType.Document ? (
<HoverPreviewDocument
id={data.meta.id}
url={data.url}
title={data.title}
description={data.description}
info={data.meta.info}
/>
) : (
<HoverPreviewLink
url={data.url}
thumbnailUrl={data.thumbnailUrl}
title={data.title}
description={data.description}
/>
)}
<Pointer
top={pointerTop}
left={pointerLeft}
direction={pointerDir}
/>
</Animate>
)}
</DataLoader>
</Position>
</Portal>
);
}
function DataLoader({
url,
children,
}: {
url: string;
children: (data: any) => React.ReactNode;
}) {
const { ui } = useStores();
const { data, request, loading } = useRequest(
React.useCallback(
() =>
client.post("/urls.unfurl", {
url,
documentId: ui.activeDocumentId,
}),
[url, ui.activeDocumentId]
)
);
React.useEffect(() => {
if (url) {
void request();
}
}, [url, request]);
if (loading) {
return <LoadingIndicator />;
@@ -159,46 +182,7 @@ function HoverPreviewInternal({ element, onClose }: Props) {
return null;
}
return (
<Portal>
<Position top={cardTop} left={cardLeft} aria-hidden>
{isVisible ? (
<Animate
initial={{ opacity: 0, y: -20, pointerEvents: "none" }}
animate={{ opacity: 1, y: 0, pointerEvents: "auto" }}
>
{data.type === UnfurlType.Mention ? (
<HoverPreviewMention
ref={cardRef}
url={data.thumbnailUrl}
title={data.title}
info={data.meta.info}
color={data.meta.color}
/>
) : data.type === UnfurlType.Document ? (
<HoverPreviewDocument
ref={cardRef}
id={data.meta.id}
url={data.url}
title={data.title}
description={data.description}
info={data.meta.info}
/>
) : (
<HoverPreviewLink
ref={cardRef}
url={data.url}
thumbnailUrl={data.thumbnailUrl}
title={data.title}
description={data.description}
/>
)}
<Pointer offset={pointerOffset} />
</Animate>
) : null}
</Position>
</Portal>
);
return <>{children(data)}</>;
}
function HoverPreview({ element, ...rest }: Props) {
@@ -207,7 +191,64 @@ function HoverPreview({ element, ...rest }: Props) {
return null;
}
return <HoverPreviewInternal {...rest} element={element} />;
return <HoverPreviewDesktop {...rest} element={element} />;
}
function useHoverPosition({
cardRef,
element,
isVisible,
}: {
cardRef: React.RefObject<HTMLDivElement>;
element: HTMLElement | null;
isVisible: boolean;
}) {
const [cardLeft, setCardLeft] = React.useState(0);
const [cardTop, setCardTop] = React.useState(0);
const [pointerLeft, setPointerLeft] = React.useState(0);
const [pointerTop, setPointerTop] = React.useState(0);
const [pointerDir, setPointerDir] = React.useState(Direction.UP);
React.useLayoutEffect(() => {
if (isVisible && element && cardRef.current) {
const elem = element.getBoundingClientRect();
const card = cardRef.current.getBoundingClientRect();
let cTop = elem.bottom + window.scrollY + CARD_MARGIN;
let pTop = -POINTER_HEIGHT;
let pDir = Direction.UP;
if (cTop + card.height > window.innerHeight + window.scrollY) {
// shift card upwards if it goes out of screen
const bottom = elem.top + window.scrollY;
cTop = bottom - card.height;
// shift a little further to leave some margin between card and element boundary
cTop -= CARD_MARGIN;
// pointer should be shifted downwards to align with card's bottom
pTop = card.height;
pDir = Direction.DOWN;
}
setCardTop(cTop);
setPointerTop(pTop);
setPointerDir(pDir);
let cLeft = elem.left;
let pLeft = elem.width / 2;
if (cLeft + card.width > window.innerWidth) {
// shift card leftwards by the amount it went out of screen
let shiftBy = cLeft + card.width - window.innerWidth;
// shift a little further to leave some margin between card and window boundary
shiftBy += CARD_MARGIN;
cLeft -= shiftBy;
// shift pointer rightwards by same amount so as to position it back correctly
pLeft += shiftBy;
}
setCardLeft(cLeft);
setPointerLeft(pLeft);
}
}, [isVisible, cardRef, element]);
return { cardLeft, cardTop, pointerLeft, pointerTop, pointerDir };
}
const Animate = styled(m.div)`
@@ -217,7 +258,6 @@ const Animate = styled(m.div)`
`;
const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
margin-top: 10px;
position: ${({ fixed }) => (fixed ? "fixed" : "absolute")};
z-index: ${depths.hoverPreview};
display: flex;
@@ -227,11 +267,11 @@ const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
${({ left }) => (left !== undefined ? `left: ${left}px` : "")};
`;
const Pointer = styled.div<{ offset: number }>`
top: -22px;
left: ${(props) => props.offset}px;
width: 22px;
height: 22px;
const Pointer = styled.div<{ top: number; left: number; direction: Direction }>`
top: ${(props) => props.top}px;
left: ${(props) => props.left}px;
width: ${POINTER_WIDTH}px;
height: ${POINTER_HEIGHT}px;
position: absolute;
transform: translateX(-50%);
pointer-events: none;
@@ -241,20 +281,26 @@ const Pointer = styled.div<{ offset: number }>`
content: "";
display: inline-block;
position: absolute;
bottom: 0;
right: 0;
${({ direction }) => (direction === Direction.UP ? "bottom: 0" : "top: 0")};
${({ direction }) => (direction === Direction.UP ? "right: 0" : "left: 0")};
}
&:before {
border: 8px solid transparent;
border-bottom-color: ${(props) =>
props.theme.menuBorder || "rgba(0, 0, 0, 0.1)"};
right: -1px;
${({ direction, theme }) =>
direction === Direction.UP
? `border-bottom-color: ${theme.menuBorder || "rgba(0, 0, 0, 0.1)"}`
: `border-top-color: ${theme.menuBorder || "rgba(0, 0, 0, 0.1)"}`};
${({ direction }) =>
direction === Direction.UP ? "right: -1px" : "left: -1px"};
}
&:after {
border: 7px solid transparent;
border-bottom-color: ${s("menuBackground")};
${({ direction, theme }) =>
direction === Direction.UP
? `border-bottom-color: ${theme.menuBackground}`
: `border-top-color: ${theme.menuBackground}`};
}
`;
@@ -26,9 +26,9 @@ const HoverPreviewLink = React.forwardRef(function _HoverPreviewLink(
) {
return (
<Preview as="a" href={url} target="_blank" rel="noopener noreferrer">
<Flex column>
<Flex column ref={ref}>
{thumbnailUrl ? <Thumbnail src={thumbnailUrl} alt={""} /> : null}
<Card ref={ref}>
<Card>
<CardContent>
<Flex column>
<Title>{title}</Title>
+23 -10
View File
@@ -49,11 +49,7 @@ import NudeButton from "~/components/NudeButton";
import Text from "~/components/Text";
import lazyWithRetry from "~/utils/lazyWithRetry";
import DelayedMount from "./DelayedMount";
const style = {
width: 30,
height: 30,
};
import LetterIcon from "./Icons/LetterIcon";
const TwitterPicker = lazyWithRetry(
() => import("react-color/lib/components/twitter/Twitter")
@@ -136,6 +132,10 @@ export const icons = {
component: LightningIcon,
keywords: "lightning fast zap",
},
letter: {
component: LetterIcon,
keywords: "letter",
},
math: {
component: MathIcon,
keywords: "math formula",
@@ -206,11 +206,19 @@ type Props = {
onOpen?: () => void;
onClose?: () => void;
onChange: (color: string, icon: string) => void;
initial: string;
icon: string;
color: string;
};
function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
function IconPicker({
onOpen,
onClose,
icon,
initial,
color,
onChange,
}: Props) {
const { t } = useTranslation();
const theme = useTheme();
const menu = useMenuState({
@@ -230,7 +238,9 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
as={icons[icon || "collection"].component}
color={color}
size={30}
/>
>
{initial}
</Icon>
</Button>
)}
</MenuButton>
@@ -238,6 +248,7 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
{...menu}
onOpen={onOpen}
onClose={onClose}
maxWidth={308}
aria-label={t("Choose icon")}
>
<Icons>
@@ -251,13 +262,14 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
<IconButton
style={
{
...style,
"--delay": `${index * 8}ms`,
} as React.CSSProperties
}
{...props}
>
<Icon as={icons[name].component} color={color} size={30} />
<Icon as={icons[name].component} color={color} size={30}>
{initial}
</Icon>
</IconButton>
)}
</MenuItem>
@@ -318,7 +330,7 @@ const Icons = styled.div`
padding: 8px;
${breakpoint("tablet")`
width: 276px;
width: 304px;
`};
`;
@@ -329,6 +341,7 @@ const Button = styled(NudeButton)`
`;
const IconButton = styled(NudeButton)`
vertical-align: top;
border-radius: 4px;
margin: 0px 6px 6px 0px;
width: 30px;
+5 -1
View File
@@ -39,7 +39,11 @@ function ResolvedCollectionIcon({
if (collection.icon && collection.icon !== "collection") {
try {
const Component = icons[collection.icon].component;
return <Component color={color} size={size} />;
return (
<Component color={color} size={size}>
{collection.initial}
</Component>
);
} catch (error) {
Logger.warn("Failed to render custom icon", {
icon: collection.icon,
+3 -3
View File
@@ -2,9 +2,9 @@ import * as React from "react";
import styled from "styled-components";
type Props = {
/* The emoji to render */
/** The emoji to render */
emoji: string;
/* The size of the emoji, 24px is default to match standard icons */
/** The size of the emoji, 24px is default to match standard icons */
size?: number;
};
@@ -29,5 +29,5 @@ const Span = styled.span<{ $size: number }>`
width: ${(props) => props.$size}px;
height: ${(props) => props.$size}px;
text-indent: -0.15em;
font-size: 14px;
font-size: ${(props) => props.$size - 10}px;
`;
+35
View File
@@ -0,0 +1,35 @@
import * as React from "react";
import styled from "styled-components";
import { s } from "@shared/styles";
import Squircle from "../Squircle";
type Props = {
/** The width and height of the icon, including standard padding. */
size?: number;
children: React.ReactNode;
};
/**
* A squircle shaped icon with a letter inside, used for collections.
*/
const LetterIcon = ({ children, size = 24, ...rest }: Props) => (
<LetterIconWrapper $size={size}>
<Squircle size={Math.round(size * 0.66)} {...rest}>
{children}
</Squircle>
</LetterIconWrapper>
);
const LetterIconWrapper = styled.div<{ $size: number }>`
display: inline-flex;
align-items: center;
justify-content: center;
width: ${({ $size }) => $size}px;
height: ${({ $size }) => $size}px;
font-weight: 700;
font-size: ${({ $size }) => $size / 2}px;
color: ${s("background")};
`;
export default LetterIcon;
+1 -1
View File
@@ -5,7 +5,7 @@ type Props = {
size?: number;
/** The color of the icon, defaults to the current text color */
color?: string;
/* Whether the safe area should be removed and have graphic across full size */
/** Whether the safe area should be removed and have graphic across full size */
cover?: boolean;
};
+37 -12
View File
@@ -1,4 +1,5 @@
import * as React from "react";
import { mergeRefs } from "react-merge-refs";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
@@ -7,7 +8,7 @@ import Flex from "~/components/Flex";
import Text from "~/components/Text";
import { undraggableOnDesktop } from "~/styles";
const RealTextarea = styled.textarea<{ hasIcon?: boolean }>`
export const NativeTextarea = styled.textarea<{ hasIcon?: boolean }>`
border: 0;
flex: 1;
padding: 8px 12px 8px ${(props) => (props.hasIcon ? "8px" : "12px")};
@@ -18,10 +19,11 @@ const RealTextarea = styled.textarea<{ hasIcon?: boolean }>`
&:disabled,
&::placeholder {
color: ${s("placeholder")};
opacity: 1;
}
`;
const RealInput = styled.input<{ hasIcon?: boolean }>`
export const NativeInput = styled.input<{ hasIcon?: boolean }>`
border: 0;
flex: 1;
padding: 8px 12px 8px ${(props) => (props.hasIcon ? "8px" : "12px")};
@@ -38,6 +40,7 @@ const RealInput = styled.input<{ hasIcon?: boolean }>`
&:disabled,
&::placeholder {
color: ${s("placeholder")};
opacity: 1;
}
&:-webkit-autofill,
@@ -55,7 +58,7 @@ const RealInput = styled.input<{ hasIcon?: boolean }>`
`};
`;
const Wrapper = styled.div<{
export const Wrapper = styled.div<{
flex?: boolean;
short?: boolean;
minHeight?: number;
@@ -110,9 +113,11 @@ export const LabelText = styled.div`
display: inline-block;
`;
export type Props = React.InputHTMLAttributes<
HTMLInputElement | HTMLTextAreaElement
> & {
export interface Props
extends Omit<
React.InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement>,
"prefix"
> {
type?: "text" | "email" | "checkbox" | "search" | "textarea";
labelHidden?: boolean;
label?: string;
@@ -120,19 +125,25 @@ export type Props = React.InputHTMLAttributes<
short?: boolean;
margin?: string | number;
error?: string;
/** Optional component that appears inside the input before the textarea and any icon */
prefix?: React.ReactNode;
/** Optional icon that appears inside the input before the textarea */
icon?: React.ReactNode;
/* Callback is triggered with the CMD+Enter keyboard combo */
/** Like autoFocus, but also select any text in the input */
autoSelect?: boolean;
/** Callback is triggered with the CMD+Enter keyboard combo */
onRequestSubmit?: (
ev: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>
) => unknown;
onFocus?: (ev: React.SyntheticEvent) => unknown;
onBlur?: (ev: React.SyntheticEvent) => unknown;
};
}
function Input(
props: Props,
ref: React.RefObject<HTMLInputElement | HTMLTextAreaElement>
) {
const internalRef = React.useRef<HTMLInputElement | HTMLTextAreaElement>();
const [focused, setFocused] = React.useState(false);
const handleBlur = (ev: React.SyntheticEvent) => {
@@ -165,6 +176,12 @@ function Input(
}
};
React.useEffect(() => {
if (props.autoSelect && internalRef.current) {
internalRef.current.select();
}
}, [props.autoSelect, internalRef]);
const {
type = "text",
icon,
@@ -174,6 +191,7 @@ function Input(
className,
short,
flex,
prefix,
labelHidden,
onFocus,
onBlur,
@@ -194,10 +212,14 @@ function Input(
wrappedLabel
))}
<Outline focused={focused} margin={margin}>
{prefix}
{icon && <IconWrapper>{icon}</IconWrapper>}
{type === "textarea" ? (
<RealTextarea
ref={ref as React.RefObject<HTMLTextAreaElement>}
<NativeTextarea
ref={mergeRefs([
internalRef,
ref as React.RefObject<HTMLTextAreaElement>,
])}
onBlur={handleBlur}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
@@ -205,8 +227,11 @@ function Input(
{...rest}
/>
) : (
<RealInput
ref={ref as React.RefObject<HTMLInputElement>}
<NativeInput
ref={mergeRefs([
internalRef,
ref as React.RefObject<HTMLInputElement>,
])}
onBlur={handleBlur}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
@@ -2,31 +2,18 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { s } from "@shared/styles";
import { CollectionPermission } from "@shared/types";
import InputSelect, { Props as SelectProps } from "~/components/InputSelect";
import { Permission } from "~/types";
export default function InputMemberPermissionSelect(
props: Partial<SelectProps>
props: Partial<SelectProps> & { permissions: Permission[] }
) {
const { t } = useTranslation();
return (
<Select
label={t("Permissions")}
options={[
{
label: t("View only"),
value: CollectionPermission.Read,
},
{
label: t("View and edit"),
value: CollectionPermission.ReadWrite,
},
{
label: t("Admin"),
value: CollectionPermission.Admin,
},
]}
options={props.permissions}
ariaLabel={t("Permissions")}
labelHidden
nude
-63
View File
@@ -1,63 +0,0 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Trans } from "react-i18next";
import styled from "styled-components";
import Editor from "~/components/Editor";
import { LabelText, Outline } from "~/components/Input";
import Text from "~/components/Text";
type Props = {
label: string;
minHeight?: number;
maxHeight?: number;
readOnly?: boolean;
};
function InputRich({ label, minHeight, maxHeight, ...rest }: Props) {
const [focused, setFocused] = React.useState<boolean>(false);
const handleBlur = React.useCallback(() => {
setFocused(false);
}, []);
const handleFocus = React.useCallback(() => {
setFocused(true);
}, []);
return (
<>
<LabelText>{label}</LabelText>
<StyledOutline
maxHeight={maxHeight}
minHeight={minHeight}
focused={focused}
>
<React.Suspense
fallback={
<Text type="secondary">
<Trans>Loading editor</Trans>
</Text>
}
>
<Editor onBlur={handleBlur} onFocus={handleFocus} grow {...rest} />
</React.Suspense>
</StyledOutline>
</>
);
}
const StyledOutline = styled(Outline)<{
minHeight?: number;
maxHeight?: number;
focused?: boolean;
}>`
display: block;
padding: 8px 12px;
min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : "0")};
max-height: ${({ maxHeight }) => (maxHeight ? `${maxHeight}px` : "auto")};
overflow-y: auto;
> * {
display: block;
}
`;
export default observer(InputRich);
+13 -8
View File
@@ -46,6 +46,10 @@ export type Props = {
onChange?: (value: string | null) => void;
};
interface InnerProps extends React.HTMLAttributes<HTMLDivElement> {
placement: Placement;
}
const getOptionFromValue = (options: Option[], value: string | null) =>
options.find((option) => option.value === value);
@@ -102,6 +106,10 @@ const InputSelect = (props: Props) => {
(option) => option.value === select.selectedValue
);
React.useEffect(() => {
select.setSelectedValue(value);
}, [value]);
React.useEffect(() => {
if (previousValue.current === select.selectedValue) {
return;
@@ -147,11 +155,7 @@ const InputSelect = (props: Props) => {
)}
</Select>
<SelectPopover {...select} {...popOver} aria-label={ariaLabel}>
{(
props: React.HTMLAttributes<HTMLDivElement> & {
placement: Placement;
}
) => {
{(props: InnerProps) => {
const topAnchor = props.style?.top === "0";
const rightAnchor = props.placement === "bottom-end";
@@ -163,6 +167,7 @@ const InputSelect = (props: Props) => {
topAnchor={topAnchor}
rightAnchor={rightAnchor}
hiddenScrollbars
maxWidth={400}
style={
maxHeight && topAnchor
? {
@@ -244,8 +249,8 @@ const StyledButton = styled(Button)<{ nude?: boolean }>`
${Inner} {
line-height: 28px;
padding-left: 16px;
padding-right: 8px;
padding-left: 12px;
padding-right: 4px;
}
svg {
@@ -267,7 +272,7 @@ const Wrapper = styled.label<{ short?: boolean }>`
max-width: ${(props) => (props.short ? "350px" : "100%")};
`;
const Positioner = styled(Position)`
export const Positioner = styled(Position)`
&.focus-visible {
${StyledSelectOption} {
&[aria-selected="true"] {
+6 -5
View File
@@ -2,6 +2,7 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { $Diff } from "utility-types";
import { CollectionPermission } from "@shared/types";
import { EmptySelectValue } from "~/types";
import InputSelect, { Props, Option } from "./InputSelect";
export default function InputSelectPermission(
@@ -17,8 +18,8 @@ export default function InputSelectPermission(
const { t } = useTranslation();
const handleChange = React.useCallback(
(value) => {
if (value === "no_access") {
value = "";
if (value === EmptySelectValue) {
value = null;
}
onChange?.(value);
@@ -31,7 +32,7 @@ export default function InputSelectPermission(
label={t("Default access")}
options={[
{
label: t("View and edit"),
label: t("Can edit"),
value: CollectionPermission.ReadWrite,
},
{
@@ -40,11 +41,11 @@ export default function InputSelectPermission(
},
{
label: t("No access"),
value: "no_access",
value: EmptySelectValue,
},
]}
ariaLabel={t("Default access")}
value={value || "no_access"}
value={value || EmptySelectValue}
onChange={handleChange}
{...rest}
/>
+1 -1
View File
@@ -18,7 +18,7 @@ const InputSelectRole = (
label={t("Role")}
options={[
{
label: t("Member"),
label: t("Editor"),
value: "member",
},
{
+1 -1
View File
@@ -1,7 +1,7 @@
import styled from "styled-components";
type Props = {
/* Set to true if displaying a single symbol character to disable monospace */
/** Set to true if displaying a single symbol character to disable monospace */
symbol?: boolean;
};
+3 -5
View File
@@ -1,4 +1,4 @@
import { find } from "lodash";
import find from "lodash/find";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import styled from "styled-components";
@@ -42,7 +42,7 @@ function Icon({ className }: { className?: string }) {
}
export default function LanguagePrompt() {
const { auth, ui } = useStores();
const { ui } = useStores();
const { t } = useTranslation();
const user = useCurrentUser();
const language = detectLanguage();
@@ -75,9 +75,7 @@ export default function LanguagePrompt() {
<Link
onClick={async () => {
ui.setLanguagePromptDismissed();
await auth.updateUser({
language,
});
await user.save({ language });
}}
>
{t("Change Language")}
+6 -8
View File
@@ -22,12 +22,10 @@ type Props = {
sidebarRight?: React.ReactNode;
};
const Layout: React.FC<Props> = ({
title,
children,
sidebar,
sidebarRight,
}: Props) => {
const Layout = React.forwardRef(function Layout_(
{ title, children, sidebar, sidebarRight }: Props,
ref: React.RefObject<HTMLDivElement>
) {
const { ui } = useStores();
const sidebarCollapsed = !sidebar || ui.sidebarIsClosed;
@@ -40,7 +38,7 @@ const Layout: React.FC<Props> = ({
});
return (
<Container column auto>
<Container column auto ref={ref}>
<Helmet>
<title>{title ? title : env.APP_NAME}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -75,7 +73,7 @@ const Layout: React.FC<Props> = ({
</Container>
</Container>
);
};
});
const Container = styled(Flex)`
background: ${s("background")};
+11 -1
View File
@@ -12,6 +12,7 @@ export type Props = Omit<React.HTMLAttributes<HTMLAnchorElement>, "title"> & {
title: React.ReactNode;
subtitle?: React.ReactNode;
actions?: React.ReactNode;
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
border?: boolean;
small?: boolean;
};
@@ -74,10 +75,12 @@ const ListItem = (
const Wrapper = styled.a<{
$small?: boolean;
$border?: boolean;
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
to?: LocationDescriptor;
}>`
display: flex;
padding: ${(props) => (props.$border === false ? 0 : "8px 0")};
min-height: 32px;
margin: ${(props) =>
props.$border === false ? (props.$small ? "8px 0" : "16px 0") : 0};
border-bottom: 1px solid
@@ -88,7 +91,13 @@ const Wrapper = styled.a<{
border-bottom: 0;
}
cursor: ${({ to }) => (to ? "var(--pointer)" : "default")};
&:hover {
background: ${(props) =>
props.onClick ? props.theme.secondaryBackground : "inherit"};
}
cursor: ${(props) =>
props.to || props.onClick ? "var(--pointer)" : "default"};
`;
const Image = styled(Flex)`
@@ -126,6 +135,7 @@ const Subtitle = styled.p<{ $small?: boolean; $selected?: boolean }>`
export const Actions = styled(Flex)<{ $selected?: boolean }>`
align-self: center;
justify-content: center;
flex-shrink: 0;
color: ${(props) =>
props.$selected ? props.theme.white : props.theme.textSecondary};
`;
+1 -1
View File
@@ -1,4 +1,4 @@
import { times } from "lodash";
import times from "lodash/times";
import * as React from "react";
import styled from "styled-components";
import Fade from "~/components/Fade";
+1 -1
View File
@@ -20,7 +20,7 @@ function eachMinute(fn: () => void) {
};
}
type Props = {
export type Props = {
children?: React.ReactNode;
dateTime: string;
tooltipDelay?: number;
+21
View File
@@ -0,0 +1,21 @@
import * as React from "react";
import styled from "styled-components";
import useMobile from "~/hooks/useMobile";
type Props = {
children: React.ReactNode;
};
const MobileWrapper = styled.div`
width: 100vw;
height: 100vh;
overflow: auto;
-webkit-overflow-scrolling: touch;
`;
const MobileScrollWrapper = ({ children }: Props) => {
const isMobile = useMobile();
return isMobile ? <MobileWrapper>{children}</MobileWrapper> : <>{children}</>;
};
export default MobileScrollWrapper;
+4 -6
View File
@@ -94,11 +94,9 @@ const Modal: React.FC<Props> = ({
{title}
</Text>
)}
<Text as="span" size="large">
<NudeButton onClick={onRequestClose}>
<CloseIcon />
</NudeButton>
</Text>
<NudeButton onClick={onRequestClose}>
<CloseIcon />
</NudeButton>
</Header>
</Centered>
</Small>
@@ -259,7 +257,7 @@ const Small = styled.div`
margin: auto auto;
width: 30vw;
min-width: 350px;
max-width: 500px;
max-width: 450px;
z-index: ${depths.modal};
display: flex;
justify-content: center;
+16 -3
View File
@@ -1,4 +1,4 @@
import { LocationDescriptor } from "history";
import { LocationDescriptor, LocationDescriptorObject } from "history";
import * as React from "react";
import { match, NavLink, Route } from "react-router-dom";
@@ -9,10 +9,20 @@ type Props = React.ComponentProps<typeof NavLink> & {
[x: string]: string | undefined;
}>
| boolean
| null
| null,
location: LocationDescriptorObject
) => React.ReactNode;
/**
* If true, the tab will only be active if the path matches exactly.
*/
exact?: boolean;
/**
* CSS properties to apply to the link when it is active.
*/
activeStyle?: React.CSSProperties;
/**
* The path to match against the current location.
*/
to: LocationDescriptor;
};
@@ -25,7 +35,10 @@ function NavLinkWithChildrenFunc(
{({ match, location }) => (
<NavLink {...rest} to={to} exact={exact} ref={ref}>
{children
? children(rest.isActive ? rest.isActive(match, location) : match)
? children(
rest.isActive ? rest.isActive(match, location) : match,
location
)
: null}
</NavLink>
)}
@@ -1,19 +1,18 @@
import { observer } from "mobx-react";
import { SubscribeIcon } from "outline-icons";
import * as React from "react";
import styled, { useTheme } from "styled-components";
import styled from "styled-components";
import { s } from "@shared/styles";
import useStores from "~/hooks/useStores";
import Relative from "../Sidebar/components/Relative";
const NotificationIcon = () => {
const { notifications } = useStores();
const theme = useTheme();
const count = notifications.approximateUnreadCount;
return (
<Relative style={{ height: 24 }}>
<SubscribeIcon color={theme.textTertiary} />
<SubscribeIcon />
{count > 0 && <Badge />}
</Relative>
);
@@ -40,7 +40,7 @@ function NotificationListItem({ notification, onNavigate }: Props) {
};
return (
<Link to={notification.path} onClick={handleClick}>
<Link to={notification.path ?? ""} onClick={handleClick}>
<Container gap={8} $unread={!notification.viewedAt}>
<StyledAvatar model={notification.actor} size={AvatarSize.Large} />
<Flex column>
@@ -87,6 +87,7 @@ const StyledAvatar = styled(Avatar)`
const Container = styled(Flex)<{ $unread: boolean }>`
position: relative;
padding: 8px 12px;
padding-right: 40px;
margin: 0 8px;
border-radius: 4px;
@@ -22,7 +22,7 @@ import Tooltip from "../Tooltip";
import NotificationListItem from "./NotificationListItem";
type Props = {
/* Callback when the notification panel wants to close. */
/** Callback when the notification panel wants to close. */
onRequestClose: () => void;
};
+7 -1
View File
@@ -1,8 +1,9 @@
import { darken } from "polished";
import styled from "styled-components";
import ActionButton, {
Props as ActionButtonProps,
} from "~/components/ActionButton";
import { undraggableOnDesktop } from "~/styles";
import { hover, undraggableOnDesktop } from "~/styles";
type Props = ActionButtonProps & {
width?: number | string;
@@ -32,6 +33,11 @@ const NudeButton = styled(ActionButton).attrs((props: Props) => ({
user-select: none;
color: inherit;
${undraggableOnDesktop()}
&:${hover},
&[aria-expanded="true"] {
background: ${(props) => darken(0.05, props.theme.buttonNeutralBackground)};
}
`;
export default NudeButton;
+6 -12
View File
@@ -3,8 +3,7 @@ import { shallow } from "enzyme";
import { TFunction } from "i18next";
import * as React from "react";
import { getI18n } from "react-i18next";
import { DEFAULT_PAGINATION_LIMIT } from "~/stores/BaseStore";
import RootStore from "~/stores/RootStore";
import { Pagination } from "@shared/constants";
import { runAllPromises } from "~/test/support";
import { Component as PaginatedList } from "./PaginatedList";
@@ -12,17 +11,12 @@ describe("PaginatedList", () => {
const render = () => null;
const i18n = getI18n();
const { logout, ...store } = new RootStore();
const props = {
i18n,
tReady: true,
t: ((key: string) => key) as TFunction,
logout: () => {
//
},
...store,
};
} as any;
it("with no items renders nothing", () => {
const list = shallow(
@@ -59,13 +53,13 @@ describe("PaginatedList", () => {
);
expect(fetch).toHaveBeenCalledWith({
...options,
limit: DEFAULT_PAGINATION_LIMIT,
limit: Pagination.defaultLimit,
offset: 0,
});
});
it("calls fetch when options prop changes", async () => {
const fetchedItems = Array(DEFAULT_PAGINATION_LIMIT).fill(undefined);
const fetchedItems = Array(Pagination.defaultLimit).fill(undefined);
const fetch = jest.fn().mockReturnValue(Promise.resolve(fetchedItems));
const list = shallow(
<PaginatedList
@@ -81,7 +75,7 @@ describe("PaginatedList", () => {
await runAllPromises();
expect(fetch).toHaveBeenCalledWith({
id: "one",
limit: DEFAULT_PAGINATION_LIMIT,
limit: Pagination.defaultLimit,
offset: 0,
});
fetch.mockReset();
@@ -95,7 +89,7 @@ describe("PaginatedList", () => {
await runAllPromises();
expect(fetch).toHaveBeenCalledWith({
id: "two",
limit: DEFAULT_PAGINATION_LIMIT,
limit: Pagination.defaultLimit,
offset: 0,
});
});
+6 -6
View File
@@ -1,11 +1,11 @@
import { isEqual } from "lodash";
import isEqual from "lodash/isEqual";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import { withTranslation, WithTranslation } from "react-i18next";
import { Waypoint } from "react-waypoint";
import { CompositeStateReturn } from "reakit/Composite";
import { DEFAULT_PAGINATION_LIMIT } from "~/stores/BaseStore";
import { Pagination } from "@shared/constants";
import RootStore from "~/stores/RootStore";
import ArrowKeyNavigation from "~/components/ArrowKeyNavigation";
import DelayedMount from "~/components/DelayedMount";
@@ -86,7 +86,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
reset = () => {
this.offset = 0;
this.allowLoadMore = true;
this.renderCount = DEFAULT_PAGINATION_LIMIT;
this.renderCount = Pagination.defaultLimit;
this.isFetching = false;
this.isFetchingInitial = false;
this.isFetchingMore = false;
@@ -99,7 +99,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
}
this.isFetching = true;
const counter = ++this.fetchCounter;
const limit = this.props.options?.limit ?? DEFAULT_PAGINATION_LIMIT;
const limit = this.props.options?.limit ?? Pagination.defaultLimit;
this.error = undefined;
try {
@@ -139,12 +139,12 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
const leftToRender = (this.props.items?.length ?? 0) - this.renderCount;
if (leftToRender > 0) {
this.renderCount += DEFAULT_PAGINATION_LIMIT;
this.renderCount += Pagination.defaultLimit;
}
// If there are less than a pages results in the cache go ahead and fetch
// another page from the server
if (leftToRender <= DEFAULT_PAGINATION_LIMIT) {
if (leftToRender <= Pagination.defaultLimit) {
this.isFetchingMore = true;
await this.fetchResults();
}
+2 -1
View File
@@ -51,5 +51,6 @@ export default function PlaceholderDocument({
const Wrapper = styled(Fade)`
display: block;
margin: 40px 0;
margin: 6vh 0;
padding: 12px 0;
`;
+25 -4
View File
@@ -1,9 +1,10 @@
import * as React from "react";
import { Dialog } from "reakit/Dialog";
import { Popover as ReakitPopover, PopoverProps } from "reakit/Popover";
import styled, { css } from "styled-components";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { depths, s } from "@shared/styles";
import useKeyDown from "~/hooks/useKeyDown";
import useMobile from "~/hooks/useMobile";
import { fadeAndScaleIn } from "~/styles/animations";
@@ -15,6 +16,8 @@ type Props = PopoverProps & {
tabIndex?: number;
scrollable?: boolean;
mobilePosition?: "top" | "bottom";
show: () => void;
hide: () => void;
};
const Popover: React.FC<Props> = ({
@@ -28,6 +31,21 @@ const Popover: React.FC<Props> = ({
}: Props) => {
const isMobile = useMobile();
// Custom Escape handler rather than using hideOnEsc from reakit so we can
// prevent default behavior of exiting fullscreen.
useKeyDown(
"Escape",
(event) => {
if (rest.visible && rest.hideOnEsc !== false) {
event.preventDefault();
rest.hide();
}
},
{
allowInInput: true,
}
);
if (isMobile) {
return (
<Dialog {...rest} modal>
@@ -44,7 +62,7 @@ const Popover: React.FC<Props> = ({
}
return (
<ReakitPopover {...rest}>
<ReakitPopover {...rest} hideOnEsc={false}>
<Contents
$shrink={shrink}
$width={width}
@@ -77,10 +95,13 @@ const Contents = styled.div<ContentsProps>`
width: ${(props) => props.$width}px;
${(props) =>
props.$scrollable &&
css`
props.$scrollable
? `
overflow-x: hidden;
overflow-y: auto;
`
: `
overflow: hidden;
`}
${breakpoint("mobile", "tablet")`
+13 -1
View File
@@ -5,12 +5,21 @@ import Header from "~/components/Header";
import PageTitle from "~/components/PageTitle";
type Props = {
/** An icon to display in the header when content has scrolled past the title */
icon?: React.ReactNode;
/** The title of the scene */
title?: React.ReactNode;
/** The title of the scene, as text only required if the title prop is not plain text */
textTitle?: string;
/** A component to display on the left side of the header */
left?: React.ReactNode;
/** A component to display on the right side of the header */
actions?: React.ReactNode;
/** Whether to center the content horizontally with the standard maximum width (default: true) */
centered?: boolean;
/** Whether to use the full width of the screen (default: false) */
wide?: boolean;
/** The content of the scene */
children?: React.ReactNode;
};
@@ -22,6 +31,7 @@ const Scene: React.FC<Props> = ({
left,
children,
centered,
wide,
}: Props) => (
<FillWidth>
<PageTitle title={textTitle || title} />
@@ -40,7 +50,9 @@ const Scene: React.FC<Props> = ({
left={left}
/>
{centered !== false ? (
<CenteredContent withStickyHeader>{children}</CenteredContent>
<CenteredContent maxWidth={wide ? "100vw" : undefined} withStickyHeader>
{children}
</CenteredContent>
) : (
children
)}
+3 -1
View File
@@ -11,7 +11,9 @@ export default function SearchActions() {
React.useEffect(() => {
if (!searches.isLoaded) {
void searches.fetchPage({});
void searches.fetchPage({
source: "app",
});
}
}, [searches]);
+2 -2
View File
@@ -116,7 +116,7 @@ const Heading = styled.h4<{ rtl?: boolean }>`
display: flex;
justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")};
align-items: center;
height: 18px;
height: 22px;
margin-top: 0;
margin-bottom: 0.25em;
overflow: hidden;
@@ -138,7 +138,7 @@ const ResultContext = styled(Highlight)`
color: ${s("textTertiary")};
font-size: 14px;
margin-top: -0.25em;
margin-bottom: 0.25em;
margin-bottom: 0;
${ellipsis()}
${Mark} {
+17 -4
View File
@@ -1,4 +1,4 @@
import { debounce } from "lodash";
import debounce from "lodash/debounce";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
@@ -17,7 +17,9 @@ import useStores from "~/hooks/useStores";
import { SearchResult } from "~/types";
import SearchListItem from "./SearchListItem";
type Props = React.HTMLAttributes<HTMLInputElement> & { shareId: string };
interface Props extends React.HTMLAttributes<HTMLInputElement> {
shareId: string;
}
function SearchPopover({ shareId }: Props) {
const { t } = useTranslation();
@@ -31,9 +33,11 @@ function SearchPopover({ shareId }: Props) {
});
const [query, setQuery] = React.useState("");
const searchResults = documents.searchResults(query);
const { show, hide } = popover;
const [searchResults, setSearchResults] = React.useState<
PaginatedItem[] | undefined
>();
const [cachedQuery, setCachedQuery] = React.useState(query);
const [cachedSearchResults, setCachedSearchResults] = React.useState<
PaginatedItem[] | undefined
@@ -50,7 +54,16 @@ function SearchPopover({ shareId }: Props) {
const performSearch = React.useCallback(
async ({ query, ...options }) => {
if (query?.length > 0) {
return await documents.search(query, { shareId, ...options });
const response: PaginatedItem[] = await documents.search(query, {
shareId,
...options,
});
if (response.length) {
setSearchResults(response);
}
return response;
}
return undefined;
},
@@ -0,0 +1,141 @@
import { t } from "i18next";
import orderBy from "lodash/orderBy";
import { observer } from "mobx-react";
import * as React from "react";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import { Pagination } from "@shared/constants";
import Document from "~/models/Document";
import UserMembership from "~/models/UserMembership";
import LoadingIndicator from "~/components/LoadingIndicator";
import useCurrentUser from "~/hooks/useCurrentUser";
import usePolicy from "~/hooks/usePolicy";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import { homePath } from "~/utils/routeHelpers";
import MemberListItem from "./MemberListItem";
type Props = {
/** Document to which team members are supposed to be invited */
document: Document;
/** Children to be rendered before the list of members */
children?: React.ReactNode;
/** List of users that have been invited to the document during the current editing session */
invitedInSession: string[];
};
function DocumentMembersList({ document, invitedInSession }: Props) {
const { users, userMemberships } = useStores();
const user = useCurrentUser();
const history = useHistory();
const can = usePolicy(document);
const { loading: loadingTeamMembers, request: fetchTeamMembers } = useRequest(
React.useCallback(
() => users.fetchPage({ limit: Pagination.defaultLimit }),
[users]
)
);
const { loading: loadingDocumentMembers, request: fetchDocumentMembers } =
useRequest(
React.useCallback(
() =>
userMemberships.fetchDocumentMemberships({
id: document.id,
limit: Pagination.defaultLimit,
}),
[userMemberships, document.id]
)
);
React.useEffect(() => {
void fetchTeamMembers();
void fetchDocumentMembers();
}, [fetchTeamMembers, fetchDocumentMembers]);
const handleRemoveUser = React.useCallback(
async (item) => {
try {
await userMemberships.delete({
documentId: document.id,
userId: item.id,
} as UserMembership);
if (item.id === user.id) {
history.push(homePath());
} else {
toast.success(
t(`{{ userName }} was removed from the document`, {
userName: item.name,
})
);
}
} catch (err) {
toast.error(t("Could not remove user"));
}
},
[history, userMemberships, user, document]
);
const handleUpdateUser = React.useCallback(
async (user, permission) => {
try {
await userMemberships.create({
documentId: document.id,
userId: user.id,
permission,
});
toast.success(
t(`Permissions for {{ userName }} updated`, {
userName: user.name,
})
);
} catch (err) {
toast.error(t("Could not update user"));
}
},
[userMemberships, document]
);
// Order newly added users first during the current editing session, on reload members are
// ordered by name
const members = React.useMemo(
() =>
orderBy(
document.members,
(user) =>
(invitedInSession.includes(user.id) ? "_" : "") +
user.name.toLowerCase(),
"asc"
),
[document.members, invitedInSession]
);
if (loadingTeamMembers || loadingDocumentMembers) {
return <LoadingIndicator />;
}
return (
<>
{members.map((item) => (
<MemberListItem
key={item.id}
user={item}
membership={item.getMembership(document)}
onRemove={() => handleRemoveUser(item)}
onUpdate={
can.manageUsers
? (permission) => handleUpdateUser(item, permission)
: undefined
}
onLeave={
item.id === user.id ? () => handleRemoveUser(item) : undefined
}
/>
))}
</>
);
}
export default observer(DocumentMembersList);
+128
View File
@@ -0,0 +1,128 @@
import { observer } from "mobx-react";
import { PlusIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { DocumentPermission } from "@shared/types";
import User from "~/models/User";
import UserMembership from "~/models/UserMembership";
import Avatar from "~/components/Avatar";
import { AvatarSize } from "~/components/Avatar/Avatar";
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
import ListItem from "~/components/List/Item";
import { EmptySelectValue, Permission } from "~/types";
type Props = {
user: User;
membership?: UserMembership | undefined;
onAdd?: () => void;
onRemove?: () => void;
onLeave?: () => void;
onUpdate?: (permission: DocumentPermission) => void;
};
const MemberListItem = ({
user,
membership,
onRemove,
onLeave,
onUpdate,
}: Props) => {
const { t } = useTranslation();
const handleChange = React.useCallback(
(permission: DocumentPermission | typeof EmptySelectValue) => {
if (permission === EmptySelectValue) {
onRemove?.();
} else {
onUpdate?.(permission);
}
},
[onRemove, onUpdate]
);
const permissions: Permission[] = [
{
label: t("View only"),
value: DocumentPermission.Read,
},
{
label: t("Can edit"),
value: DocumentPermission.ReadWrite,
},
{
label: t("No access"),
value: EmptySelectValue,
},
];
const currentPermission = permissions.find(
(p) => p.value === membership?.permission
);
if (!currentPermission) {
return null;
}
const disabled = !onUpdate && !onLeave;
return (
<StyledListItem
title={user.name}
image={
<Avatar model={user} size={AvatarSize.Medium} showBorder={false} />
}
subtitle={
membership?.sourceId
? t("Has access through parent")
: user.isSuspended
? t("Suspended")
: user.isInvited
? t("Invited")
: user.isViewer
? t("Viewer")
: user.email
? user.email
: t("Member")
}
actions={
disabled ? null : (
<div style={{ marginRight: -8 }}>
<InputMemberPermissionSelect
permissions={
onLeave
? [
currentPermission,
{
label: `${t("Leave")}`,
value: EmptySelectValue,
},
]
: permissions
}
value={membership?.permission}
onChange={handleChange}
/>
</div>
)
}
/>
);
};
export const InviteIcon = styled(PlusIcon)`
opacity: 0;
`;
export const StyledListItem = styled(ListItem).attrs({
small: true,
border: false,
})`
margin: 0 -16px;
padding: 6px 16px;
border-radius: 8px;
&:hover ${InviteIcon} {
opacity: 1;
}
`;
export default observer(MemberListItem);
+216
View File
@@ -0,0 +1,216 @@
import invariant from "invariant";
import debounce from "lodash/debounce";
import isEmpty from "lodash/isEmpty";
import { observer } from "mobx-react";
import { CopyIcon, GlobeIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { toast } from "sonner";
import styled, { useTheme } from "styled-components";
import { s } from "@shared/styles";
import { SHARE_URL_SLUG_REGEX } from "@shared/utils/urlHelpers";
import Document from "~/models/Document";
import Share from "~/models/Share";
import Input, { NativeInput } from "~/components/Input";
import Switch from "~/components/Switch";
import env from "~/env";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import { AvatarSize } from "../Avatar/Avatar";
import CopyToClipboard from "../CopyToClipboard";
import NudeButton from "../NudeButton";
import { ResizingHeightContainer } from "../ResizingHeightContainer";
import Squircle from "../Squircle";
import Tooltip from "../Tooltip";
import { StyledListItem } from "./MemberListItem";
type Props = {
/** The document to share. */
document: Document;
/** The existing share model, if any. */
share: Share | null | undefined;
/** The existing share parent model, if any. */
sharedParent: Share | null | undefined;
/** Ref to the Copy Link button */
copyButtonRef?: React.RefObject<HTMLButtonElement>;
onRequestClose?: () => void;
};
function PublicAccess({ document, share, sharedParent }: Props) {
const { shares } = useStores();
const { t } = useTranslation();
const theme = useTheme();
const [slugValidationError, setSlugValidationError] = React.useState("");
const [urlSlug, setUrlSlug] = React.useState("");
const inputRef = React.useRef<HTMLInputElement>(null);
const can = usePolicy(share);
const documentAbilities = usePolicy(document);
const canPublish = can.update && documentAbilities.share;
const handlePublishedChange = React.useCallback(
async (event) => {
const share = shares.getByDocumentId(document.id);
invariant(share, "Share must exist");
try {
await share.save({
published: event.currentTarget.checked,
});
} catch (err) {
toast.error(err.message);
}
},
[document.id, shares]
);
const handleUrlSlugChange = React.useMemo(
() =>
debounce(async (ev) => {
if (!share) {
return;
}
const val = ev.target.value;
setUrlSlug(val);
if (val && !SHARE_URL_SLUG_REGEX.test(val)) {
setSlugValidationError(
t("Only lowercase letters, digits and dashes allowed")
);
} else {
setSlugValidationError("");
if (share.urlId !== val) {
try {
await share.save({
urlId: isEmpty(val) ? null : val,
});
} catch (err) {
if (err.message.includes("must be unique")) {
setSlugValidationError(
t("Sorry, this link has already been used")
);
}
}
}
}
}, 500),
[t, share]
);
const handleCopied = React.useCallback(() => {
toast.success(t("Public link copied to clipboard"));
}, [t]);
const documentTitle = sharedParent?.documentTitle;
const shareUrl = sharedParent?.url
? `${sharedParent.url}${document.url}`
: share?.url ?? "";
const copyButton = (
<Tooltip tooltip={t("Copy public link")} delay={500} placement="top">
<CopyToClipboard text={shareUrl} onCopy={handleCopied}>
<NudeButton type="button" disabled={!share} style={{ marginRight: 3 }}>
<CopyIcon color={theme.placeholder} size={18} />
</NudeButton>
</CopyToClipboard>
</Tooltip>
);
return (
<Wrapper>
<StyledListItem
title={t("Web")}
subtitle={
<>
{sharedParent && !document.isDraft ? (
<Trans>
Anyone with the link can access because the parent document,{" "}
<StyledLink to={`/doc/${sharedParent.documentId}`}>
{documentTitle}
</StyledLink>
, is shared
</Trans>
) : (
t("Anyone with the link can access")
)}
.
</>
}
image={
<Squircle color={theme.text} size={AvatarSize.Medium}>
<GlobeIcon color={theme.background} size={18} />
</Squircle>
}
actions={
sharedParent && !document.isDraft ? null : (
<Switch
aria-label={t("Publish to internet")}
checked={share?.published ?? false}
onChange={handlePublishedChange}
disabled={!canPublish}
width={26}
height={14}
/>
)
}
/>
<ResizingHeightContainer>
{sharedParent?.published ? (
<ShareLinkInput type="text" disabled defaultValue={shareUrl}>
{copyButton}
</ShareLinkInput>
) : share?.published ? (
<ShareLinkInput
type="text"
ref={inputRef}
placeholder={share?.id}
onChange={handleUrlSlugChange}
error={slugValidationError}
defaultValue={urlSlug}
prefix={
<DomainPrefix
readOnly
onClick={() => inputRef.current?.focus()}
value={env.URL.replace(/https?:\/\//, "") + "/s/"}
/>
}
>
{copyButton}
</ShareLinkInput>
) : null}
</ResizingHeightContainer>
</Wrapper>
);
}
const Wrapper = styled.div`
margin-bottom: 8px;
`;
const DomainPrefix = styled(NativeInput)`
flex: 0 1 auto;
padding-right: 0 !important;
margin-right: -10px;
cursor: text;
color: ${s("placeholder")};
user-select: none;
`;
const ShareLinkInput = styled(Input)`
margin-top: 12px;
min-width: 100px;
flex: 1;
${NativeInput} {
padding: 4px 8px;
}
`;
const StyledLink = styled(Link)`
color: ${s("textSecondary")};
text-decoration: underline;
`;
export default observer(PublicAccess);
+494
View File
@@ -0,0 +1,494 @@
import { AnimatePresence, m } from "framer-motion";
import { observer } from "mobx-react";
import {
BackIcon,
LinkIcon,
MoreIcon,
QuestionMarkIcon,
UserIcon,
} from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled, { useTheme } from "styled-components";
import { s } from "@shared/styles";
import { CollectionPermission } from "@shared/types";
import Collection from "~/models/Collection";
import Document from "~/models/Document";
import Share from "~/models/Share";
import User from "~/models/User";
import CopyToClipboard from "~/components/CopyToClipboard";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useBoolean from "~/hooks/useBoolean";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useCurrentUser from "~/hooks/useCurrentUser";
import useKeyDown from "~/hooks/useKeyDown";
import useMobile from "~/hooks/useMobile";
import usePolicy from "~/hooks/usePolicy";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import useThrottledCallback from "~/hooks/useThrottledCallback";
import { documentPath, urlify } from "~/utils/routeHelpers";
import Avatar from "../Avatar";
import { AvatarSize } from "../Avatar/Avatar";
import ButtonSmall from "../ButtonSmall";
import Empty from "../Empty";
import CollectionIcon from "../Icons/CollectionIcon";
import Input, { NativeInput } from "../Input";
import NudeButton from "../NudeButton";
import Squircle from "../Squircle";
import Tooltip from "../Tooltip";
import DocumentMembersList from "./DocumentMemberList";
import { InviteIcon, StyledListItem } from "./MemberListItem";
import PublicAccess from "./PublicAccess";
type Props = {
/** The document to share. */
document: Document;
/** The existing share model, if any. */
share: Share | null | undefined;
/** The existing share parent model, if any. */
sharedParent: Share | null | undefined;
/** Callback fired when the popover requests to be closed. */
onRequestClose: () => void;
/** Whether the popover is visible. */
visible: boolean;
};
const presence = {
initial: {
opacity: 0,
width: 0,
marginRight: 0,
},
animate: {
opacity: 1,
width: "auto",
marginRight: 8,
transition: {
type: "spring",
duration: 0.2,
bounce: 0,
},
},
exit: {
opacity: 0,
width: 0,
marginRight: 0,
},
};
function useUsersInCollection(collection?: Collection) {
const { users, memberships } = useStores();
const { request } = useRequest(() =>
memberships.fetchPage({ limit: 1, id: collection!.id })
);
React.useEffect(() => {
if (collection && !collection.permission) {
void request();
}
}, [collection]);
return collection
? collection.permission
? true
: users.inCollection(collection.id).length > 1
: false;
}
function SharePopover({
document,
share,
sharedParent,
onRequestClose,
visible,
}: Props) {
const team = useCurrentTeam();
const { t } = useTranslation();
const can = usePolicy(document);
const { userMemberships } = useStores();
const isMobile = useMobile();
const [query, setQuery] = React.useState("");
const [picker, showPicker, hidePicker] = useBoolean();
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
const linkButtonRef = React.useRef<HTMLButtonElement>(null);
const [invitedInSession, setInvitedInSession] = React.useState<string[]>([]);
const collectionSharingDisabled = document.collection?.sharing === false;
useKeyDown(
"Escape",
(ev) => {
ev.preventDefault();
ev.stopImmediatePropagation();
if (picker) {
hidePicker();
} else {
onRequestClose();
}
},
{
allowInInput: true,
}
);
// Fetch sharefocus the link button when the popover is opened
React.useEffect(() => {
if (visible) {
void document.share();
linkButtonRef.current?.focus();
}
}, [document, hidePicker, visible]);
// Hide the picker when the popover is closed
React.useEffect(() => {
if (visible) {
hidePicker();
}
}, [hidePicker, visible]);
// Clear the query when picker is closed
React.useEffect(() => {
if (!picker) {
setQuery("");
}
}, [picker]);
const handleCopied = React.useCallback(() => {
onRequestClose();
timeout.current = setTimeout(() => {
toast.message(t("Link copied to clipboard"));
}, 100);
return () => {
if (timeout.current) {
clearTimeout(timeout.current);
}
};
}, [onRequestClose, t]);
const handleInvite = React.useCallback(
async (user: User) => {
setInvitedInSession((prev) => [...prev, user.id]);
await userMemberships.create({
documentId: document.id,
userId: user.id,
});
toast.message(
t("{{ userName }} was invited to the document", { userName: user.name })
);
},
[t, userMemberships, document.id]
);
const handleQuery = React.useCallback(
(event) => {
showPicker();
setQuery(event.target.value);
},
[showPicker, setQuery]
);
const backButton = (
<>
{picker && (
<NudeButton key="back" as={m.button} {...presence} onClick={hidePicker}>
<BackIcon />
</NudeButton>
)}
</>
);
const doneButton = picker ? (
invitedInSession.length ? (
<ButtonSmall onClick={hidePicker} neutral>
{t("Done")}
</ButtonSmall>
) : null
) : (
<CopyToClipboard
text={urlify(documentPath(document))}
onCopy={handleCopied}
>
<NudeButton type="button" disabled={!share} ref={linkButtonRef}>
<LinkIcon size={20} />
</NudeButton>
</CopyToClipboard>
);
return (
<>
{can.manageUsers &&
(isMobile ? (
<Flex align="center" style={{ marginBottom: 12 }} auto>
{backButton}
<Input
key="input"
placeholder={`${t("Invite by name")}`}
value={query}
onChange={handleQuery}
onClick={showPicker}
margin={0}
flex
>
{doneButton}
</Input>
</Flex>
) : (
<HeaderInput align="center">
<AnimatePresence initial={false}>
{backButton}
<NativeInput
key="input"
placeholder={`${t("Invite by name")}`}
value={query}
onChange={handleQuery}
onClick={showPicker}
style={{ padding: "6px 0" }}
/>
{doneButton}
</AnimatePresence>
</HeaderInput>
))}
{picker && (
<div>
<Picker document={document} query={query} onInvite={handleInvite} />
</div>
)}
<div style={{ display: picker ? "none" : "block" }}>
<DocumentOtherAccessList document={document}>
<DocumentMembersList
document={document}
invitedInSession={invitedInSession}
/>
</DocumentOtherAccessList>
{team.sharing && can.share && !collectionSharingDisabled && (
<>
{document.members.length ? <Separator /> : null}
<PublicAccess
document={document}
share={share}
sharedParent={sharedParent}
onRequestClose={onRequestClose}
/>
</>
)}
</div>
</>
);
}
const Picker = observer(
({
document,
query,
onInvite,
}: {
document: Document;
query: string;
onInvite: (user: User) => Promise<void>;
}) => {
const { users } = useStores();
const { t } = useTranslation();
const user = useCurrentUser();
const fetchUsersByQuery = useThrottledCallback(
(query) => users.fetchPage({ query }),
250
);
const suggestions = React.useMemo(
() =>
users.notInDocument(document.id, query).filter((u) => u.id !== user.id),
[users, users.orderedData, document.id, document.members, user.id, query]
);
React.useEffect(() => {
if (query) {
void fetchUsersByQuery(query);
}
}, [query, fetchUsersByQuery]);
return suggestions.length ? (
<>
{suggestions.map((suggestion) => (
<StyledListItem
key={suggestion.id}
onClick={() => onInvite(suggestion)}
title={suggestion.name}
subtitle={
suggestion.isSuspended
? t("Suspended")
: suggestion.isInvited
? t("Invited")
: suggestion.isViewer
? t("Viewer")
: suggestion.email
? suggestion.email
: t("Member")
}
image={
<Avatar
model={suggestion}
size={AvatarSize.Medium}
showBorder={false}
/>
}
actions={<InviteIcon />}
/>
))}
</>
) : (
<Empty>{t("No matches")}</Empty>
);
}
);
const DocumentOtherAccessList = observer(
({
document,
children,
}: {
document: Document;
children: React.ReactNode;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const collection = document.collection;
const usersInCollection = useUsersInCollection(collection);
const user = useCurrentUser();
return (
<>
{collection ? (
<>
{collection.permission ? (
<StyledListItem
image={
<Squircle color={theme.accent} size={AvatarSize.Medium}>
<UserIcon color={theme.accentText} size={16} />
</Squircle>
}
title={t("All members")}
subtitle={t("Everyone in the workspace")}
actions={
<AccessTooltip>
{collection?.permission === CollectionPermission.ReadWrite
? t("Can edit")
: t("Can view")}
</AccessTooltip>
}
/>
) : usersInCollection ? (
<StyledListItem
image={<CollectionIcon collection={collection} />}
title={collection.name}
subtitle={t("Everyone in the collection")}
actions={<AccessTooltip>{t("Can view")}</AccessTooltip>}
/>
) : (
<StyledListItem
image={<Avatar model={user} showBorder={false} />}
title={user.name}
subtitle={t("You have full access")}
actions={<AccessTooltip>{t("Can edit")}</AccessTooltip>}
/>
)}
{children}
</>
) : document.isDraft ? (
<>
<StyledListItem
image={<Avatar model={document.createdBy} showBorder={false} />}
title={document.createdBy.name}
actions={
<AccessTooltip tooltip={t("Created the document")}>
{t("Can edit")}
</AccessTooltip>
}
/>
{children}
</>
) : (
<>
{children}
<StyledListItem
image={
<Squircle color={theme.accent} size={AvatarSize.Medium}>
<MoreIcon color={theme.accentText} size={16} />
</Squircle>
}
title={t("Other members")}
subtitle={t("Other workspace members may have access")}
actions={
<AccessTooltip
tooltip={t(
"This document may be shared with more workspace members through a parent document or collection you do not have access to"
)}
/>
}
/>
</>
)}
</>
);
}
);
const AccessTooltip = ({
children,
tooltip,
}: {
children?: React.ReactNode;
tooltip?: string;
}) => {
const { t } = useTranslation();
return (
<Flex align="center" gap={2}>
<Text type="secondary" size="small" as="span">
{children}
</Text>
<Tooltip tooltip={tooltip ?? t("Access inherited from collection")}>
<QuestionMarkIcon size={18} />
</Tooltip>
</Flex>
);
};
const Separator = styled.div`
border-top: 1px dashed ${s("divider")};
margin: 12px 0;
`;
const HeaderInput = styled(Flex)`
position: sticky;
z-index: 1;
top: 0;
background: ${s("menuBackground")};
color: ${s("textTertiary")};
border-bottom: 1px solid ${s("inputBorder")};
padding: 0 24px 8px 24px;
margin-top: 0;
margin-left: -24px;
margin-right: -24px;
margin-bottom: 12px;
&:before {
content: "";
position: absolute;
left: 0;
right: 0;
top: -20px;
height: 20px;
background: ${s("menuBackground")};
}
`;
export default observer(SharePopover);

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