Compare commits

...

445 Commits

Author SHA1 Message Date
Tom Moor 21ce145f67 0.67.2 2023-01-16 15:50:56 -08:00
Tom Moor 0a4c7091b5 chore: Yarn dedupe 2023-01-16 15:50:52 -08:00
dependabot[bot] f0f574812d chore(deps-dev): bump @babel/preset-typescript from 7.16.7 to 7.18.6 (#4723)
Bumps [@babel/preset-typescript](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-typescript) from 7.16.7 to 7.18.6.
- [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.18.6/packages/babel-preset-typescript)

---
updated-dependencies:
- dependency-name: "@babel/preset-typescript"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 08:29:44 -08:00
dependabot[bot] 830763a9eb chore(deps): bump jsdom from 20.0.3 to 21.0.0 (#4721)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 20.0.3 to 21.0.0.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Changelog](https://github.com/jsdom/jsdom/blob/master/Changelog.md)
- [Commits](https://github.com/jsdom/jsdom/compare/20.0.3...21.0.0)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 08:29:25 -08:00
Tom Moor 55f2989a3d New Crowdin updates (#4624)
* 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 German 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 Russian translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: New Turkish 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 Ukrainian translations from Crowdin [ci skip]
2023-01-15 12:48:57 -08:00
Tom Moor 788450136e test: documents.update apiVersion 2 response 2023-01-15 09:43:56 -05:00
Tom Moor 0c269081d9 fix: Sidebar jumps when publish or unpublish document (#4706)
* Return updated collection in API response for documents.unpublish and documents.update
Allows for improved UX on clientside

* test

* tsc

* tsc
2023-01-15 06:01:06 -08:00
Tom Moor 31f743eb4c fix: Incorrect spacing on IconPicker since menu style was changed
closes #4711
2023-01-14 23:27:45 -05:00
Aditya Sharma 4ca0fc32c1 show collection name in pin menu (#4705)
* show collection name in pin menu

* fix: fetching right collection name

* refactor: better variable names

* Update app/actions/definitions/documents.tsx

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Avoid additional translation

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-01-14 19:17:25 -08:00
Tom Moor 0bed01a062 fix: Cannot click on input in sidebar when editing doc/collection (#4716)
fix: Start editing with all text selected
fix: Styling issue around input
2023-01-14 19:14:50 -08:00
Tom Moor a25372c186 fix: Full-width images in editor are incorrectly aligned in RTL documents
closes #4712
2023-01-14 21:47:33 -05:00
Tom Moor 51baba8fa8 fix: Small unfocusable area below last block in editor, closes #4713 2023-01-14 20:45:15 -05:00
Tom Moor 0cccd7141d fix(collaboration): Avoid writing spurious changes from read-only users when serialized output changes 2023-01-13 23:54:44 -05:00
Tom Moor 6b438e3467 fix: Background on TOC does not transition smoothly on theme change 2023-01-13 22:22:31 -05:00
Tom Moor 0d53e5a7ba chore: Clean build/ folder on every build, remove duplicative scripts 2023-01-13 22:18:25 -05:00
Tom Moor e3db7455b3 feat: Add optional SMTP_NAME configuration for connecting to SMTP servers that require the client to have a specific hostname 2023-01-13 21:49:57 -05:00
Tom Moor d20f379943 feat: Publish and Unpublish available in command menu
fix: Add default error handling for menu actions
2023-01-12 22:39:14 -05:00
Tom Moor e347404502 chore: Improvements to document move behavior (#4689)
* chore: Improvements to document move behavior

* test
2023-01-12 18:48:09 -08:00
Tom Moor 17a8dbb3f0 fix: Prevent moving documents into drafts 2023-01-12 11:15:54 -05:00
Tom Moor 3be170ddb8 0.67.1 2023-01-11 21:27:37 -05:00
Tom Moor 4a97b35d5a fix: Collection structure update on document move 2023-01-11 20:56:14 -05:00
Tom Moor 785b9888dd Respond to slack url verification challenge with JSON instead of raw body 2023-01-11 09:07:15 -05:00
Tom Moor d5158f0a34 0.67.0 2023-01-10 20:32:43 -05:00
Tom Moor 64b72bcf82 /bin/bash -> /bin/sh for Docker 2023-01-10 20:32:37 -05:00
Tom Moor 6be7409d85 fix: Mistake in i18n key 2023-01-10 18:13:59 -05:00
dependabot[bot] fddcbbd7af chore(deps): bump react-hook-form from 7.37.0 to 7.41.5 (#4667)
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.37.0 to 7.41.5.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.37.0...v7.41.5)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 19:02:44 -08:00
dependabot[bot] 3e7f823e17 chore(deps): bump aws-sdk from 2.1189.0 to 2.1290.0 (#4664)
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.1189.0 to 2.1290.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js/compare/v2.1189.0...v2.1290.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 19:02:19 -08:00
dependabot[bot] 31c47ab37b chore(deps): bump @joplin/turndown-plugin-gfm from 1.0.44 to 1.0.45 (#4663)
Bumps [@joplin/turndown-plugin-gfm](https://github.com/laurent22/joplin-turndown-plugin-gfm) from 1.0.44 to 1.0.45.
- [Release notes](https://github.com/laurent22/joplin-turndown-plugin-gfm/releases)
- [Commits](https://github.com/laurent22/joplin-turndown-plugin-gfm/commits)

---
updated-dependencies:
- dependency-name: "@joplin/turndown-plugin-gfm"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 15:39:52 -08:00
dependabot[bot] cbfb7d2c23 chore(deps): bump luxon from 3.0.1 to 3.2.1 (#4660)
Bumps [luxon](https://github.com/moment/luxon) from 3.0.1 to 3.2.1.
- [Release notes](https://github.com/moment/luxon/releases)
- [Changelog](https://github.com/moment/luxon/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moment/luxon/compare/3.0.1...3.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 07:31:22 -08:00
Tom Moor 9617a15ae8 fix: No value supplied for attribute src
It shouldn't be possible to have an image node with no src, but it does happen occassionally and this prevents the document from persisting. It's better to have a broken image than a doc that won't save
2023-01-07 15:26:06 -05:00
Tom Moor 53414ec3ba feat: Server side translation setup (#4657)
* Server side translation setup

* docs
2023-01-07 11:52:09 -08:00
Tom Moor a333f48102 fix: Hanging } 2023-01-07 10:58:22 -05:00
Tom Moor d4da33424b fix: Math block overrides heading backspace key behavior 2023-01-07 10:01:21 -05:00
Tom Moor e67ac1215a feat: Allow moving draft documents (#4652)
* feat: Allow moving draft documents

* Allow drag-n-drop move of draft documents

* fix: Allow moving draft without a collection

* fix: Allow moving draft without a collection
2023-01-06 19:31:06 -08:00
Tom Moor 9f825b9adf fix: Allow sort by title in documents.list 2023-01-06 16:58:39 -05:00
Tom Moor 4b47bffcf5 fix: images and files with cyrillic names don't import (#4654)
closes #4559
2023-01-06 06:02:15 -08:00
Tom Moor cbd9971bc7 fix: Missing translation on Import screen 2023-01-06 08:32:55 -05:00
Tom Moor c80aec5eb2 fix: build.sh copies dev webpack incorrectly 2023-01-06 05:26:13 -08:00
Tom Moor 5b60f1ab00 tsc 2023-01-05 21:25:17 -05:00
Tom Moor ec2da746dc chore: Convert LinkToolbar to functional component
Co-authored-by: Ítalo Sousa <italusousa@gmail.com>
2023-01-05 21:11:28 -05:00
Tom Moor a065a8426f fix: OL/UL inside of checkbox list is not styled correctly (#4648)
closes #4635
2023-01-05 17:18:07 -08:00
Apoorv Mishra b6141442b7 Validate API request query (#4642)
* fix: refactor to accommodate authentication, transaction and pagination together into ctx.state

* feat: allow passing response type to APIContext

* feat: preliminary work for initial review

* fix: use unknown for base types

* fix: api/attachments

* fix: api/documents

* fix: jsdoc comment for input

* fix: replace at() with index access for compatibility

* fix: validation err message

* fix: error handling

* fix: remove unnecessary extend
2023-01-05 20:24:03 +05:30
Tom Moor 445d19f43e chore: Extract product name from translation strings (#4646) 2023-01-04 19:00:57 -08:00
Tom Moor f655288f67 fix: Issue where possibly logged into the wrong workspace when signing in via desktop app with multiple workspaces 2023-01-04 21:13:13 -05:00
Tom Moor fc6bb3caef fix: Unstable t method causing stars.list to fetch run multiple times. Seems this behavior changed in the recent dep bump 2023-01-04 20:42:48 -05:00
Apoorv Mishra f4461573de Refactor to accommodate authentication, transaction and pagination states together (#4636)
* fix: refactor to accommodate authentication, transaction and pagination together into ctx.state

* feat: allow passing response type to APIContext
2023-01-04 23:51:44 +05:30
Tom Moor bb568d2e62 fix: Exports generate invalid internal links (#4639)
* refactoring

* Refactoring continues

* Refactor export, fix internal links in exported docs

* fix: Dupe document name detection

* sigh
2023-01-04 04:18:59 -08:00
Tom Moor eb50c9e1f1 test: Remove tests associated with exporting documents from shareId 2023-01-03 22:49:02 -05:00
Tom Moor a2e183627c fix: Disabled authentication providers show as enabled in settings 2023-01-03 19:32:16 -05:00
Tom Moor 1c9eee2134 fix: Search on shared documents with custom slug not working
fix: Should not be able to export a document with shareId
2023-01-03 19:28:04 -05:00
Tom Moor 64d8f3091a fix: 'Search titles' filter wraps to multiple lines on small screens 2023-01-02 21:25:24 -05:00
Tom Moor 0d920e02b1 chore: Remove query logging in test env 2023-01-02 21:06:39 -05:00
Tom Moor 6efcf1c1a8 chore: Refactor SearchHelper internals 2023-01-02 20:14:46 -05:00
Tom Moor 435969cf4b chore: Refactor build:server to bashfile 2023-01-02 16:00:01 -05:00
Tom Moor 28a54113e1 fix: Always redirect to custom domain on server if set 2023-01-02 15:48:06 -05:00
Aditya Sharma 712ff8265e feat: add search title only filter for search options (#4587)
* feat: search title only filter

* fix: page reload will keep settings

* fix: working with additional filters

* style changes
2023-01-02 10:00:10 -08:00
dependabot[bot] b6234848fb chore(deps): bump react-i18next from 11.16.6 to 12.1.1 (#4634)
* chore(deps): bump react-i18next from 11.16.6 to 12.1.1

Bumps [react-i18next](https://github.com/i18next/react-i18next) from 11.16.6 to 12.1.1.
- [Release notes](https://github.com/i18next/react-i18next/releases)
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v11.16.6...v12.1.1)

---
updated-dependencies:
- dependency-name: react-i18next
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update related deps, TS fixes

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-01-02 09:30:44 -08:00
dependabot[bot] e4880daadf chore(deps-dev): bump yarn-deduplicate from 3.1.0 to 6.0.1 (#4633)
Bumps [yarn-deduplicate](https://github.com/scinos/yarn-deduplicate) from 3.1.0 to 6.0.1.
- [Release notes](https://github.com/scinos/yarn-deduplicate/releases)
- [Changelog](https://github.com/scinos/yarn-deduplicate/blob/master/CHANGELOG.md)
- [Commits](https://github.com/scinos/yarn-deduplicate/compare/v3.1.0...v6.0.1)

---
updated-dependencies:
- dependency-name: yarn-deduplicate
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-02 08:36:59 -08:00
dependabot[bot] 97b0fd465d chore(deps-dev): bump html-webpack-plugin from 4.5.1 to 4.5.2 (#4632)
Bumps [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) from 4.5.1 to 4.5.2.
- [Release notes](https://github.com/jantimon/html-webpack-plugin/releases)
- [Changelog](https://github.com/jantimon/html-webpack-plugin/blob/v4.5.2/CHANGELOG.md)
- [Commits](https://github.com/jantimon/html-webpack-plugin/compare/v4.5.1...v4.5.2)

---
updated-dependencies:
- dependency-name: html-webpack-plugin
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-02 08:36:18 -08:00
dependabot[bot] 423f961ca1 chore(deps): bump katex from 0.16.3 to 0.16.4 (#4630)
Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.16.3 to 0.16.4.
- [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.3...v0.16.4)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-02 08:36:06 -08:00
Tom Moor 4ccff8cb29 chore: Convert GroupListItem, AddGroupsToCollection, AddPeopleToCollection, Drafts to functional components 2023-01-02 11:26:51 -05:00
Tom Moor 8c54f6330f chore: Clarify import/export options 2023-01-02 10:39:49 -05:00
Tom Moor 846a1f8eab fix: Remove extra spaces around hard break serialization, closes #4616 2023-01-02 10:26:46 -05:00
Tom Moor 205f7d2a7e chore: Move Input to functional component (#4629)
* chore: Remove ReactHookWrappedInput workaround
Move Input to functional component

* I love Typescript
2023-01-01 10:35:02 -08:00
Tom Moor 2494ca39c1 fix: Improved GA4 integration 2023-01-01 12:27:09 -05:00
Tom Moor 8e4270c321 feat: Add GA integration, support for GA4 (#4626)
* GA integration settings

* trackingId -> measurementId
Hook up script

* Public page GA tracking
Correct layout of settings

* Remove multiple codepaths for loading GA measurementID, add missing db index

* Remove unneccessary changes, tsc

* test
2023-01-01 07:29:08 -08:00
Mohamed ELIDRISSI dc795604a4 refactor: add server-side validation schema for events (#4622)
* refactor: move files to subfolder

* refactor: schema for events.list

* refactor: update nullable fields in Event model

* fix: event actor not nullable

* fix: team not nullable
2022-12-31 13:56:37 -08:00
Tom Moor 05a4f050bb chore: Improve graceful server shutdown (#4625)
* chore: Improve graceful server shutdown

* Replace node timers with custom promise timeout
2022-12-31 13:56:27 -08:00
Tom Moor ad9525bfa3 chore: Refactor icon components
fix: Alignment of Outline logo component
2022-12-31 12:26:39 -05:00
Tom Moor 575f70a9e2 fix: useMousePosition can set state after component unmounted
fix: Clicking item in SubMenu does not close parent menu
2022-12-31 10:34:23 -05:00
Tom Moor e70f1ed84c fix: Extra spacing below inputs
fix: UX fixes in share popover with custom links
2022-12-31 10:34:23 -05:00
Tom Moor 16958560e6 New Crowdin updates (#4586) 2022-12-31 06:06:46 -08:00
Tom Moor cdbc6df485 chore: More tracing improvements 2022-12-31 09:04:45 -05:00
Tom Moor c6fb764631 chore: Move tracing decorators into the codebase (#4623)
* Vendorize tracing, finally fix service name issues

* Upgrade datadaog-metrics, rename decorators -> tracing

* lint
2022-12-31 04:54:51 -08:00
Tom Moor 1e036ebd0e fix: Nested documents do not respect export format 2022-12-30 20:36:15 -05:00
Tom Moor 7a1e6a1b73 feat: Bulk HTML export for collection 2022-12-30 20:13:29 -05:00
Tom Moor 1328162921 fix: Missing cascade constraints on stars table 2022-12-30 18:45:24 -05:00
Tom Moor 2e36ad9d1f fix: Retain title attribute when parsing from Markdown sources 2022-12-30 15:54:31 -05:00
Tom Moor c6c06ac4ce test: Add test for table content 2022-12-30 15:27:18 -05:00
Tom Moor b29a9fbeee chore: Remove reliance on Markdown for document.getSummary, towards #3000 2022-12-30 15:14:59 -05:00
Tom Moor 0f489d54c3 Add DocumentHelper.toPlainText 2022-12-30 14:49:53 -05:00
Tom Moor 7c47ab560e fix: Add check for 'name' returned from OIDC provider, closes #4453 2022-12-30 14:02:00 -05:00
Tom Moor 997d796eb7 chore: Remove anomalous serviceName from traces 2022-12-30 13:42:22 -05:00
Tom Moor 18b69fad99 fix: Improve contrast on danger color 2022-12-30 13:07:40 -05:00
Mohamed ELIDRISSI 318e1df13b refactor: add server side validation schema for attachments (#4606)
* refactor: schema for attachments.create

* refactor: schema for attachments.delete

* refactor: remove deprecated "public" request param
2022-12-30 09:49:01 -08:00
Tom Moor f3469d25fe feat: Bulk HTML export (#4620)
* wip

* Working bulk html export

* Refactor

* test

* test
2022-12-30 09:42:20 -08:00
Tom Moor 1b8dd9399c chore: Export improvements (#4617)
* wip

* i18n
2022-12-27 09:51:39 -08:00
dependabot[bot] ee37ba9355 chore(deps-dev): bump @types/prosemirror-gapcursor from 1.0.4 to 1.3.0 (#4615)
Bumps [@types/prosemirror-gapcursor](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/prosemirror-gapcursor) from 1.0.4 to 1.3.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/prosemirror-gapcursor)

---
updated-dependencies:
- dependency-name: "@types/prosemirror-gapcursor"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-26 08:57:08 -08:00
dependabot[bot] 68ad7607b0 chore(deps): bump pg from 8.5.1 to 8.8.0 (#4614)
Bumps [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) from 8.5.1 to 8.8.0.
- [Release notes](https://github.com/brianc/node-postgres/releases)
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.8.0/packages/pg)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-26 08:56:29 -08:00
dependabot[bot] 393d9c4a72 chore(deps-dev): bump @types/react-helmet from 6.1.5 to 6.1.6 (#4612)
Bumps [@types/react-helmet](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-helmet) from 6.1.5 to 6.1.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-helmet)

---
updated-dependencies:
- dependency-name: "@types/react-helmet"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-26 08:54:37 -08:00
Tom Moor c41bd9592e feat: Add date/time template options to template titles 2022-12-24 15:53:05 +00:00
Tom Moor b8f748be52 Merge branch 'main' of github.com:outline/outline 2022-12-24 12:41:15 +00:00
Tom Moor 82c565f1d4 Add additional filters to search_titles endpoint (#4563)
* Add additional filters to search_titles endpoint

* tests, fixes for drafts

* fix: dateFilter results in 500

* fix: Draft documents returned in collection-only search
2022-12-24 03:44:22 -08:00
dependabot[bot] 504693c68d chore(deps): bump jsonwebtoken from 8.5.1 to 9.0.0 (#4600)
Bumps [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) from 8.5.1 to 9.0.0.
- [Release notes](https://github.com/auth0/node-jsonwebtoken/releases)
- [Changelog](https://github.com/auth0/node-jsonwebtoken/blob/master/CHANGELOG.md)
- [Commits](https://github.com/auth0/node-jsonwebtoken/compare/v8.5.1...v9.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-22 04:01:05 -08:00
Tom Moor d261aa4d32 fix: Temporary fix for Mermaid Gantt charts – hardcode width
closes #4594
2022-12-22 06:50:57 -05:00
dependabot[bot] 09c3ee50ba chore(deps): bump prosemirror-dropcursor and @types/prosemirror-dropcursor (#4592)
Bumps [prosemirror-dropcursor](https://github.com/prosemirror/prosemirror-dropcursor) and [@types/prosemirror-dropcursor](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/prosemirror-dropcursor). These dependencies needed to be updated together.

Updates `prosemirror-dropcursor` from 1.4.0 to 1.6.1
- [Release notes](https://github.com/prosemirror/prosemirror-dropcursor/releases)
- [Changelog](https://github.com/ProseMirror/prosemirror-dropcursor/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prosemirror/prosemirror-dropcursor/compare/1.4.0...1.6.1)

Updates `@types/prosemirror-dropcursor` from 1.0.3 to 1.5.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/prosemirror-dropcursor)

---
updated-dependencies:
- dependency-name: prosemirror-dropcursor
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: "@types/prosemirror-dropcursor"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-20 16:51:12 -08:00
dependabot[bot] 0cb439857d chore(deps): bump babel-plugin-styled-components from 1.12.0 to 2.0.7 (#4589)
Bumps [babel-plugin-styled-components](https://github.com/styled-components/babel-plugin-styled-components) from 1.12.0 to 2.0.7.
- [Release notes](https://github.com/styled-components/babel-plugin-styled-components/releases)
- [Commits](https://github.com/styled-components/babel-plugin-styled-components/compare/v1.12.0...v2.0.7)

---
updated-dependencies:
- dependency-name: babel-plugin-styled-components
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-20 16:50:21 -08:00
dependabot[bot] 1a69cb057c chore(deps): bump exports-loader from 0.6.4 to 1.1.1 (#4590)
Bumps [exports-loader](https://github.com/webpack-contrib/exports-loader) from 0.6.4 to 1.1.1.
- [Release notes](https://github.com/webpack-contrib/exports-loader/releases)
- [Changelog](https://github.com/webpack-contrib/exports-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/exports-loader/compare/v0.6.4...v1.1.1)

---
updated-dependencies:
- dependency-name: exports-loader
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-20 16:49:41 -08:00
Tom Moor 0fa583e492 fix: Line numbers go missing when editing multiple blocks
closes #4440
2022-12-18 22:19:37 -05:00
Tom Moor 67ec5a1a33 fix: Missing onDelete constraint 2022-12-18 20:49:28 -05:00
Tom Moor 9618d514e1 fix: Switch workspace unreliable 2022-12-17 21:50:50 -05:00
Tom Moor 4b66a52a52 fix: Missing onDelete constraint 2022-12-17 21:19:55 -05:00
Tom Moor bf21863dbf New Crowdin updates (#4565)
* fix: New French 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 German 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 Russian translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

* fix: New Dutch translations from Crowdin [ci skip]
2022-12-17 17:17:28 -08:00
Tom Moor acf74b83a8 feat: Full width images (#4389)
* feat: Full width images

* lint

* fix: Enable TOC overlaid on full size images

* Vendorize react-medium-image-zoom

* tsc

* fix

* Remove body scroll lock
2022-12-17 17:17:15 -08:00
Tom Moor f8ba393f7c Use auth.availableTeams endpoint for workspace switching (#4585) 2022-12-17 17:17:02 -08:00
Tom Moor 1995a3fb19 Dynamic bottom padding 2022-12-15 21:03:47 -05:00
Tom Moor 6f57767b7c fix: Return after redirect 2022-12-15 20:04:42 -05:00
Tom Moor a9683f4d53 fix: Add additional padding at the bottom of documents when editing 2022-12-14 20:44:38 -05:00
Tom Moor 600b3e4b3e fix: Small tweaks/fixes to custom domain UI 2022-12-14 08:46:35 -05:00
Tom Moor 02b352a382 fix: Cross-subdomain redirect for shares with custom link 2022-12-14 08:46:20 -05:00
Apoorv Mishra 79829a3129 Ability to create share url slug (#4550)
* feat: share url slug

* feat: add col urlId

* feat: allow updating urlId

* fix: typo

* fix: migrations

* fix: urlId model validation

* fix: input label

* fix: debounce slug request

* feat: link preview

* fix: send slug variant in response if available

* fix: temporary redirect to slug variant if available

* fix: move up the custom link field

* fix: process and display backend err

* fix: reset custom link state on popover close and remove isCopied

* fix: document link preview

* fix: set urlId when available

* fix: keep unique(urlId, teamId)

* fix: codeql

* fix: get rid of preview type

* fix: width not needed for block elem

* fix: migrations

* fix: array not required

* fix: use val

* fix: validation on shareId and test

* fix: allow clearing urlId

* fix: do not escape

* fix: unique error text

* fix: keep team
2022-12-13 17:26:36 -08:00
dependabot[bot] b9dd060736 chore(deps-dev): bump eslint-plugin-prettier from 3.1.4 to 4.2.1 (#4567)
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 3.1.4 to 4.2.1.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v3.1.4...v4.2.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-12 14:35:22 -08:00
dependabot[bot] 301fde26b6 chore(deps): bump copy-to-clipboard from 3.3.1 to 3.3.3 (#4570)
Bumps [copy-to-clipboard](https://github.com/sudodoki/copy-to-clipboard) from 3.3.1 to 3.3.3.
- [Release notes](https://github.com/sudodoki/copy-to-clipboard/releases)
- [Commits](https://github.com/sudodoki/copy-to-clipboard/compare/v3.3.1...v3.3.3)

---
updated-dependencies:
- dependency-name: copy-to-clipboard
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-12 13:32:56 -08:00
dependabot[bot] 662012da08 chore(deps-dev): bump husky from 7.0.4 to 8.0.2 (#4568)
Bumps [husky](https://github.com/typicode/husky) from 7.0.4 to 8.0.2.
- [Release notes](https://github.com/typicode/husky/releases)
- [Commits](https://github.com/typicode/husky/compare/v7.0.4...v8.0.2)

---
updated-dependencies:
- dependency-name: husky
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-12 13:32:40 -08:00
dependabot[bot] cab8b69e8d chore(deps): bump class-validator from 0.13.2 to 0.14.0 (#4569)
Bumps [class-validator](https://github.com/typestack/class-validator) from 0.13.2 to 0.14.0.
- [Release notes](https://github.com/typestack/class-validator/releases)
- [Changelog](https://github.com/typestack/class-validator/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/typestack/class-validator/compare/v0.13.2...v0.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-12 13:32:15 -08:00
Tom Moor 91155295fe fix: Loosen url validation 2022-12-11 21:28:09 -05:00
Tom Moor 80780eedda fix: Mermaid diagrams do not respect dark mode (#4564)
* wip

* Move event binding
2022-12-11 11:33:17 -08:00
Tom Moor e4da92a359 New Crowdin updates (#4533) 2022-12-11 08:35:40 -08:00
Tom Moor 0f19c550f9 fix: Uploaded and immediately deleted images are not removed from storage (#4562)
* fix: Uploaded and immediately deleted images are not removed from storage upon permanant delete
closes #4557

* Move attachment deletion async
2022-12-11 08:29:38 -08:00
Tom Moor 7e22526cc7 Improve error handling 2022-12-11 11:27:54 -05:00
Apoorv Mishra 5c842087a5 feat(server): rate limit all routes (#4561) 2022-12-10 05:47:18 -08:00
Apoorv Mishra 053d10d893 Enhance server side error handling (#4537)
* fix: server side error handling

* fix: push only unknown 500 errors to sentry

* fix: use in-house onerror in favor of errorHandling middleware

* fix: split error template into dev and prod envs

* fix: check Error instance

* fix: error routes in test env

* fix: review comments

* Remove koa-onerror

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-12-09 21:51:42 +05:30
Tom Moor 4f67437b81 chore: Bump Sentry deps (#4556) 2022-12-08 18:05:13 -08:00
Alex 7db2284564 build: harden calibreapp-image-actions.yml permissions (#4555)
Signed-off-by: Alex <aleksandrosansan@gmail.com>

Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-12-08 17:49:15 -08:00
Tom Moor 92ab7c1700 Bump bull, ioredis (#4553) 2022-12-07 19:06:44 -08:00
Tom Moor 239db70374 fix: Bump qs, fix prototype pollution 2022-12-07 22:05:35 -05:00
dependabot[bot] a650e92979 chore(deps): bump fetch-retry from 4.1.1 to 5.0.3 (#4539)
Bumps [fetch-retry](https://github.com/jonbern/fetch-retry) from 4.1.1 to 5.0.3.
- [Release notes](https://github.com/jonbern/fetch-retry/releases)
- [Commits](https://github.com/jonbern/fetch-retry/compare/4.1.1...5.0.3)

---
updated-dependencies:
- dependency-name: fetch-retry
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 20:21:52 -08:00
dependabot[bot] 5f121ff268 chore(deps-dev): bump eslint-plugin-import from 2.25.3 to 2.26.0 (#4542)
Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.25.3 to 2.26.0.
- [Release notes](https://github.com/import-js/eslint-plugin-import/releases)
- [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.25.3...v2.26.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-import
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 20:11:09 -08:00
Tom Moor ea63023fca feat: added user avatars in author search dropdown (#4551)
* feat: added user avatars in author search dropdown

* cleanup

* cleanup

* feat: added user avatars in author search dropdown

* cleanup

* cleanup

* added user icon

* Size tweaks

Co-authored-by: Aditya Sharma <aditya167411@gmail.com>
2022-12-06 20:10:56 -08:00
Tom Moor 549b7ab030 tsc 2022-12-06 09:32:13 -05:00
Tom Moor 4ce8ea8cd6 fix: Cannot create document from selection in root of collection 2022-12-06 09:10:45 -05:00
Tom Moor 98d79e1e8b perf: Improve collab persistence performance (#4544)
* stash

* Remove query of state in documentCollaborativeUpdater
2022-12-06 05:21:10 -08:00
Tom Moor b0b7c7d647 fix: Document import schema 2022-12-04 22:31:54 -05:00
Tom Moor 481382ee9f Add link to download app (macOS) 2022-12-04 18:03:35 -05:00
Tom Moor 3d6da26ad6 fix: Flash of external link decoration when creating doc from selected text 2022-12-04 17:41:19 -05:00
Tom Moor 0a68266365 fix: Server error in document search with single quotes 2022-12-04 17:10:06 -05:00
Tom Moor 908ca36de2 Merge branch 'main' of github.com:outline/outline 2022-12-04 15:32:33 -05:00
Tom Moor 435a7ab26b Dependency update 2022-12-04 15:07:17 -05:00
Aditya Sharma 8513200900 fix: Don't embed pasted links in list (#4535)
closes https://github.com/outline/outline/issues/4154
2022-12-04 11:41:03 -08:00
Tom Moor 1fd3f3137a chore: Upgrade hocuspocus (#4443)
* dep bump

* Bump to beta 6

* fix: Race condition in indexeddb provider
2022-12-04 11:39:58 -08:00
Tom Moor d16133fda8 Remove ui.isEditing, enable forcing desktop window open without sidebar 2022-12-04 11:55:33 -05:00
Tom Moor cd29cd3aec fix: Various improvements to select input, closes #4528 2022-12-04 10:57:09 -05:00
Tom Moor 13db16283a fix: Cleanup user menu (#4532)
Move confirmation dialogs from window confirmations
2022-12-03 15:33:16 -08:00
Tom Moor d6d1eb4485 feat: Prefix api keys 2022-12-03 18:21:33 -05:00
Tom Moor 0f31d5b45f fix: Generate signing secret on webhook creation 2022-12-03 10:23:31 -05:00
Tom Moor 08a471f230 Add 'members' filter to user details page, closes #4529 2022-12-03 09:36:28 -05:00
Tom Moor e15ad530de tsc 2022-12-03 09:25:53 -05:00
Denis 6354acca85 if OIDC provider gets user_id as integer Postgres failed query (#4527) 2022-12-02 21:46:56 -08:00
Tom Moor e32f2c2257 New Crowdin updates (#4493) 2022-12-02 20:05:45 -08:00
dependabot[bot] 6903a1c481 chore(deps-dev): bump babel-jest from 28.1.3 to 29.3.1 (#4497)
Bumps [babel-jest](https://github.com/facebook/jest/tree/HEAD/packages/babel-jest) from 28.1.3 to 29.3.1.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v29.3.1/packages/babel-jest)

---
updated-dependencies:
- dependency-name: babel-jest
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-02 19:35:19 -08:00
Denis 571f812771 fix: getEncryptedColumn for empty token (#4507) 2022-12-02 19:28:43 -08:00
Tom Moor 4d1bbf3f80 Upgrade socket.io (#4526)
* Upgrade socket.io, drop support with v2 clients

* tsc
2022-12-02 19:06:37 -08:00
Tom Moor 0a0498d139 fix: Allow subdomains upto 255 in self-hosted, closes #4524 2022-12-02 22:06:13 -05:00
dependabot[bot] 0f19a82488 chore(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#4525)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-02 17:16:33 -08:00
Tom Moor fc9d685ef5 fix: Remove unused S3 methods (#4515) 2022-12-01 19:45:24 -08:00
Tom Moor b305154715 Add onLogout method to desktop bridge 2022-12-01 18:14:51 -05:00
Tom Moor d09a3de800 fix: Desktop redirect on Safari 2022-12-01 08:26:01 -05:00
Tom Moor 83b687a632 fix: teamPermanentDeleter execution order 2022-11-30 23:07:37 -05:00
Tom Moor 648424fe2c fix: Desktop redirect on Safari 2022-11-30 23:00:40 -05:00
Tom Moor 63cef45284 fix: documents endpoints allow slug as id parameter 2022-11-29 22:16:46 -05:00
Tom Moor bc299a00f5 Add additional keywords for date slash commands 2022-11-28 23:36:30 -05:00
Tom Moor b40bb71adf fix: Allow clicking anywhere outside command menu to close 2022-11-28 23:35:41 -05:00
Tom Moor 59d9859a64 fix: Prevent hover styling when MenuItem is controlled 2022-11-28 21:18:15 -05:00
Tom Moor fbeaa2ec9f Handle keyboard shortcut request 2022-11-28 20:52:50 -05:00
dependabot[bot] 53669a4be6 chore(deps-dev): bump webpack from 4.44.1 to 4.46.0 (#4495)
Bumps [webpack](https://github.com/webpack/webpack) from 4.44.1 to 4.46.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.44.1...v4.46.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-28 16:56:17 -08:00
dependabot[bot] 201c3e1f05 chore(deps): bump @babel/preset-react from 7.16.0 to 7.18.6 (#4496)
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.16.0 to 7.18.6.
- [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.18.6/packages/babel-preset-react)

---
updated-dependencies:
- dependency-name: "@babel/preset-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-28 16:55:50 -08:00
dependabot[bot] 572ffe44aa chore(deps): bump polished from 3.7.2 to 4.2.2 (#4494)
Bumps [polished](https://github.com/styled-components/polished) from 3.7.2 to 4.2.2.
- [Release notes](https://github.com/styled-components/polished/releases)
- [Commits](https://github.com/styled-components/polished/compare/v3.7.2...v4.2.2)

---
updated-dependencies:
- dependency-name: polished
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-28 16:55:35 -08:00
Tom Moor 8ca5d66204 Desktop improvements (#4492)
* Update version to non-promise interface

* History navigation in sidebar
2022-11-28 15:30:50 -08:00
Tom Moor a5e2ac6570 fix: Negated quote query error 2022-11-28 09:01:03 -05:00
Tom Moor b5570a7587 New Crowdin updates (#4479) 2022-11-28 05:52:12 -08:00
Apoorv Mishra d09c583c72 Allow document to be dragged to the top of a collection (#4489) 2022-11-28 05:42:58 -08:00
Tom Moor cc333637dd Desktop support (#4484)
* Remove home link on desktop app

* Spellcheck, installation toasts, background styling, …

* Add email,slack, auth support

* More desktop style tweaks

* Move redirect to client

* cleanup

* Record desktop usage

* docs

* fix: Selection state in search input when double clicking header
2022-11-27 15:07:48 -08:00
Tom Moor ea9680c3d7 lint 2022-11-27 11:05:39 -05:00
Tom Moor d22b44dcff Further improved search matches 2022-11-27 10:46:54 -05:00
Tom Moor fa8685d241 Add support for LaTeX inline and block expressions. (#4446)
* Add support for LaTeX inline and block expressions. (#4364)

Co-authored-by: Tom Moor <tom@getoutline.com>

* tsc

* Show heading markers when LaTeX block is being edited

* Tab to space, name katex chunk

* Fork htmldiff, add support for math nodes

Co-authored-by: luisbc92 <luiscarlos.banuelos@gmail.com>
2022-11-27 06:27:56 -08:00
Tom Moor cb1b8e9764 fix: Improved search engine-like behavior for text queries 2022-11-27 09:26:10 -05:00
Tom Moor 957e9ba0ff fix: Cleanup build directory before building new server assets 2022-11-25 12:02:42 -05:00
Tom Moor 5a42f70b65 Add case for un-mapped custom domain to login page 2022-11-25 10:22:45 -05:00
Tom Moor fd9625b57e fix: TOC not rendered in non-seamless-editing mode 2022-11-24 11:35:29 -05:00
Tom Moor 18535d949e New Crowdin updates (#4476) 2022-11-24 06:51:47 -08:00
Tom Moor a8936039e5 Improved paste handling (#4474)
* Improved paste handling

* Remove file
2022-11-24 06:50:31 -08:00
Apoorv Mishra a6125be6f1 Introduce zod for server-side validations (#4397)
* chore(server): use zod for validations

* fix(server): use ctx.input for documents.list

* fix(server): schema for documents.archived

* fix(server): documents.deleted, documents.viewed & documents.drafts

* fix(server): documents.info

* fix(server): documents.export & documents.restore

* fix(server): documents.search_titles & documents.search

* fix(server): documents.templatize

* fix(server): replace nullish() with optional()

* fix(server): documents.update

* fix(server): documents.move

* fix(server): remaining

* fix(server): add validation for snippet min and max words

* fix(server): fix update types

* fix(server): remove DocumentSchema

* fix(server): collate duplicate schemas

* fix: typos

* fix: reviews

* chore: Fixed case of Metrics import

* fix: restructure /api

* fix: loosen validation for id as it can be a slug too

* Add test for query by slug
Simplify import

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-11-24 10:11:43 +05:30
Tom Moor 100d05035b Merge branch 'main' of github.com:outline/outline 2022-11-23 20:30:38 -05:00
Tom Moor 5b00741b1a New Crowdin updates (#4460) 2022-11-23 17:13:58 -08:00
Aditya Sharma 95f2c69f81 fix: correct stats for update-checker (#4470)
fixes https://github.com/outline/outline/issues/4462
2022-11-23 17:13:39 -08:00
Tom Moor 0794450596 chore: Fixed case of Metrics import 2022-11-22 23:08:52 -05:00
Tom Moor 09f5462068 Enable viewers to upload attachments for documents in collections where they have edit permission (#4468) 2022-11-22 19:05:08 -08:00
dependabot[bot] 4cb1652005 chore(deps-dev): bump babel-plugin-transform-inline-environment-variables from 0.4.3 to 0.4.4 (#4457)
Bumps [babel-plugin-transform-inline-environment-variables](https://github.com/babel/minify) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/babel/minify/releases)
- [Changelog](https://github.com/babel/minify/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/minify/compare/babel-plugin-transform-inline-environment-variables@0.4.3...babel-plugin-transform-inline-environment-variables@0.4.4)

---
updated-dependencies:
- dependency-name: babel-plugin-transform-inline-environment-variables
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-21 19:12:11 -08:00
dependabot[bot] 7dbf098d68 chore(deps): bump jsdom from 20.0.0 to 20.0.3 (#4455)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 20.0.0 to 20.0.3.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Changelog](https://github.com/jsdom/jsdom/blob/master/Changelog.md)
- [Commits](https://github.com/jsdom/jsdom/compare/20.0.0...20.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-21 19:11:43 -08:00
dependabot[bot] 32e47d86a5 chore(deps-dev): bump @types/crypto-js from 4.1.0 to 4.1.1 (#4454)
Bumps [@types/crypto-js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/crypto-js) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/crypto-js)

---
updated-dependencies:
- dependency-name: "@types/crypto-js"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-21 19:11:18 -08:00
dependabot[bot] e76e547d8a chore(deps): bump rate-limiter-flexible from 2.3.7 to 2.4.1 (#4456)
Bumps [rate-limiter-flexible](https://github.com/animir/node-rate-limiter-flexible) from 2.3.7 to 2.4.1.
- [Release notes](https://github.com/animir/node-rate-limiter-flexible/releases)
- [Commits](https://github.com/animir/node-rate-limiter-flexible/commits/v2.4.1)

---
updated-dependencies:
- dependency-name: rate-limiter-flexible
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-21 19:11:07 -08:00
Tom Moor e605961e23 feat: Optional branding on shared documents (#4450)
* feat: Optional branding on shared documents

* Refactor
Remove unneccessarily exposed team id
Remove top-level collapsible document on publicly shared urls

* fix: Branding disappears when revising document
fix: Clicking title should go back to main app when logged in
2022-11-21 16:20:50 -08:00
Tom Moor 088ef81133 fix: Improved styles for diffs, closes #4430 2022-11-20 14:09:11 -05:00
Tom Moor 6e36ffb706 feat: Allow data imports larger than the standard attachment size (#4449)
* feat: Allow data imports larger than the standard attachment size

* Use correct preset for data imports

* Cleanup of expired attachments

* lint
2022-11-20 09:22:57 -08:00
Tom Moor 1f49bd167d fix: Flash of misaligned floating images upon loading 2022-11-19 20:18:55 -05:00
Tom Moor c27987569b fix: Flash of embeds visible upon loading when disabled 2022-11-19 20:09:56 -05:00
Tom Moor ae6855f3df chore: Refactors towards shared menu component (#4445) 2022-11-19 13:15:38 -08:00
Tom Moor 924b554281 fix: Padding below editor not clickable 2022-11-16 23:38:08 -05:00
Tom Moor 552c0ecf01 fix: Pasting document content in title should behave as expected 2022-11-16 23:38:08 -05:00
dependabot[bot] 19d33a7658 chore(deps): bump loader-utils from 1.4.1 to 1.4.2 (#4439)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 19:18:47 -08:00
Tom Moor f43f253286 Refactoring endpoints to transaction middleware 2022-11-15 22:22:35 -05:00
Tom Moor 01a482552a fix: Unable to subscribe to fileOperation webhook events 2022-11-15 20:03:20 -05:00
Tom Moor e6ef5a16cc Fix TOC headings 2022-11-14 21:14:56 -05:00
Tom Moor 4c8138ad4a fix: Some react warnings in dev mode 2022-11-14 19:36:24 -05:00
Tom Moor 4047ec73bb feat: Integrate Zapier App Directory 2022-11-14 18:10:10 -05:00
dependabot[bot] 1e723be556 chore(deps): bump koa-sslify from 2.1.2 to 5.0.0 (#4424)
* chore(deps): bump koa-sslify from 2.1.2 to 5.0.0

Bumps [koa-sslify](https://github.com/turboMaCk/koa-sslify) from 2.1.2 to 5.0.0.
- [Release notes](https://github.com/turboMaCk/koa-sslify/releases)
- [Changelog](https://github.com/turboMaCk/koa-sslify/blob/master/CHANGELOG.md)
- [Commits](https://github.com/turboMaCk/koa-sslify/compare/2.1.2...5.0.0)

---
updated-dependencies:
- dependency-name: koa-sslify
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Nice try dependabot

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-11-14 14:17:05 -08:00
dependabot[bot] 1f5171053e chore(deps): bump core-js from 3.26.0 to 3.26.1 (#4421)
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.26.0 to 3.26.1.
- [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.26.1/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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 13:35:19 -08:00
dependabot[bot] 829214d9a3 chore(deps): bump @juggle/resize-observer from 3.3.1 to 3.4.0 (#4422)
Bumps [@juggle/resize-observer](https://github.com/juggle/resize-observer) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/juggle/resize-observer/releases)
- [Commits](https://github.com/juggle/resize-observer/compare/v3.3.1...v3.4.0)

---
updated-dependencies:
- dependency-name: "@juggle/resize-observer"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 13:34:52 -08:00
dependabot[bot] 441055a05e chore(deps-dev): bump @types/validator from 13.7.8 to 13.7.10 (#4423)
Bumps [@types/validator](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/validator) from 13.7.8 to 13.7.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/validator)

---
updated-dependencies:
- dependency-name: "@types/validator"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 13:34:40 -08:00
Tom Moor 24a1eac804 Sidebar improvements from comments branch (#4419) 2022-11-13 18:26:53 -08:00
Tom Moor b60c66316a Insights refinements 2022-11-13 14:47:20 -05:00
Tom Moor 3880a956a3 Add document insights panel (#4418)
* Add document context to allow accessing editor in header, modals, and elsewhere

* lint

* framework

* Hacking together fast

* Insights

* Spacing tweak, docs
2022-11-13 10:19:09 -08:00
Tom Moor 762341a4ec feat: Track attachments access (#4416) 2022-11-12 08:41:59 -08:00
Tom Moor 622f464b9f Store import<->document relationship (#4415)
* Store import<->document relationship

* Update 20221112152649-import-document-relationship.js

* Store importId on collection, UI tweaks on import screen
2022-11-12 08:22:41 -08:00
Tom Moor cafe4ed848 Add seed script 2022-11-12 10:16:19 -05:00
Tom Moor cff67f4ca7 fix: Self-hosted logic for allowed domains (#4412)
* fix: Self-hosted logic for allowed domains

* test
2022-11-11 19:19:46 -08:00
Tom Moor 6788005115 Add missing team->user constraint (#4411)
* Add missing team->user constraint

* fix: teamPermanentDeleter cannot complete when team has domains
2022-11-11 09:40:52 -08:00
Tom Moor 26946853da fix: DATABASE_URL missing in env does not produce validation error (#4409)
* fix: DATABASE_URL missing in env does not produce validation error

* lint
2022-11-11 07:11:10 -08:00
Tom Moor d0827e21c1 tsc 2022-11-09 07:40:41 -05:00
Tom Moor eee0abe415 test 2022-11-08 21:42:08 -05:00
Tom Moor e7af0ce6de fix: getPreference fallback 2022-11-08 21:30:14 -05:00
Tom Moor 369ac487b1 feat: Add preference to disable download option for viewers 2022-11-08 21:26:02 -05:00
Tom Moor 587f062677 Remove usage of tiley (#4406)
* First pass

* Mooarrr

* lint

* snapshots
2022-11-08 17:12:22 -08:00
dependabot[bot] 920b58c006 chore(deps): bump loader-utils from 1.4.0 to 1.4.1 (#4405)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-08 05:31:03 -08:00
dependabot[bot] d16d94a0f6 chore(deps): bump core-js from 3.10.2 to 3.26.0 (#4395)
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.10.2 to 3.26.0.
- [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.26.0/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-08 05:26:06 -08:00
dependabot[bot] 4d4cd42740 chore(deps): bump @bull-board/koa from 4.2.2 to 4.6.2 (#4394)
Bumps [@bull-board/koa](https://github.com/felixmosh/bull-board/tree/HEAD/packages/koa) from 4.2.2 to 4.6.2.
- [Release notes](https://github.com/felixmosh/bull-board/releases)
- [Changelog](https://github.com/felixmosh/bull-board/blob/master/CHANGELOG.md)
- [Commits](https://github.com/felixmosh/bull-board/commits/v4.6.2/packages/koa)

---
updated-dependencies:
- dependency-name: "@bull-board/koa"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-08 05:25:35 -08:00
dependabot[bot] b55ba473d1 chore(deps): bump @babel/plugin-transform-destructuring from 7.16.0 to 7.20.2 (#4396)
Bumps [@babel/plugin-transform-destructuring](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-destructuring) from 7.16.0 to 7.20.2.
- [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.20.2/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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-08 05:24:55 -08:00
Tom Moor c859d2dd84 fix: Deleted text style in history, closes #4231 2022-11-07 21:55:14 -05:00
Tom Moor fd799e00a7 fix: Unauthenticated shared document should default to user preferred language
closes #4384
2022-11-06 18:53:05 -05:00
Tom Moor 5f4d67e2f9 Bump caniuse 2022-11-06 14:10:48 -05:00
Tom Moor 9936f42882 Avoid fsstat on every request, remove koa-static (#4387)
* Avoid fsstat on every request, remove koa-static

* tsx

* Move compression middleware
2022-11-05 06:50:46 -07:00
Translate-O-Tron bac1c9dc14 New Crowdin updates (#4327) 2022-11-03 18:39:23 -07:00
Tom Moor 2df4b352a1 fix: Hard to resize image larger than documen width immediately after uploading 2022-11-03 20:16:56 -04:00
Tom Moor f2fb5dd1e5 fix: Team creation with private avatar. Do not attempt to copy, closes #4378 2022-11-03 08:58:33 -04:00
Tom Moor c2de6b70bc fix: Image resizing on mobile/touchscreen 2022-11-03 08:11:11 -04:00
Tom Moor 88188a0a59 fix: Sanitize url before opening 2022-11-03 07:38:35 -04:00
Tom Moor 5e17b24869 feat: Image resizing (#4368)
* wip

* works

* wip

* refactor

* Support replacing image and retain width
fix: Copy paste does not retain size

* cleanup

* fix: Cannot resize past 100%
fix: Borders to edges on unresized images

* Handle Escape key while dragging

* fix: Embeds and images dont render when edit state changes
fix: Small animation regression
2022-11-02 18:40:37 -07:00
Tom Moor 6f8d01df21 fix: Sanitize url before opening 2022-11-02 21:38:16 -04:00
Mihir Shah 881ea34dfe chore: convert AddGroupToCollection component to functional component (#4204) (#4360) 2022-11-02 17:31:13 -07:00
Tom Moor 3cb0b88f0f Update MarkAsViewed.ts 2022-11-01 18:42:59 -07:00
dependabot[bot] d4cac4983c chore(deps-dev): bump @types/enzyme from 3.10.10 to 3.10.12 (#4371)
Bumps [@types/enzyme](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/enzyme) from 3.10.10 to 3.10.12.
- [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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-01 05:22:17 -07:00
Tom Moor ab6c5c2e78 Missing observer 2022-11-01 08:21:59 -04:00
Mihir Shah ade26139e6 chore: convert AddPeopleToGroup component to functional component (#4204) (#4359) 2022-11-01 05:17:26 -07:00
Ítalo Sousa 17977064aa Functional Component Refactor: MarkAsViewed (#4365)
* chore: convert MarkAsViewed to functional component

* chore: fix issues with MarkAsViewed refactor
2022-11-01 05:08:07 -07:00
dependabot[bot] 5b55f7ab1c chore(deps): bump jszip from 3.10.0 to 3.10.1 (#4370)
Bumps [jszip](https://github.com/Stuk/jszip) from 3.10.0 to 3.10.1.
- [Release notes](https://github.com/Stuk/jszip/releases)
- [Changelog](https://github.com/Stuk/jszip/blob/main/CHANGES.md)
- [Commits](https://github.com/Stuk/jszip/compare/v3.10.0...v3.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-31 14:21:08 -07:00
Tom Moor 1e62d25861 Refactor document search 2022-10-30 12:41:52 -04:00
Tom Moor 86aa531fad Utility method to re-derive text field from CRDT 2022-10-30 10:29:32 -04:00
Tom Moor f6f90ff406 fix: Unable to login with matching email from another auth provider (#4356)
* fix: Unable to login with matching email from another auth provider

* refactor
2022-10-29 13:46:29 -07:00
Tom Moor 79cbe304da fix: Allow viewers to upload avatar (#4349)
* fix: Allow viewers to upload avatar

* DeleteAttachmentTask

* fix: Previous avatar should be deleted on change, if possible

* fix: Also cleanup team logo on change
2022-10-29 06:08:20 -07:00
Apoorv Mishra 19e26ba402 Return correct permissions upon document update (#4352) 2022-10-28 05:20:42 -07:00
Tom Moor c916d4f594 spike: db transaction as middleware (#4301) 2022-10-26 17:38:37 -07:00
dependabot[bot] 51b3371bf5 chore(deps): bump mermaid and @types/mermaid (#4257)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-10-26 19:07:52 -04:00
Tom Moor 39d8eb8a3a Merge branch 'main' of github.com:outline/outline 2022-10-26 19:01:32 -04:00
Mihir Shah c1fb8c74ff chore: convert Avatar component to functional component (#4204) (#4337) 2022-10-26 16:01:28 -07:00
Duale Siad ca255d9210 Added syntax highlighting for various languages (#4341)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-10-26 16:01:12 -07:00
Tom Moor fe3e8d3830 More flexible subdomain validation 2022-10-26 07:23:33 -04:00
Mihir Shah 808eb540a7 chore: convert PlaceholderText component to functional component (#4204) (#4336) 2022-10-25 14:44:20 -07:00
Apoorv Mishra a89d30c735 Allow drafts to be created without requiring a collection (#4175)
* feat(server): allow document to be created without collectionId

* fix(server): policies for a draft doc without collection

* fix(app): hide share button for drafts

* feat(server): permissions around publishing a draft

* fix(server): return drafts without collection

* fix(server): handle draft deletion

* fix(server): show drafts in deleted docs

* fix(server): allow drafts without collection to be restored

* feat(server): return drafts in search results

* fix: use buildDraftDocument for drafts

* fix: remove isDraftWithoutCollection

* fix: do not return drafts for team

* fix: put invariants

* fix: query clause

* fix: check only for undefined

* fix: restore includeDrafts clause as it was before
2022-10-25 18:01:57 +05:30
dependabot[bot] 6b74d43380 chore(deps-dev): bump eslint-import-resolver-typescript from 3.5.1 to 3.5.2 (#4329)
Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 3.5.1 to 3.5.2.
- [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.1...v3.5.2)

---
updated-dependencies:
- dependency-name: eslint-import-resolver-typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-24 17:17:51 -07:00
dependabot[bot] 249f340b21 chore(deps-dev): bump nodemon from 2.0.18 to 2.0.20 (#4330)
Bumps [nodemon](https://github.com/remy/nodemon) from 2.0.18 to 2.0.20.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v2.0.18...v2.0.20)

---
updated-dependencies:
- dependency-name: nodemon
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-24 17:17:38 -07:00
Tom Moor f63bf336f1 fix: Show code block line numbers by default 2022-10-24 19:18:43 -04:00
Tom Moor c3c1de09ab Settings 2022-10-24 18:01:27 -04:00
Tom Moor df46d3754a feat: Authentication provider display (#4332)
* layout

* Refactor

* wip

* Quick changes to make this deployable without full management

* test
2022-10-24 14:01:40 -07:00
Tom Moor 434bb989cc Stray console.log 2022-10-24 12:04:55 -07:00
Translate-O-Tron b5b349be29 New Crowdin updates (#4302) 2022-10-24 06:45:04 -07:00
Tom Moor 87761e9bf2 feat: Code blocks can now optionally display line numbers (#4324)
* feat: Code blocks can now optionally display line numbers as a user preference

* Touch more breathing room
2022-10-24 06:44:46 -07:00
Tom Moor 708f9a3fd6 feat: Add additional 'smart text' replacements 2022-10-23 17:43:17 -04:00
dependabot[bot] 8d47a05591 chore(deps): bump react-hook-form from 7.31.2 to 7.37.0 (#4309)
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.31.2 to 7.37.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.31.2...v7.37.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-23 11:04:40 -07:00
Daniyaal Beg fabdcd03e2 Update Architecture.md (#4323) 2022-10-23 11:04:08 -07:00
Tom Moor 44ce377c38 fix #4183 2022-10-23 14:00:53 -04:00
github-actions[bot] d43423fc39 chore: Auto Compress Images (#4322)
Co-authored-by: tommoor <tommoor@users.noreply.github.com>
2022-10-23 10:45:12 -07:00
Tom Moor e00e3e232a fix: Hard to select text in link editor input 2022-10-23 13:43:22 -04:00
Tom Moor 1f1dd23e18 feat: Add block command to create Mermaid diagram
closes #4272
2022-10-23 13:28:01 -04:00
Tom Moor 8ba911b56d fix: Slight misalignment of archived badge 2022-10-23 13:00:57 -04:00
Tom Moor e714e934cb Tweak layout of login screen to account for new custom logos 2022-10-23 11:36:54 -04:00
Tom Moor 60f6a1f1c6 fix: More flexible link validation in editor allows custom protocols
closes #4319
2022-10-23 08:15:15 -04:00
Tom Moor 9af22017fe fix: Do not oclude text on completed tasks, closes #4283 2022-10-23 07:52:23 -04:00
Tom Moor f6ae32deef Co-authored-by: Nan Yu <thenanyu@users.noreply.github.com> 2022-10-22 23:29:39 -04:00
Tom Moor c0a86753bd 0.66.2 2022-10-22 11:24:04 -04:00
Tom Moor f277d08982 fix: Actor on users.delete 2022-10-22 11:17:31 -04:00
Tom Moor 49d53ccfc2 fix: Disallow adding self to collection (#4299)
* api

* ui

* update collection permissions
2022-10-22 11:14:18 -04:00
dependabot[bot] c108a91195 chore(deps-dev): bump @types/markdown-it-container from 2.0.4 to 2.0.5 (#4308)
Bumps [@types/markdown-it-container](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/markdown-it-container) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/markdown-it-container)

---
updated-dependencies:
- dependency-name: "@types/markdown-it-container"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-20 04:44:59 -07:00
dependabot[bot] 6caa61f4a5 chore(deps-dev): bump @types/validator from 13.7.1 to 13.7.8 (#4310)
Bumps [@types/validator](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/validator) from 13.7.1 to 13.7.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/validator)

---
updated-dependencies:
- dependency-name: "@types/validator"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-20 04:44:30 -07:00
Tom Moor a814543aaf fix: Drag-n-drop 2022-10-19 21:53:22 -04:00
Tom Moor 167ade0d59 fix: Flash of starred documents on Home screen 2022-10-19 21:53:22 -04:00
Tom Moor b8b0d927f2 Fade in viewer information 2022-10-19 21:53:22 -04:00
Tom Moor 6072d3320a Handle keyboard navigation 2022-10-19 21:53:22 -04:00
Tom Moor 1a88fd5515 Working fast-click 2022-10-19 21:53:22 -04:00
Tom Moor 3f3c05c800 stash 2022-10-19 21:53:22 -04:00
Apoorv Mishra bb21fa725c Add team preference to use logo for branding (#4285)
* feat: add team preference to use logo for branding

* fix: allow on cloud version too
2022-10-19 10:01:24 +05:30
Tom Moor 98f997387c fix: Multi-method handlers, regressed in methodOverride removal 2022-10-18 23:50:14 -04:00
Tom Moor 7a9c75b9f1 Remove stray log 2022-10-18 21:49:37 -04:00
Tom Moor 87e3f18e6d chore: Remove method override middleware (#4315)
* chore: Remove method override middleware

* wip

* CodeQL

* max/min
2022-10-18 16:03:25 -07:00
Tom Moor 0da46321b8 perf: Don't go to disk for html more than once (#4312) 2022-10-17 17:51:30 -07:00
Tom Moor cbb2bdf80c Update text column with CRDT backfill 2022-10-17 14:20:54 -04:00
Tom Moor 5d5fe66e77 fix: Logging in with email on a subdomain should not forward to other subdomains (#4305) 2022-10-16 08:20:46 -07:00
Tom Moor ac31850a53 Revert i18n changes 2022-10-16 09:17:45 -04:00
Nan Yu 39fc8d5c14 feat: allow ad-hoc creation of new teams (#3964)
Co-authored-by: Tom Moor <tom@getoutline.com>
2022-10-16 05:57:27 -07:00
Tom Moor 1fbc000e03 chore: Reduce test boilerplate (#4300)
* chore: Reduce test boilerplate

* mo
2022-10-15 19:40:21 -07:00
Tom Moor 1915a453db fix: Disallow adding self to collection (#4299)
* api

* ui

* update collection permissions
2022-10-15 19:11:09 -07:00
Kedas 97a50b20da Add SENTRY_TUNNEL option (#4298)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-10-15 15:12:38 -07:00
Tom Moor 7bac696eaf fix #4294 2022-10-15 14:06:56 -04:00
Tom Moor 258225149a chore: Dependency bumps (#4295)
* chore: Remove dupe dep of body-scroll-lock

* chore: Update dd-trace

* Sentry

* typescript-eslint (fixes warning)
2022-10-15 10:02:55 -07:00
mastqe 515e1a0d25 Functional Component Refactor: TypeForm, Vimeo, Whimsical, YouTube (#4265) 2022-10-15 07:02:12 -07:00
mastqe ca31823228 Functional Component Refactor: Pitch, Prezi, Spotify, Trello (#4264) 2022-10-15 07:02:02 -07:00
mastqe 7b69f7a6e2 Functional Component Refactor: Marvel, Mindmeister, Miro, ModeAnalytics (#4263) 2022-10-15 07:01:53 -07:00
mastqe 557ad75fc2 Functional Component Refactor: InVision, Loom, Lucidchart (#4262) 2022-10-15 07:01:43 -07:00
mastqe 28371a4942 Functional Component Refactor: Google Calendar, DataStudio, & Drawings (#4261) 2022-10-15 07:01:32 -07:00
mastqe 42d866931b Functional Component Refactor: Figma, Framer, Gist (#4260) 2022-10-15 07:01:10 -07:00
mastqe 4dc336eeab Functional Component Refactor: Google Docs, Drive, Sheets, & Slides (#4259) 2022-10-15 07:00:59 -07:00
Translate-O-Tron 136d98792b New Crowdin updates (#4269) 2022-10-15 07:00:47 -07:00
Tom Moor def40e38ba Update ClickUp.tsx 2022-10-13 06:39:20 -07:00
Apoorv Mishra 2708d429a9 Set subscribe/unsubscribe state correctly for documents (#4266) 2022-10-12 16:48:43 -07:00
Tom Moor 7199088d1b fix: Multiplayer changes attributed to incorrect user (#4282)
* fix: Multiplayer changes attributed to the wrong user, performance improvements

* fix: Actually use _last_ editor
2022-10-12 06:19:07 -07:00
Tom Moor 484e912947 fix: Min-width collapsing search inputs 2022-10-11 22:21:11 -04:00
Tom Moor cb89c3aa79 Draw.io -> Self-hosted
fix: Existing draw.io setting not appearing on first load
2022-10-11 22:09:33 -04:00
Tom Moor 5654c312b1 Remove TLDraw from embed menu as it no longer supports embedding 2022-10-11 21:47:39 -04:00
Apoorv Mishra 21b91ff060 Remove invite button should not overlap with member dropdown button (#4243) 2022-10-10 17:31:53 -07:00
dependabot[bot] b29344efce chore(deps): bump string-replace-to-array from 1.0.3 to 2.1.0 (#4255)
Bumps [string-replace-to-array](https://github.com/appfigures/string-replace-to-array) from 1.0.3 to 2.1.0.
- [Release notes](https://github.com/appfigures/string-replace-to-array/releases)
- [Changelog](https://github.com/appfigures/string-replace-to-array/blob/master/changelog.md)
- [Commits](https://github.com/appfigures/string-replace-to-array/compare/v1.0.3...v2.1.0)

---
updated-dependencies:
- dependency-name: string-replace-to-array
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-10 17:31:31 -07:00
dependabot[bot] 8d92da1027 chore(deps-dev): bump @types/utf8 from 3.0.0 to 3.0.1 (#4253)
Bumps [@types/utf8](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/utf8) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/utf8)

---
updated-dependencies:
- dependency-name: "@types/utf8"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-10 17:30:40 -07:00
Tom Moor 5ee3f2a608 fix: Performance degredation when multiple tabs are open 2022-10-10 18:47:37 -04:00
Tom Moor 65e903582f memo 2022-10-10 08:57:36 -04:00
Tom Moor 2f2e367e91 fix: Bad functional refactor 2022-10-10 07:47:35 -04:00
Translate-O-Tron 73b604cd9d New Crowdin updates (#4215) 2022-10-09 05:56:17 -07:00
Tom Moor 804db1b0e4 Add CRDT backfill script 2022-10-08 18:25:49 -04:00
Tom Moor b1cd19df2f Improve error handling on cookie parsing, closes #4246 2022-10-08 10:31:21 -04:00
Tom Moor 051c79d651 Improved network debugging 2022-10-08 10:08:17 -04:00
pbkompasz c8f990018c Refactor DBDiagram class component to functional (#4228) 2022-10-08 06:50:08 -07:00
pbkompasz 013a134084 Refactor Bilibili class component to functional (#4227) 2022-10-08 06:48:24 -07:00
Chavda Bhavik 2938c4e18c Refactored Analytics component to functional component (#4247) 2022-10-08 06:47:24 -07:00
Tom Moor 0d6b3a9816 fix: Unable to connect slack on custom domains 2022-10-07 22:09:40 -04:00
Tom Moor 1a88cb9d08 fix: Upgrade popper, tippy to fix error (#4224)
* Upgrade popper, tippy to fix error

* tsc
2022-10-04 10:13:46 -07:00
pbkompasz db47b643be Refactor Airtable class component to functional (#4226) 2022-10-04 06:35:44 -07:00
Tom Moor 8417818528 test 2022-10-04 09:26:34 -04:00
Tom Moor 4e68d312e3 chore: Bump react-refresh-webpack-plugin 2022-10-03 21:39:48 -04:00
Tom Moor 125ddec60b Shortcircuit notification generation if there is no diff to render 2022-10-03 21:04:32 -04:00
dependabot[bot] dcae92ddfc chore(deps-dev): bump react-refresh from 0.9.0 to 0.14.0 (#4220)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-03 18:02:16 -07:00
pbkompasz 4df0d06eb2 Refractor Abstract class component to functional (#4216) 2022-10-03 06:15:37 -07:00
Tom Moor 55e622e22f chore: More rate limited endpoints 2022-10-02 19:27:21 -04:00
Translate-O-Tron a7683dda57 New Crowdin updates (#4166) 2022-10-02 16:06:10 -07:00
Kyriakos Georgiou 6871261139 feat(build): update postgres port in docker-compose.yml (#4211) 2022-10-02 15:51:47 -07:00
Tom Moor 933fbb2578 feat: Option for separate edit mode (#4203)
* stash

* wip

* cleanup

* Remove collaborativeEditing toggle, it will always be on in next release.
Flip separateEdit -> seamlessEdit

* Clarify language, hide toggle when collaborative editing is disabled

* Flip boolean to match, easier to reason about
2022-10-02 08:58:33 -07:00
Tom Moor b9bf2e58cb feat: Add cursor style user preference (#4199)
* feat: Add cursor style user preference

* Remove headings for now
2022-10-01 04:39:45 -07:00
vgwidt ee8c47eb3b fix: remove strikethrough text background (#4202)
* Update Styles.ts

* Update Styles.ts

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-10-01 04:39:33 -07:00
Tom Moor 4bb2a8ca1c tsc 2022-09-30 22:44:13 -04:00
Tom Moor 923afad032 Bump Sentry 2022-09-30 20:46:09 -04:00
Tom Moor ca4663f78a fix: Remove 'More options' on share popover when sharing disabled 2022-09-29 09:15:58 -04:00
Tom Moor 41da156b0e feat: Add view count to shared links in settings 2022-09-29 08:53:24 -04:00
Denis Olsem 492affb29a Share document link that opens full editor (#4134)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-09-29 04:49:35 -07:00
Pablo Yus 5b33aa6649 Enter in table cell adds a row after the current one (#4186)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-09-28 20:03:28 -07:00
Tom Moor 7c3ad09974 fix: Overlapping logo, closes #4188 2022-09-28 23:03:00 -04:00
Tom Moor 047b17b479 fix: Increase possible length of user and team avatar urls 2022-09-27 23:14:03 -04:00
dependabot[bot] 463a8c7ccd chore(deps): bump react-avatar-editor and @types/react-avatar-editor (#4180)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-27 06:30:48 -07:00
Tom Moor be17d6b4f9 Inline css in diff emails (#4181)
* Extract email styles into head

* tsc

* Inline CSS in emails
2022-09-26 18:46:55 -07:00
Tom Moor 6e25d1b6d4 fix: Remove events that are not sent from webhooks UI 2022-09-26 21:44:31 -04:00
dependabot[bot] 0f1b32e05a chore(deps-dev): bump @types/react-helmet from 6.1.4 to 6.1.5 (#4178)
Bumps [@types/react-helmet](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-helmet) from 6.1.4 to 6.1.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-helmet)

---
updated-dependencies:
- dependency-name: "@types/react-helmet"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-26 16:01:29 -07:00
dependabot[bot] 58f330f9ce chore(deps): bump json-loader from 0.5.4 to 0.5.7 (#4179)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-26 16:01:16 -07:00
Tom Moor dcf700072d Extract email styles into head (#4172)
* Extract email styles into head

* tsc
2022-09-26 06:43:38 -07:00
Tom Moor 89a133ea59 Add sameSite attribute for auth cookies 2022-09-24 21:46:25 -04:00
Tom Moor 61a8230b47 Merge branch 'main' of github.com:outline/outline 2022-09-24 17:29:31 -04:00
Tom Moor 91d8d27f2d feat: Render diffs in email notifications (#4164)
* deps

* diffCompact

* Diffs in email

* test

* fix: Fade deleted images
fix: Don't include empty paragraphs as context
fix: Allow for same image multiple times and refactor

* Remove target _blank

* fix: Table heading incorrect color
2022-09-24 14:29:11 -07:00
dependabot[bot] 0c5859222f chore(deps-dev): bump concurrently from 7.3.0 to 7.4.0 (#4111)
Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/open-cli-tools/concurrently/releases)
- [Commits](https://github.com/open-cli-tools/concurrently/compare/v7.3.0...v7.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-24 14:22:51 -07:00
dependabot[bot] 4171725697 chore(deps): bump query-string from 7.0.1 to 7.1.1 (#4110)
Bumps [query-string](https://github.com/sindresorhus/query-string) from 7.0.1 to 7.1.1.
- [Release notes](https://github.com/sindresorhus/query-string/releases)
- [Commits](https://github.com/sindresorhus/query-string/compare/v7.0.1...v7.1.1)

---
updated-dependencies:
- dependency-name: query-string
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-24 14:22:22 -07:00
dependabot[bot] 50353304cb chore(deps-dev): bump url-loader from 0.6.2 to 4.1.1 (#4113)
Bumps [url-loader](https://github.com/webpack-contrib/url-loader) from 0.6.2 to 4.1.1.
- [Release notes](https://github.com/webpack-contrib/url-loader/releases)
- [Changelog](https://github.com/webpack-contrib/url-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/url-loader/compare/v0.6.2...v4.1.1)

---
updated-dependencies:
- dependency-name: url-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-24 14:22:08 -07:00
Apoorv Mishra 7a590550c9 Sign webhook requests (#4156)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2022-09-24 14:19:26 -07:00
Translate-O-Tron 75fb0826c5 New Crowdin updates (#4125) 2022-09-24 12:36:43 -07:00
Tom Moor 1ac33a9466 Small tweak to menu backdrop on mobile 2022-09-22 22:55:43 -04:00
Tom Moor 996a11f5e3 fix: Incorrect integration settings for Draw.IO being used 2022-09-22 22:49:37 -04:00
Tom Moor 39e1f43598 fix: Allow expanding current selection in tables, closes #4128 2022-09-22 21:55:57 -04:00
Tom Moor 0232f3ee98 fix: Don't show colored ring on inactive collaborators, closes #4130 2022-09-22 21:49:03 -04:00
Tom Moor 7da4b50f4f Allow click on link decoration to open, closes #4076 2022-09-22 21:14:07 -04:00
Tom Moor da62307b43 fix: Embeds should not trigger when pasting urls in code, closes #4138 2022-09-22 21:02:45 -04:00
Tom Moor 6455b5332d feat: Cmd+Enter opens selected link, closes #4151 2022-09-22 20:53:25 -04:00
Tom Moor 61154ba618 fix: Scroll to header does not work when header contains Chinese characters 2022-09-22 09:59:31 -04:00
Tom Moor 4f40c64101 fix: Share links containing share links can lead to 'Not found' pages 2022-09-22 09:11:45 -04:00
Tom Moor 62b4f520de fix: Do not forward to a disabled authentication provider when attempting to sign-in with email 2022-09-22 07:27:03 -04:00
Tom Moor d825ed957d tsc 2022-09-21 10:44:58 -04:00
Tom Moor cfabc2e8a0 test 2022-09-21 09:39:39 -04:00
Tom Moor 98e44f528f 0.66.1 2022-09-21 09:05:39 -04:00
Tom Moor 0e79795856 fix: Cannot download export result, closes #4059 2022-09-21 09:05:26 -04:00
Tom Moor 4f9a99c9b4 test 2022-09-18 18:09:28 -04:00
Tom Moor f8912732b8 chore: Flag users with platform used 2022-09-18 17:53:55 -04:00
Tom Moor ae697339ac fix: Remove restriction on team domains for self-hosted installs 2022-09-18 17:16:50 -04:00
Tom Moor d16a0365d7 chore: Move language and account delete from Profile -> Preferences 2022-09-18 16:43:18 -04:00
Apoorv Mishra 6502b108e3 Introduce account preferences to remember user's previous location (#4126) 2022-09-18 06:01:47 -07:00
Tom Moor b68e58fad5 Improve keyboard navigation on sidebar tree items 2022-09-17 19:47:16 -04:00
Tom Moor 58c1a83ef0 chore: Bump dnd-kit 2022-09-17 19:23:42 -04:00
Tom Moor f8895dacda fix: Don't redirect to document after dragging pin 2022-09-17 19:23:25 -04:00
Tom Moor 15505cf951 fix double border on document card curl 2022-09-17 21:27:23 +01:00
Tom Moor dccf86c491 fix: Hide membership preview on mobile 2022-09-16 07:25:19 +01:00
Tom Moor a74635a37f fix: Improved breakpoints for pins on mobile
fix: Prevent clock icon shrinking
fix: Prevent metadata wrapping
2022-09-15 23:04:43 +01:00
Tom Moor 410c9900c1 feat: Updated designs for pinned docs (#4124)
* Updated designs

* css
2022-09-15 00:51:51 -07:00
Translate-O-Tron 03a496929c New Crowdin updates (#4057) 2022-09-14 15:52:27 -07:00
Tom Moor c6e11bac71 feat: Add Dutch translations to language selector (#4120)
noramlize wording and order of available languages
2022-09-14 15:52:08 -07:00
Apoorv Mishra ce410c4bf3 Support user and team preferences (#4081)
* feat: support user preferences

* feat: support team preferences

* fix: update snapshots

* feat: update last visited url by user

* fix: update snapshots

* fix: use path instead of complete url

* fix: do not expose preferences to other users with the exception of admin

* feat: support defaultDocumentStatus as a team preference

* feat: allow edit even when collaborative editing is enabled

* Revert "feat: allow edit even when collaborative editing is enabled"

This reverts commit a22a02a406d01eb418dab32249b8b846bf77c59b.

* Revert "feat: support defaultDocumentStatus as a team preference"

This reverts commit 4928cffe5c682952b1e469a3e50a1a34d05dcc58.

* fix: keep preference as a boolean
2022-09-14 16:07:39 +05:30
Tom Moor 607a795dd0 tsc 2022-09-14 11:04:38 +01:00
Tom Moor e1e7f1b97d fix: Include the maximum document import size in the error message 2022-09-14 09:20:17 +01:00
Tom Moor 6bb1b1ac1d fix: Include the maximum document import size in the error message 2022-09-13 09:09:04 +01:00
Tom Moor 7d92b60e97 feat: Improve translations, fade inactive collection members 2022-09-13 08:47:41 +01:00
Tom Moor 6502aff4ef fix: Toggling history sidebar should not push into history stack 2022-09-13 00:34:00 +01:00
Tom Moor 34fd039b6c Add 'Open command menu' to keyboard shortcuts 2022-09-13 00:30:15 +01:00
Tom Moor 5e2e8afd92 Update home icon 2022-09-13 00:28:37 +01:00
Tom Moor edd7aed7b2 fix: Line breaks inside of imported HTML image src fail import 2022-09-12 23:08:59 +01:00
Tom Moor fe3ff1215e Make submenus dismissable on mobile, alternative solution closes #3948 2022-09-12 10:12:42 +01:00
Tom Moor abb03cc113 fix: Consistent capitalization 2022-09-12 09:37:01 +01:00
Tom Moor 9f17b4a545 fix: Spelling on collection export modal 2022-09-12 09:37:01 +01:00
vgwidt ad3e880491 fix: Dialog doesn't close after deleting a document with a parent (#4108) 2022-09-12 01:26:09 -07:00
Tom Moor 15877fbb39 Update NudeButton.tsx 2022-09-11 14:54:26 -07:00
Tom Moor a3907918e4 fix: CMD+F twice should allow page search
closes #4105
2022-09-11 15:48:45 +01:00
Tom Moor afc7fb5f1d fixes #4104 2022-09-11 15:27:19 +01:00
Tom Moor 0587968f8b perf: More selective resource pre-fetching 2022-09-11 15:14:03 +01:00
Tom Moor 2c5b18c76b fix: Avoid requesting recent searches until command bar is opened 2022-09-11 15:06:28 +01:00
Tom Moor 6877312b7a fix: integrations.list requested more than once 2022-09-11 15:03:25 +01:00
Tom Moor ec13220881 Simplify heading 2022-09-11 14:41:56 +01:00
Tom Moor c89567991b fix: Unsure filename when downloading an untitled document
fix: Unsure unique filename when downloading revision
2022-09-11 14:32:38 +01:00
Tom Moor 0fd576cdd5 feat: Updated collection header (#4101)
* Return total results from collection membership endpoints

* Display membership preview on collections

* fix permissions

* Revert unneccessary changes
2022-09-11 05:54:57 -07:00
Tom Moor 3aa7f34a73 fix: Regression in new docs starting with 'Untitled' 2022-09-10 23:32:30 +01:00
Tom Moor 1f93399447 feat: Add availableTeams to auth.info endpoint (#3981)
* Index emails migration

* feat: Add available teams to auth.info endpoint

* test

* separate presenter

* Include data from sessions cookie, include likely logged in state

* test

* test: Add test for team only in session cookie

* Suggested query change in PR feedback
2022-09-10 06:58:38 -07:00
Tom Moor c10be0ebaa fix: Missing spacing on document history loading state 2022-09-10 14:41:50 +01:00
dependabot[bot] 9ebc69a830 chore(deps): bump @renderlesskit/react from 0.6.0 to 0.11.0 (#4065)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-09 16:44:40 -07:00
dependabot[bot] a8b8953f4b chore(deps): bump @babel/plugin-proposal-decorators (#3945)
Bumps [@babel/plugin-proposal-decorators](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-proposal-decorators) from 7.12.1 to 7.18.10.
- [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.18.10/packages/babel-plugin-proposal-decorators)

---
updated-dependencies:
- dependency-name: "@babel/plugin-proposal-decorators"
  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>
2022-09-09 16:44:22 -07:00
dependabot[bot] 3a55ba4fd7 chore(deps-dev): bump webpack-cli from 3.3.12 to 4.10.0 (#3941)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-09 16:39:26 -07:00
dependabot[bot] c0b4b4ab75 chore(deps-dev): bump eslint from 7.13.0 to 7.32.0 (#3944)
Bumps [eslint](https://github.com/eslint/eslint) from 7.13.0 to 7.32.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.13.0...v7.32.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-09 16:29:52 -07:00
Tom Moor 8a0c46adeb fix: Collections not loaded if sidebar item collapsed
closes #4073
2022-09-09 23:54:22 +01:00
Tom Moor 04aad08e78 fix: Spacing around empty history state 2022-09-09 23:11:55 +01:00
Tom Moor 6f11bff91e fix: Strange sidebar animation when history open and switching between docs
closes #4091
2022-09-09 23:00:56 +01:00
Tom Moor c963abeb8b fix: Missing cascade constraints on notifications table (#4096) 2022-09-09 14:31:38 -07:00
Tom Moor 35ea1cdff8 fix: Missing recipient.user, closes #4093 2022-09-09 22:30:31 +01:00
Tom Moor 12bb97ea99 fix: Server error viewing history with emoji in document, closes #4092 2022-09-09 22:29:42 +01:00
Tom Moor 876803362f fix: Server error when code is passed as null to users.delete, closes #4070 2022-09-09 22:10:32 +01:00
Tom Moor 54dc0521e5 fix: Missing recipient.user, closes #4093 2022-09-09 22:05:28 +01:00
Tom Moor b44aa62432 Bump prosemirror-commands 2022-09-09 09:41:57 +01:00
Tom Moor c2876ca396 fix: Retain scroll position when navigating through document history
closes #4087
2022-09-08 13:01:01 +01:00
Tom Moor 810ef2134a fix: Escape key to exit history view 2022-09-08 12:04:09 +01:00
Tom Moor e0c74483d1 fix: Alignment of skeleton on document history sidebar 2022-09-08 12:02:33 +01:00
Tom Moor fa75d5585f feat: Show diff when navigating revision history (#4069)
* tidy

* Add title to HTML export

* fix: Add compatability for documents without collab state

* Add HTML download option to UI

* docs

* fix nodes that required document to render

* Refactor to allow for styling of HTML export

* div>article for easier programatic content extraction

* Allow DocumentHelper to be used with Revisions

* Add revisions.diff endpoint, first version

* Allow arbitrary revisions to be compared

* test

* HTML driven revision viewer

* fix: Dark mode styles for document diffs

* Add revision restore button to header

* test

* Support RTL languages in revision history viewer

* fix: RTL support
Remove unneccessary API requests

* Prefetch revision data

* Animate history sidebar

* fix: Cannot toggle history from timestamp
fix: Animation on each revision click

* Clarify currently editing history item
2022-09-08 02:17:52 -07:00
Apoorv Mishra 97f70edd93 Permanently redirect to /s/... for share links (#4067) 2022-09-08 00:44:25 -07:00
Tom Moor c36dcc9712 feat: Open random document in command menu 2022-09-07 22:45:37 +01:00
Tom Moor e8a6de3f18 feat: Add HTML export option (#4056)
* tidy

* Add title to HTML export

* fix: Add compatability for documents without collab state

* Add HTML download option to UI

* docs

* fix nodes that required document to render

* Refactor to allow for styling of HTML export

* div>article for easier programatic content extraction
2022-09-07 04:34:39 -07:00
Fawzi E. Abdulfattah eb5126335c Improving the urls to not break protocols and adding tests (#3995)
* Improving the urls utils to not break dynamic protocols and testing the utils

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Adding a list of blocked protocols

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Update the way of sanitizing blocked protocols

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Update shared/utils/urls.ts

Javascript pseudo protocol does not require the //

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* updating the javascript protocol sanitizing tests

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Update shared/utils/urls.test.ts

Co-authored-by: Apoorv Mishra <apoorvmishra101092@gmail.com>

* Update shared/utils/urls.ts

Co-authored-by: Apoorv Mishra <apoorvmishra101092@gmail.com>

* Using toBe instead of toEqual in tests

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Sanitizing data: and vbscript:

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Using toBeUndefined instead of toEqual in tests

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Using URL to check the protocols

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Allowing sms, fax, and tel protocols

Signed-off-by: iifawzi <iifawzie@gmail.com>

* Update shared/utils/urls.ts

inlining the protocols in the same file

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* removing unused protocols constant

Signed-off-by: iifawzi <iifawzie@gmail.com>

Signed-off-by: iifawzi <iifawzie@gmail.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
Co-authored-by: Apoorv Mishra <apoorvmishra101092@gmail.com>
2022-09-07 16:51:56 +05:30
Apoorv Mishra 1e39b564fe Throttle email notifications upon updating document frequently (#4026)
* feat: add needed columns for throttling notifs

* feat: update model

* feat: deliver only one notif in a 12 hour window

* fix: address review comments

* prevent retry if notification update fails
* fix type compatibility instead of circumventing it
* add index for emailedAt

* fix: add metadata attr to EmailProps

* chore: decouple metadata from EmailProps

* chore: add test

* chore: revert sending metadata in props
2022-09-07 16:51:30 +05:30
Tom Moor e4023d87e2 fix: Animation of InputSelect is janky (#4061) 2022-09-06 01:17:52 -07:00
Tom Moor 2d39a6f0ab Update SERVICES.md 2022-09-05 12:51:30 -07:00
Tom Moor 34b586724b fix: Cannot download export result, closes #4059 2022-09-05 10:22:17 +02:00
Tom Moor 09b2d0babe 0.66.0 2022-09-05 00:07:38 +02:00
720 changed files with 33084 additions and 17178 deletions
+7 -7
View File
@@ -21,7 +21,7 @@ DATABASE_CONNECTION_POOL_MAX=
# For redis you can either specify an ioredis compatible url like this
REDIS_URL=redis://localhost:6379
# or alternatively, if you would like to provide addtional connection options,
# or alternatively, if you would like to provide additional connection options,
# use a base64 encoded JSON connection option object. Refer to the ioredis documentation
# for a list of available options.
# Example: Use Redis Sentinel for high availability
@@ -38,7 +38,7 @@ PORT=3000
COLLABORATION_URL=
# To support uploading of images for avatars and document attachments an
# s3-compatible storage must be provided. AWS S3 is recommended for redundency
# s3-compatible storage must be provided. AWS S3 is recommended for redundancy
# however if you want to keep all file storage local an alternative such as
# minio (https://github.com/minio/minio) can be used.
@@ -131,7 +131,7 @@ ENABLE_UPDATES=true
# available memory by 512 for a rough estimate
WEB_CONCURRENCY=1
# Override the maxium size of document imports, could be required if you have
# Override the maximum size of document imports, could be required if you have
# especially large Word documents with embedded imagery
MAXIMUM_IMPORT_SIZE=5120000
@@ -150,8 +150,11 @@ SLACK_MESSAGE_ACTIONS=true
# Optionally enable google analytics to track pageviews in the knowledge base
GOOGLE_ANALYTICS_ID=
# Optionally enable Sentry (sentry.io) to track errors and performance
# Optionally enable Sentry (sentry.io) to track errors and performance,
# and optionally add a Sentry proxy tunnel for bypassing ad blockers in the UI:
# https://docs.sentry.io/platforms/javascript/troubleshooting/#using-the-tunnel-option)
SENTRY_DSN=
SENTRY_TUNNEL=
# To support sending outgoing transactional emails such as "document updated" or
# "you've been invited" you'll need to provide authentication for an SMTP server
@@ -164,9 +167,6 @@ SMTP_REPLY_EMAIL=
SMTP_TLS_CIPHERS=
SMTP_SECURE=true
# Custom logo that displays on the authentication screen, scaled to height: 60px
# TEAM_LOGO=https://example.com/images/logo.png
# The default interface language. See translate.getoutline.com for a list of
# available language codes and their rough percentage translated.
DEFAULT_LANGUAGE=en_US
@@ -24,8 +24,13 @@ on:
workflow_dispatch:
schedule:
- cron: "00 20 * * 0"
permissions: {}
jobs:
build:
permissions:
contents: write
pull-requests: write # to comment on pull request
name: calibreapp/image-actions
runs-on: ubuntu-latest
# Only run on main repo on and PRs that match the main repo.
+1 -1
View File
@@ -11,4 +11,4 @@ fakes3/*
.idea
*.pem
*.key
*.cert
*.cert
+2 -2
View File
@@ -195,8 +195,8 @@
"description": "An API key for Sentry if you wish to collect error reporting (optional)",
"required": false
},
"TEAM_LOGO": {
"description": "A logo that will be displayed on the signed out home page",
"SENTRY_TUNNEL": {
"description": "A sentry tunnel URL for bypassing ad blockers in the UI (optional)",
"required": false
},
"DEFAULT_LANGUAGE": {
+25 -2
View File
@@ -1,6 +1,7 @@
import {
CollectionIcon,
EditIcon,
PadlockIcon,
PlusIcon,
StarredIcon,
UnstarredIcon,
@@ -10,7 +11,8 @@ import stores from "~/stores";
import Collection from "~/models/Collection";
import CollectionEdit from "~/scenes/CollectionEdit";
import CollectionNew from "~/scenes/CollectionNew";
import DynamicCollectionIcon from "~/components/CollectionIcon";
import CollectionPermissions from "~/scenes/CollectionPermissions";
import DynamicCollectionIcon from "~/components/Icons/CollectionIcon";
import { createAction } from "~/actions";
import { CollectionSection } from "~/actions/sections";
import history from "~/utils/history";
@@ -56,7 +58,8 @@ export const createCollection = createAction({
});
export const editCollection = createAction({
name: ({ t }) => t("Edit collection"),
name: ({ t, isContextMenu }) =>
isContextMenu ? `${t("Edit")}` : t("Edit collection"),
section: CollectionSection,
icon: <EditIcon />,
visible: ({ stores, activeCollectionId }) =>
@@ -79,6 +82,26 @@ export const editCollection = createAction({
},
});
export const editCollectionPermissions = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? `${t("Permissions")}` : t("Collection permissions"),
section: CollectionSection,
icon: <PadlockIcon />,
visible: ({ stores, activeCollectionId }) =>
!!activeCollectionId &&
stores.policies.abilities(activeCollectionId).update,
perform: ({ t, activeCollectionId }) => {
if (!activeCollectionId) {
return;
}
stores.dialogs.openModal({
title: t("Collection permissions"),
content: <CollectionPermissions collectionId={activeCollectionId} />,
});
},
});
export const starCollection = createAction({
name: ({ t }) => t("Star"),
section: CollectionSection,
+206 -7
View File
@@ -17,8 +17,14 @@ import {
TrashIcon,
CrossIcon,
ArchiveIcon,
ShuffleIcon,
HistoryIcon,
LightBulbIcon,
UnpublishIcon,
PublishIcon,
} from "outline-icons";
import * as React from "react";
import { ExportContentType } from "@shared/types";
import { getEventFiles } from "@shared/utils/files";
import DocumentDelete from "~/scenes/DocumentDelete";
import DocumentMove from "~/scenes/DocumentMove";
@@ -26,8 +32,15 @@ import DocumentPermanentDelete from "~/scenes/DocumentPermanentDelete";
import DocumentTemplatizeDialog from "~/components/DocumentTemplatizeDialog";
import { createAction } from "~/actions";
import { DocumentSection } from "~/actions/sections";
import env from "~/env";
import history from "~/utils/history";
import { homePath, newDocumentPath, searchPath } from "~/utils/routeHelpers";
import {
documentInsightsUrl,
documentHistoryUrl,
homePath,
newDocumentPath,
searchPath,
} from "~/utils/routeHelpers";
export const openDocument = createAction({
name: ({ t }) => t("Open document"),
@@ -117,6 +130,61 @@ export const unstarDocument = createAction({
},
});
export const publishDocument = createAction({
name: ({ t }) => t("Publish"),
section: DocumentSection,
icon: <PublishIcon />,
visible: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return false;
}
const document = stores.documents.get(activeDocumentId);
return (
!!document?.isDraft && stores.policies.abilities(activeDocumentId).update
);
},
perform: ({ activeDocumentId, stores, t }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.save({
publish: true,
});
stores.toasts.showToast(t("Document published"), {
type: "success",
});
},
});
export const unpublishDocument = createAction({
name: ({ t }) => t("Unpublish"),
section: DocumentSection,
icon: <UnpublishIcon />,
visible: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return false;
}
return stores.policies.abilities(activeDocumentId).unpublish;
},
perform: ({ activeDocumentId, stores, t }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.unpublish();
stores.toasts.showToast(t("Document unpublished"), {
type: "success",
});
},
});
export const subscribeDocument = createAction({
name: ({ t }) => t("Subscribe"),
section: DocumentSection,
@@ -179,12 +247,12 @@ export const unsubscribeDocument = createAction({
},
});
export const downloadDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Download") : t("Download document"),
export const downloadDocumentAsHTML = createAction({
name: ({ t }) => t("HTML"),
section: DocumentSection,
keywords: "html export",
icon: <DownloadIcon />,
keywords: "export",
iconInContextMenu: false,
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).download,
perform: ({ activeDocumentId, stores }) => {
@@ -193,10 +261,68 @@ export const downloadDocument = createAction({
}
const document = stores.documents.get(activeDocumentId);
document?.download();
document?.download(ExportContentType.Html);
},
});
export const downloadDocumentAsPDF = createAction({
name: ({ t }) => t("PDF"),
section: DocumentSection,
keywords: "export",
icon: <DownloadIcon />,
iconInContextMenu: false,
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId &&
stores.policies.abilities(activeDocumentId).download &&
env.PDF_EXPORT_ENABLED,
perform: ({ activeDocumentId, t, stores }) => {
if (!activeDocumentId) {
return;
}
const id = stores.toasts.showToast(`${t("Exporting")}`, {
type: "loading",
timeout: 30 * 1000,
});
const document = stores.documents.get(activeDocumentId);
document
?.download(ExportContentType.Pdf)
.finally(() => id && stores.toasts.hideToast(id));
},
});
export const downloadDocumentAsMarkdown = createAction({
name: ({ t }) => t("Markdown"),
section: DocumentSection,
keywords: "md markdown export",
icon: <DownloadIcon />,
iconInContextMenu: false,
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).download,
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
document?.download(ExportContentType.Markdown);
},
});
export const downloadDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Download") : t("Download document"),
section: DocumentSection,
icon: <DownloadIcon />,
keywords: "export",
children: [
downloadDocumentAsHTML,
downloadDocumentAsPDF,
downloadDocumentAsMarkdown,
],
});
export const duplicateDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Duplicate") : t("Duplicate document"),
@@ -226,7 +352,17 @@ export const duplicateDocument = createAction({
* of the collection for all collection members to see.
*/
export const pinDocumentToCollection = createAction({
name: ({ t }) => t("Pin to collection"),
name: ({ activeDocumentId = "", t, stores }) => {
const selectedDocument = stores.documents.get(activeDocumentId);
const collectionName = selectedDocument
? stores.documents.getCollectionForDocument(selectedDocument)?.name
: t("collection");
return t("Pin to {{collectionName}}", {
collectionName,
});
},
section: DocumentSection,
icon: <PinIcon />,
iconInContextMenu: false,
@@ -389,6 +525,24 @@ export const createTemplate = createAction({
},
});
export const openRandomDocument = createAction({
id: "random",
section: DocumentSection,
name: ({ t }) => t(`Open random document`),
icon: <ShuffleIcon />,
perform: ({ stores, activeDocumentId }) => {
const documentPaths = stores.collections.pathsToDocuments.filter(
(path) => path.type === "document" && path.id !== activeDocumentId
);
const documentPath =
documentPaths[Math.round(Math.random() * documentPaths.length)];
if (documentPath) {
history.push(documentPath.url);
}
},
});
export const searchDocumentsForQuery = (searchQuery: string) =>
createAction({
id: "search",
@@ -525,6 +679,46 @@ export const permanentlyDeleteDocument = createAction({
},
});
export const openDocumentHistory = createAction({
name: ({ t }) => t("History"),
section: DocumentSection,
icon: <HistoryIcon />,
visible: ({ activeDocumentId, stores }) => {
const can = stores.policies.abilities(activeDocumentId ?? "");
return !!activeDocumentId && can.read && !can.restore;
},
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
if (!document) {
return;
}
history.push(documentHistoryUrl(document));
},
});
export const openDocumentInsights = createAction({
name: ({ t }) => t("Insights"),
section: DocumentSection,
icon: <LightBulbIcon />,
visible: ({ activeDocumentId, stores }) => {
const can = stores.policies.abilities(activeDocumentId ?? "");
return !!activeDocumentId && can.read;
},
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
if (!document) {
return;
}
history.push(documentInsightsUrl(document));
},
});
export const rootDocumentActions = [
openDocument,
archiveDocument,
@@ -535,12 +729,17 @@ export const rootDocumentActions = [
downloadDocument,
starDocument,
unstarDocument,
publishDocument,
unpublishDocument,
subscribeDocument,
unsubscribeDocument,
duplicateDocument,
moveDocument,
openRandomDocument,
permanentlyDeleteDocument,
printDocument,
pinDocumentToCollection,
pinDocumentToHome,
openDocumentHistory,
openDocumentInsights,
];
+28
View File
@@ -11,6 +11,7 @@ import {
EmailIcon,
LogoutIcon,
ProfileIcon,
BrowserIcon,
} from "outline-icons";
import * as React from "react";
import {
@@ -24,10 +25,14 @@ import SearchQuery from "~/models/SearchQuery";
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 {
organizationSettingsPath,
profileSettingsPath,
accountPreferencesPath,
homePath,
searchPath,
draftsPath,
@@ -104,6 +109,14 @@ export const navigateToProfileSettings = createAction({
perform: () => history.push(profileSettingsPath()),
});
export const navigateToAccountPreferences = createAction({
name: ({ t }) => t("Preferences"),
section: NavigationSection,
iconInContextMenu: false,
icon: <SettingsIcon />,
perform: () => history.push(accountPreferencesPath()),
});
export const openAPIDocumentation = createAction({
name: ({ t }) => t("API documentation"),
section: NavigationSection,
@@ -148,6 +161,20 @@ export const openKeyboardShortcuts = createAction({
},
});
export const downloadApp = createAction({
name: ({ t }) =>
t("Download {{ platform }} app", {
platform: isMac() ? "macOS" : "Windows",
}),
section: NavigationSection,
iconInContextMenu: false,
icon: <BrowserIcon />,
visible: () => !Desktop.isElectron() && isMac() && isCloudHosted,
perform: () => {
window.open("https://desktop.getoutline.com");
},
});
export const logout = createAction({
name: ({ t }) => t("Log out"),
section: NavigationSection,
@@ -161,6 +188,7 @@ export const rootNavigationActions = [
navigateToTemplates,
navigateToArchive,
navigateToTrash,
downloadApp,
openAPIDocumentation,
openFeedbackUrl,
openBugReportUrl,
+85
View File
@@ -0,0 +1,85 @@
import copy from "copy-to-clipboard";
import { LinkIcon, RestoreIcon } from "outline-icons";
import * as React from "react";
import { matchPath } from "react-router-dom";
import stores from "~/stores";
import { createAction } from "~/actions";
import { RevisionSection } from "~/actions/sections";
import history from "~/utils/history";
import { documentHistoryUrl, matchDocumentHistory } from "~/utils/routeHelpers";
export const restoreRevision = createAction({
name: ({ t }) => t("Restore revision"),
icon: <RestoreIcon />,
section: RevisionSection,
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).update,
perform: async ({ t, event, location, activeDocumentId }) => {
event?.preventDefault();
if (!activeDocumentId) {
return;
}
const match = matchPath<{ revisionId: string }>(location.pathname, {
path: matchDocumentHistory,
});
const revisionId = match?.params.revisionId;
const { team } = stores.auth;
const document = stores.documents.get(activeDocumentId);
if (!document) {
return;
}
if (team?.collaborativeEditing) {
history.push(document.url, {
restore: true,
revisionId,
});
} else {
await document.restore({
revisionId,
});
stores.toasts.showToast(t("Document restored"), {
type: "success",
});
history.push(document.url);
}
},
});
export const copyLinkToRevision = createAction({
name: ({ t }) => t("Copy link"),
icon: <LinkIcon />,
section: RevisionSection,
perform: async ({ activeDocumentId, stores, t }) => {
if (!activeDocumentId) {
return;
}
const match = matchPath<{ revisionId: string }>(location.pathname, {
path: matchDocumentHistory,
});
const revisionId = match?.params.revisionId;
const document = stores.documents.get(activeDocumentId);
if (!document) {
return;
}
const url = `${window.location.origin}${documentHistoryUrl(
document,
revisionId
)}`;
copy(url, {
format: "text/plain",
onCopy: () => {
stores.toasts.showToast(t("Link copied"), {
type: "info",
});
},
});
},
});
export const rootRevisionActions = [];
+61 -28
View File
@@ -1,40 +1,73 @@
import { PlusIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import { stringToColor } from "@shared/utils/color";
import RootStore from "~/stores/RootStore";
import TeamNew from "~/scenes/TeamNew";
import TeamLogo from "~/components/TeamLogo";
import { createAction } from "~/actions";
import { loadSessionsFromCookie } from "~/hooks/useSessions";
import { ActionContext } from "~/types";
import { TeamSection } from "../sections";
export const changeTeam = createAction({
name: ({ t }) => t("Switch team"),
placeholder: ({ t }) => t("Select a team"),
keywords: "change workspace organization",
section: "Account",
visible: ({ currentTeamId }) => {
const sessions = loadSessionsFromCookie();
const otherSessions = sessions.filter(
(session) => session.teamId !== currentTeamId
);
return otherSessions.length > 0;
},
children: ({ currentTeamId }) => {
const sessions = loadSessionsFromCookie();
const otherSessions = sessions.filter(
(session) => session.teamId !== currentTeamId
);
return otherSessions.map((session) => ({
id: session.url,
export const createTeamsList = ({ stores }: { stores: RootStore }) => {
return (
stores.auth.availableTeams?.map((session) => ({
id: `switch-${session.id}`,
name: session.name,
section: "Account",
icon: <Logo alt={session.name} src={session.logoUrl} />,
section: TeamSection,
keywords: "change switch workspace organization team",
icon: () => (
<StyledTeamLogo
alt={session.name}
model={{
initial: session.name[0],
avatarUrl: session.avatarUrl,
id: session.id,
color: stringToColor(session.id),
}}
size={24}
/>
),
visible: ({ currentTeamId }: ActionContext) =>
currentTeamId !== session.id,
perform: () => (window.location.href = session.url),
}));
})) ?? []
);
};
export const switchTeam = createAction({
name: ({ t }) => t("Switch workspace"),
placeholder: ({ t }) => t("Select a workspace"),
keywords: "change switch workspace organization team",
section: TeamSection,
visible: ({ stores }) =>
!!stores.auth.availableTeams && stores.auth.availableTeams?.length > 1,
children: createTeamsList,
});
export const createTeam = createAction({
name: ({ t }) => `${t("New workspace")}`,
keywords: "create change switch workspace organization team",
section: TeamSection,
icon: <PlusIcon />,
visible: ({ stores, currentTeamId }) => {
return stores.policies.abilities(currentTeamId ?? "").createTeam;
},
perform: ({ t, event, stores }) => {
event?.preventDefault();
event?.stopPropagation();
const { user } = stores.auth;
user &&
stores.dialogs.openModal({
title: t("Create a workspace"),
content: <TeamNew user={user} />,
});
},
});
const Logo = styled("img")`
const StyledTeamLogo = styled(TeamLogo)`
border-radius: 2px;
width: 24px;
height: 24px;
border: 0;
`;
export const rootTeamActions = [changeTeam];
export const rootTeamActions = [switchTeam, createTeam];
+1 -1
View File
@@ -8,7 +8,7 @@ import { UserSection } from "~/actions/sections";
export const inviteUser = createAction({
name: ({ t }) => `${t("Invite people")}`,
icon: <PlusIcon />,
keywords: "team member user",
keywords: "team member workspace user",
section: UserSection,
visible: ({ stores }) =>
stores.policies.abilities(stores.auth.team?.id || "").inviteUser,
+11 -3
View File
@@ -57,8 +57,16 @@ export function actionToMenuItem(
icon,
visible,
dangerous: action.dangerous,
onClick: () => action.perform && action.perform(context),
selected: action.selected ? action.selected(context) : undefined,
onClick: () => {
try {
action.perform?.(context);
} catch (err) {
context.stores.toasts.showToast(err.message, {
type: "error",
});
}
},
selected: action.selected?.(context),
};
}
@@ -70,7 +78,7 @@ export function actionToKBar(
return [];
}
const resolvedIcon = resolve<React.ReactElement<any>>(action.icon, context);
const resolvedIcon = resolve<React.ReactElement>(action.icon, context);
const resolvedChildren = resolve<Action[]>(action.children, context);
const resolvedSection = resolve<string>(action.section, context);
const resolvedName = resolve<string>(action.name, context);
+2
View File
@@ -2,6 +2,7 @@ import { rootCollectionActions } from "./definitions/collections";
import { rootDeveloperActions } from "./definitions/developer";
import { rootDocumentActions } from "./definitions/documents";
import { rootNavigationActions } from "./definitions/navigation";
import { rootRevisionActions } from "./definitions/revisions";
import { rootSettingsActions } from "./definitions/settings";
import { rootTeamActions } from "./definitions/teams";
import { rootUserActions } from "./definitions/users";
@@ -11,6 +12,7 @@ export default [
...rootDocumentActions,
...rootUserActions,
...rootNavigationActions,
...rootRevisionActions,
...rootSettingsActions,
...rootDeveloperActions,
...rootTeamActions,
+4
View File
@@ -6,11 +6,15 @@ export const DeveloperSection = ({ t }: ActionContext) => t("Debug");
export const DocumentSection = ({ t }: ActionContext) => t("Document");
export const RevisionSection = ({ t }: ActionContext) => t("Revision");
export const SettingsSection = ({ t }: ActionContext) => t("Settings");
export const NavigationSection = ({ t }: ActionContext) => t("Navigation");
export const UserSection = ({ t }: ActionContext) => t("People");
export const TeamSection = ({ t }: ActionContext) => t("Workspace");
export const RecentSearchesSection = ({ t }: ActionContext) =>
t("Recent searches");
+2 -8
View File
@@ -2,7 +2,7 @@ import * as React from "react";
import Tooltip, { Props as TooltipProps } from "~/components/Tooltip";
import { Action, ActionContext } from "~/types";
export type Props = {
export type Props = React.ComponentPropsWithoutRef<"button"> & {
/** Show the button in a disabled state */
disabled?: boolean;
/** Hide the button entirely if action is not applicable */
@@ -20,13 +20,7 @@ export type Props = {
*/
const ActionButton = React.forwardRef(
(
{
action,
context,
tooltip,
hideOnActionDisabled,
...rest
}: Props & React.HTMLAttributes<HTMLButtonElement>,
{ action, context, tooltip, hideOnActionDisabled, ...rest }: Props,
ref: React.Ref<HTMLButtonElement>
) => {
const disabled = rest.disabled;
-41
View File
@@ -1,41 +0,0 @@
/* global ga */
import * as React from "react";
import env from "~/env";
export default class Analytics extends React.Component {
componentDidMount() {
if (!env.GOOGLE_ANALYTICS_ID) {
return;
}
// standard Google Analytics script
window.ga =
window.ga ||
function (...args) {
(ga.q = ga.q || []).push(args);
};
ga.l = +new Date();
ga("create", env.GOOGLE_ANALYTICS_ID, "auto");
ga("set", {
dimension1: "true",
});
ga("send", "pageview");
const script = document.createElement("script");
script.src = "https://www.google-analytics.com/analytics.js";
script.async = true;
// Track PWA install event
window.addEventListener("appinstalled", () => {
ga("send", "event", "pwa", "install");
});
if (document.body) {
document.body.appendChild(script);
}
}
render() {
return this.props.children || null;
}
}
+66
View File
@@ -0,0 +1,66 @@
/* eslint-disable prefer-rest-params */
/* global ga */
import { escape } from "lodash";
import * as React from "react";
import { IntegrationService } from "@shared/types";
import env from "~/env";
const Analytics: React.FC = ({ children }) => {
// Google Analytics 3
React.useEffect(() => {
if (!env.GOOGLE_ANALYTICS_ID) {
return;
}
// standard Google Analytics script
window.ga =
window.ga ||
function (...args) {
(ga.q = ga.q || []).push(args);
};
ga.l = +new Date();
ga("create", env.GOOGLE_ANALYTICS_ID, "auto");
ga("send", "pageview");
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://www.google-analytics.com/analytics.js";
script.async = true;
// Track PWA install event
window.addEventListener("appinstalled", () => {
ga("send", "event", "pwa", "install");
});
document.getElementsByTagName("head")[0]?.appendChild(script);
}, []);
// Google Analytics 4
React.useEffect(() => {
if (env.analytics.service !== IntegrationService.GoogleAnalytics) {
return;
}
const measurementId = escape(env.analytics.settings?.measurementId);
window.dataLayer = window.dataLayer || [];
window.gtag = function () {
window.dataLayer.push(arguments);
};
window.gtag("js", new Date());
window.gtag("config", measurementId, {
allow_google_signals: false,
restricted_data_processing: true,
});
const script = document.createElement("script");
script.type = "text/javascript";
script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
script.async = true;
document.getElementsByTagName("head")[0]?.appendChild(script);
}, []);
return <>{children}</>;
};
export default Analytics;
+1 -1
View File
@@ -6,7 +6,7 @@ import {
CompositeStateReturn,
} from "reakit/Composite";
type Props = {
type Props = React.HTMLAttributes<HTMLDivElement> & {
children: (composite: CompositeStateReturn) => React.ReactNode;
onEscape?: (ev: React.KeyboardEvent<HTMLDivElement>) => void;
};
+5 -4
View File
@@ -7,28 +7,29 @@ import SlackLogo from "./SlackLogo";
type Props = {
providerName: string;
size?: number;
color?: string;
};
function AuthLogo({ providerName, size = 16 }: Props) {
function AuthLogo({ providerName, color, size = 16 }: Props) {
switch (providerName) {
case "slack":
return (
<Logo>
<SlackLogo size={size} />
<SlackLogo size={size} fill={color} />
</Logo>
);
case "google":
return (
<Logo>
<GoogleLogo size={size} />
<GoogleLogo size={size} fill={color} />
</Logo>
);
case "azure":
return (
<Logo>
<MicrosoftLogo size={size} />
<MicrosoftLogo size={size} fill={color} />
</Logo>
);
+79 -59
View File
@@ -1,29 +1,41 @@
import { observable } from "mobx";
import { observer } from "mobx-react";
import { AnimatePresence } from "framer-motion";
import { observer, useLocalStore } from "mobx-react";
import * as React from "react";
import { withTranslation, WithTranslation } from "react-i18next";
import { Switch, Route } from "react-router-dom";
import RootStore from "~/stores/RootStore";
import { Switch, Route, useLocation, matchPath } from "react-router-dom";
import ErrorSuspended from "~/scenes/ErrorSuspended";
import DocumentContext from "~/components/DocumentContext";
import type { DocumentContextValue } from "~/components/DocumentContext";
import Layout from "~/components/Layout";
import RegisterKeyDown from "~/components/RegisterKeyDown";
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 usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import history from "~/utils/history";
import {
searchPath,
matchDocumentSlug as slug,
newDocumentPath,
settingsPath,
matchDocumentHistory,
matchDocumentInsights,
} from "~/utils/routeHelpers";
import Fade from "./Fade";
import withStores from "./withStores";
const DocumentHistory = React.lazy(
() =>
import(
/* webpackChunkName: "document-history" */
"~/components/DocumentHistory"
"~/scenes/Document/components/History"
)
);
const DocumentInsights = React.lazy(
() =>
import(
/* webpackChunkName: "document-insights" */
"~/scenes/Document/components/Insights"
)
);
const CommandBar = React.lazy(
@@ -34,16 +46,19 @@ const CommandBar = React.lazy(
)
);
type Props = WithTranslation & RootStore;
const AuthenticatedLayout: React.FC = ({ children }) => {
const { ui, auth } = useStores();
const location = useLocation();
const can = usePolicy(ui.activeCollectionId);
const { user, team } = auth;
const documentContext = useLocalStore<DocumentContextValue>(() => ({
editor: null,
setEditor: (editor: TEditor) => {
documentContext.editor = editor;
},
}));
@observer
class AuthenticatedLayout extends React.Component<Props> {
scrollable: HTMLDivElement | null | undefined;
@observable
keyboardShortcutsOpen = false;
goToSearch = (ev: KeyboardEvent) => {
const goToSearch = (ev: KeyboardEvent) => {
if (!ev.metaKey && !ev.ctrlKey) {
ev.preventDefault();
ev.stopPropagation();
@@ -51,60 +66,65 @@ class AuthenticatedLayout extends React.Component<Props> {
}
};
goToNewDocument = (event: KeyboardEvent) => {
const goToNewDocument = (event: KeyboardEvent) => {
if (event.metaKey || event.altKey) {
return;
}
const { activeCollectionId } = this.props.ui;
if (!activeCollectionId) {
return;
}
const can = this.props.policies.abilities(activeCollectionId);
if (!can.update) {
const { activeCollectionId } = ui;
if (!activeCollectionId || !can.update) {
return;
}
history.push(newDocumentPath(activeCollectionId));
};
render() {
const { auth } = this.props;
const { user, team } = auth;
const showSidebar = auth.authenticated && user && team;
if (auth.isSuspended) {
return <ErrorSuspended />;
}
if (auth.isSuspended) {
return <ErrorSuspended />;
}
const sidebar = showSidebar ? (
<Fade>
<Switch>
<Route path={settingsPath()} component={SettingsSidebar} />
<Route component={Sidebar} />
</Switch>
</Fade>
) : undefined;
const showSidebar = auth.authenticated && user && team;
const rightRail = (
<React.Suspense fallback={null}>
<Switch>
<Route
path={`/doc/${slug}/history/:revisionId?`}
component={DocumentHistory}
/>
</Switch>
</React.Suspense>
);
const sidebar = showSidebar ? (
<Fade>
<Switch>
<Route path={settingsPath()} component={SettingsSidebar} />
<Route component={Sidebar} />
</Switch>
</Fade>
) : undefined;
return (
<Layout title={team?.name} sidebar={sidebar} rightRail={rightRail}>
<RegisterKeyDown trigger="n" handler={this.goToNewDocument} />
<RegisterKeyDown trigger="t" handler={this.goToSearch} />
<RegisterKeyDown trigger="/" handler={this.goToSearch} />
{this.props.children}
const showHistory = !!matchPath(location.pathname, {
path: matchDocumentHistory,
});
const showInsights = !!matchPath(location.pathname, {
path: matchDocumentInsights,
});
const sidebarRight = (
<AnimatePresence key={ui.activeDocumentId}>
{(showHistory || showInsights) && (
<Route path={`/doc/${slug}`}>
<SidebarRight>
<React.Suspense fallback={null}>
{showHistory && <DocumentHistory />}
{showInsights && <DocumentInsights />}
</React.Suspense>
</SidebarRight>
</Route>
)}
</AnimatePresence>
);
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}
<CommandBar />
</Layout>
);
}
}
</DocumentContext.Provider>
);
};
export default withTranslation()(withStores(AuthenticatedLayout));
export default observer(AuthenticatedLayout);
+41 -31
View File
@@ -1,52 +1,60 @@
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import User from "~/models/User";
import useBoolean from "~/hooks/useBoolean";
import Initials from "./Initials";
import placeholder from "./placeholder.png";
export interface IAvatar {
avatarUrl: string | null;
color: string;
initial: string;
id: string;
}
type Props = {
src: string;
size: number;
src?: string;
icon?: React.ReactNode;
user?: User;
model?: IAvatar;
alt?: string;
showBorder?: boolean;
onClick?: React.MouseEventHandler<HTMLImageElement>;
className?: string;
};
@observer
class Avatar extends React.Component<Props> {
@observable
error: boolean;
function Avatar(props: Props) {
const { icon, showBorder, model, ...rest } = props;
const src = props.src || model?.avatarUrl;
const [error, handleError] = useBoolean(false);
static defaultProps = {
size: 24,
};
handleError = () => {
this.error = true;
};
render() {
const { src, icon, showBorder, ...rest } = this.props;
return (
<AvatarWrapper>
return (
<Relative>
{src ? (
<CircleImg
onError={this.handleError}
src={this.error ? placeholder : src}
onError={handleError}
src={error ? placeholder : src}
$showBorder={showBorder}
{...rest}
/>
{icon && <IconWrapper>{icon}</IconWrapper>}
</AvatarWrapper>
);
}
) : model ? (
<Initials color={model.color} $showBorder={showBorder} {...rest}>
{model.initial}
</Initials>
) : (
<Initials $showBorder={showBorder} {...rest} />
)}
{icon && <IconWrapper>{icon}</IconWrapper>}
</Relative>
);
}
const AvatarWrapper = styled.div`
Avatar.defaultProps = {
size: 24,
};
const Relative = styled.div`
position: relative;
flex-shrink: 0;
`;
const IconWrapper = styled.div`
@@ -66,10 +74,12 @@ const CircleImg = styled.img<{ size: number; $showBorder?: boolean }>`
width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
border-radius: 50%;
border: 2px solid
${(props) =>
props.$showBorder === false ? "transparent" : props.theme.background};
border: ${(props) =>
props.$showBorder === false
? "none"
: `2px solid ${props.theme.background}`};
flex-shrink: 0;
overflow: hidden;
`;
export default Avatar;
+78 -105
View File
@@ -1,141 +1,114 @@
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";
import styled, { css } from "styled-components";
import User from "~/models/User";
import UserProfile from "~/scenes/UserProfile";
import Avatar from "~/components/Avatar";
import Tooltip from "~/components/Tooltip";
type Props = WithTranslation & {
type Props = {
user: User;
isPresent: boolean;
isEditing: boolean;
isObserving: boolean;
isCurrentUser: boolean;
profileOnClick: boolean;
onClick?: React.MouseEventHandler<HTMLImageElement>;
};
@observer
class AvatarWithPresence extends React.Component<Props> {
@observable
isOpen = false;
function AvatarWithPresence({
onClick,
user,
isPresent,
isEditing,
isObserving,
isCurrentUser,
}: Props) {
const { t } = useTranslation();
const status = isPresent
? isEditing
? t("currently editing")
: t("currently viewing")
: t("previously edited");
handleOpenProfile = () => {
this.isOpen = true;
};
handleCloseProfile = () => {
this.isOpen = false;
};
render() {
const {
onClick,
user,
isPresent,
isEditing,
isObserving,
isCurrentUser,
t,
} = this.props;
const status = isPresent
? isEditing
? t("currently editing")
: t("currently viewing")
: t("previously edited");
return (
<>
<Tooltip
tooltip={
<Centered>
<strong>{user.name}</strong> {isCurrentUser && `(${t("You")})`}
{status && (
<>
<br />
{status}
</>
)}
</Centered>
}
placement="bottom"
return (
<>
<Tooltip
tooltip={
<Centered>
<strong>{user.name}</strong> {isCurrentUser && `(${t("You")})`}
{status && (
<>
<br />
{status}
</>
)}
</Centered>
}
placement="bottom"
>
<AvatarWrapper
$isPresent={isPresent}
$isObserving={isObserving}
$color={user.color}
>
<AvatarWrapper
$isPresent={isPresent}
$isObserving={isObserving}
$color={user.color}
>
<Avatar
src={user.avatarUrl}
onClick={
this.props.profileOnClick === false
? onClick
: this.handleOpenProfile
}
size={32}
/>
</AvatarWrapper>
</Tooltip>
{this.props.profileOnClick && (
<UserProfile
user={user}
isOpen={this.isOpen}
onRequestClose={this.handleCloseProfile}
/>
)}
</>
);
}
<Avatar model={user} onClick={onClick} size={32} />
</AvatarWrapper>
</Tooltip>
</>
);
}
const Centered = styled.div`
text-align: center;
`;
const AvatarWrapper = styled.div<{
type AvatarWrapperProps = {
$isPresent: boolean;
$isObserving: boolean;
$color: string;
}>`
};
const AvatarWrapper = styled.div<AvatarWrapperProps>`
opacity: ${(props) => (props.$isPresent ? 1 : 0.5)};
transition: opacity 250ms ease-in-out;
border-radius: 50%;
position: relative;
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
transition: border-color 100ms ease-in-out;
border: 2px solid transparent;
pointer-events: none;
${(props) =>
props.$isPresent &&
css<AvatarWrapperProps>`
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
transition: border-color 100ms ease-in-out;
border: 2px solid transparent;
pointer-events: none;
${(props) =>
props.$isObserving &&
css`
border: 2px solid ${props.$color};
box-shadow: inset 0 0 0 2px ${props.theme.background};
${(props) =>
props.$isObserving &&
css`
border: 2px solid ${props.$color};
box-shadow: inset 0 0 0 2px ${props.theme.background};
&:hover {
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
}
`}
}
&:hover {
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
}
`}
}
&:hover:after {
border: 2px solid ${(props) => props.$color};
box-shadow: inset 0 0 0 2px ${(props) => props.theme.background};
}
&:hover:after {
border: 2px solid ${(props) => props.$color};
box-shadow: inset 0 0 0 2px ${(props) => props.theme.background};
}
`}
`;
export default withTranslation()(AvatarWithPresence);
export default observer(AvatarWithPresence);
+27
View File
@@ -0,0 +1,27 @@
import styled from "styled-components";
import Flex from "~/components/Flex";
const Initials = styled(Flex)<{
color?: string;
size: number;
$showBorder?: boolean;
}>`
align-items: center;
justify-content: center;
border-radius: 50%;
width: 100%;
height: 100%;
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};
flex-shrink: 0;
font-size: ${(props) => props.size / 2}px;
font-weight: 500;
`;
export default Initials;
+3 -3
View File
@@ -3,7 +3,7 @@ import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { depths } from "@shared/styles";
import env from "~/env";
import OutlineLogo from "./OutlineLogo";
import OutlineIcon from "./Icons/OutlineIcon";
type Props = {
href?: string;
@@ -12,8 +12,8 @@ type Props = {
function Branding({ href = env.URL }: Props) {
return (
<Link href={href}>
<OutlineLogo size={16} />
&nbsp;Outline
<OutlineIcon size={20} />
&nbsp;{env.APP_NAME}
</Link>
);
}
+1
View File
@@ -67,6 +67,7 @@ const Item = styled(Link)<{ $highlight: boolean; $withIcon: boolean }>`
display: flex;
flex-shrink: 1;
min-width: 0;
cursor: var(--pointer);
color: ${(props) => props.theme.text};
font-size: 15px;
height: 24px;
+52 -22
View File
@@ -3,16 +3,22 @@ import { ExpandedIcon } from "outline-icons";
import { darken, lighten } from "polished";
import * as React from "react";
import styled from "styled-components";
import ActionButton, {
Props as ActionButtonProps,
} from "~/components/ActionButton";
import { undraggableOnDesktop } from "~/styles";
const RealButton = styled.button<{
fullwidth?: boolean;
borderOnHover?: boolean;
type RealProps = {
$fullwidth?: boolean;
$borderOnHover?: boolean;
$neutral?: boolean;
danger?: boolean;
iconColor?: string;
}>`
display: ${(props) => (props.fullwidth ? "block" : "inline-block")};
width: ${(props) => (props.fullwidth ? "100%" : "auto")};
$danger?: boolean;
$iconColor?: string;
};
const RealButton = styled(ActionButton)<RealProps>`
display: ${(props) => (props.$fullwidth ? "block" : "inline-block")};
width: ${(props) => (props.$fullwidth ? "100%" : "auto")};
margin: 0;
padding: 0;
border: 0;
@@ -25,15 +31,16 @@ const RealButton = styled.button<{
height: 32px;
text-decoration: none;
flex-shrink: 0;
cursor: pointer;
cursor: var(--pointer);
user-select: none;
appearance: none !important;
${undraggableOnDesktop()}
${(props) =>
!props.borderOnHover &&
!props.$borderOnHover &&
`
svg {
fill: ${props.iconColor || "currentColor"};
fill: ${props.$iconColor || "currentColor"};
}
`}
@@ -64,16 +71,16 @@ const RealButton = styled.button<{
background: ${props.theme.buttonNeutralBackground};
color: ${props.theme.buttonNeutralText};
box-shadow: ${
props.borderOnHover
props.$borderOnHover
? "none"
: `rgba(0, 0, 0, 0.07) 0px 1px 2px, ${props.theme.buttonNeutralBorder} 0 0 0 1px inset`
};
${
props.borderOnHover
props.$borderOnHover
? ""
: `svg {
fill: ${props.iconColor || "currentColor"};
fill: ${props.$iconColor || "currentColor"};
}`
}
@@ -81,7 +88,7 @@ const RealButton = styled.button<{
&:hover:not(:disabled),
&[aria-expanded="true"] {
background: ${
props.borderOnHover
props.$borderOnHover
? props.theme.buttonNeutralBackground
: darken(0.05, props.theme.buttonNeutralBackground)
};
@@ -101,7 +108,7 @@ const RealButton = styled.button<{
`}
${(props) =>
props.danger &&
props.$danger &&
`
background: ${props.theme.danger};
color: ${props.theme.white};
@@ -146,14 +153,13 @@ export const Inner = styled.span<{
${(props) => props.hasIcon && !props.hasText && "padding: 0 4px;"};
`;
export type Props<T> = {
export type Props<T> = ActionButtonProps & {
icon?: React.ReactNode;
iconColor?: string;
children?: React.ReactNode;
disclosure?: boolean;
neutral?: boolean;
danger?: boolean;
primary?: boolean;
fullwidth?: boolean;
as?: T;
to?: LocationDescriptor;
@@ -168,14 +174,38 @@ const Button = <T extends React.ElementType = "button">(
props: Props<T> & React.ComponentPropsWithoutRef<T>,
ref: React.Ref<HTMLButtonElement>
) => {
const { type, icon, children, value, disclosure, neutral, ...rest } = props;
const {
type,
children,
value,
disclosure,
neutral,
action,
icon,
iconColor,
borderOnHover,
fullwidth,
danger,
...rest
} = props;
const hasText = children !== undefined || value !== undefined;
const hasIcon = icon !== undefined;
const ic = action?.icon ?? icon;
const hasIcon = ic !== undefined;
return (
<RealButton type={type || "button"} ref={ref} $neutral={neutral} {...rest}>
<RealButton
type={type || "button"}
ref={ref}
$neutral={neutral}
action={action}
$danger={danger}
$fullwidth={fullwidth}
$borderOnHover={borderOnHover}
$iconColor={iconColor}
{...rest}
>
<Inner hasIcon={hasIcon} hasText={hasText} disclosure={disclosure}>
{hasIcon && icon}
{hasIcon && ic}
{hasText && <Label hasIcon={hasIcon}>{children || value}</Label>}
{disclosure && <ExpandedIcon color="currentColor" />}
</Inner>
+4 -4
View File
@@ -1,9 +1,9 @@
import styled from "styled-components";
const ClickablePadding = styled.div<{ grow?: boolean }>`
min-height: 10em;
cursor: ${({ onClick }) => (onClick ? "text" : "default")};
${({ grow }) => grow && `flex-grow: 100;`};
const ClickablePadding = styled.div<{ grow?: boolean; minHeight?: string }>`
min-height: ${(props) => props.minHeight || "50vh"};
flex-grow: 100;
cursor: text;
`;
export default ClickablePadding;
-1
View File
@@ -90,7 +90,6 @@ function Collaborators(props: Props) {
isEditing={isEditing}
isObserving={isObserving}
isCurrentUser={currentUserId === collaborator.id}
profileOnClick={false}
onClick={
isObservable
? (ev) => {
+2 -2
View File
@@ -11,7 +11,7 @@ import CommandBarResults from "~/components/CommandBarResults";
import SearchActions from "~/components/SearchActions";
import rootActions from "~/actions/root";
import useCommandBarActions from "~/hooks/useCommandBarActions";
import useSettingsActions from "~/hooks/useSettingsAction";
import useSettingsActions from "~/hooks/useSettingsActions";
import useStores from "~/hooks/useStores";
import { CommandBarAction } from "~/types";
import { metaDisplay } from "~/utils/keyboard";
@@ -38,10 +38,10 @@ function CommandBar() {
return (
<>
<SearchActions />
<KBarPortal>
<Positioner>
<Animator>
<SearchActions />
<SearchInput
placeholder={`${
rootAction?.placeholder ||
+1 -1
View File
@@ -98,7 +98,7 @@ const Item = styled.div<{ active?: boolean }>`
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
cursor: var(--pointer);
text-overflow: ellipsis;
white-space: nowrap;
+1 -1
View File
@@ -51,7 +51,7 @@ const ConfirmationDialog: React.FC<Props> = ({
<form onSubmit={handleSubmit}>
<Text type="secondary">{children}</Text>
<Button type="submit" disabled={isSaving} danger={danger} autoFocus>
{isSaving ? savingText : submitText}
{isSaving && savingText ? savingText : submitText}
</Button>
</form>
</Flex>
+2 -2
View File
@@ -118,8 +118,8 @@ const ContentEditable = React.forwardRef(
}
}, [value, contentRef]);
// Ensure only plain text can be pasted into title when pasting from another
// rich text editor
// Ensure only plain text can be pasted into input when pasting from another
// rich text source
const handlePaste = React.useCallback(
(event: React.ClipboardEvent<HTMLSpanElement>) => {
event.preventDefault();
+48 -23
View File
@@ -1,6 +1,7 @@
import { LocationDescriptor } from "history";
import { CheckmarkIcon } from "outline-icons";
import * as React from "react";
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";
@@ -8,6 +9,7 @@ import MenuIconWrapper from "../MenuIconWrapper";
type Props = {
onClick?: (event: React.SyntheticEvent) => void | Promise<void>;
active?: boolean;
selected?: boolean;
disabled?: boolean;
dangerous?: boolean;
@@ -18,29 +20,31 @@ type Props = {
hide?: () => void;
level?: number;
icon?: React.ReactElement;
children?: React.ReactNode;
};
const MenuItem: React.FC<Props> = ({
onClick,
children,
selected,
disabled,
as,
hide,
icon,
...rest
}) => {
const MenuItem = (
{
onClick,
children,
active,
selected,
disabled,
as,
hide,
icon,
...rest
}: Props,
ref: React.Ref<HTMLAnchorElement>
) => {
const handleClick = React.useCallback(
(ev) => {
if (onClick) {
ev.preventDefault();
ev.stopPropagation();
onClick(ev);
}
if (hide) {
hide();
}
hide?.();
},
[onClick, hide]
);
@@ -63,10 +67,14 @@ const MenuItem: React.FC<Props> = ({
{(props) => (
<MenuAnchor
{...props}
$toggleable={selected !== undefined}
$active={active}
as={onClick ? "button" : as}
onClick={handleClick}
onMouseDown={handleMouseDown}
ref={mergeRefs([
ref,
props.ref as React.RefObject<HTMLAnchorElement>,
])}
>
{selected !== undefined && (
<>
@@ -97,6 +105,7 @@ type MenuAnchorProps = {
disabled?: boolean;
dangerous?: boolean;
disclosure?: boolean;
$active?: boolean;
};
export const MenuAnchorCSS = css<MenuAnchorProps>`
@@ -104,6 +113,7 @@ export const MenuAnchorCSS = css<MenuAnchorProps>`
margin: 0;
border: 0;
padding: 12px;
border-radius: 4px;
padding-left: ${(props) => 12 + (props.level || 0) * 10}px;
width: 100%;
min-height: 32px;
@@ -127,11 +137,12 @@ export const MenuAnchorCSS = css<MenuAnchorProps>`
opacity: ${(props) => (props.disabled ? ".5" : 1)};
}
${(props) =>
props.disabled
? "pointer-events: none;"
: `
${(props) => props.disabled && "pointer-events: none;"}
${(props) =>
props.$active === undefined &&
!props.disabled &&
`
@media (hover: hover) {
&:hover,
&:focus,
@@ -139,25 +150,39 @@ export const MenuAnchorCSS = css<MenuAnchorProps>`
color: ${props.theme.white};
background: ${props.dangerous ? props.theme.danger : props.theme.primary};
box-shadow: none;
cursor: pointer;
cursor: var(--pointer);
svg {
fill: ${props.theme.white};
}
}
}
`};
`}
${(props) =>
props.$active &&
!props.disabled &&
`
color: ${props.theme.white};
background: ${props.dangerous ? props.theme.danger : props.theme.primary};
box-shadow: none;
cursor: var(--pointer);
svg {
fill: ${props.theme.white};
}
`}
${breakpoint("tablet")`
padding: 4px 12px;
padding-right: ${(props: MenuAnchorProps) =>
props.disclosure ? 32 : 12}px;
font-size: 14px;
`};
`}
`;
export const MenuAnchor = styled.a`
${MenuAnchorCSS}
`;
export default MenuItem;
export default React.forwardRef<HTMLAnchorElement, Props>(MenuItem);
+1 -1
View File
@@ -11,5 +11,5 @@ export default function Separator(rest: React.HTMLAttributes<HTMLHRElement>) {
}
const HorizontalRule = styled.hr`
margin: 0.5em 12px;
margin: 6px 0;
`;
+17 -9
View File
@@ -6,6 +6,7 @@ import {
useMenuState,
MenuButton,
MenuItem as BaseMenuItem,
MenuStateReturn,
} from "reakit/Menu";
import styled, { useTheme } from "styled-components";
import Flex from "~/components/Flex";
@@ -25,7 +26,7 @@ import MouseSafeArea from "./MouseSafeArea";
import Separator from "./Separator";
import ContextMenu from ".";
type Props = {
type Props = Omit<MenuStateReturn, "items"> & {
actions?: (Action | MenuSeparator | MenuHeading)[];
context?: Partial<ActionContext>;
items?: TMenuItem[];
@@ -37,13 +38,15 @@ const Disclosure = styled(ExpandedIcon)`
right: 8px;
`;
const Submenu = React.forwardRef(
type SubMenuProps = MenuStateReturn & {
templateItems: TMenuItem[];
parentMenuState: Omit<MenuStateReturn, "items">;
title: React.ReactNode;
};
const SubMenu = React.forwardRef(
(
{
templateItems,
title,
...rest
}: { templateItems: TMenuItem[]; title: React.ReactNode },
{ templateItems, title, parentMenuState, ...rest }: SubMenuProps,
ref: React.LegacyRef<HTMLButtonElement>
) => {
const { t } = useTranslation();
@@ -59,7 +62,11 @@ const Submenu = React.forwardRef(
</MenuAnchor>
)}
</MenuButton>
<ContextMenu {...menu} aria-label={t("Submenu")}>
<ContextMenu
{...menu}
aria-label={t("Submenu")}
onClick={parentMenuState.hide}
>
<MouseSafeArea parentRef={menu.unstable_popoverRef} />
<Template {...menu} items={templateItems} />
</ContextMenu>
@@ -177,8 +184,9 @@ function Template({ items, actions, context, ...menu }: Props) {
return (
<BaseMenuItem
key={index}
as={Submenu}
as={SubMenu}
templateItems={item.items}
parentMenuState={menu}
title={<Title title={item.title} icon={item.icon} />}
{...menu}
/>
+49 -46
View File
@@ -1,14 +1,14 @@
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Portal } from "react-portal";
import { Menu } from "reakit/Menu";
import { Menu, MenuStateReturn } from "reakit/Menu";
import styled, { DefaultTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { depths } from "@shared/styles";
import Scrollable from "~/components/Scrollable";
import useMenuContext from "~/hooks/useMenuContext";
import useMenuHeight from "~/hooks/useMenuHeight";
import useMobile from "~/hooks/useMobile";
import usePrevious from "~/hooks/usePrevious";
import useStores from "~/hooks/useStores";
import useUnmount from "~/hooks/useUnmount";
@@ -36,21 +36,23 @@ export type Placement =
| "left"
| "left-start";
type Props = {
type Props = MenuStateReturn & {
"aria-label": string;
visible?: boolean;
placement?: Placement;
animating?: boolean;
unstable_disclosureRef?: React.RefObject<HTMLElement | null>;
/** The parent menu state if this is a submenu. */
parentMenuState?: MenuStateReturn;
/** Called when the context menu is opened. */
onOpen?: () => void;
/** Called when the context menu is closed. */
onClose?: () => void;
hide?: () => void;
/** Called when the context menu is clicked. */
onClick?: (ev: React.MouseEvent) => void;
};
const ContextMenu: React.FC<Props> = ({
children,
onOpen,
onClose,
parentMenuState,
...rest
}) => {
const previousVisible = usePrevious(rest.visible);
@@ -59,6 +61,7 @@ const ContextMenu: React.FC<Props> = ({
const { ui } = useStores();
const { t } = useTranslation();
const { setIsMenuOpen } = useMenuContext();
const isMobile = useMobile();
useUnmount(() => {
setIsMenuOpen(false);
@@ -66,19 +69,17 @@ const ContextMenu: React.FC<Props> = ({
React.useEffect(() => {
if (rest.visible && !previousVisible) {
if (onOpen) {
onOpen();
}
if (rest["aria-label"] !== t("Submenu")) {
onOpen?.();
if (!parentMenuState) {
setIsMenuOpen(true);
}
}
if (!rest.visible && previousVisible) {
if (onClose) {
onClose();
}
if (rest["aria-label"] !== t("Submenu")) {
onClose?.();
if (!parentMenuState) {
setIsMenuOpen(false);
}
}
@@ -89,7 +90,7 @@ const ContextMenu: React.FC<Props> = ({
rest.visible,
ui.sidebarCollapsed,
setIsMenuOpen,
rest,
parentMenuState,
t,
]);
@@ -115,7 +116,7 @@ const ContextMenu: React.FC<Props> = ({
// trigger and the bottom of the window
return (
<>
<Menu hideOnClickOutside preventBodyScroll={false} {...rest}>
<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
@@ -125,32 +126,38 @@ const ContextMenu: React.FC<Props> = ({
const rightAnchor = props.placement === "bottom-end";
return (
<Position {...props}>
<Background
dir="auto"
topAnchor={topAnchor}
rightAnchor={rightAnchor}
ref={backgroundRef}
hiddenScrollbars
style={
maxHeight && topAnchor
? {
maxHeight,
}
: undefined
}
>
{rest.visible || rest.animating ? children : null}
</Background>
</Position>
<>
{isMobile && (
<Backdrop
onClick={(ev) => {
ev.preventDefault();
ev.stopPropagation();
rest.hide?.();
}}
/>
)}
<Position {...props}>
<Background
dir="auto"
topAnchor={topAnchor}
rightAnchor={rightAnchor}
ref={backgroundRef}
hiddenScrollbars
style={
maxHeight && topAnchor
? {
maxHeight,
}
: undefined
}
>
{rest.visible || rest.animating ? children : null}
</Background>
</Position>
</>
);
}}
</Menu>
{(rest.visible || rest.animating) && (
<Portal>
<Backdrop onClick={rest.hide} />
</Portal>
)}
</>
);
};
@@ -166,10 +173,6 @@ export const Backdrop = styled.div`
bottom: 0;
background: ${(props) => props.theme.backdrop};
z-index: ${depths.menu - 1};
${breakpoint("tablet")`
display: none;
`};
`;
export const Position = styled.div`
@@ -202,7 +205,7 @@ export const Background = styled(Scrollable)<BackgroundProps>`
max-width: 100%;
background: ${(props) => props.theme.menuBackground};
border-radius: 6px;
padding: 6px 0;
padding: 6px;
min-width: 180px;
min-height: 44px;
max-height: 75vh;
@@ -2,8 +2,8 @@ import { HomeIcon } from "outline-icons";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Optional } from "utility-types";
import CollectionIcon from "~/components/CollectionIcon";
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";
+56
View File
@@ -0,0 +1,56 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
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() {
useDesktopTitlebar();
const { t } = useTranslation();
const history = useHistory();
const { dialogs } = useStores();
const { showToast } = useToasts();
React.useEffect(() => {
Desktop.bridge?.redirect((path: string, replace = false) => {
if (replace) {
history.replace(path);
} else {
history.push(path);
}
});
Desktop.bridge?.updateDownloaded(() => {
showToast("An update is ready to install.", {
type: "info",
timeout: Infinity,
action: {
text: "Install now",
onClick: () => {
Desktop.bridge?.restartAndInstall();
},
},
});
});
Desktop.bridge?.focus(() => {
window.document.body.classList.remove("backgrounded");
});
Desktop.bridge?.blur(() => {
window.document.body.classList.add("backgrounded");
});
Desktop.bridge?.openKeyboardShortcuts(() => {
dialogs.openGuide({
title: t("Keyboard shortcuts"),
content: <KeyboardShortcuts />,
});
});
}, [t, history, dialogs, showToast]);
return null;
}
+4 -3
View File
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Document from "~/models/Document";
import Breadcrumb from "~/components/Breadcrumb";
import CollectionIcon from "~/components/CollectionIcon";
import CollectionIcon from "~/components/Icons/CollectionIcon";
import useStores from "~/hooks/useStores";
import { MenuInternalLink, NavigationNode } from "~/types";
import { collectionUrl } from "~/utils/routeHelpers";
@@ -77,8 +77,9 @@ const DocumentBreadcrumb: React.FC<Props> = ({
}
const path = React.useMemo(
() => collection?.pathToDocument?.(document.id).slice(0, -1) || [],
[collection, document]
() => collection?.pathToDocument(document.id).slice(0, -1) || [],
// eslint-disable-next-line react-hooks/exhaustive-deps
[collection, document, document.collectionId, document.parentDocumentId]
);
const items = React.useMemo(() => {
+121 -104
View File
@@ -3,18 +3,19 @@ import { CSS } from "@dnd-kit/utilities";
import { m } from "framer-motion";
import { observer } from "mobx-react";
import { CloseIcon, DocumentIcon, ClockIcon } from "outline-icons";
import { getLuminance, transparentize } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import styled, { css } from "styled-components";
import styled, { useTheme } from "styled-components";
import Document from "~/models/Document";
import Pin from "~/models/Pin";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
import Time from "~/components/Time";
import useStores from "~/hooks/useStores";
import CollectionIcon from "./CollectionIcon";
import CollectionIcon from "./Icons/CollectionIcon";
import EmojiIcon from "./Icons/EmojiIcon";
import Squircle from "./Squircle";
import Text from "./Text";
import Tooltip from "./Tooltip";
@@ -32,6 +33,7 @@ type Props = {
function DocumentCard(props: Props) {
const { t } = useTranslation();
const { collections } = useStores();
const theme = useTheme();
const { document, pin, canUpdatePin, isDraggable } = props;
const collection = collections.get(document.collectionId);
const {
@@ -41,16 +43,24 @@ function DocumentCard(props: Props) {
transform,
transition,
isDragging,
} = useSortable({ id: props.document.id });
} = useSortable({
id: props.document.id,
disabled: !isDraggable || !canUpdatePin,
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
const handleUnpin = React.useCallback(() => {
pin?.delete();
}, [pin]);
const handleUnpin = React.useCallback(
(ev) => {
ev.preventDefault();
ev.stopPropagation();
pin?.delete();
},
[pin]
);
return (
<Reorderable
@@ -58,6 +68,8 @@ function DocumentCard(props: Props) {
style={style}
$isDragging={isDragging}
{...attributes}
{...listeners}
tabIndex={-1}
>
<AnimatePresence
initial={{ opacity: 0, scale: 0.95 }}
@@ -73,12 +85,6 @@ function DocumentCard(props: Props) {
>
<DocumentLink
dir={document.dir}
style={{
background:
collection?.color && getLuminance(collection.color) < 0.6
? collection.color
: undefined,
}}
$isDragging={isDragging}
to={{
pathname: document.url,
@@ -88,89 +94,117 @@ function DocumentCard(props: Props) {
}}
>
<Content justify="space-between" column>
{collection?.icon &&
collection?.icon !== "collection" &&
!pin?.collectionId ? (
<CollectionIcon collection={collection} color="white" />
<Fold
width="20"
height="21"
viewBox="0 0 20 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M19.5 20.5H6C2.96243 20.5 0.5 18.0376 0.5 15V0.5H0.792893L19.5 19.2071V20.5Z" />
<path d="M19.5 19.5H6C2.96243 19.5 0.5 17.0376 0.5 14V0.5H0.792893L19.5 19.2071V19.5Z" />
</Fold>
{document.emoji ? (
<Squircle color={theme.slateLight}>
<EmojiIcon emoji={document.emoji} size={26} />
</Squircle>
) : (
<DocumentIcon color="white" />
<Squircle color={collection?.color}>
{collection?.icon &&
collection?.icon !== "collection" &&
!pin?.collectionId ? (
<CollectionIcon collection={collection} color="white" />
) : (
<DocumentIcon color="white" />
)}
</Squircle>
)}
<div>
<Heading dir={document.dir}>{document.titleWithDefault}</Heading>
<Heading dir={document.dir}>
{document.emoji
? document.titleWithDefault.replace(document.emoji, "")
: document.titleWithDefault}
</Heading>
<DocumentMeta size="xsmall">
<ClockIcon color="currentColor" size={18} />{" "}
<Time dateTime={document.updatedAt} addSuffix shorten />
<Clock color="currentColor" size={18} />
<Time
dateTime={document.updatedAt}
tooltipDelay={500}
addSuffix
shorten
/>
</DocumentMeta>
</div>
</Content>
{canUpdatePin && (
<Actions dir={document.dir} gap={4}>
{!isDragging && pin && (
<Tooltip tooltip={t("Unpin")}>
<PinButton onClick={handleUnpin} aria-label={t("Unpin")}>
<CloseIcon color="currentColor" />
</PinButton>
</Tooltip>
)}
</Actions>
)}
</DocumentLink>
{canUpdatePin && (
<Actions dir={document.dir} gap={4}>
{!isDragging && pin && (
<Tooltip tooltip={t("Unpin")}>
<PinButton onClick={handleUnpin} aria-label={t("Unpin")}>
<CloseIcon color="currentColor" />
</PinButton>
</Tooltip>
)}
{isDraggable && (
<DragHandle $isDragging={isDragging} {...listeners}>
:::
</DragHandle>
)}
</Actions>
)}
</AnimatePresence>
</Reorderable>
);
}
const Clock = styled(ClockIcon)`
flex-shrink: 0;
`;
const AnimatePresence = styled(m.div)`
width: 100%;
height: 100%;
`;
const Fold = styled.svg`
fill: ${(props) => props.theme.background};
stroke: ${(props) => props.theme.inputBorder};
background: ${(props) => props.theme.background};
position: absolute;
top: -1px;
right: -2px;
`;
const PinButton = styled(NudeButton)`
color: ${(props) => props.theme.white75};
color: ${(props) => props.theme.textTertiary};
&:hover,
&:active {
color: ${(props) => props.theme.white};
color: ${(props) => props.theme.text};
}
`;
const Actions = styled(Flex)`
position: absolute;
top: 12px;
right: ${(props) => (props.dir === "rtl" ? "auto" : "12px")};
left: ${(props) => (props.dir === "rtl" ? "12px" : "auto")};
top: 4px;
right: ${(props) => (props.dir === "rtl" ? "auto" : "4px")};
left: ${(props) => (props.dir === "rtl" ? "4px" : "auto")};
opacity: 0;
transition: opacity 100ms ease-in-out;
color: ${(props) => props.theme.textTertiary};
// move actions above content
z-index: 2;
`;
const DragHandle = styled.div<{ $isDragging: boolean }>`
cursor: ${(props) => (props.$isDragging ? "grabbing" : "grab")};
padding: 0 4px;
font-weight: bold;
color: ${(props) => props.theme.white75};
line-height: 1.35;
&:hover,
&:active {
color: ${(props) => props.theme.white};
}
`;
const AnimatePresence = m.div;
const Reorderable = styled.div<{ $isDragging: boolean }>`
position: relative;
user-select: none;
border-radius: 8px;
touch-action: none;
width: 170px;
height: 180px;
transition: box-shadow 200ms ease;
// move above other cards when dragging
z-index: ${(props) => (props.$isDragging ? 1 : "inherit")};
transform: scale(${(props) => (props.$isDragging ? "1.025" : "1")});
box-shadow: ${(props) =>
props.$isDragging ? "0 0 20px rgba(0,0,0,0.3);" : "0 0 0 rgba(0,0,0,0)"};
pointer-events: ${(props) => (props.$isDragging ? "none" : "inherit")};
&:hover ${Actions} {
opacity: 1;
@@ -180,45 +214,34 @@ const Reorderable = styled.div<{ $isDragging: boolean }>`
const Content = styled(Flex)`
min-width: 0;
height: 100%;
// move content above ::after
position: relative;
z-index: 1;
`;
const DocumentMeta = styled(Text)`
display: flex;
align-items: center;
gap: 2px;
color: ${(props) => transparentize(0.25, props.theme.white)};
margin: 0;
color: ${(props) => props.theme.textTertiary};
margin: 0 0 0 -2px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`;
const DocumentLink = styled(Link)<{
$menuOpen?: boolean;
$isDragging?: boolean;
}>`
position: relative;
display: block;
padding: 12px;
width: 100%;
height: 100%;
border-radius: 8px;
height: 160px;
background: ${(props) => props.theme.slate};
color: ${(props) => props.theme.white};
cursor: var(--pointer);
background: ${(props) => props.theme.background};
transition: transform 50ms ease-in-out;
&:after {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.1));
border-radius: 8px;
pointer-events: none;
}
border: 1px solid ${(props) => props.theme.inputBorder};
border-bottom-width: 2px;
border-right-width: 2px;
${Actions} {
opacity: 0;
@@ -228,28 +251,22 @@ const DocumentLink = styled(Link)<{
&:active,
&:focus,
&:focus-within {
transform: ${(props) => (props.$isDragging ? "scale(1.1)" : "scale(1.08)")}
rotate(-2deg);
box-shadow: ${(props) =>
props.$isDragging
? "0 0 20px rgba(0,0,0,0.2);"
: "0 0 10px rgba(0,0,0,0.1)"};
z-index: 1;
${Fold} {
display: none;
}
${Actions} {
opacity: 1;
}
${(props) =>
!props.$isDragging &&
css`
&:after {
background: rgba(0, 0, 0, 0.1);
}
`}
}
${(props) =>
props.$menuOpen &&
css`
background: ${(props) => props.theme.listItemHoverBackground};
${Actions} {
opacity: 1;
}
`}
`;
const Heading = styled.h3`
@@ -259,7 +276,7 @@ const Heading = styled.h3`
max-height: 66px; // 3*line-height
overflow: hidden;
color: ${(props) => props.theme.white};
color: ${(props) => props.theme.text};
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
`;
+19
View File
@@ -0,0 +1,19 @@
import * as React from "react";
import { Editor } from "~/editor";
export type DocumentContextValue = {
/** The current editor instance for this document. */
editor: Editor | null;
/** Set the current editor instance for this document. */
setEditor: (editor: Editor) => void;
};
const DocumentContext = React.createContext<DocumentContextValue>({
editor: null,
// eslint-disable-next-line @typescript-eslint/no-empty-function
setEditor() {},
});
export const useDocumentContext = () => React.useContext(DocumentContext);
export default DocumentContext;
-133
View File
@@ -1,133 +0,0 @@
import { observer } from "mobx-react";
import { CloseIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory, useRouteMatch } from "react-router-dom";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Event from "~/models/Event";
import Button from "~/components/Button";
import Empty from "~/components/Empty";
import Flex from "~/components/Flex";
import PaginatedEventList from "~/components/PaginatedEventList";
import Scrollable from "~/components/Scrollable";
import useStores from "~/hooks/useStores";
import { documentUrl } from "~/utils/routeHelpers";
const EMPTY_ARRAY: Event[] = [];
function DocumentHistory() {
const { events, documents } = useStores();
const { t } = useTranslation();
const match = useRouteMatch<{ documentSlug: string }>();
const history = useHistory();
const document = documents.getByUrl(match.params.documentSlug);
const eventsInDocument = document
? events.inDocument(document.id)
: EMPTY_ARRAY;
const onCloseHistory = () => {
if (document) {
history.push(documentUrl(document));
} else {
history.goBack();
}
};
const items = React.useMemo(() => {
if (
eventsInDocument[0] &&
document &&
eventsInDocument[0].createdAt !== document.updatedAt
) {
eventsInDocument.unshift(
new Event(
{
name: "documents.latest_version",
documentId: document.id,
createdAt: document.updatedAt,
actor: document.updatedBy,
},
events
)
);
}
return eventsInDocument;
}, [eventsInDocument, events, document]);
return (
<Sidebar>
{document ? (
<Position column>
<Header>
<Title>{t("History")}</Title>
<Button
icon={<CloseIcon />}
onClick={onCloseHistory}
borderOnHover
neutral
/>
</Header>
<Scrollable topShadow>
<PaginatedEventList
aria-label={t("History")}
fetch={events.fetchPage}
events={items}
options={{
documentId: document.id,
}}
document={document}
empty={<Empty>{t("Oh weird, there's nothing here")}</Empty>}
/>
</Scrollable>
</Position>
) : null}
</Sidebar>
);
}
const Position = styled(Flex)`
position: fixed;
top: 0;
bottom: 0;
width: ${(props) => props.theme.sidebarWidth}px;
`;
const Sidebar = styled(Flex)`
display: none;
position: relative;
flex-shrink: 0;
background: ${(props) => props.theme.background};
width: ${(props) => props.theme.sidebarWidth}px;
border-left: 1px solid ${(props) => props.theme.divider};
z-index: 1;
${breakpoint("tablet")`
display: flex;
`};
`;
const Title = styled(Flex)`
font-size: 16px;
font-weight: 600;
text-align: center;
align-items: center;
justify-content: flex-start;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: 0;
flex-grow: 1;
`;
const Header = styled(Flex)`
align-items: center;
position: relative;
padding: 12px;
color: ${(props) => props.theme.text};
flex-shrink: 0;
`;
export default observer(DocumentHistory);
+1
View File
@@ -201,6 +201,7 @@ const DocumentLink = styled(Link)<{
border-radius: 8px;
max-height: 50vh;
width: calc(100vw - 8px);
cursor: var(--pointer);
&:focus-visible {
outline: none;
+27 -19
View File
@@ -12,30 +12,13 @@ import Time from "~/components/Time";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
const Container = styled(Flex)<{ rtl?: boolean }>`
justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")};
color: ${(props) => props.theme.textTertiary};
font-size: 13px;
white-space: nowrap;
overflow: hidden;
min-width: 0;
`;
const Viewed = styled.span`
text-overflow: ellipsis;
overflow: hidden;
`;
const Modified = styled.span<{ highlight?: boolean }>`
font-weight: ${(props) => (props.highlight ? "600" : "400")};
`;
type Props = {
showCollection?: boolean;
showPublished?: boolean;
showLastViewed?: boolean;
showParentDocuments?: boolean;
document: Document;
replace?: boolean;
to?: LocationDescriptor;
};
@@ -46,6 +29,7 @@ const DocumentMeta: React.FC<Props> = ({
showParentDocuments,
document,
children,
replace,
to,
...rest
}) => {
@@ -163,7 +147,13 @@ const DocumentMeta: React.FC<Props> = ({
return (
<Container align="center" rtl={document.dir === "rtl"} {...rest} dir="ltr">
{to ? <Link to={to}>{content}</Link> : content}
{to ? (
<Link to={to} replace={replace}>
{content}
</Link>
) : (
content
)}
{showCollection && collection && (
<span>
&nbsp;{t("in")}&nbsp;
@@ -192,4 +182,22 @@ const DocumentMeta: React.FC<Props> = ({
);
};
const Container = styled(Flex)<{ rtl?: boolean }>`
justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")};
color: ${(props) => props.theme.textTertiary};
font-size: 13px;
white-space: nowrap;
overflow: hidden;
min-width: 0;
`;
const Viewed = styled.span`
text-overflow: ellipsis;
overflow: hidden;
`;
const Modified = styled.span<{ highlight?: boolean }>`
font-weight: ${(props) => (props.highlight ? "600" : "400")};
`;
export default observer(DocumentMeta);
+21 -35
View File
@@ -2,13 +2,13 @@ import { LocationDescriptor } from "history";
import { observer, useObserver } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { usePopoverState, PopoverDisclosure } from "reakit/Popover";
import { Link, useRouteMatch } from "react-router-dom";
import styled from "styled-components";
import Document from "~/models/Document";
import DocumentMeta from "~/components/DocumentMeta";
import DocumentViews from "~/components/DocumentViews";
import Popover from "~/components/Popover";
import useStores from "~/hooks/useStores";
import { documentUrl, documentInsightsUrl } from "~/utils/routeHelpers";
import Fade from "./Fade";
type Props = {
document: Document;
@@ -20,46 +20,32 @@ type Props = {
function DocumentMetaWithViews({ to, isDraft, document, ...rest }: Props) {
const { views } = useStores();
const { t } = useTranslation();
const match = useRouteMatch();
const documentViews = useObserver(() => views.inDocument(document.id));
const totalViewers = documentViews.length;
const onlyYou = totalViewers === 1 && documentViews[0].user.id;
const viewsLoadedOnMount = React.useRef(totalViewers > 0);
React.useEffect(() => {
if (!document.isDeleted) {
views.fetchPage({
documentId: document.id,
});
}
}, [views, document.id, document.isDeleted]);
const popover = usePopoverState({
gutter: 8,
placement: "bottom",
modal: true,
});
const insightsUrl = documentInsightsUrl(document);
const Wrapper = viewsLoadedOnMount.current ? React.Fragment : Fade;
return (
<Meta document={document} to={to} {...rest}>
<Meta document={document} to={to} replace {...rest}>
{totalViewers && !isDraft ? (
<PopoverDisclosure {...popover}>
{(props) => (
<>
&nbsp;&nbsp;
<a {...props}>
{t("Viewed by")}{" "}
{onlyYou
? t("only you")
: `${totalViewers} ${
totalViewers === 1 ? t("person") : t("people")
}`}
</a>
</>
)}
</PopoverDisclosure>
<Wrapper>
&nbsp;&nbsp;
<Link
to={match.url === insightsUrl ? documentUrl(document) : insightsUrl}
>
{t("Viewed by")}{" "}
{onlyYou
? t("only you")
: `${totalViewers} ${
totalViewers === 1 ? t("person") : t("people")
}`}
</Link>
</Wrapper>
) : null}
<Popover {...popover} width={300} aria-label={t("Viewers")} tabIndex={0}>
<DocumentViews document={document} isOpen={popover.visible} />
</Popover>
</Meta>
);
}
+2 -1
View File
@@ -1,7 +1,8 @@
import { TFunction } from "i18next";
import { observer } from "mobx-react";
import { DoneIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, TFunction } from "react-i18next";
import { useTranslation } from "react-i18next";
import styled, { useTheme } from "styled-components";
import Document from "~/models/Document";
import CircularProgressBar from "~/components/CircularProgressBar";
+7 -7
View File
@@ -43,10 +43,10 @@ function DocumentViews({ document, isOpen }: Props) {
<PaginatedList
aria-label={t("Viewers")}
items={users}
renderItem={(item: User) => {
const view = documentViews.find((v) => v.user.id === item.id);
const isPresent = presentIds.includes(item.id);
const isEditing = editingIds.includes(item.id);
renderItem={(model: User) => {
const view = documentViews.find((v) => v.user.id === model.id);
const isPresent = presentIds.includes(model.id);
const isEditing = editingIds.includes(model.id);
const subtitle = isPresent
? isEditing
? t("Currently editing")
@@ -58,10 +58,10 @@ function DocumentViews({ document, isOpen }: Props) {
});
return (
<ListItem
key={item.id}
title={item.name}
key={model.id}
title={model.name}
subtitle={subtitle}
image={<Avatar key={item.id} src={item.avatarUrl} size={32} />}
image={<Avatar key={model.id} model={model} size={32} />}
border={false}
small
/>
+25 -16
View File
@@ -8,6 +8,7 @@ import { mergeRefs } from "react-merge-refs";
import { Optional } from "utility-types";
import insertFiles from "@shared/editor/commands/insertFiles";
import { Heading } from "@shared/editor/lib/getHeadings";
import { AttachmentPreset } from "@shared/types";
import { getDataTransferFiles } from "@shared/utils/files";
import parseDocumentSlug from "@shared/utils/parseDocumentSlug";
import { isInternalUrl } from "@shared/utils/urls";
@@ -25,13 +26,14 @@ import { NotFoundError } from "~/utils/errors";
import { uploadFile } from "~/utils/files";
import history from "~/utils/history";
import { isModKey } from "~/utils/keyboard";
import { sharedDocumentPath } from "~/utils/routeHelpers";
import { isHash } from "~/utils/urls";
import DocumentBreadcrumb from "./DocumentBreadcrumb";
const LazyLoadedEditor = React.lazy(
() =>
import(
/* webpackChunkName: "shared-editor" */
/* webpackChunkName: "preload-shared-editor" */
"~/editor"
)
);
@@ -48,23 +50,26 @@ export type Props = Optional<
> & {
shareId?: string | undefined;
embedsDisabled?: boolean;
grow?: boolean;
onHeadingsChange?: (headings: Heading[]) => void;
onSynced?: () => Promise<void>;
onPublish?: (event: React.MouseEvent) => any;
bottomPadding?: string;
};
function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
const { id, shareId, onChange, onHeadingsChange } = props;
const { documents } = useStores();
const { documents, auth } = useStores();
const { showToast } = useToasts();
const dictionary = useDictionary();
const embeds = useEmbeds(!shareId);
const localRef = React.useRef<SharedEditor>();
const preferences = auth.user?.preferences;
const previousHeadings = React.useRef<Heading[] | null>(null);
const [
activeLinkEvent,
setActiveLinkEvent,
] = React.useState<MouseEvent | null>(null);
const previousHeadings = React.useRef<Heading[] | null>(null);
const handleLinkActive = React.useCallback((event: MouseEvent) => {
setActiveLinkEvent(event);
@@ -131,6 +136,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
async (file: File) => {
const result = await uploadFile(file, {
documentId: id,
preset: AttachmentPreset.DocumentAttachment,
});
return result.url;
},
@@ -159,8 +165,10 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
}
}
if (shareId) {
navigateTo = `/share/${shareId}${navigateTo}`;
// 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);
@@ -172,8 +180,8 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
);
const focusAtEnd = React.useCallback(() => {
ref?.current?.focusAtEnd();
}, [ref]);
localRef?.current?.focusAtEnd();
}, [localRef]);
const handleDrop = React.useCallback(
(event: React.DragEvent<HTMLDivElement>) => {
@@ -181,7 +189,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
event.stopPropagation();
const files = getDataTransferFiles(event);
const view = ref?.current?.view;
const view = localRef?.current?.view;
if (!view) {
return;
}
@@ -225,7 +233,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
});
},
[
ref,
localRef,
props.onFileUploadStart,
props.onFileUploadStop,
dictionary,
@@ -246,7 +254,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
// Calculate if headings have changed and trigger callback if so
const updateHeadings = React.useCallback(() => {
if (onHeadingsChange) {
const headings = ref?.current?.getHeadings();
const headings = localRef?.current?.getHeadings();
if (
headings &&
headings.map((h) => h.level + h.title).join("") !==
@@ -256,7 +264,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
onHeadingsChange(headings);
}
}
}, [ref, onHeadingsChange]);
}, [localRef, onHeadingsChange]);
const handleChange = React.useCallback(
(event) => {
@@ -268,7 +276,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
const handleRefChanged = React.useCallback(
(node: SharedEditor | null) => {
if (node && !previousHeadings.current) {
if (node) {
updateHeadings();
}
},
@@ -279,10 +287,11 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
<ErrorBoundary reloadOnChunkMissing>
<>
<LazyLoadedEditor
ref={mergeRefs([ref, handleRefChanged])}
ref={mergeRefs([ref, localRef, handleRefChanged])}
uploadFile={onUploadFile}
onShowToast={showToast}
embeds={embeds}
userPreferences={preferences}
dictionary={dictionary}
{...props}
onHoverLink={handleLinkActive}
@@ -292,12 +301,12 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
placeholder={props.placeholder || ""}
defaultValue={props.defaultValue || ""}
/>
{props.grow && !props.readOnly && (
{props.bottomPadding && !props.readOnly && (
<ClickablePadding
onClick={focusAtEnd}
onDrop={handleDrop}
onDragOver={handleDragOver}
grow
minHeight={props.bottomPadding}
/>
)}
{activeLinkEvent && !shareId && (
+39 -24
View File
@@ -1,11 +1,13 @@
import { LocationDescriptor } from "history";
import { observer } from "mobx-react";
import {
TrashIcon,
ArchiveIcon,
EditIcon,
PublishIcon,
MoveIcon,
CheckboxIcon,
UnpublishIcon,
LightningIcon,
} from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
@@ -20,7 +22,7 @@ import CompositeItem, {
} from "~/components/List/CompositeItem";
import Item, { Actions } from "~/components/List/Item";
import Time from "~/components/Time";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import RevisionMenu from "~/menus/RevisionMenu";
import { documentHistoryUrl } from "~/utils/routeHelpers";
@@ -32,36 +34,45 @@ type Props = {
const EventListItem = ({ event, latest, document, ...rest }: Props) => {
const { t } = useTranslation();
const { revisions } = useStores();
const location = useLocation();
const can = usePolicy(document);
const opts = {
userName: event.actor.name,
};
const isRevision = event.name === "revisions.create";
let meta, icon, to;
let meta, icon, to: LocationDescriptor | undefined;
const ref = React.useRef<HTMLAnchorElement>(null);
// the time component tends to steal focus when clicked
// ...so forward the focus back to the parent item
const handleTimeClick = React.useCallback(() => {
const handleTimeClick = () => {
ref.current?.focus();
}, [ref]);
};
const prefetchRevision = () => {
if (event.name === "revisions.create" && event.modelId) {
revisions.fetch(event.modelId);
}
};
switch (event.name) {
case "revisions.create":
case "documents.latest_version": {
if (latest) {
icon = <CheckboxIcon color="currentColor" size={16} checked />;
meta = t("Latest version");
to = documentHistoryUrl(document);
break;
} else {
icon = <EditIcon color="currentColor" size={16} />;
meta = t("{{userName}} edited", opts);
to = documentHistoryUrl(document, event.modelId || "");
break;
}
}
icon = <EditIcon color="currentColor" size={16} />;
meta = t("{{userName}} edited", opts);
to = {
pathname: documentHistoryUrl(document, event.modelId || ""),
state: { retainScrollPosition: true },
};
break;
case "documents.live_editing":
icon = <LightningIcon color="currentColor" size={16} />;
meta = t("Latest");
to = {
pathname: documentHistoryUrl(document),
state: { retainScrollPosition: true },
};
break;
case "documents.archive":
icon = <ArchiveIcon color="currentColor" size={16} />;
@@ -104,7 +115,10 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
return null;
}
const isActive = location.pathname === to;
const isActive =
typeof to === "string"
? location.pathname === to
: location.pathname === to?.pathname;
if (document.isDeleted) {
to = undefined;
@@ -128,7 +142,7 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
onClick={handleTimeClick}
/>
}
image={<Avatar src={event.actor?.avatarUrl} size={32} />}
image={<Avatar model={event.actor} size={32} />}
subtitle={
<Subtitle>
{icon}
@@ -136,10 +150,11 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
</Subtitle>
}
actions={
isRevision && isActive && event.modelId && can.update ? (
isRevision && isActive && event.modelId ? (
<RevisionMenu document={document} revisionId={event.modelId} />
) : undefined
}
onMouseEnter={prefetchRevision}
ref={ref}
{...rest}
/>
@@ -166,7 +181,7 @@ const Subtitle = styled.span`
const ItemStyle = css`
border: 0;
position: relative;
margin: 8px;
margin: 8px 0;
padding: 8px;
border-radius: 8px;
@@ -217,4 +232,4 @@ const CompositeListItem = styled(CompositeItem)`
${ItemStyle}
`;
export default EventListItem;
export default observer(EventListItem);
+142
View File
@@ -0,0 +1,142 @@
import { observer } from "mobx-react";
import { CodeIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import styled from "styled-components";
import { FileOperationFormat } from "@shared/types";
import Collection from "~/models/Collection";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Flex from "~/components/Flex";
import MarkdownIcon from "~/components/Icons/MarkdownIcon";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
collection?: Collection;
onSubmit: () => void;
};
function ExportDialog({ collection, onSubmit }: Props) {
const [format, setFormat] = React.useState<FileOperationFormat>(
FileOperationFormat.MarkdownZip
);
const { showToast } = useToasts();
const { collections, notificationSettings } = useStores();
const { t } = useTranslation();
React.useEffect(() => {
notificationSettings.fetchPage({});
}, [notificationSettings]);
const handleFormatChange = React.useCallback(
(ev: React.ChangeEvent<HTMLInputElement>) => {
setFormat(ev.target.value as FileOperationFormat);
},
[]
);
const handleSubmit = async () => {
if (collection) {
await collection.export(format);
} else {
await collections.export(format);
}
onSubmit();
showToast(t("Export started"), { type: "success" });
};
return (
<ConfirmationDialog onSubmit={handleSubmit} submitText={t("Export")}>
{collection && (
<Text>
<Trans
defaults="Exporting the collection <em>{{collectionName}}</em> may take some time."
values={{
collectionName: collection.name,
}}
components={{
em: <strong />,
}}
/>{" "}
{notificationSettings.getByEvent("emails.export_completed") &&
t("You will receive an email when it's complete.")}
</Text>
)}
<Flex gap={12} column>
<Option>
<Input
type="radio"
name="format"
value={FileOperationFormat.MarkdownZip}
checked={format === FileOperationFormat.MarkdownZip}
onChange={handleFormatChange}
/>
<Format>
<MarkdownIcon size={32} color="currentColor" />
Markdown
</Format>
<Text size="small">
<Trans>
A ZIP file containing the images, and documents in the Markdown
format.
</Trans>
</Text>
</Option>
<Option>
<Input
type="radio"
name="format"
value={FileOperationFormat.HTMLZip}
checked={format === FileOperationFormat.HTMLZip}
onChange={handleFormatChange}
/>
<Format>
<CodeIcon size={32} color="currentColor" />
HTML
</Format>
<Text size="small">
<Trans>
A ZIP file containing the images, and documents as HTML files.
</Trans>
</Text>
</Option>
</Flex>
</ConfirmationDialog>
);
}
const Format = styled.div`
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
background: ${(props) => props.theme.secondaryBackground};
border-radius: 6px;
width: 25%;
font-weight: 500;
font-size: 14px;
text-align: center;
padding: 10px 8px;
cursor: var(--pointer);
`;
const Option = styled.label`
display: flex;
align-items: center;
gap: 16px;
p {
margin: 0;
}
`;
const Input = styled.input`
display: none;
&:checked + ${Format} {
box-shadow: inset 0 0 0 2px ${(props) => props.theme.inputBorderFocused};
}
`;
export default observer(ExportDialog);
+9 -5
View File
@@ -9,7 +9,7 @@ type Props = {
users: User[];
size?: number;
overflow?: number;
onClick?: React.MouseEventHandler<HTMLDivElement>;
limit?: number;
renderAvatar?: (user: User) => React.ReactNode;
};
@@ -17,6 +17,7 @@ function Facepile({
users,
overflow = 0,
size = 32,
limit = 8,
renderAvatar = DefaultAvatar,
...rest
}: Props) {
@@ -24,10 +25,13 @@ function Facepile({
<Avatars {...rest}>
{overflow > 0 && (
<More size={size}>
<span>+{overflow}</span>
<span>
{users.length ? "+" : ""}
{overflow}
</span>
</More>
)}
{users.map((user) => (
{users.slice(0, limit).map((user) => (
<AvatarWrapper key={user.id}>{renderAvatar(user)}</AvatarWrapper>
))}
</Avatars>
@@ -35,7 +39,7 @@ function Facepile({
}
function DefaultAvatar(user: User) {
return <Avatar user={user} src={user.avatarUrl} size={32} />;
return <Avatar model={user} size={32} />;
}
const AvatarWrapper = styled.div`
@@ -65,7 +69,7 @@ const More = styled.div<{ size: number }>`
const Avatars = styled(Flex)`
align-items: center;
flex-direction: row-reverse;
cursor: pointer;
cursor: var(--pointer);
`;
export default observer(Facepile);
+8
View File
@@ -10,6 +10,7 @@ type TFilterOption = {
key: string;
label: string;
note?: string;
icon?: React.ReactNode;
};
type Props = {
@@ -57,6 +58,7 @@ const FilterOptions = ({
selected={option.key === activeKey}
{...menu}
>
{option.icon && <Icon>{option.icon}</Icon>}
{option.note ? (
<LabelWithNote>
{option.label}
@@ -106,6 +108,12 @@ const StyledButton = styled(Button)`
}
`;
const Icon = styled.div`
margin-right: 8px;
width: 18px;
height: 18px;
`;
const Wrapper = styled.div`
margin-right: 8px;
`;
-29
View File
@@ -1,29 +0,0 @@
import * as React from "react";
type Props = {
size?: number;
fill?: string;
className?: string;
};
function GithubLogo({ size = 34, fill = "#FFF", className }: Props) {
return (
<svg
fill={fill}
width={size}
height={size}
viewBox="0 0 36 36"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="#191717"
d="M18,1.4C9,1.4,1.7,8.7,1.7,17.7c0,7.2,4.7,13.3,11.1,15.5 c0.8,0.1,1.1-0.4,1.1-0.8c0-0.4,0-1.4,0-2.8c-4.5,1-5.5-2.2-5.5-2.2c-0.7-1.9-1.8-2.4-1.8-2.4c-1.5-1,0.1-1,0.1-1 c1.6,0.1,2.5,1.7,2.5,1.7c1.5,2.5,3.8,1.8,4.7,1.4c0.1-1.1,0.6-1.8,1-2.2c-3.6-0.4-7.4-1.8-7.4-8.1c0-1.8,0.6-3.2,1.7-4.4 c-0.2-0.4-0.7-2.1,0.2-4.3c0,0,1.4-0.4,4.5,1.7c1.3-0.4,2.7-0.5,4.1-0.5c1.4,0,2.8,0.2,4.1,0.5c3.1-2.1,4.5-1.7,4.5-1.7 c0.9,2.2,0.3,3.9,0.2,4.3c1,1.1,1.7,2.6,1.7,4.4c0,6.3-3.8,7.6-7.4,8c0.6,0.5,1.1,1.5,1.1,3c0,2.2,0,3.9,0,4.5 c0,0.4,0.3,0.9,1.1,0.8c6.5-2.2,11.1-8.3,11.1-15.5C34.3,8.7,27,1.4,18,1.4z"
/>
</svg>
);
}
export default GithubLogo;
+57 -68
View File
@@ -1,10 +1,9 @@
import { observable } from "mobx";
import { observer } from "mobx-react";
import { GroupIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { MAX_AVATAR_DISPLAY } from "@shared/constants";
import RootStore from "~/stores/RootStore";
import CollectionGroupMembership from "~/models/CollectionGroupMembership";
import Group from "~/models/Group";
import GroupMembers from "~/scenes/GroupMembers";
@@ -12,9 +11,11 @@ import Facepile from "~/components/Facepile";
import Flex from "~/components/Flex";
import ListItem from "~/components/List/Item";
import Modal from "~/components/Modal";
import withStores from "~/components/withStores";
import useBoolean from "~/hooks/useBoolean";
import useStores from "~/hooks/useStores";
import NudeButton from "./NudeButton";
type Props = RootStore & {
type Props = {
group: Group;
membership?: CollectionGroupMembership;
showFacepile?: boolean;
@@ -22,69 +23,57 @@ type Props = RootStore & {
renderActions: (params: { openMembersModal: () => void }) => React.ReactNode;
};
@observer
class GroupListItem extends React.Component<Props> {
@observable
membersModalOpen = false;
function GroupListItem({ group, showFacepile, renderActions }: Props) {
const { groupMemberships } = useStores();
const { t } = useTranslation();
const [
membersModalOpen,
setMembersModalOpen,
setMembersModalClosed,
] = useBoolean();
const memberCount = group.memberCount;
const membershipsInGroup = groupMemberships.inGroup(group.id);
const users = membershipsInGroup
.slice(0, MAX_AVATAR_DISPLAY)
.map((gm) => gm.user);
const overflow = memberCount - users.length;
handleMembersModalOpen = () => {
this.membersModalOpen = true;
};
handleMembersModalClose = () => {
this.membersModalOpen = false;
};
render() {
const { group, groupMemberships, showFacepile, renderActions } = this.props;
const memberCount = group.memberCount;
const membershipsInGroup = groupMemberships.inGroup(group.id);
const users = membershipsInGroup
.slice(0, MAX_AVATAR_DISPLAY)
.map((gm) => gm.user);
const overflow = memberCount - users.length;
return (
<>
<ListItem
image={
<Image>
<GroupIcon size={24} />
</Image>
}
title={
<Title onClick={this.handleMembersModalOpen}>{group.name}</Title>
}
subtitle={
<>
{memberCount} member{memberCount === 1 ? "" : "s"}
</>
}
actions={
<Flex align="center" gap={8}>
{showFacepile && (
<Facepile
onClick={this.handleMembersModalOpen}
users={users}
overflow={overflow}
/>
)}
{renderActions({
openMembersModal: this.handleMembersModalOpen,
})}
</Flex>
}
/>
<Modal
title="Group members"
onRequestClose={this.handleMembersModalClose}
isOpen={this.membersModalOpen}
>
<GroupMembers group={group} />
</Modal>
</>
);
}
return (
<>
<ListItem
image={
<Image>
<GroupIcon size={24} />
</Image>
}
title={<Title onClick={setMembersModalOpen}>{group.name}</Title>}
subtitle={t("{{ count }} member", { count: memberCount })}
actions={
<Flex align="center" gap={8}>
{showFacepile && (
<NudeButton
width="auto"
height="auto"
onClick={setMembersModalOpen}
>
<Facepile users={users} overflow={overflow} />
</NudeButton>
)}
{renderActions({
openMembersModal: setMembersModalOpen,
})}
</Flex>
}
/>
<Modal
title={t("Group members")}
onRequestClose={setMembersModalClosed}
isOpen={membersModalOpen}
>
<GroupMembers group={group} />
</Modal>
</>
);
}
const Image = styled(Flex)`
@@ -99,8 +88,8 @@ const Image = styled(Flex)`
const Title = styled.span`
&:hover {
text-decoration: underline;
cursor: pointer;
cursor: var(--pointer);
}
`;
export default withStores(GroupListItem);
export default observer(GroupListItem);
+24 -10
View File
@@ -12,22 +12,23 @@ import Flex from "~/components/Flex";
import useEventListener from "~/hooks/useEventListener";
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 = {
breadcrumb?: React.ReactNode;
left?: React.ReactNode;
title: React.ReactNode;
actions?: React.ReactNode;
hasSidebar?: boolean;
};
function Header({ breadcrumb, title, actions, hasSidebar }: Props) {
function Header({ left, title, actions, hasSidebar }: Props) {
const { ui } = useStores();
const isMobile = useMobile();
const hasMobileSidebar = hasSidebar && isMobile;
const passThrough = !actions && !breadcrumb && !title;
const passThrough = !actions && !left && !title;
const [isScrolled, setScrolled] = React.useState(false);
const handleScroll = React.useMemo(
@@ -50,8 +51,13 @@ function Header({ breadcrumb, title, actions, hasSidebar }: Props) {
}, []);
return (
<Wrapper align="center" shrink={false} $passThrough={passThrough}>
{breadcrumb || hasMobileSidebar ? (
<Wrapper
align="center"
shrink={false}
$passThrough={passThrough}
$insetTitleAdjust={ui.sidebarIsClosed && Desktop.hasInsetTitlebar()}
>
{left || hasMobileSidebar ? (
<Breadcrumbs>
{hasMobileSidebar && (
<MobileMenuButton
@@ -61,7 +67,7 @@ function Header({ breadcrumb, title, actions, hasSidebar }: Props) {
neutral
/>
)}
{breadcrumb}
{left}
</Breadcrumbs>
) : null}
@@ -98,7 +104,12 @@ const Actions = styled(Flex)`
`};
`;
const Wrapper = styled(Flex)<{ $passThrough?: boolean }>`
type WrapperProps = {
$passThrough?: boolean;
$insetTitleAdjust?: boolean;
};
const Wrapper = styled(Flex)<WrapperProps>`
top: 0;
z-index: ${depths.header};
position: sticky;
@@ -120,6 +131,8 @@ const Wrapper = styled(Flex)<{ $passThrough?: boolean }>`
transform: translate3d(0, 0, 0);
min-height: 64px;
justify-content: flex-start;
${draggableOnDesktop()}
${fadeOnDesktopBackgrounded()}
@supports (backdrop-filter: blur(20px)) {
backdrop-filter: blur(20px);
@@ -133,7 +146,8 @@ const Wrapper = styled(Flex)<{ $passThrough?: boolean }>`
${breakpoint("tablet")`
padding: 16px;
justify-content: center;
`};
${(props: WrapperProps) => props.$insetTitleAdjust && `padding-left: 64px;`}
`};
`;
const Title = styled("div")`
@@ -143,7 +157,7 @@ const Title = styled("div")`
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
cursor: var(--pointer);
min-width: 0;
${breakpoint("tablet")`
+1 -1
View File
@@ -50,7 +50,7 @@ function HoverPreviewDocument({ url, children }: Props) {
}
const Content = styled(Link)`
cursor: pointer;
cursor: var(--pointer);
`;
const Heading = styled.h2`
+12 -8
View File
@@ -257,7 +257,7 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
);
})}
</Icons>
<Flex>
<Colors>
<React.Suspense fallback={<Loading>{t("Loading")}</Loading>}>
<ColorPicker
color={color}
@@ -266,6 +266,10 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
triangle="hide"
styles={{
default: {
body: {
padding: 0,
marginRight: -8,
},
hash: {
color: theme.text,
background: theme.inputBorder,
@@ -279,7 +283,7 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
}}
/>
</React.Suspense>
</Flex>
</Colors>
</ContextMenu>
</Wrapper>
);
@@ -289,12 +293,16 @@ const Icon = styled.svg`
transition: fill 150ms ease-in-out;
`;
const Colors = styled(Flex)`
padding: 8px;
`;
const Label = styled.label`
display: block;
`;
const Icons = styled.div`
padding: 16px 8px 0 16px;
padding: 8px;
${breakpoint("tablet")`
width: 276px;
@@ -321,11 +329,7 @@ const Loading = styled(Text)`
const ColorPicker = styled(TwitterPicker)`
box-shadow: none !important;
background: transparent !important;
width: auto !important;
${breakpoint("tablet")`
width: 276px;
`};
width: 100% !important;
`;
const Wrapper = styled("div")`
@@ -8,9 +8,13 @@ import useStores from "~/hooks/useStores";
import Logger from "~/utils/Logger";
type Props = {
/** The collection to show an icon for */
collection: Collection;
/** Whether the icon should be the "expanded" graphic when displaying the default collection icon */
expanded?: boolean;
/** The size of the icon, 24px is default to match standard icons */
size?: number;
/** The color of the icon, defaults to the collection color */
color?: string;
};
+25
View File
@@ -0,0 +1,25 @@
import * as React from "react";
type Props = {
/** The size of the icon, 24px is default to match standard icons */
size?: number;
/** The color of the icon, defaults to the current text color */
color?: string;
};
export default function GoogleIcon({
size = 24,
color = "currentColor",
}: Props) {
return (
<svg
fill={color}
width={size}
height={size}
viewBox="-8 -8 48 48"
version="1.1"
>
<path d="M32.6162791,13.9090909 L16.8837209,13.9090909 L16.8837209,20.4772727 L25.9395349,20.4772727 C25.0953488,24.65 21.5651163,27.0454545 16.8837209,27.0454545 C11.3581395,27.0454545 6.90697674,22.5636364 6.90697674,17 C6.90697674,11.4363636 11.3581395,6.95454545 16.8837209,6.95454545 C19.2627907,6.95454545 21.4116279,7.80454545 23.1,9.19545455 L28.0116279,4.25 C25.0186047,1.62272727 21.1813953,0 16.8837209,0 C7.52093023,0 0,7.57272727 0,17 C0,26.4272727 7.52093023,34 16.8837209,34 C25.3255814,34 33,27.8181818 33,17 C33,15.9954545 32.8465116,14.9136364 32.6162791,13.9090909 Z" />
</svg>
);
}
+33
View File
@@ -0,0 +1,33 @@
import * as React from "react";
type Props = {
/** The size of the icon, 24px is default to match standard icons */
size?: number;
/** The color of the icon, defaults to the current text color */
color?: string;
};
export default function MarkdownIcon({
size = 24,
color = "currentColor",
}: Props) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.2692 7H3.86538C3.38745 7 3 7.38476 3 7.85938V16.2812C3 16.7559 3.38745 17.1406 3.86538 17.1406H19.2692C19.7472 17.1406 20.1346 16.7559 20.1346 16.2812V7.85938C20.1346 7.38476 19.7472 7 19.2692 7Z"
stroke={color}
stroke-width="2"
/>
<path
d="M5.16345 14.9922V9.14844H6.89422L8.62499 11.2969L10.3558 9.14844H12.0865V14.9922H10.3558V11.6406L8.62499 13.7891L6.89422 11.6406V14.9922H5.16345ZM15.9808 14.9922L13.3846 12.1562H15.1154V9.14844H16.8461V12.1562H18.5769L15.9808 14.9922Z"
fill={color}
/>
</svg>
);
}
+28
View File
@@ -0,0 +1,28 @@
import * as React from "react";
type Props = {
/** The size of the icon, 24px is default to match standard icons */
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 */
cover?: boolean;
};
export default function OutlineIcon({
size = 24,
cover,
color = "currentColor",
}: Props) {
return (
<svg
fill={color}
width={size}
height={size}
viewBox={cover ? "2 2 20 20" : "0 0 24 24"}
version="1.1"
>
<path d="M14.6667 20.2155V20.7163C14.6667 21.4253 14.0697 22 13.3333 22C13.1044 22 12.8792 21.9432 12.6797 21.8351L4.67965 17.5028C4.25982 17.2754 4 16.8478 4 16.384V7.61623C4 7.15248 4.25982 6.72478 4.67965 6.49742L12.6797 2.16508C13.3215 1.81751 14.1344 2.03666 14.4954 2.65456C14.6077 2.8467 14.6667 3.06343 14.6667 3.28388V3.78471L15.6169 3.51027C16.3222 3.30655 17.0655 3.69189 17.2771 4.37093C17.3144 4.49059 17.3333 4.61486 17.3333 4.73979V5.26091L18.5013 5.12036C19.232 5.03242 19.8984 5.53141 19.9897 6.23488C19.9966 6.2877 20 6.34088 20 6.3941V17.6061C20 18.3151 19.403 18.8898 18.6667 18.8898C18.6114 18.8898 18.5561 18.8865 18.5013 18.8799L17.3333 18.7393V19.2604C17.3333 19.9694 16.7364 20.5441 16 20.5441C15.8702 20.5441 15.7412 20.5259 15.6169 20.49L14.6667 20.2155ZM14.6667 18.8753L16 19.2604V4.73979L14.6667 5.12488V18.8753ZM17.3333 6.55456V17.4457L18.6667 17.6061V6.3941L17.3333 6.55456ZM5.33333 7.61623V16.384L13.3333 20.7163V3.28388L5.33333 7.61623ZM6.66667 8.47006L8 7.82823V16.172L6.66667 15.5302V8.47006Z" />
</svg>
);
}
@@ -1,15 +1,21 @@
import * as React from "react";
type Props = {
/** The size of the icon, 24px is default to match standard icons */
size?: number;
/** The color of the icon, defaults to the current text color */
color?: string;
};
export default function SlackIcon({ color = "#4E5C6E" }: Props) {
export default function SlackIcon({
size = 24,
color = "currentColor",
}: Props) {
return (
<svg
fill={color}
width="24px"
height="24px"
width={size}
height={size}
viewBox="0 0 24 24"
version="1.1"
>
@@ -1,15 +1,21 @@
import * as React from "react";
type Props = {
/** The size of the icon, 24px is default to match standard icons */
size?: number;
/** The color of the icon, defaults to the current text color */
color?: string;
};
export default function ZapierIcon({ color = "#4E5C6E" }: Props) {
export default function ZapierIcon({
size = 24,
color = "currentColor",
}: Props) {
return (
<svg
width={size}
height={size}
fill={color}
width="24px"
height="24px"
viewBox="0 0 24 24"
version="1.1"
>
+87 -72
View File
@@ -1,10 +1,10 @@
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import { undraggableOnDesktop } from "~/styles";
const RealTextarea = styled.textarea<{ hasIcon?: boolean }>`
border: 0;
@@ -32,6 +32,7 @@ const RealInput = styled.input<{ hasIcon?: boolean }>`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
${undraggableOnDesktop()}
&:disabled,
&::placeholder {
@@ -58,11 +59,13 @@ const Wrapper = styled.div<{
flex?: boolean;
short?: boolean;
minHeight?: number;
minWidth?: number;
maxHeight?: number;
}>`
flex: ${(props) => (props.flex ? "1" : "0")};
width: ${(props) => (props.short ? "49%" : "auto")};
max-width: ${(props) => (props.short ? "350px" : "100%")};
min-width: ${({ minWidth }) => (minWidth ? `${minWidth}px` : "initial")};
min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : "0")};
max-height: ${({ maxHeight }) => (maxHeight ? `${maxHeight}px` : "initial")};
`;
@@ -96,6 +99,9 @@ export const Outline = styled(Flex)<{
align-items: center;
overflow: hidden;
background: ${(props) => props.theme.background};
/* Prevents an issue where input placeholder appears in a selected style when double clicking title bar */
user-select: none;
`;
export const LabelText = styled.div`
@@ -113,92 +119,101 @@ export type Props = React.InputHTMLAttributes<
flex?: boolean;
short?: boolean;
margin?: string | number;
error?: string;
icon?: React.ReactNode;
innerRef?: React.Ref<any>;
onFocus?: (ev: React.SyntheticEvent) => unknown;
onBlur?: (ev: React.SyntheticEvent) => unknown;
};
@observer
class Input extends React.Component<Props> {
input = this.props.innerRef;
function Input(
props: Props,
ref: React.RefObject<HTMLInputElement | HTMLTextAreaElement>
) {
const [focused, setFocused] = React.useState(false);
@observable
focused = false;
const handleBlur = (ev: React.SyntheticEvent) => {
setFocused(false);
handleBlur = (ev: React.SyntheticEvent) => {
this.focused = false;
if (this.props.onBlur) {
this.props.onBlur(ev);
if (props.onBlur) {
props.onBlur(ev);
}
};
handleFocus = (ev: React.SyntheticEvent) => {
this.focused = true;
const handleFocus = (ev: React.SyntheticEvent) => {
setFocused(true);
if (this.props.onFocus) {
this.props.onFocus(ev);
if (props.onFocus) {
props.onFocus(ev);
}
};
render() {
const {
type = "text",
icon,
label,
margin,
className,
short,
flex,
labelHidden,
onFocus,
onBlur,
...rest
} = this.props;
const {
type = "text",
icon,
label,
margin,
error,
className,
short,
flex,
labelHidden,
onFocus,
onBlur,
...rest
} = props;
const wrappedLabel = <LabelText>{label}</LabelText>;
const wrappedLabel = <LabelText>{label}</LabelText>;
return (
<Wrapper className={className} short={short} flex={flex}>
<label>
{label &&
(labelHidden ? (
<VisuallyHidden>{wrappedLabel}</VisuallyHidden>
) : (
wrappedLabel
))}
<Outline focused={this.focused} margin={margin}>
{icon && <IconWrapper>{icon}</IconWrapper>}
{type === "textarea" ? (
<RealTextarea
ref={this.props.innerRef}
onBlur={this.props.onBlur}
onFocus={this.handleFocus}
hasIcon={!!icon}
{...rest}
/>
) : (
<RealInput
ref={this.props.innerRef}
onBlur={this.props.onBlur}
onFocus={this.handleFocus}
hasIcon={!!icon}
type={type}
{...rest}
/>
)}
</Outline>
</label>
</Wrapper>
);
}
return (
<Wrapper className={className} short={short} flex={flex}>
<label>
{label &&
(labelHidden ? (
<VisuallyHidden>{wrappedLabel}</VisuallyHidden>
) : (
wrappedLabel
))}
<Outline focused={focused} margin={margin}>
{icon && <IconWrapper>{icon}</IconWrapper>}
{type === "textarea" ? (
<RealTextarea
ref={ref as React.RefObject<HTMLTextAreaElement>}
onBlur={handleBlur}
onFocus={handleFocus}
hasIcon={!!icon}
{...rest}
/>
) : (
<RealInput
ref={ref as React.RefObject<HTMLInputElement>}
onBlur={handleBlur}
onFocus={handleFocus}
hasIcon={!!icon}
type={type}
{...rest}
/>
)}
</Outline>
</label>
{error && (
<TextWrapper>
<StyledText type="danger" size="xsmall">
{error}
</StyledText>
</TextWrapper>
)}
</Wrapper>
);
}
export const ReactHookWrappedInput = React.forwardRef(
(props: Omit<Props, "innerRef">, ref: React.Ref<any>) => {
return <Input {...{ ...props, innerRef: ref }} />;
}
);
export const TextWrapper = styled.span`
min-height: 16px;
display: block;
margin-top: -16px;
`;
export default Input;
export const StyledText = styled(Text)`
margin-bottom: 0;
`;
export default React.forwardRef(Input);
+1 -1
View File
@@ -42,7 +42,7 @@ function InputSearch(
onBlur={handleBlur}
margin={0}
labelHidden
innerRef={ref}
ref={ref}
{...rest}
/>
);
+7 -6
View File
@@ -35,14 +35,11 @@ function InputSearchPage({
const history = useHistory();
const { t } = useTranslation();
const [isFocused, setFocused, setUnfocused] = useBoolean(false);
const focus = React.useCallback(() => {
inputRef.current?.focus();
}, []);
useKeyDown("f", (ev: KeyboardEvent) => {
if (isModKey(ev)) {
if (isModKey(ev) && document.activeElement !== inputRef.current) {
ev.preventDefault();
focus();
inputRef.current?.focus();
}
});
@@ -57,6 +54,10 @@ function InputSearchPage({
})
);
}
if (ev.key === "Escape") {
ev.preventDefault();
inputRef.current?.blur();
}
if (onKeyDown) {
onKeyDown(ev);
@@ -67,7 +68,7 @@ function InputSearchPage({
return (
<InputMaxWidth
innerRef={inputRef}
ref={inputRef}
type="search"
placeholder={placeholder || `${t("Search")}`}
value={value}
+37 -36
View File
@@ -8,12 +8,18 @@ import {
import { CheckmarkIcon } from "outline-icons";
import * as React from "react";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import scrollIntoView from "smooth-scroll-into-view-if-needed";
import styled, { css } from "styled-components";
import Button, { Inner } from "~/components/Button";
import Text from "~/components/Text";
import useMenuHeight from "~/hooks/useMenuHeight";
import { Position, Background, Backdrop, Placement } from "./ContextMenu";
import useMobile from "~/hooks/useMobile";
import { fadeAndScaleIn } from "~/styles/animations";
import {
Position,
Background as ContextMenuBackground,
Backdrop,
Placement,
} from "./ContextMenu";
import { MenuAnchorCSS } from "./ContextMenu/MenuItem";
import { LabelText } from "./Input";
@@ -36,7 +42,7 @@ export type Props = {
icon?: React.ReactNode;
options: Option[];
note?: React.ReactNode;
onChange: (value: string | null) => void;
onChange?: (value: string | null) => void;
};
const getOptionFromValue = (options: Option[], value: string | null) => {
@@ -72,16 +78,25 @@ const InputSelect = (props: Props) => {
disabled,
});
const isMobile = useMobile();
const previousValue = React.useRef<string | null>(value);
const contentRef = React.useRef<HTMLDivElement>(null);
const selectedRef = React.useRef<HTMLDivElement>(null);
const buttonRef = React.useRef<HTMLButtonElement>(null);
const [offset, setOffset] = React.useState(0);
const contentRef = React.useRef<HTMLDivElement>(null);
const minWidth = buttonRef.current?.offsetWidth || 0;
const maxHeight = useMenuHeight(
const margin = 8;
const menuMaxHeight = useMenuHeight(
select.visible,
select.unstable_disclosureRef
select.unstable_disclosureRef,
margin
);
const maxHeight = Math.min(
menuMaxHeight ?? 0,
window.innerHeight -
(buttonRef.current?.getBoundingClientRect().bottom ?? 0) -
margin
);
const wrappedLabel = <LabelText>{label}</LabelText>;
const selectedValueIndex = options.findIndex(
(option) => option.value === select.selectedValue
@@ -94,32 +109,21 @@ const InputSelect = (props: Props) => {
previousValue.current = select.selectedValue;
async function load() {
await onChange(select.selectedValue);
await onChange?.(select.selectedValue);
}
load();
}, [onChange, select.selectedValue]);
// Ensure selected option is visible when opening the input
React.useEffect(() => {
if (!select.animating && selectedRef.current) {
scrollIntoView(selectedRef.current, {
scrollMode: "if-needed",
behavior: "auto",
block: "start",
});
}
}, [select.animating]);
React.useLayoutEffect(() => {
if (select.visible) {
const offset = Math.round(
(selectedRef.current?.getBoundingClientRect().top || 0) -
(contentRef.current?.getBoundingClientRect().top || 0)
);
setOffset(offset);
requestAnimationFrame(() => {
if (contentRef.current) {
contentRef.current.scrollTop = selectedValueIndex * 32;
}
});
}
}, [select.visible]);
}, [select.visible, selectedValueIndex]);
return (
<>
@@ -152,17 +156,9 @@ const InputSelect = (props: Props) => {
placement: Placement;
}
) => {
if (!props.style) {
props.style = {};
}
const topAnchor = props.style.top === "0";
const topAnchor = props.style?.top === "0";
const rightAnchor = props.placement === "bottom-end";
// offset top of select to place selected item under the cursor
if (selectedValueIndex !== -1) {
props.style.top = `-${offset + 32}px`;
}
return (
<Positioner {...props}>
<Background
@@ -170,6 +166,7 @@ const InputSelect = (props: Props) => {
ref={contentRef}
topAnchor={topAnchor}
rightAnchor={rightAnchor}
hiddenScrollbars
style={
maxHeight && topAnchor
? {
@@ -211,11 +208,15 @@ const InputSelect = (props: Props) => {
{note}
</Text>
)}
{select.visible && <Backdrop />}
{select.visible && isMobile && <Backdrop />}
</>
);
};
const Background = styled(ContextMenuBackground)`
animation: ${fadeAndScaleIn} 200ms ease;
`;
const Placeholder = styled.span`
color: ${(props) => props.theme.placeholder};
`;
@@ -277,7 +278,7 @@ const Positioner = styled(Position)`
color: ${(props) => props.theme.white};
background: ${(props) => props.theme.primary};
box-shadow: none;
cursor: pointer;
cursor: var(--pointer);
svg {
fill: ${(props) => props.theme.white};
+1 -1
View File
@@ -21,7 +21,7 @@ export default function InputSelectPermission(
value = "";
}
onChange(value);
onChange?.(value);
},
[onChange]
);
+3 -1
View File
@@ -6,6 +6,7 @@ import { languages, languageOptions } from "@shared/i18n";
import ButtonLink from "~/components/ButtonLink";
import Flex from "~/components/Flex";
import NoticeTip from "~/components/NoticeTip";
import env from "~/env";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import { detectLanguage } from "~/utils/language";
@@ -57,6 +58,7 @@ export default function LanguagePrompt() {
const option = find(languageOptions, (o) => o.value === language);
const optionLabel = option ? option.label : "";
const appName = env.APP_NAME;
return (
<NoticeTip>
@@ -64,7 +66,7 @@ export default function LanguagePrompt() {
<LanguageIcon />
<span>
<Trans>
Outline is available in your language{" "}
{{ appName }} is available in your language{" "}
{{
optionLabel,
}}
+11 -5
View File
@@ -7,6 +7,7 @@ import Flex from "~/components/Flex";
import { LoadingIndicatorBar } from "~/components/LoadingIndicator";
import SkipNavContent from "~/components/SkipNavContent";
import SkipNavLink from "~/components/SkipNavLink";
import env from "~/env";
import useKeyDown from "~/hooks/useKeyDown";
import { MenuProvider } from "~/hooks/useMenuContext";
import useStores from "~/hooks/useStores";
@@ -15,12 +16,17 @@ import { isModKey } from "~/utils/keyboard";
type Props = {
title?: string;
sidebar?: React.ReactNode;
rightRail?: React.ReactNode;
sidebarRight?: React.ReactNode;
};
const Layout: React.FC<Props> = ({ title, children, sidebar, rightRail }) => {
const Layout: React.FC<Props> = ({
title,
children,
sidebar,
sidebarRight,
}) => {
const { ui } = useStores();
const sidebarCollapsed = !sidebar || ui.isEditing || ui.sidebarCollapsed;
const sidebarCollapsed = !sidebar || ui.sidebarIsClosed;
useKeyDown(".", (event) => {
if (isModKey(event)) {
@@ -31,7 +37,7 @@ const Layout: React.FC<Props> = ({ title, children, sidebar, rightRail }) => {
return (
<Container column auto>
<Helmet>
<title>{title ? title : "Outline"}</title>
<title>{title ? title : env.APP_NAME}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Helmet>
@@ -60,7 +66,7 @@ const Layout: React.FC<Props> = ({ title, children, sidebar, rightRail }) => {
{children}
</Content>
{rightRail}
{sidebarRight}
</Container>
</Container>
);
+5 -4
View File
@@ -1,11 +1,12 @@
import { LocationDescriptor } from "history";
import * as React from "react";
import styled, { useTheme } from "styled-components";
import Flex from "~/components/Flex";
import NavLink from "~/components/NavLink";
export type Props = {
export type Props = Omit<React.HTMLAttributes<HTMLAnchorElement>, "title"> & {
image?: React.ReactNode;
to?: string;
to?: LocationDescriptor;
exact?: boolean;
title: React.ReactNode;
subtitle?: React.ReactNode;
@@ -72,7 +73,7 @@ const ListItem = (
const Wrapper = styled.a<{
$small?: boolean;
$border?: boolean;
to?: string;
to?: LocationDescriptor;
}>`
display: flex;
padding: ${(props) => (props.$border === false ? 0 : "8px 0")};
@@ -86,7 +87,7 @@ const Wrapper = styled.a<{
border-bottom: 0;
}
cursor: ${({ to }) => (to ? "pointer" : "default")};
cursor: ${({ to }) => (to ? "var(--pointer)" : "default")};
`;
const Image = styled(Flex)`
+2 -2
View File
@@ -14,7 +14,7 @@ type Props = {
body?: PlaceholderTextProps;
};
const ListPlaceHolder = ({ count, className, header, body }: Props) => {
const Placeholder = ({ count, className, header, body }: Props) => {
return (
<Fade>
{times(count || 2, (index) => (
@@ -31,4 +31,4 @@ const Item = styled(Flex)`
padding: 10px 0;
`;
export default ListPlaceHolder;
export default Placeholder;
+4 -2
View File
@@ -15,6 +15,7 @@ import useMobile from "~/hooks/useMobile";
import usePrevious from "~/hooks/usePrevious";
import useUnmount from "~/hooks/useUnmount";
import { fadeAndScaleIn } from "~/styles/animations";
import Desktop from "~/utils/Desktop";
let openModals = 0;
type Props = {
@@ -222,7 +223,7 @@ const Back = styled(NudeButton)`
position: absolute;
display: none;
align-items: center;
top: 2rem;
top: ${Desktop.hasInsetTitlebar() ? "3rem" : "2rem"};
left: 2rem;
opacity: 0.75;
color: ${(props) => props.theme.text};
@@ -251,8 +252,9 @@ const Small = styled.div`
animation: ${fadeAndScaleIn} 250ms ease;
margin: auto auto;
width: 30vw;
min-width: 350px;
max-width: 30vw;
max-width: 450px;
z-index: ${depths.modal};
display: flex;
justify-content: center;
+3 -2
View File
@@ -1,3 +1,4 @@
import { LocationDescriptor } from "history";
import * as React from "react";
import { match, NavLink, Route } from "react-router-dom";
@@ -12,7 +13,7 @@ type Props = React.ComponentProps<typeof NavLink> & {
) => React.ReactNode;
exact?: boolean;
activeStyle?: React.CSSProperties;
to: string;
to: LocationDescriptor;
};
function NavLinkWithChildrenFunc(
@@ -20,7 +21,7 @@ function NavLinkWithChildrenFunc(
ref?: React.Ref<HTMLAnchorElement>
) {
return (
<Route path={to} exact={exact}>
<Route path={typeof to === "string" ? to : to?.pathname} exact={exact}>
{({ match, location }) => (
<NavLink {...rest} to={to} exact={exact} ref={ref}>
{children
+15 -7
View File
@@ -2,28 +2,36 @@ import styled from "styled-components";
import ActionButton, {
Props as ActionButtonProps,
} from "~/components/ActionButton";
import { undraggableOnDesktop } from "~/styles";
type Props = ActionButtonProps & {
width?: number;
height?: number;
width?: number | string;
height?: number | string;
size?: number;
type?: "button" | "submit" | "reset";
};
const StyledNudeButton = styled(ActionButton).attrs((props: Props) => ({
const NudeButton = styled(ActionButton).attrs((props: Props) => ({
type: "type" in props ? props.type : "button",
}))<Props>`
width: ${(props) => props.width || props.size || 24}px;
height: ${(props) => props.height || props.size || 24}px;
width: ${(props) =>
typeof props.width === "string"
? props.width
: `${props.width || props.size || 24}px`};
height: ${(props) =>
typeof props.height === "string"
? props.height
: `${props.height || props.size || 24}px`};
background: none;
border-radius: 4px;
display: inline-block;
line-height: 0;
border: 0;
padding: 0;
cursor: pointer;
cursor: var(--pointer);
user-select: none;
color: inherit;
${undraggableOnDesktop()}
`;
export default StyledNudeButton;
export default NudeButton;
-24
View File
@@ -1,24 +0,0 @@
import * as React from "react";
type Props = {
size?: number;
fill?: string;
className?: string;
};
function OutlineLogo({ size = 32, fill = "#333", className }: Props) {
return (
<svg
fill={fill}
width={size}
height={size}
viewBox="0 0 64 64"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path d="M32,57.6 L32,59.1606101 C32,61.3697491 30.209139,63.1606101 28,63.1606101 C27.3130526,63.1606101 26.6376816,62.9836959 26.038955,62.6469122 L2.03895504,49.1469122 C0.779447116,48.438439 -4.3614532e-15,47.1057033 -7.10542736e-15,45.6606101 L-7.10542736e-15,18.3393899 C-7.28240024e-15,16.8942967 0.779447116,15.561561 2.03895504,14.8530878 L26.038955,1.35308779 C27.9643866,0.270032565 30.4032469,0.952913469 31.4863021,2.87834498 C31.8230858,3.47707155 32,4.15244252 32,4.83938994 L32,6.4 L34.8506085,5.54481746 C36.9665799,4.91002604 39.1965137,6.11075966 39.8313051,8.22673106 C39.9431692,8.59961116 40,8.98682435 40,9.3761226 L40,11 L43.5038611,10.5620174 C45.6959408,10.2880074 47.6951015,11.8429102 47.9691115,14.0349899 C47.9896839,14.1995692 48,14.3652688 48,14.5311289 L48,49.4688711 C48,51.6780101 46.209139,53.4688711 44,53.4688711 C43.8341399,53.4688711 43.6684404,53.458555 43.5038611,53.4379826 L40,53 L40,54.6238774 C40,56.8330164 38.209139,58.6238774 36,58.6238774 C35.6107017,58.6238774 35.2234886,58.5670466 34.8506085,58.4551825 L32,57.6 Z M32,53.4238774 L36,54.6238774 L36,9.3761226 L32,10.5761226 L32,53.4238774 Z M40,15.0311289 L40,48.9688711 L44,49.4688711 L44,14.5311289 L40,15.0311289 Z M5.32907052e-15,44.4688711 L5.32907052e-15,19.5311289 L3.55271368e-15,44.4688711 Z M4,18.3393899 L4,45.6606101 L28,59.1606101 L28,4.83938994 L4,18.3393899 Z M8,21 L12,19 L12,45 L8,43 L8,21 Z" />
</svg>
);
}
export default OutlineLogo;
+5 -3
View File
@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Helmet } from "react-helmet";
import { cdnPath } from "@shared/utils/urls";
import env from "~/env";
import useStores from "~/hooks/useStores";
type Props = {
@@ -16,15 +17,16 @@ const PageTitle = ({ title, favicon }: Props) => {
return (
<Helmet>
<title>
{team?.name ? `${title} - ${team.name}` : `${title} - Outline`}
{team?.name ? `${title} - ${team.name}` : `${title} - ${env.APP_NAME}`}
</title>
{favicon ? (
<link rel="shortcut icon" href={favicon} />
<link rel="shortcut icon" href={favicon} key={favicon} />
) : (
<link
rel="shortcut icon"
type="image/png"
href={cdnPath("/favicon-32.png")}
key="favicon"
href={cdnPath("/images/favicon-32.png")}
sizes="32x32"
/>
)}
+17 -14
View File
@@ -13,6 +13,7 @@ type Props = {
heading?: React.ReactNode;
empty?: React.ReactNode;
};
const PaginatedEventList = React.memo<Props>(function PaginatedEventList({
empty,
heading,
@@ -23,32 +24,34 @@ const PaginatedEventList = React.memo<Props>(function PaginatedEventList({
...rest
}: Props) {
return (
<PaginatedList
<StyledPaginatedList
items={events}
empty={empty}
heading={heading}
fetch={fetch}
options={options}
renderItem={(item: Event, index, compositeProps) => {
return (
<EventListItem
key={item.id}
event={item}
document={document}
latest={index === 0}
{...compositeProps}
/>
);
}}
renderItem={(item: Event, index, compositeProps) => (
<EventListItem
key={item.id}
event={item}
document={document}
latest={index === 0}
{...compositeProps}
/>
)}
renderHeading={(name) => <Heading>{name}</Heading>}
{...rest}
/>
);
});
const Heading = styled("h3")`
font-size: 14px;
const StyledPaginatedList = styled(PaginatedList)`
padding: 0 12px;
`;
const Heading = styled("h3")`
font-size: 15px;
padding: 0 4px;
`;
export default PaginatedEventList;
+2 -1
View File
@@ -1,5 +1,6 @@
import "../stores";
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";
@@ -16,7 +17,7 @@ describe("PaginatedList", () => {
const props = {
i18n,
tReady: true,
t: (key: string) => key,
t: ((key: string) => key) as TFunction,
logout: () => {
//
},
+13 -4
View File
@@ -11,7 +11,7 @@ import ArrowKeyNavigation from "~/components/ArrowKeyNavigation";
import DelayedMount from "~/components/DelayedMount";
import PlaceholderList from "~/components/List/Placeholder";
import withStores from "~/components/withStores";
import { dateToHeading } from "~/utils/dates";
import { dateToHeading } from "~/utils/date";
export interface PaginatedItem {
id: string;
@@ -29,6 +29,7 @@ type Props<T> = WithTranslation &
empty?: React.ReactNode;
loading?: React.ReactElement;
items?: T[];
className?: string;
renderItem: (
item: T,
index: number,
@@ -53,11 +54,14 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
@observable
isFetching = false;
@observable
isFetchingInitial = !this.props.items?.length;
@observable
fetchCounter = 0;
@observable
renderCount: number = DEFAULT_PAGINATION_LIMIT;
renderCount = 15;
@observable
offset = 0;
@@ -84,6 +88,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
this.allowLoadMore = true;
this.renderCount = DEFAULT_PAGINATION_LIMIT;
this.isFetching = false;
this.isFetchingInitial = false;
this.isFetchingMore = false;
};
@@ -111,6 +116,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
}
this.renderCount += limit;
this.isFetchingInitial = false;
} catch (err) {
this.error = err;
} finally {
@@ -158,13 +164,15 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
const showLoading =
this.isFetching &&
!this.isFetchingMore &&
(!items?.length || this.fetchCounter === 0);
(!items?.length || (this.fetchCounter <= 1 && this.isFetchingInitial));
if (showLoading) {
return (
this.props.loading || (
<DelayedMount>
<PlaceholderList count={5} />
<div className={this.props.className}>
<PlaceholderList count={5} />
</div>
</DelayedMount>
)
);
@@ -184,6 +192,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
<ArrowKeyNavigation
aria-label={this.props["aria-label"]}
onEscape={onEscape}
className={this.props.className}
>
{(composite: CompositeStateReturn) => {
let previousHeading = "";
+1 -1
View File
@@ -5,8 +5,8 @@ import styled from "styled-components";
import { DocumentPath } from "~/stores/CollectionsStore";
import Collection from "~/models/Collection";
import Document from "~/models/Document";
import CollectionIcon from "~/components/CollectionIcon";
import Flex from "~/components/Flex";
import CollectionIcon from "~/components/Icons/CollectionIcon";
type Props = {
result: DocumentPath;
+11 -6
View File
@@ -42,7 +42,12 @@ function PinnedDocuments({ limit, pins, canUpdate, ...rest }: Props) {
}, [pins]);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(PointerSensor, {
activationConstraint: {
delay: 100,
tolerance: 5,
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
@@ -54,8 +59,8 @@ function PinnedDocuments({ limit, pins, canUpdate, ...rest }: Props) {
if (over && active.id !== over.id) {
setItems((items) => {
const activePos = items.indexOf(active.id);
const overPos = items.indexOf(over.id);
const activePos = items.indexOf(active.id as string);
const overPos = items.indexOf(over.id as string);
const overIndex = pins[overPos]?.index || null;
const nextIndex = pins[overPos + 1]?.index || null;
@@ -121,7 +126,7 @@ function PinnedDocuments({ limit, pins, canUpdate, ...rest }: Props) {
const List = styled.div`
display: grid;
column-gap: 8px;
row-gap: 8px;
row-gap: 12px;
grid-template-columns: repeat(2, minmax(0, 1fr));
padding: 0;
list-style: none;
@@ -131,11 +136,11 @@ const List = styled.div`
display: none;
}
${breakpoint("tablet")`
${breakpoint("mobileLarge")`
grid-template-columns: repeat(3, minmax(0, 1fr));
`};
${breakpoint("desktop")`
${breakpoint("tablet")`
grid-template-columns: repeat(4, minmax(0, 1fr));
`};
`;
+7 -17
View File
@@ -12,23 +12,11 @@ export type Props = {
delay?: number;
};
class PlaceholderText extends React.Component<Props> {
width = randomInteger(this.props.minWidth || 75, this.props.maxWidth || 100);
function PlaceholderText({ minWidth, maxWidth, ...restProps }: Props) {
// We only want to compute the width once so we are storing it inside ref
const widthRef = React.useRef(randomInteger(minWidth || 75, maxWidth || 100));
shouldComponentUpdate() {
return false;
}
render() {
return (
<Mask
width={this.width}
height={this.props.height}
delay={this.props.delay}
header={this.props.header}
/>
);
}
return <Mask width={widthRef.current} {...restProps} />;
}
const Mask = styled(Flex)<{
@@ -51,4 +39,6 @@ const Mask = styled(Flex)<{
}
`;
export default PlaceholderText;
// We don't want the component to re-render on any props change
// So returning true from the custom comparison function to avoid re-render
export default React.memo(PlaceholderText, () => true);
+3 -2
View File
@@ -45,8 +45,9 @@ const Contents = styled.div<{ $shrink?: boolean; $width?: number }>`
background: ${(props) => props.theme.menuBackground};
border-radius: 6px;
padding: ${(props) => (props.$shrink ? "6px 0" : "12px 24px")};
max-height: 50vh;
overflow-y: scroll;
max-height: 75vh;
overflow-x: hidden;
overflow-y: auto;
box-shadow: ${(props) => props.theme.menuShadow};
width: ${(props) => props.$width}px;
+3 -3
View File
@@ -8,7 +8,7 @@ type Props = {
icon?: React.ReactNode;
title?: React.ReactNode;
textTitle?: string;
breadcrumb?: React.ReactNode;
left?: React.ReactNode;
actions?: React.ReactNode;
centered?: boolean;
};
@@ -18,7 +18,7 @@ const Scene: React.FC<Props> = ({
icon,
textTitle,
actions,
breadcrumb,
left,
children,
centered,
}) => {
@@ -37,7 +37,7 @@ const Scene: React.FC<Props> = ({
)
}
actions={actions}
breadcrumb={breadcrumb}
left={left}
/>
{centered !== false ? (
<CenteredContent withStickyHeader>{children}</CenteredContent>
+10 -3
View File
@@ -8,11 +8,14 @@ type Props = {
};
export default function ScrollToTop({ children }: Props) {
const location = useLocation();
const location = useLocation<{ retainScrollPosition?: boolean }>();
const previousLocationPathname = usePrevious(location.pathname);
React.useEffect(() => {
if (location.pathname === previousLocationPathname) {
if (
location.pathname === previousLocationPathname ||
location.state?.retainScrollPosition
) {
return;
}
// exception for when entering or exiting document edit, scroll position should not reset
@@ -23,7 +26,11 @@ export default function ScrollToTop({ children }: Props) {
return;
}
window.scrollTo(0, 0);
}, [location.pathname, previousLocationPathname]);
}, [
location.pathname,
previousLocationPathname,
location.state?.retainScrollPosition,
]);
return children;
}
+1 -1
View File
@@ -92,7 +92,7 @@ const Wrapper = styled.div<{
return "none";
}};
transition: all 100ms ease-in-out;
transition: box-shadow 100ms ease-in-out;
${(props) =>
props.$hiddenScrollbars &&
+3 -1
View File
@@ -10,7 +10,9 @@ export default function SearchActions() {
const { searches } = useStores();
React.useEffect(() => {
searches.fetchPage({});
if (!searches.isLoaded) {
searches.fetchPage({});
}
}, [searches]);
const { searchQuery } = useKBar((state) => ({
+5 -1
View File
@@ -7,6 +7,7 @@ import breakpoint from "styled-components-breakpoint";
import Document from "~/models/Document";
import Highlight, { Mark } from "~/components/Highlight";
import { hover } from "~/styles";
import { sharedDocumentPath } from "~/utils/routeHelpers";
type Props = {
document: Document;
@@ -38,7 +39,9 @@ function DocumentListItem(
ref={ref}
dir={document.dir}
to={{
pathname: shareId ? `/share/${shareId}${document.url}` : document.url,
pathname: shareId
? sharedDocumentPath(shareId, document.url)
: document.url,
state: {
title: document.titleWithDefault,
},
@@ -80,6 +83,7 @@ const DocumentLink = styled(Link)<{
align-items: center;
padding: 6px 12px;
max-height: 50vh;
cursor: var(--pointer);
&:not(:last-child) {
margin-bottom: 4px;
+13 -12
View File
@@ -14,6 +14,7 @@ import useCurrentUser from "~/hooks/useCurrentUser";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import OrganizationMenu from "~/menus/OrganizationMenu";
import Desktop from "~/utils/Desktop";
import {
homePath,
draftsPath,
@@ -24,9 +25,10 @@ import TeamLogo from "../TeamLogo";
import Sidebar from "./Sidebar";
import ArchiveLink from "./components/ArchiveLink";
import Collections from "./components/Collections";
import HeaderButton, { HeaderButtonProps } from "./components/HeaderButton";
import HistoryNavigation from "./components/HistoryNavigation";
import Section from "./components/Section";
import SidebarAction from "./components/SidebarAction";
import SidebarButton, { SidebarButtonProps } from "./components/SidebarButton";
import SidebarLink from "./components/SidebarLink";
import Starred from "./components/Starred";
import TrashLink from "./components/TrashLink";
@@ -56,21 +58,25 @@ function AppSidebar() {
return (
<Sidebar ref={handleSidebarRef}>
<HistoryNavigation />
{dndArea && (
<DndProvider backend={HTML5Backend} options={html5Options}>
<OrganizationMenu>
{(props: SidebarButtonProps) => (
<SidebarButton
{(props: HeaderButtonProps) => (
<HeaderButton
{...props}
title={team.name}
image={
<StyledTeamLogo
src={team.avatarUrl}
width={32}
height={32}
<TeamLogo
model={team}
size={Desktop.hasInsetTitlebar() ? 24 : 32}
alt={t("Logo")}
/>
}
style={
// Move the logo over to align with smaller size
Desktop.hasInsetTitlebar() ? { paddingLeft: 8 } : undefined
}
showDisclosure
/>
)}
@@ -139,11 +145,6 @@ function AppSidebar() {
);
}
const StyledTeamLogo = styled(TeamLogo)`
margin-right: 4px;
background: white;
`;
const Drafts = styled(Text)`
margin: 0 4px;
`;
+128
View File
@@ -0,0 +1,128 @@
import { m } from "framer-motion";
import { observer } from "mobx-react";
import * as React from "react";
import styled, { useTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Flex from "~/components/Flex";
import ResizeBorder from "~/components/Sidebar/components/ResizeBorder";
import usePersistedState from "~/hooks/usePersistedState";
type Props = React.HTMLAttributes<HTMLDivElement> & {
children: React.ReactNode;
border?: boolean;
};
function Right({ children, border, className }: Props) {
const theme = useTheme();
const [width, setWidth] = usePersistedState(
"rightSidebarWidth",
theme.sidebarWidth
);
const [isResizing, setResizing] = React.useState(false);
const maxWidth = theme.sidebarMaxWidth;
const minWidth = theme.sidebarMinWidth + 16; // padding
const handleDrag = React.useCallback(
(event: MouseEvent) => {
// suppresses text selection
event.preventDefault();
const width = Math.max(
Math.min(window.innerWidth - event.pageX, maxWidth),
minWidth
);
setWidth(width);
},
[minWidth, maxWidth, setWidth]
);
const handleReset = React.useCallback(() => {
setWidth(theme.sidebarWidth);
}, [setWidth, theme.sidebarWidth]);
const handleStopDrag = React.useCallback(() => {
setResizing(false);
if (document.activeElement) {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'blur' does not exist on type 'Element'.
document.activeElement.blur();
}
}, []);
const handleMouseDown = React.useCallback(() => {
setResizing(true);
}, []);
React.useEffect(() => {
if (isResizing) {
document.addEventListener("mousemove", handleDrag);
document.addEventListener("mouseup", handleStopDrag);
}
return () => {
document.removeEventListener("mousemove", handleDrag);
document.removeEventListener("mouseup", handleStopDrag);
};
}, [isResizing, handleDrag, handleStopDrag]);
const style = React.useMemo(
() => ({
width: `${width}px`,
}),
[width]
);
return (
<Sidebar
initial={{
width: 0,
}}
animate={{
transition: isResizing
? { duration: 0 }
: {
type: "spring",
bounce: 0.2,
duration: 0.6,
},
width,
}}
exit={{
width: 0,
}}
$border={border}
className={className}
>
<Position style={style} column>
{children}
<ResizeBorder
onMouseDown={handleMouseDown}
onDoubleClick={handleReset}
dir="right"
/>
</Position>
</Sidebar>
);
}
const Position = styled(Flex)`
position: fixed;
top: 0;
bottom: 0;
`;
const Sidebar = styled(m.div)<{ $border?: boolean }>`
display: none;
position: relative;
flex-shrink: 0;
background: ${(props) => props.theme.background};
width: ${(props) => props.theme.sidebarWidth}px;
border-left: 1px solid ${(props) => props.theme.divider};
transition: border-left 100ms ease-in-out;
z-index: 1;
${breakpoint("tablet")`
display: flex;
`};
`;
export default observer(Right);
+8 -5
View File
@@ -7,19 +7,21 @@ import { useHistory } from "react-router-dom";
import styled from "styled-components";
import Flex from "~/components/Flex";
import Scrollable from "~/components/Scrollable";
import useAuthorizedSettingsConfig from "~/hooks/useAuthorizedSettingsConfig";
import useSettingsConfig from "~/hooks/useSettingsConfig";
import Desktop from "~/utils/Desktop";
import isCloudHosted from "~/utils/isCloudHosted";
import Sidebar from "./Sidebar";
import Header from "./components/Header";
import HeaderButton from "./components/HeaderButton";
import HistoryNavigation from "./components/HistoryNavigation";
import Section from "./components/Section";
import SidebarButton from "./components/SidebarButton";
import SidebarLink from "./components/SidebarLink";
import Version from "./components/Version";
function SettingsSidebar() {
const { t } = useTranslation();
const history = useHistory();
const configs = useAuthorizedSettingsConfig();
const configs = useSettingsConfig();
const groupedConfig = groupBy(configs, "group");
const returnToApp = React.useCallback(() => {
@@ -28,11 +30,12 @@ function SettingsSidebar() {
return (
<Sidebar>
<SidebarButton
<HistoryNavigation />
<HeaderButton
title={t("Return to App")}
image={<StyledBackIcon color="currentColor" />}
onClick={returnToApp}
minHeight={48}
minHeight={Desktop.hasInsetTitlebar() ? undefined : 48}
/>
<Flex auto column>
+27 -5
View File
@@ -1,33 +1,52 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Team from "~/models/Team";
import Scrollable from "~/components/Scrollable";
import SearchPopover from "~/components/SearchPopover";
import useStores from "~/hooks/useStores";
import { NavigationNode } from "~/types";
import history from "~/utils/history";
import { homePath, sharedDocumentPath } from "~/utils/routeHelpers";
import TeamLogo from "../TeamLogo";
import Sidebar from "./Sidebar";
import HeaderButton from "./components/HeaderButton";
import Section from "./components/Section";
import DocumentLink from "./components/SharedDocumentLink";
type Props = {
team?: Team;
rootNode: NavigationNode;
shareId: string;
};
function SharedSidebar({ rootNode, shareId }: Props) {
const { ui, documents } = useStores();
function SharedSidebar({ rootNode, team, shareId }: Props) {
const { ui, documents, auth } = useStores();
const { t } = useTranslation();
return (
<Sidebar>
<ScrollContainer flex>
{team && (
<HeaderButton
title={team.name}
image={<TeamLogo model={team} size={32} alt={t("Logo")} />}
onClick={() =>
history.push(
auth.user ? homePath() : sharedDocumentPath(shareId, rootNode.url)
)
}
/>
)}
<ScrollContainer topShadow flex>
<TopSection>
<SearchPopover shareId={shareId} />
</TopSection>
<Section>
<DocumentLink
index={0}
depth={0}
shareId={shareId}
depth={1}
node={rootNode}
activeDocumentId={ui.activeDocumentId}
activeDocument={documents.active}
@@ -44,8 +63,11 @@ const ScrollContainer = styled(Scrollable)`
const TopSection = styled(Section)`
// this weird looking && increases the specificity of the style rule
&& {
&&:first-child {
margin-top: 16px;
}
&& {
margin-bottom: 16px;
}
`;
+21 -16
View File
@@ -11,10 +11,12 @@ import useMenuContext from "~/hooks/useMenuContext";
import usePrevious from "~/hooks/usePrevious";
import useStores from "~/hooks/useStores";
import AccountMenu from "~/menus/AccountMenu";
import { draggableOnDesktop, fadeOnDesktopBackgrounded } from "~/styles";
import { fadeIn } from "~/styles/animations";
import Desktop from "~/utils/Desktop";
import Avatar from "../Avatar";
import HeaderButton, { HeaderButtonProps } from "./components/HeaderButton";
import ResizeBorder from "./components/ResizeBorder";
import SidebarButton, { SidebarButtonProps } from "./components/SidebarButton";
import Toggle, { ToggleButton, Positioner } from "./components/Toggle";
const ANIMATION_MS = 250;
@@ -35,7 +37,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(
const { user } = auth;
const width = ui.sidebarWidth;
const collapsed = (ui.isEditing || ui.sidebarCollapsed) && !isMenuOpen;
const collapsed = ui.sidebarIsClosed && !isMenuOpen;
const maxWidth = theme.sidebarMaxWidth;
const minWidth = theme.sidebarMinWidth + 16; // padding
@@ -170,15 +172,15 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(
{user && (
<AccountMenu>
{(props: SidebarButtonProps) => (
<SidebarButton
{(props: HeaderButtonProps) => (
<HeaderButton
{...props}
showMoreMenu
title={user.name}
image={
<StyledAvatar
alt={user.name}
src={user.avatarUrl}
model={user}
size={24}
showBorder={false}
/>
@@ -189,9 +191,9 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(
)}
<ResizeBorder
onMouseDown={handleMouseDown}
onDoubleClick={ui.sidebarCollapsed ? undefined : handleReset}
onDoubleClick={ui.sidebarIsClosed ? undefined : handleReset}
/>
{ui.sidebarCollapsed && !ui.isEditing && (
{ui.sidebarIsClosed && (
<Toggle
onClick={ui.toggleCollapsedSidebar}
direction={"right"}
@@ -199,14 +201,12 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(
/>
)}
</Container>
{!ui.isEditing && (
<Toggle
style={toggleStyle}
onClick={ui.toggleCollapsedSidebar}
direction={ui.sidebarCollapsed ? "right" : "left"}
aria-label={ui.sidebarCollapsed ? t("Expand") : t("Collapse")}
/>
)}
<Toggle
style={toggleStyle}
onClick={ui.toggleCollapsedSidebar}
direction={ui.sidebarIsClosed ? "right" : "left"}
aria-label={ui.sidebarIsClosed ? t("Expand") : t("Collapse")}
/>
</>
);
}
@@ -251,6 +251,9 @@ const Container = styled(Flex)<ContainerProps>`
z-index: ${depths.sidebar};
max-width: 70%;
min-width: 280px;
padding-top: ${Desktop.hasInsetTitlebar() ? 36 : 0}px;
${draggableOnDesktop()}
${fadeOnDesktopBackgrounded()}
${Positioner} {
display: none;
@@ -265,7 +268,9 @@ const Container = styled(Flex)<ContainerProps>`
margin: 0;
min-width: 0;
transform: translateX(${(props: ContainerProps) =>
props.$collapsed ? "calc(-100% + 16px)" : 0});
props.$collapsed
? `calc(-100% + ${Desktop.hasInsetTitlebar() ? 8 : 16}px)`
: 0});
&:hover,
&:focus-within {
@@ -8,8 +8,8 @@ import { useHistory } from "react-router-dom";
import Collection from "~/models/Collection";
import Document from "~/models/Document";
import DocumentReparent from "~/scenes/DocumentReparent";
import CollectionIcon from "~/components/CollectionIcon";
import Fade from "~/components/Fade";
import CollectionIcon from "~/components/Icons/CollectionIcon";
import NudeButton from "~/components/NudeButton";
import { createDocument } from "~/actions/definitions/documents";
import useActionContext from "~/hooks/useActionContext";
@@ -27,7 +27,7 @@ import { useStarredContext } from "./StarredContext";
type Props = {
collection: Collection;
expanded?: boolean;
onDisclosureClick: (ev: React.MouseEvent<HTMLButtonElement>) => void;
onDisclosureClick: (ev?: React.MouseEvent<HTMLButtonElement>) => void;
activeDocument: Document | undefined;
isDraggingAnyCollection?: boolean;
};
@@ -62,7 +62,7 @@ const CollectionLink: React.FC<Props> = ({
// Drop to re-parent document
const [{ isOver, canDrop }, drop] = useDrop({
accept: "document",
drop: (item: DragObject, monitor) => {
drop: async (item: DragObject, monitor) => {
const { id, collectionId } = item;
if (monitor.didDrop()) {
return;
@@ -81,7 +81,8 @@ const CollectionLink: React.FC<Props> = ({
if (
prevCollection &&
prevCollection.permission === null &&
prevCollection.permission !== collection.permission
prevCollection.permission !== collection.permission &&
!document?.isDraft
) {
itemRef.current = item;
@@ -97,7 +98,11 @@ const CollectionLink: React.FC<Props> = ({
),
});
} else {
documents.move(id, collection.id);
await documents.move(id, collection.id);
if (!expanded) {
onDisclosureClick();
}
}
},
canDrop: () => canUpdate,
@@ -43,6 +43,10 @@ function Collections() {
}),
});
React.useEffect(() => {
collections.fetchPage({ limit: 100 });
}, [collections]);
return (
<Flex column>
<Header id="collections" title={t("Collections")}>
@@ -50,8 +54,6 @@ function Collections() {
<PaginatedList
aria-label={t("Collections")}
items={collections.orderedData}
fetch={collections.fetchPage}
options={{ limit: 100 }}
loading={<PlaceholderCollections />}
heading={
isDraggingAnyCollection ? (
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import styled, { css } from "styled-components";
import NudeButton from "~/components/NudeButton";
type Props = {
type Props = React.ComponentProps<typeof Button> & {
onClick?: React.MouseEventHandler<HTMLButtonElement>;
expanded: boolean;
root?: boolean;
@@ -105,8 +105,7 @@ function InnerDocumentLink(
const handleDisclosureClick = React.useCallback(
(ev) => {
ev.preventDefault();
ev.stopPropagation();
ev?.preventDefault();
setExpanded(!expanded);
},
[expanded]
@@ -150,14 +149,10 @@ function InnerDocumentLink(
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
canDrag: () => {
return (
!isDraft &&
(policies.abilities(node.id).move ||
policies.abilities(node.id).archive ||
policies.abilities(node.id).delete)
);
},
canDrag: () =>
policies.abilities(node.id).move ||
policies.abilities(node.id).archive ||
policies.abilities(node.id).delete,
});
const hoverExpanding = React.useRef<ReturnType<typeof setTimeout>>();
@@ -174,14 +169,15 @@ function InnerDocumentLink(
// Drop to re-parent
const [{ isOverReparent, canDropToReparent }, dropToReparent] = useDrop({
accept: "document",
drop: (item: DragObject, monitor) => {
drop: async (item: DragObject, monitor) => {
if (monitor.didDrop()) {
return;
}
if (!collection) {
return;
}
documents.move(item.id, collection.id, node.id);
await documents.move(item.id, collection.id, node.id);
setExpanded(true);
},
canDrop: (_item, monitor) =>
!isDraft &&
@@ -291,6 +287,21 @@ function InnerDocumentLink(
const isExpanded = expanded && !isDragging;
const hasChildren = nodeChildren.length > 0;
const handleKeyDown = React.useCallback(
(ev: React.KeyboardEvent) => {
if (!hasChildren) {
return;
}
if (ev.key === "ArrowRight" && !expanded) {
setExpanded(true);
}
if (ev.key === "ArrowLeft" && expanded) {
setExpanded(false);
}
},
[hasChildren, expanded]
);
return (
<>
<Relative onDragLeave={resetHoverExpanding}>
@@ -299,6 +310,7 @@ function InnerDocumentLink(
ref={drag}
$isDragging={isDragging}
$isMoving={isMoving}
onKeyDown={handleKeyDown}
>
<div ref={dropToReparent}>
<DropToImport documentId={node.id} activeClassName="activeDropZone">
@@ -91,7 +91,7 @@ function DraggableCollectionLink({
}, [collection.id, ui.activeCollectionId, locationStateStarred]);
const handleDisclosureClick = React.useCallback((ev) => {
ev.preventDefault();
ev?.preventDefault();
setExpanded((e) => !e);
}, []);
@@ -46,6 +46,15 @@ function EditableTitle({
[originalValue]
);
const stopPropagation = React.useCallback((event) => {
event.preventDefault();
event.stopPropagation();
}, []);
const handleFocus = React.useCallback((event) => {
event.target.select();
}, []);
const handleSave = React.useCallback(
async (ev) => {
ev.preventDefault();
@@ -85,9 +94,11 @@ function EditableTitle({
dir="auto"
type="text"
value={value}
onClick={stopPropagation}
onKeyDown={handleKeyDown}
onChange={handleChange}
onBlur={handleSave}
onFocus={handleFocus}
autoFocus
{...rest}
/>
@@ -102,11 +113,11 @@ function EditableTitle({
}
const Input = styled.input`
color: ${(props) => props.theme.sidebarText};
color: ${(props) => props.theme.text};
background: ${(props) => props.theme.background};
width: calc(100% + 12px);
border-radius: 3px;
border: 1px solid ${(props) => props.theme.inputBorderFocused};
border: 0;
padding: 5px 6px;
margin: -4px;
height: 32px;

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