Compare commits

...

632 Commits

Author SHA1 Message Date
Tom Moor 472b183c0c fix 2023-02-15 20:01:56 -05:00
Tom Moor 62404aaadc Remove assets in path 2023-02-15 19:45:25 -05:00
Tom Moor b73cc766ec Merge branch 'main' of github.com:outline/outline into feat/replace-webpack-with-vite 2023-02-15 10:02:19 -05:00
Tom Moor e9dd7d5f15 fix: Preload request credentials mode does not match warning 2023-02-14 21:54:19 -05:00
Tom Moor 778cc196e4 fix: non-precached-url error 2023-02-14 21:41:42 -05:00
Tom Moor 2a5e7da5c8 fix: ErrorBoundary catching of unavailable chunks 2023-02-14 21:10:45 -05:00
Tom Moor 490d05b68b fix: Incorrect key events trigger on non-qwerty layouts 2023-02-14 19:09:44 -05:00
Tom Moor b50bee1ec7 fix: ga is not defined 2023-02-14 19:03:54 -05:00
Mohamed ELIDRISSI 0976e85a1a refactor: add server side validation schema for authProviders (#4876)
* refactor: move files to subfolder

* refactor: schema for authenticationProviders.info

* refactor: schema for authenticationProviders.update

* refactor: use validated body
2023-02-14 14:49:24 -08:00
Tom Moor e09f6a67b8 build: Use @relative-ci/agent CLI (#4877)
Co-authored-by: Vio <vio@beanon.com>
2023-02-14 14:48:06 -08:00
dependabot[bot] ab7b20958b chore(deps): bump fs-extra and @types/fs-extra (#4871)
Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) and [@types/fs-extra](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/fs-extra). These dependencies needed to be updated together.

Updates `fs-extra` from 4.0.3 to 11.1.0
- [Release notes](https://github.com/jprichardson/node-fs-extra/releases)
- [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jprichardson/node-fs-extra/compare/4.0.3...11.1.0)

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

---
updated-dependencies:
- dependency-name: fs-extra
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: "@types/fs-extra"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-14 05:57:10 -08:00
dependabot[bot] 8fc1f67697 chore(deps): bump react-i18next from 12.1.1 to 12.1.5 (#4872)
Bumps [react-i18next](https://github.com/i18next/react-i18next) from 12.1.1 to 12.1.5.
- [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/v12.1.1...v12.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-14 05:56:46 -08:00
Hans Pagel 9384708a60 remove debugging output 2023-02-13 17:57:04 +01:00
Hans Pagel d8e687a2a0 just use model names, no need to replace digits 2023-02-13 17:56:10 +01:00
Hans Pagel f2533ab33e remove workbox-precaching from dependencies 2023-02-13 17:54:46 +01:00
Hans Pagel 6d5a6fd71a don’t proxy custom service worker files 2023-02-13 17:50:08 +01:00
Hans Pagel b0bf08e2a7 update comment 2023-02-13 17:48:34 +01:00
Hans Pagel b169d8eb24 use custom logger instead of console.log 2023-02-13 17:45:18 +01:00
Hans Pagel 0c57851137 remove paths to custom service worker files from gitignore 2023-02-13 17:42:41 +01:00
Hans Pagel 2b5d480a33 don’t cache the HTML 2023-02-13 17:33:53 +01:00
Hans Pagel 811d21e7f9 try to add the babel class properties plugin 2023-02-13 17:14:48 +01:00
Hans Pagel c42965e2ed fresh yarn.lock file, let’s try again 2023-02-13 16:39:34 +01:00
Hans Pagel 490260e1cc remove custom service worker code 2023-02-13 16:33:18 +01:00
Hans Pagel d1c05cc202 fix sw.js path 2023-02-13 16:21:25 +01:00
Hans Pagel 5d59ba4708 fix yarn.lock file (wip) 2023-02-13 16:13:23 +01:00
Hans Pagel 2b8cd208b3 disable custom registerSW script 2023-02-13 16:05:42 +01:00
Hans Pagel ee375effc4 use old yarn.lock file 2023-02-13 16:05:05 +01:00
Hans Pagel 64342b52b9 rewrite yarn.lock file again 2023-02-13 15:57:03 +01:00
Hans Pagel 8494d24e41 rewrite yarn.lock file 2023-02-13 15:54:30 +01:00
Hans Pagel dddb050eb6 use vite-plugin-pwa instead of custom service worker code 2023-02-13 15:34:18 +01:00
Tom Moor f6de2b5c9f Merge main 2023-02-12 23:13:51 -05:00
Tom Moor 60101c507a Move bulk of webhook logic to plugin (#4866)
* Move bulk of webhook logic to plugin

* Re-enable cleanup task

* cron tasks
2023-02-12 16:28:11 -08:00
Tom Moor 7895ee207c Clear previous plugin and server files in build directory before rebuilding 2023-02-12 16:43:20 -05:00
Tom Moor e028715afb Minor fixes from enterprise codebase 2023-02-12 16:31:15 -05:00
Tom Moor 33afa2f029 Plugin architecture (#4861)
* wip

* Refactor, tasks, processors, routes loading

* Move Slack settings config to plugin

* Fix translations in plugins

* Move Slack auth to plugin

* test

* Move other slack-related files into plugin

* Forgot to save

* refactor
2023-02-12 10:11:30 -08:00
Mohamed ELIDRISSI 492beedf00 refactor: add server side validation schema for apiKeys (#4859)
* refactor: add tests for apiKey api routes

* refactor: move files to subfolder

* refactor: schema for apiKeys.create and apiKeys.delete
2023-02-11 15:02:52 -08:00
vgwidt 9302beb630 fix: Visual Basic syntax higlighting (#4769) (#4770) 2023-02-11 09:31:37 -08:00
Tom Moor c5cb02e980 fix: Text color on key component 2023-02-10 23:18:10 -05:00
Tom Moor db446ba67d New Crowdin updates (#4734) 2023-02-10 19:36:51 -08:00
Tom Moor 237313a97d fix: Action children not triggerable 2023-02-10 22:36:00 -05:00
Tom Moor fcbd4d3d28 Track action usage 2023-02-10 18:56:12 -05:00
Tom Moor bb6f4b1c1e fix: Attachment converted to links when AWS ACL is public-read, closes #4853 2023-02-10 17:42:09 -05:00
Tom Moor 491dde7e6f Move terser to dev dep 2023-02-10 10:53:20 -05:00
Tom Moor 6a78622327 Actually add terser 2023-02-10 10:13:33 -05:00
Tom Moor c8f37707d3 try terser 2023-02-10 10:10:05 -05:00
Tom Moor 278177fac2 try esbuild 2023-02-10 10:02:24 -05:00
Tom Moor 3238752421 wip 2023-02-10 09:46:18 -05:00
Tom Moor d4ada01b19 Update rollup 2023-02-09 23:08:16 -05:00
Tom Moor 01abda82a6 fix: Mangled classnames in build, perhaps 2023-02-09 22:18:38 -05:00
Tom Moor e7c2a8f65d test 2023-02-09 20:48:47 -05:00
Tom Moor e03b08a964 Attempt to fix path 2023-02-09 20:05:46 -05:00
Hans Pagel 34a1fe3f2d add base path (wip) 2023-02-09 18:49:00 +01:00
Hans Pagel be4babe70a add hash to entry file name 2023-02-09 18:46:03 +01:00
Hans Pagel 460834eb49 another try to fix the CDN URL 2023-02-09 17:38:18 +01:00
Hans Pagel d0a6e54bc8 fix CDN URL 2023-02-09 17:31:36 +01:00
Hans Pagel 5a88492766 fix html path (again) 2023-02-09 17:24:15 +01:00
Hans Pagel 2df413f7f0 fix tests 2023-02-09 17:01:39 +01:00
Hans Pagel 931305b84d revert node version change 2023-02-09 16:56:50 +01:00
Hans Pagel db0ec6faa7 just use process.env in the build process 2023-02-09 16:48:26 +01:00
Hans Pagel 084f15e734 tsconfig: don’t check libraries 2023-02-09 16:25:25 +01:00
Hans Pagel d55d664f08 fix service worker and CDN issues, copy static images 2023-02-09 15:47:26 +01:00
Hans Pagel b425669734 update vite 2023-02-09 15:44:35 +01:00
Tom Moor 23b8cc307e Revert "allow node 17 & 18 when installing dependencies (#4844)" (#4846)
This reverts commit c75c61ca4b.
2023-02-08 15:47:09 -08:00
Hans Pagel 24cfe60d9f rewrite yarn.lock file 2023-02-08 17:17:08 +01:00
Hans Pagel b9e2a7b0ec separate frontend and backend scripts 2023-02-08 17:16:58 +01:00
Hans Pagel 6f2c5982f5 Merge branch 'feat/replace-webpack-with-vite' of github.com:outline/outline into feat/replace-webpack-with-vite 2023-02-08 17:01:02 +01:00
Hans Pagel 0cecb3f929 add a todo (wip) 2023-02-08 17:00:50 +01:00
Hans Pagel bbeac9fa31 hide the service worker behind a globally registered function 2023-02-08 16:57:46 +01:00
Hans Pagel a2a2628073 Merge branch 'main' into feat/replace-webpack-with-vite 2023-02-08 16:28:14 +01:00
Hans Pagel 43800acf0d improve service worker (wip) 2023-02-08 16:26:24 +01:00
Hans Pagel c75c61ca4b allow node 17 & 18 when installing dependencies (#4844) 2023-02-08 16:22:56 +01:00
Tom Moor 81f655f402 fix: Links with strikethrough do not have hover preview (#4841)
* fix: Links with strikethrough do not have hover preview

* refactor
2023-02-07 19:36:15 -08:00
dependabot[bot] bb1fe1a25f chore(deps): bump zod from 3.19.1 to 3.20.2 (#4833)
Bumps [zod](https://github.com/colinhacks/zod) from 3.19.1 to 3.20.2.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Changelog](https://github.com/colinhacks/zod/blob/master/CHANGELOG.md)
- [Commits](https://github.com/colinhacks/zod/compare/v3.19.1...v3.20.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 18:34:22 -08:00
dependabot[bot] b9ffe8aaa3 chore(deps): bump tiny-cookie from 2.3.2 to 2.4.0 (#4836)
Bumps [tiny-cookie](https://github.com/Alex1990/tiny-cookie) from 2.3.2 to 2.4.0.
- [Release notes](https://github.com/Alex1990/tiny-cookie/releases)
- [Changelog](https://github.com/Alex1990/tiny-cookie/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Alex1990/tiny-cookie/compare/v2.3.2...v2.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 18:33:56 -08:00
dependabot[bot] 97775e14df chore(deps): bump immutable from 4.2.2 to 4.2.4 (#4835)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 4.2.2 to 4.2.4.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v4.2.2...v4.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 18:33:27 -08:00
Hans Pagel d8478d1847 build custom service worker code (wip) 2023-02-06 22:08:34 +01:00
Hans Pagel d12c6063cd move service worker code to a separate file 2023-02-06 17:54:10 +01:00
Hans Pagel 0ded55bf21 Merge branch 'feat/replace-webpack-with-vite' of github.com:outline/outline into feat/replace-webpack-with-vite 2023-02-06 17:46:31 +01:00
Hans Pagel 706e13eddc don’t require a build to run in development mode 2023-02-06 17:46:24 +01:00
Tom Moor eeb393d634 Merge main 2023-02-04 17:18:12 -05:00
Tom Moor 78e9dbb336 Remove webpackChunkName annotation 2023-02-04 17:16:22 -05:00
Tom Moor 926402b820 fix: Loading index.html in development 2023-02-04 17:14:14 -05:00
Tom Moor 0b6c9d1838 Improve drag-and-drop (#4824)
* Improve drag-and-drop

* fixes

* fix drop highlight showing on ghosted sidebar item
2023-02-04 12:00:32 -08:00
Tom Moor 239e9e294d fix: Microsoft auth silently errors when logged into multiple Microsoft accounts, allow account selection in OAuth flow 2023-02-04 14:56:52 -05:00
Tom Moor 9b002abae3 fix: Ensure tsvector content is below 1Mb limitation
closes #4714
2023-02-04 14:26:54 -05:00
Tom Moor 534eeacc97 fix: Documents with images exported as HTML have broken images
closes #4822
2023-02-04 14:04:38 -05:00
Tom Moor 8b28d6f6e0 fix: Suppress Slack updated notifications when publishing
closes #4821
2023-02-04 14:00:30 -05:00
Tom Moor 59b02154b9 Merge branch 'main' of github.com:outline/outline 2023-02-03 23:08:34 -05:00
Apoorv Mishra 0d6651b0da Scroll children into view upon expansion (#4812)
* feat: smoothly scroll children into view

* fix: disable smooth scroll and throttling
2023-02-03 09:41:24 +05:30
Tom Moor a1cefa9771 fix: FORCE_HTTPS setting results in redirect loop when Outline terminates SSL 2023-02-02 21:45:33 -05:00
Apoorv Mishra 1caa51f58e Viewer should be allowed to subscribe to a document (#4814)
* fix: viewer should be allowed to subscribe to a document

* fix: allow subscribe only if the user has read permission for collection
2023-01-31 19:46:10 +05:30
Tom Moor a2e07e9593 chore: Bump kbar 2023-01-31 08:53:38 -05:00
dependabot[bot] 8f166ca775 chore(deps-dev): bump jest-cli from 28.1.3 to 29.4.1 (#4805)
* chore(deps-dev): bump jest-cli from 28.1.3 to 29.4.1

Bumps [jest-cli](https://github.com/facebook/jest/tree/HEAD/packages/jest-cli) from 28.1.3 to 29.4.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.4.1/packages/jest-cli)

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

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

* Snapshots

---------

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-30 20:17:09 -08:00
dependabot[bot] d70aefe9fa chore(deps): bump immutable from 4.0.0 to 4.2.2 (#4807)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 4.0.0 to 4.2.2.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v4.0.0...v4.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 19:58:33 -08:00
dependabot[bot] 4bc441cc9f chore(deps): bump react-dnd from 14.0.1 to 16.0.1 (#4808)
Bumps [react-dnd](https://github.com/react-dnd/react-dnd) from 14.0.1 to 16.0.1.
- [Release notes](https://github.com/react-dnd/react-dnd/releases)
- [Changelog](https://github.com/react-dnd/react-dnd/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react-dnd/react-dnd/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 19:58:06 -08:00
Tom Moor f39487d25b fix: PaginatedList does not always load more (#4811) 2023-01-30 19:53:14 -08:00
dependabot[bot] 65a4874301 chore(deps): bump sequelize-typescript from 2.1.3 to 2.1.5 (#4804)
Bumps [sequelize-typescript](https://github.com/RobinBuschmann/sequelize-typescript) from 2.1.3 to 2.1.5.
- [Release notes](https://github.com/RobinBuschmann/sequelize-typescript/releases)
- [Changelog](https://github.com/sequelize/sequelize-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RobinBuschmann/sequelize-typescript/compare/v2.1.3...v2.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 18:19:01 -08:00
Tom Moor d1268167c8 Increase build speed by running concurrently 2023-01-29 16:18:54 -05:00
Tom Moor 80a8f5b7e2 feat: For changes in long tables do not print the entire table in the emailed diff (#4800) 2023-01-29 11:48:37 -08:00
Tom Moor 5473b698a4 Inject DataDog trace ID's into logs when enabled 2023-01-29 13:40:26 -05:00
Apoorv Mishra 2e6c960ae9 fix: remove document name from path (#4798) 2023-01-29 10:36:37 -08:00
Tom Moor d02d3cb55d feat: Add import/export of documents as JSON (#4621)
* feat: Add export of documents as JSON

* Rename, add structured collection description

* stash

* ui

* Add entity creation data to JSON archive

* Import JSON UI plumbing

* stash

* Messy, but working

* tsc

* tsc
2023-01-29 10:24:44 -08:00
Tom Moor 85ca25371c test (#4796) 2023-01-29 06:28:57 -08:00
Hans Pagel 8011bdb482 fix types 2023-01-28 23:55:48 +01:00
Hans Pagel 549881b297 add a new service worker (wip) 2023-01-28 23:45:36 +01:00
Tom Moor f0d9bb4898 Friendlier active tab design 2023-01-28 17:22:30 -05:00
Hans Pagel b04929db76 fix tests 2023-01-28 23:22:26 +01:00
Tom Moor 4de780c339 fix: Cmd+K styling does not match context menus
closes #4701
2023-01-28 17:17:35 -05:00
Tom Moor 075555a867 fix: Do not show actively disabled auth providers in self-hosted install (#4794)
* fix: Do not show actively disabled auth providers in self-hosted installation

* self review

* Refactor for easier mocking
2023-01-28 10:02:25 -08:00
Tom Moor aac495fa58 fix: Pipe characters in code marks within tables cause the table layout to break
closes #4783
2023-01-28 13:01:02 -05:00
Apoorv Mishra 7dbc419bbf Change "Move" dialog appearance to match that of "Publish" dialog (#4787)
* refactor: receive items as props in DocumentExplore

* refactor: leverage DocumentExplorer for DocumentMove

* fix: keyboard shortcut for moving document

* refactor: cleanup

* Revert "refactor: cleanup"

This reverts commit 9a0a98eff2.

* fix: get rid of extra parent container

* Revert "fix: get rid of extra parent container"

This reverts commit 908eaf2bba.

* refactor: remove PathToDocument component
2023-01-28 22:33:56 +05:30
Apoorv Mishra 0c572ac2c4 Duplicate docs as unpublished drafts (#4791)
* feat: duplicate docs as unpublished drafts

* fix: use isTemplate

* fix: no need of ternary
2023-01-28 21:32:59 +05:30
Apoorv Mishra 6d45566be3 fix: missing collection name in path in explorer search results (#4793) 2023-01-28 06:29:59 -08:00
Tom Moor d5eabd7771 fix: Allow loading attachments linked from other sites/emails.
Loosens same-site policy to include cookies for navigation events.
closes #4737
2023-01-27 18:52:47 -05:00
Tom Moor b5876dc844 fix: Fallback to username when name is unavailable in OIDC provider
closes #4774
2023-01-27 18:18:08 -05:00
Tom Moor 0272ea03bd fix: Text in revisions is unreadable in dark mode, closes #4782 2023-01-27 18:13:08 -05:00
Hans Pagel 440fb32868 replace Webpack with Vite in the architecture docs 2023-01-27 12:42:48 +01:00
Hans Pagel f24ef965cf remove vite-plugin-pwa 2023-01-27 12:40:38 +01:00
Hans Pagel 9ceb68920a use rollup 3, instead of 2 and 3 2023-01-27 12:26:55 +01:00
Apoorv Mishra ad902af52c Move tree implementation out of collections store (#4763)
* refactor: attaching emoji in tree node is unnecessary

* refactor: pass depth and hasChildren as separate props

* refactor: move tree impl into a separate hook

* refactor: separate out as DocumentExplorer for reuse

* fix: separate search and node

* fix: review comments

* fix: tsc
2023-01-27 11:33:51 +05:30
Hans Pagel 120132b01f disable type checks for the webpack stats plugin 2023-01-26 17:21:05 +01:00
Hans Pagel aa6d3a2081 refactor how the index file is read 2023-01-26 17:06:22 +01:00
Hans Pagel 9841e6d243 remove unused parameter from function 2023-01-26 16:57:23 +01:00
Tom Moor cc14c212b6 fix: Unable to access localStorage in document embedded in iframe with third party cookies blocked (#4777)
* fix: Pasting from Microsoft Office pastes image. Closes #3058

* fix: Use Storage wrapper implementation for all editor calls to localStorage

closes #4776
2023-01-26 04:48:56 -08:00
Tom Moor 9ea606a734 fix: Pasting from Microsoft Office pastes image. Closes #3058 2023-01-25 22:37:47 -05:00
Tom Moor 784631baf4 fix: Handle missing size on attachment 2023-01-25 22:30:21 -05:00
Hans Pagel 3974674871 remove the webpack config from the build script 2023-01-24 17:59:06 +01:00
Hans Pagel 245d91998f use vite instead of webpack in circle ci 2023-01-24 17:58:50 +01:00
Hans Pagel 10516e8d25 Merge branch 'main' into feat/replace-webpack-with-vite
# Conflicts:
#	package.json
#	yarn.lock
2023-01-24 17:19:49 +01:00
Tom Moor 6ab428a498 fix: Link toolbar does not allow doc search, closes #4757 2023-01-24 09:08:02 -05:00
Apoorv Mishra 88a1f72b59 fix: collections order in publish modal should match with the order in sidebar (#4762) 2023-01-24 04:23:58 -08:00
dependabot[bot] 2021f192bd chore(deps): bump cookiejar from 2.1.3 to 2.1.4 (#4758)
Bumps [cookiejar](https://github.com/bmeck/node-cookiejar) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/bmeck/node-cookiejar/releases)
- [Commits](https://github.com/bmeck/node-cookiejar/commits)

---
updated-dependencies:
- dependency-name: cookiejar
  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-23 17:48:14 -08:00
dependabot[bot] 1b6496dff4 chore(deps): bump koa-sslify from 5.0.0 to 5.0.1 (#4753)
Bumps [koa-sslify](https://github.com/turboMaCk/koa-sslify) from 5.0.0 to 5.0.1.
- [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/commits)

---
updated-dependencies:
- dependency-name: koa-sslify
  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-23 17:41:07 -08:00
dependabot[bot] 6c5dadff8c chore(deps-dev): bump lint-staged from 12.3.8 to 13.1.0 (#4754)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 12.3.8 to 13.1.0.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v12.3.8...v13.1.0)

---
updated-dependencies:
- dependency-name: lint-staged
  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-23 17:38:52 -08:00
Apoorv Mishra 6b286d82b8 Ability to choose publish location for a document (#4582)
* feat: initial base structure

* feat: utils for constructing and flattening collection tree

* feat: basic demo to display tree-like structure with virtualization

* feat: make it searchable

* feat: row component

* fix: handle row selection

* fix: scroll jitter

* fix: popover max-height to eliminate extra scroll

* fix: position scrollbar correctly

* fix: do not sort to maintain correct tree-like view

* feat: footer

* fix: scroll to selected item

* fix: deselect item

* fix: display selected location in footer

* fix: deselect item if any upon search trigger

* fix: create draft without collection

* fix: pass down collectionId to all the nodes

* feat: publish document under selected location

* fix: move the doc post publish in case it is supposed to be a nested doc

* fix: wrap text for selected location

* fix: footer background in dark mode and unused css

* fix: popover height in small devices

* fix: no need to spread

* refactor: remove outline

* refactor: border-radius is common

* refactor: remove active and focus

* fix: do not shrink spacer

* fix: scroll list padding with correctly adjusted scrolling

* refactor: use constants

* fix: use padding in favor of spacer

* refactor: border attrs not needed

* refactor: control title padding and icon size centrally

* fix: rename param

* fix: import path

* fix: refactor styles, avoid magic numbers

* fix: type err

* feat: make rows collapsible

* fix: fully expanded without disclosure upon search

* fix: use modal in place of popover

* fix: collapse descendants

* fix: rename PublishPopover to PublishModal

* fix: adjust collapse icon as part of tree view

* fix: enable keyboard navigation

* not sure why collapse and expand shortcuts are not working

* fix: row expansion and search box focus and blur

* fix: remove css hover, handle it via active prop

* fix: discard tree like view for search results

* fix: minor tweaks

* refactor: no need to pass onPublish

* refactor: remove unnecessary attrs from search component

* fix: publish button text

* fix: reset intial scroll offset to 0 on search

* fix: remove search highlights

* fix: clean up search component

* refactor: search and row collapse

* refactor: PublishLocation

* fix: show emoji or star icon if present

* fix: shift focus only from top item

* fix: leading emoji

* fix: baseline text

* fix: make path tertiary

* fix: do not show path for collections

* fix: path text color upon selection

* fix: deleted collection case

* fix: no results found

* fix: space around slash

* Refinement, some small refactors

* fix: Publish shortcut, use Button action

* Allow new document creation from command menu without active collection

* fix: duplicate

* fix: Unneccessary truncation

* fix: Scroll on expand/collapse
Remove wraparound

* fix: tsc

* fix: Horizontal overflow on PublishLocation
Remove pointless moveTo method

* fix: Missing translation

* Remove method indirection
Show expanded collection icon in tree when expanded

* Shrink font size a point

* Remove feature flag

* fix: Path color contrast in light mode
Remove unused expanded/show attributes

* shrink -> collapse, fix expanded disclosure without items after searching

* Mobile styles

* fix: scroll just into view

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-01-23 17:38:24 -08:00
Hans Pagel fd89ad5155 add service worker register script 2023-01-23 18:25:46 +01:00
Hans Pagel 96af0f779f add CDN URL to script tags 2023-01-23 18:11:30 +01:00
Hans Pagel 005bab23cf reinstall rollup webpack stats plugin 2023-01-23 18:10:06 +01:00
Hans Pagel 0e3e699c82 fix prefetch tags 2023-01-23 18:09:51 +01:00
Hans Pagel b008898aad Merge branch 'main' into feat/replace-webpack-with-vite
# Conflicts:
#	package.json
#	yarn.lock
2023-01-23 17:49:03 +01:00
Hans Pagel be916380fa remove unused aliasses 2023-01-23 17:07:46 +01:00
Hans Pagel 83c97a1be7 remove webpack 2023-01-23 17:03:24 +01:00
Hans Pagel 50a7e600c7 remove numbers from mangled class names (wip) 2023-01-23 16:53:11 +01:00
Hans Pagel 31f596685a improve types for the manifest util 2023-01-23 16:51:04 +01:00
Hans Pagel cfb089dec9 add vite module preload polyfill 2023-01-23 16:50:48 +01:00
Hans Pagel d9779890fe add TODO 2023-01-23 16:41:11 +01:00
Hans Pagel 834c46a095 read out entry file path from manifest file 2023-01-23 16:36:40 +01:00
Hans Pagel 5d743ff018 global shim works for dependencies, let’s use window in our code 2023-01-23 16:07:42 +01:00
Hans Pagel 59e6f3f22f improve global shim 2023-01-23 16:06:08 +01:00
Tom Moor da4a0189dc Update rateLimiter.ts 2023-01-22 14:37:52 -08:00
Tom Moor 312e11e7c1 fix: Any error from rate limiter results in 'Rate limit exceeded' screen 2023-01-22 11:31:48 -05:00
Tom Moor d3dbf53d0b chore: Add insurance rate limiter for when Redis is down/reconnecting 2023-01-22 11:04:30 -05:00
Tom Moor 5b561e98f7 chore: Add configurable per-document connection limit extension (#4717)
* chore: Add configurable per-document connection limit extension

* docs
2023-01-22 07:50:32 -08:00
Tom Moor aa88bb2a7b fix: Check browser has print ability before showing Print option 2023-01-22 10:32:31 -05:00
Tom Moor f83b0ab5e3 fix: Remove :is selector (bad compatibility) 2023-01-22 10:22:41 -05:00
Tom Moor 095028541d fix: Missing translations 2023-01-22 10:14:17 -05:00
Tom Moor c1aa4c8dde fix: Alignment of caption on fullwidth images 2023-01-22 10:13:15 -05:00
Tom Moor 049f49ebe8 fix: Fallback to initials instead of placeholder when offline 2023-01-22 09:46:52 -05:00
Tom Moor f1406577b7 Color transition on icon picker 2023-01-21 23:37:08 -05:00
dependabot[bot] 1da6847e68 chore(deps-dev): bump @typescript-eslint/parser from 5.40.0 to 5.48.1 (#4725)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.40.0 to 5.48.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.48.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  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-20 09:38:14 -08:00
Hans Pagel cb475abc69 fix issue with missing global variable 2023-01-19 15:23:30 +01:00
Hans Pagel 20b8bb6a31 fix entrypoint 2023-01-19 15:18:46 +01:00
Hans Pagel 82dbe8b6b7 add old aliasses as a TODO 2023-01-19 15:16:55 +01:00
Hans Pagel 68e30b4974 add vite react plugin docs link 2023-01-19 15:16:46 +01:00
Hans Pagel 0960f8d07b remove optimizeDeps, the package is patched anyway 2023-01-19 15:16:37 +01:00
Hans Pagel 22008db2ac configure PWA 2023-01-19 15:16:17 +01:00
Hans Pagel c23ca5d3b9 copy static files 2023-01-19 15:16:00 +01:00
Hans Pagel bd472ab5b0 don’t output gzipped size, can help speed up the build process and makes the output cleaner 2023-01-19 15:05:03 +01:00
Hans Pagel a41f35d289 copy babel config to vite config, to separate the frontend from the backend config 2023-01-19 14:59:14 +01:00
Hans Pagel 50973b7408 fix concurrently task name 2023-01-19 14:54:53 +01:00
Hans Pagel fb15ab90a1 patch prosemirror-math to fix the build issues 2023-01-19 13:36:32 +01:00
Hans Pagel bb20523266 re-enable PWA build 2023-01-19 13:01:04 +01:00
Hans Pagel 7b6d1e1c81 fix build issue with hocuspocus provider 2023-01-19 12:49:06 +01:00
Hans Pagel 1d71ee1109 fix dev server (wip) 2023-01-19 12:40:25 +01:00
Hans Pagel 4337dd8bff add basic pwa support 2023-01-19 12:27:59 +01:00
Hans Pagel 594e8f72f5 use browserlist configuration as build target 2023-01-19 11:51:29 +01:00
Hans Pagel 4e352b36a1 fix vite build process 2023-01-19 11:08:43 +01:00
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
Hans Pagel f98cac5705 add webpack-stats and legacy builds 2023-01-11 17:19:51 +01:00
Hans Pagel 860b6a7c20 use babelrc, fixes the build 2023-01-11 17:05:45 +01:00
Hans Pagel 02e5ed76ee shim node global 2023-01-11 16:55:24 +01:00
Hans Pagel 49473795ae configure build entry point 2023-01-11 16:25:30 +01: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
Hans Pagel b5e5b84cca add Vite to the dev script 2023-01-04 16:21:55 +01:00
Hans Pagel e130882549 globally define to avoid issues 2023-01-04 16:19:03 +01:00
Hans Pagel 8bd0878144 globally load setimmediate 2023-01-04 15:50:03 +01:00
Hans Pagel bc204ef15e add basic Vite setup 2023-01-04 15:43:17 +01:00
Hans Pagel db21c4d41d remove Webpack middleware 2023-01-04 15:42:45 +01:00
Hans Pagel 569009168c don’t check for auth providers for seeded teams (workaround) 2023-01-04 15:38:46 +01:00
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
799 changed files with 38491 additions and 22141 deletions
+5 -2
View File
@@ -98,8 +98,11 @@ jobs:
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
- run:
name: build-webpack
command: yarn build:webpack
name: build-vite
command: yarn vite:build
- run:
name: Send bundle stats to RelativeCI
command: npx relative-ci-agent
build-image:
executor: docker-publisher
steps:
+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.
+11 -37
View File
@@ -2,10 +2,7 @@
"projects": [
{
"displayName": "server",
"verbose": false,
"roots": [
"<rootDir>/server"
],
"roots": ["<rootDir>/server"],
"moduleNameMapper": {
"^@server/(.*)$": "<rootDir>/server/$1",
"^@shared/(.*)$": "<rootDir>/shared/$1"
@@ -14,33 +11,22 @@
"<rootDir>/__mocks__/console.js",
"<rootDir>/server/test/env.ts"
],
"setupFilesAfterEnv": [
"<rootDir>/server/test/setup.ts"
],
"setupFilesAfterEnv": ["<rootDir>/server/test/setup.ts"],
"testEnvironment": "node",
"runner": "@getoutline/jest-runner-serial"
},
{
"displayName": "app",
"verbose": false,
"roots": [
"<rootDir>/app"
],
"roots": ["<rootDir>/app"],
"moduleNameMapper": {
"^~/(.*)$": "<rootDir>/app/$1",
"^@shared/(.*)$": "<rootDir>/shared/$1",
"^.*[.](gif|ttf|eot|svg)$": "<rootDir>/__test__/fileMock.js",
"^uuid$": "<rootDir>/node_modules/uuid/dist/index.js"
},
"modulePaths": [
"<rootDir>/app"
],
"setupFiles": [
"<rootDir>/__mocks__/window.js"
],
"setupFilesAfterEnv": [
"<rootDir>/app/test/setup.ts"
],
"modulePaths": ["<rootDir>/app"],
"setupFiles": ["<rootDir>/__mocks__/window.js"],
"setupFilesAfterEnv": ["<rootDir>/app/test/setup.ts"],
"testEnvironment": "jsdom",
"testEnvironmentOptions": {
"url": "http://localhost"
@@ -48,37 +34,25 @@
},
{
"displayName": "shared-node",
"verbose": false,
"roots": [
"<rootDir>/shared"
],
"roots": ["<rootDir>/shared"],
"moduleNameMapper": {
"^@server/(.*)$": "<rootDir>/server/$1",
"^@shared/(.*)$": "<rootDir>/shared/$1"
},
"setupFiles": [
"<rootDir>/__mocks__/console.js"
],
"setupFilesAfterEnv": [
"<rootDir>/shared/test/setup.ts"
],
"setupFiles": ["<rootDir>/__mocks__/console.js"],
"setupFilesAfterEnv": ["<rootDir>/shared/test/setup.ts"],
"testEnvironment": "node"
},
{
"displayName": "shared-jsdom",
"verbose": false,
"roots": [
"<rootDir>/shared"
],
"roots": ["<rootDir>/shared"],
"moduleNameMapper": {
"^~/(.*)$": "<rootDir>/app/$1",
"^@shared/(.*)$": "<rootDir>/shared/$1",
"^.*[.](gif|ttf|eot|svg)$": "<rootDir>/__test__/fileMock.js",
"^uuid$": "<rootDir>/node_modules/uuid/dist/index.js"
},
"setupFiles": [
"<rootDir>/__mocks__/window.js"
],
"setupFiles": ["<rootDir>/__mocks__/window.js"],
"testEnvironment": "jsdom",
"testEnvironmentOptions": {
"url": "http://localhost"
+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": {
+31 -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";
@@ -21,6 +23,7 @@ const ColorCollectionIcon = ({ collection }: { collection: Collection }) => {
export const openCollection = createAction({
name: ({ t }) => t("Open collection"),
analyticsName: "Open collection",
section: CollectionSection,
shortcut: ["o", "c"],
icon: <CollectionIcon />,
@@ -40,6 +43,7 @@ export const openCollection = createAction({
export const createCollection = createAction({
name: ({ t }) => t("New collection"),
analyticsName: "New collection",
section: CollectionSection,
icon: <PlusIcon />,
keywords: "create",
@@ -56,7 +60,9 @@ export const createCollection = createAction({
});
export const editCollection = createAction({
name: ({ t }) => t("Edit collection"),
name: ({ t, isContextMenu }) =>
isContextMenu ? `${t("Edit")}` : t("Edit collection"),
analyticsName: "Edit collection",
section: CollectionSection,
icon: <EditIcon />,
visible: ({ stores, activeCollectionId }) =>
@@ -79,8 +85,30 @@ export const editCollection = createAction({
},
});
export const editCollectionPermissions = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? `${t("Permissions")}` : t("Collection permissions"),
analyticsName: "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"),
analyticsName: "Star collection",
section: CollectionSection,
icon: <StarredIcon />,
keywords: "favorite bookmark",
@@ -106,6 +134,7 @@ export const starCollection = createAction({
export const unstarCollection = createAction({
name: ({ t }) => t("Unstar"),
analyticsName: "Unstar collection",
section: CollectionSection,
icon: <UnstarredIcon />,
keywords: "unfavorite unbookmark",
+251 -21
View File
@@ -17,20 +17,35 @@ 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";
import DocumentPermanentDelete from "~/scenes/DocumentPermanentDelete";
import DocumentPublish from "~/scenes/DocumentPublish";
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"),
analyticsName: "Open document",
section: DocumentSection,
shortcut: ["o", "d"],
keywords: "go to",
@@ -55,14 +70,13 @@ export const openDocument = createAction({
export const createDocument = createAction({
name: ({ t }) => t("New document"),
analyticsName: "New document",
section: DocumentSection,
icon: <NewDocumentIcon />,
keywords: "create",
visible: ({ activeCollectionId, stores }) =>
!!activeCollectionId &&
stores.policies.abilities(activeCollectionId).update,
visible: ({ currentTeamId, stores }) =>
!!currentTeamId && stores.policies.abilities(currentTeamId).createDocument,
perform: ({ activeCollectionId, inStarredSection }) =>
activeCollectionId &&
history.push(newDocumentPath(activeCollectionId), {
starred: inStarredSection,
}),
@@ -70,6 +84,7 @@ export const createDocument = createAction({
export const starDocument = createAction({
name: ({ t }) => t("Star"),
analyticsName: "Star document",
section: DocumentSection,
icon: <StarredIcon />,
keywords: "favorite bookmark",
@@ -94,6 +109,7 @@ export const starDocument = createAction({
export const unstarDocument = createAction({
name: ({ t }) => t("Unstar"),
analyticsName: "Unstar document",
section: DocumentSection,
icon: <UnstarredIcon />,
keywords: "unfavorite unbookmark",
@@ -117,8 +133,76 @@ export const unstarDocument = createAction({
},
});
export const publishDocument = createAction({
name: ({ t }) => t("Publish"),
analyticsName: "Publish document",
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: async ({ activeDocumentId, stores, t }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
if (document?.publishedAt) {
return;
}
if (document?.collectionId) {
await document.save({
publish: true,
});
stores.toasts.showToast(t("Document published"), {
type: "success",
});
} else if (document) {
stores.dialogs.openModal({
title: t("Publish document"),
isCentered: true,
content: <DocumentPublish document={document} />,
});
}
},
});
export const unpublishDocument = createAction({
name: ({ t }) => t("Unpublish"),
analyticsName: "Unpublish document",
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"),
analyticsName: "Subscribe to document",
section: DocumentSection,
icon: <SubscribeIcon />,
visible: ({ activeDocumentId, stores }) => {
@@ -150,6 +234,7 @@ export const subscribeDocument = createAction({
export const unsubscribeDocument = createAction({
name: ({ t }) => t("Unsubscribe"),
analyticsName: "Unsubscribe from document",
section: DocumentSection,
icon: <UnsubscribeIcon />,
visible: ({ activeDocumentId, stores }) => {
@@ -179,12 +264,13 @@ 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"),
analyticsName: "Download document as 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,13 +279,75 @@ export const downloadDocument = createAction({
}
const document = stores.documents.get(activeDocumentId);
document?.download();
document?.download(ExportContentType.Html);
},
});
export const downloadDocumentAsPDF = createAction({
name: ({ t }) => t("PDF"),
analyticsName: "Download document as 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"),
analyticsName: "Download document as 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"),
analyticsName: "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"),
analyticsName: "Duplicate document",
section: DocumentSection,
icon: <DuplicateIcon />,
keywords: "copy",
@@ -226,7 +374,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,
});
},
analyticsName: "Pin document to collection",
section: DocumentSection,
icon: <PinIcon />,
iconInContextMenu: false,
@@ -262,6 +420,7 @@ export const pinDocumentToCollection = createAction({
*/
export const pinDocumentToHome = createAction({
name: ({ t }) => t("Pin to home"),
analyticsName: "Pin document to home",
section: DocumentSection,
icon: <PinIcon />,
iconInContextMenu: false,
@@ -293,6 +452,7 @@ export const pinDocumentToHome = createAction({
export const pinDocument = createAction({
name: ({ t }) => t("Pin"),
analyticsName: "Pin document",
section: DocumentSection,
icon: <PinIcon />,
children: [pinDocumentToCollection, pinDocumentToHome],
@@ -301,9 +461,10 @@ export const pinDocument = createAction({
export const printDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Print") : t("Print document"),
analyticsName: "Print document",
section: DocumentSection,
icon: <PrintIcon />,
visible: ({ activeDocumentId }) => !!activeDocumentId,
visible: ({ activeDocumentId }) => !!(activeDocumentId && window.print),
perform: async () => {
window.print();
},
@@ -311,6 +472,7 @@ export const printDocument = createAction({
export const importDocument = createAction({
name: ({ t }) => t("Import document"),
analyticsName: "Import document",
section: DocumentSection,
icon: <ImportIcon />,
keywords: "upload",
@@ -359,6 +521,7 @@ export const importDocument = createAction({
export const createTemplate = createAction({
name: ({ t }) => t("Templatize"),
analyticsName: "Templatize document",
section: DocumentSection,
icon: <ShapesIcon />,
keywords: "new create template",
@@ -389,12 +552,32 @@ export const createTemplate = createAction({
},
});
export const openRandomDocument = createAction({
id: "random",
name: ({ t }) => t(`Open random document`),
analyticsName: "Open random document",
section: DocumentSection,
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",
section: DocumentSection,
name: ({ t }) =>
t(`Search documents for "{{searchQuery}}"`, { searchQuery }),
analyticsName: "Search documents",
section: DocumentSection,
icon: <SearchIcon />,
perform: () => history.push(searchPath(searchQuery)),
visible: ({ location }) => location.pathname !== searchPath(),
@@ -402,6 +585,7 @@ export const searchDocumentsForQuery = (searchQuery: string) =>
export const moveDocument = createAction({
name: ({ t }) => t("Move"),
analyticsName: "Move document",
section: DocumentSection,
icon: <MoveIcon />,
visible: ({ activeDocumentId, stores }) => {
@@ -418,15 +602,11 @@ export const moveDocument = createAction({
}
stores.dialogs.openModal({
title: t("Move {{ documentName }}", {
documentName: document.noun,
title: t("Move {{ documentType }}", {
documentType: document.noun,
}),
content: (
<DocumentMove
document={document}
onRequestClose={stores.dialogs.closeAllModals}
/>
),
isCentered: true,
content: <DocumentMove document={document} />,
});
}
},
@@ -434,6 +614,7 @@ export const moveDocument = createAction({
export const archiveDocument = createAction({
name: ({ t }) => t("Archive"),
analyticsName: "Archive document",
section: DocumentSection,
icon: <ArchiveIcon />,
visible: ({ activeDocumentId, stores }) => {
@@ -459,6 +640,7 @@ export const archiveDocument = createAction({
export const deleteDocument = createAction({
name: ({ t }) => t("Delete"),
analyticsName: "Delete document",
section: DocumentSection,
icon: <TrashIcon />,
dangerous: true,
@@ -493,6 +675,7 @@ export const deleteDocument = createAction({
export const permanentlyDeleteDocument = createAction({
name: ({ t }) => t("Permanently delete"),
analyticsName: "Permanently delete document",
section: DocumentSection,
icon: <CrossIcon />,
dangerous: true,
@@ -525,6 +708,48 @@ export const permanentlyDeleteDocument = createAction({
},
});
export const openDocumentHistory = createAction({
name: ({ t }) => t("History"),
analyticsName: "Open document 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"),
analyticsName: "Open document 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 +760,17 @@ export const rootDocumentActions = [
downloadDocument,
starDocument,
unstarDocument,
publishDocument,
unpublishDocument,
subscribeDocument,
unsubscribeDocument,
duplicateDocument,
moveDocument,
openRandomDocument,
permanentlyDeleteDocument,
printDocument,
pinDocumentToCollection,
pinDocumentToHome,
openDocumentHistory,
openDocumentInsights,
];
+44
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,
@@ -38,6 +43,7 @@ import {
export const navigateToHome = createAction({
name: ({ t }) => t("Home"),
analyticsName: "Navigate to home",
section: NavigationSection,
shortcut: ["d"],
icon: <HomeIcon />,
@@ -49,12 +55,14 @@ export const navigateToRecentSearchQuery = (searchQuery: SearchQuery) =>
createAction({
section: RecentSearchesSection,
name: searchQuery.query,
analyticsName: "Navigate to recent search query",
icon: <SearchIcon />,
perform: () => history.push(searchPath(searchQuery.query)),
});
export const navigateToDrafts = createAction({
name: ({ t }) => t("Drafts"),
analyticsName: "Navigate to drafts",
section: NavigationSection,
icon: <EditIcon />,
perform: () => history.push(draftsPath()),
@@ -63,6 +71,7 @@ export const navigateToDrafts = createAction({
export const navigateToTemplates = createAction({
name: ({ t }) => t("Templates"),
analyticsName: "Navigate to templates",
section: NavigationSection,
icon: <ShapesIcon />,
perform: () => history.push(templatesPath()),
@@ -71,6 +80,7 @@ export const navigateToTemplates = createAction({
export const navigateToArchive = createAction({
name: ({ t }) => t("Archive"),
analyticsName: "Navigate to archive",
section: NavigationSection,
shortcut: ["g", "a"],
icon: <ArchiveIcon />,
@@ -80,6 +90,7 @@ export const navigateToArchive = createAction({
export const navigateToTrash = createAction({
name: ({ t }) => t("Trash"),
analyticsName: "Navigate to trash",
section: NavigationSection,
icon: <TrashIcon />,
perform: () => history.push(trashPath()),
@@ -88,6 +99,7 @@ export const navigateToTrash = createAction({
export const navigateToSettings = createAction({
name: ({ t }) => t("Settings"),
analyticsName: "Navigate to settings",
section: NavigationSection,
shortcut: ["g", "s"],
icon: <SettingsIcon />,
@@ -98,14 +110,25 @@ export const navigateToSettings = createAction({
export const navigateToProfileSettings = createAction({
name: ({ t }) => t("Profile"),
analyticsName: "Navigate to profile settings",
section: NavigationSection,
iconInContextMenu: false,
icon: <ProfileIcon />,
perform: () => history.push(profileSettingsPath()),
});
export const navigateToAccountPreferences = createAction({
name: ({ t }) => t("Preferences"),
analyticsName: "Navigate to account preferences",
section: NavigationSection,
iconInContextMenu: false,
icon: <SettingsIcon />,
perform: () => history.push(accountPreferencesPath()),
});
export const openAPIDocumentation = createAction({
name: ({ t }) => t("API documentation"),
analyticsName: "Open API documentation",
section: NavigationSection,
iconInContextMenu: false,
icon: <OpenIcon />,
@@ -114,6 +137,7 @@ export const openAPIDocumentation = createAction({
export const openFeedbackUrl = createAction({
name: ({ t }) => t("Send us feedback"),
analyticsName: "Open feedback",
section: NavigationSection,
iconInContextMenu: false,
icon: <EmailIcon />,
@@ -122,12 +146,14 @@ export const openFeedbackUrl = createAction({
export const openBugReportUrl = createAction({
name: ({ t }) => t("Report a bug"),
analyticsName: "Open bug report",
section: NavigationSection,
perform: () => window.open(githubIssuesUrl()),
});
export const openChangelog = createAction({
name: ({ t }) => t("Changelog"),
analyticsName: "Open changelog",
section: NavigationSection,
iconInContextMenu: false,
icon: <OpenIcon />,
@@ -136,6 +162,7 @@ export const openChangelog = createAction({
export const openKeyboardShortcuts = createAction({
name: ({ t }) => t("Keyboard shortcuts"),
analyticsName: "Open keyboard shortcuts",
section: NavigationSection,
shortcut: ["?"],
iconInContextMenu: false,
@@ -148,8 +175,24 @@ export const openKeyboardShortcuts = createAction({
},
});
export const downloadApp = createAction({
name: ({ t }) =>
t("Download {{ platform }} app", {
platform: isMac() ? "macOS" : "Windows",
}),
analyticsName: "Download app",
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"),
analyticsName: "Log out",
section: NavigationSection,
icon: <LogoutIcon />,
perform: () => stores.auth.logout(),
@@ -161,6 +204,7 @@ export const rootNavigationActions = [
navigateToTemplates,
navigateToArchive,
navigateToTrash,
downloadApp,
openAPIDocumentation,
openFeedbackUrl,
openBugReportUrl,
+87
View File
@@ -0,0 +1,87 @@
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"),
analyticsName: "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"),
analyticsName: "Copy link to revision",
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 = [];
+4
View File
@@ -7,6 +7,7 @@ import { SettingsSection } from "~/actions/sections";
export const changeToDarkTheme = createAction({
name: ({ t }) => t("Dark"),
analyticsName: "Change to dark theme",
icon: <MoonIcon />,
iconInContextMenu: false,
keywords: "theme dark night",
@@ -17,6 +18,7 @@ export const changeToDarkTheme = createAction({
export const changeToLightTheme = createAction({
name: ({ t }) => t("Light"),
analyticsName: "Change to light theme",
icon: <SunIcon />,
iconInContextMenu: false,
keywords: "theme light day",
@@ -27,6 +29,7 @@ export const changeToLightTheme = createAction({
export const changeToSystemTheme = createAction({
name: ({ t }) => t("System"),
analyticsName: "Change to system theme",
icon: <BrowserIcon />,
iconInContextMenu: false,
keywords: "theme system default",
@@ -38,6 +41,7 @@ export const changeToSystemTheme = createAction({
export const changeTheme = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Appearance") : t("Change theme"),
analyticsName: "Change theme",
placeholder: ({ t }) => t("Change theme to"),
icon: () =>
stores.ui.resolvedTheme === "light" ? <SunIcon /> : <MoonIcon />,
+64 -28
View File
@@ -1,40 +1,76 @@
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} />,
analyticsName: "Switch workspace",
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"),
analyticsName: "Switch 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")}`,
analyticsName: "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];
+2 -1
View File
@@ -7,8 +7,9 @@ import { UserSection } from "~/actions/sections";
export const inviteUser = createAction({
name: ({ t }) => `${t("Invite people")}`,
analyticsName: "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,
+31 -4
View File
@@ -9,6 +9,7 @@ import {
MenuItemButton,
MenuItemWithChildren,
} from "~/types";
import Analytics from "~/utils/Analytics";
function resolve<T>(value: any, context: ActionContext): T {
return typeof value === "function" ? value(context) : value;
@@ -17,6 +18,23 @@ function resolve<T>(value: any, context: ActionContext): T {
export function createAction(definition: Optional<Action, "id">): Action {
return {
...definition,
perform: definition.perform
? (context) => {
// We muse use the specific analytics name here as the action name is
// translated and potentially contains user strings.
if (definition.analyticsName) {
Analytics.track("perform_action", definition.analyticsName, {
context: context.isButton
? "button"
: context.isCommandBar
? "commandbar"
: "contextmenu",
});
}
return definition.perform?.(context);
}
: undefined,
id: uuidv4(),
};
}
@@ -57,8 +75,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 +96,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);
@@ -85,12 +111,13 @@ export function actionToKBar(
{
id: action.id,
name: resolvedName,
analyticsName: action.analyticsName,
section: resolvedSection,
placeholder: resolvedPlaceholder,
keywords: action.keywords ?? "",
shortcut: action.shortcut || [],
icon: resolvedIcon,
perform: action.perform ? () => action?.perform?.(context) : undefined,
perform: action.perform ? () => action.perform?.(context) : undefined,
},
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
].concat(children.map((child) => ({ ...child, parent: action.id })));
+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");
+20 -13
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,40 +20,47 @@ 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 [executing, setExecuting] = React.useState(false);
const disabled = rest.disabled;
if (!context || !action) {
return <button {...rest} ref={ref} />;
}
if (action?.visible && !action.visible(context) && hideOnActionDisabled) {
const actionContext = { ...context, isButton: true };
if (
action?.visible &&
!action.visible(actionContext) &&
hideOnActionDisabled
) {
return null;
}
const label =
typeof action.name === "function" ? action.name(context) : action.name;
typeof action.name === "function"
? action.name(actionContext)
: action.name;
const button = (
<button
{...rest}
aria-label={label}
disabled={disabled}
disabled={disabled || executing}
ref={ref}
onClick={
action?.perform && context
action?.perform && actionContext
? (ev) => {
ev.preventDefault();
ev.stopPropagation();
action.perform?.(context);
const response = action.perform?.(actionContext);
if (response?.finally) {
setExecuting(true);
response.finally(() => setExecuting(false));
}
}
: rest.onClick
}
-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>
);
+75 -69
View File
@@ -1,49 +1,50 @@
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"
)
() => import("~/scenes/Document/components/History")
);
const CommandBar = React.lazy(
() =>
import(
/* webpackChunkName: "command-bar" */
"~/components/CommandBar"
)
const DocumentInsights = React.lazy(
() => import("~/scenes/Document/components/Insights")
);
const CommandBar = React.lazy(() => import("~/components/CommandBar"));
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 +52,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 -32
View File
@@ -1,52 +1,59 @@
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 placeholder from "./placeholder.png";
import useBoolean from "~/hooks/useBoolean";
import Initials from "./Initials";
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 && !error ? (
<CircleImg
onError={this.handleError}
src={this.error ? placeholder : src}
onError={handleError}
src={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 +73,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
View File
@@ -1,6 +1,3 @@
import Avatar from "./Avatar";
import AvatarWithPresence from "./AvatarWithPresence";
export { AvatarWithPresence };
export default Avatar;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

+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;
+54 -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,18 +153,18 @@ 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;
borderOnHover?: boolean;
hideIcon?: boolean;
href?: string;
"data-on"?: string;
"data-event-category"?: string;
@@ -168,14 +175,39 @@ 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,
hideIcon,
fullwidth,
danger,
...rest
} = props;
const hasText = children !== undefined || value !== undefined;
const hasIcon = icon !== undefined;
const ic = hideIcon ? undefined : 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 -2
View File
@@ -4,7 +4,7 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { usePopoverState, PopoverDisclosure } from "reakit/Popover";
import Document from "~/models/Document";
import { AvatarWithPresence } from "~/components/Avatar";
import AvatarWithPresence from "~/components/Avatar/AvatarWithPresence";
import DocumentViews from "~/components/DocumentViews";
import Facepile from "~/components/Facepile";
import NudeButton from "~/components/NudeButton";
@@ -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 ||
+5 -2
View File
@@ -92,17 +92,20 @@ const Content = styled(Flex)`
const Item = styled.div<{ active?: boolean }>`
font-size: 15px;
padding: 10px 16px;
padding: 9px 12px;
margin: 0 8px;
border-radius: 4px;
background: ${(props) =>
props.active ? props.theme.menuItemSelected : "none"};
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
cursor: var(--pointer);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
user-select: none;
min-width: 0;
${(props) =>
+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;
}
+11 -7
View File
@@ -3,11 +3,12 @@ import { ArchiveIcon, GoToIcon, ShapesIcon, TrashIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import type { NavigationNode } from "@shared/types";
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 { MenuInternalLink } from "~/types";
import { collectionUrl } from "~/utils/routeHelpers";
type Props = {
@@ -58,7 +59,7 @@ const DocumentBreadcrumb: React.FC<Props> = ({
const category = useCategory(document);
const collection = collections.get(document.collectionId);
let collectionNode: MenuInternalLink;
let collectionNode: MenuInternalLink | undefined;
if (collection) {
collectionNode = {
@@ -67,7 +68,7 @@ const DocumentBreadcrumb: React.FC<Props> = ({
icon: <CollectionIcon collection={collection} expanded />,
to: collectionUrl(collection.url),
};
} else {
} else if (document.collectionId && !collection) {
collectionNode = {
type: "route",
title: t("Deleted Collection"),
@@ -77,8 +78,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(() => {
@@ -88,7 +90,9 @@ const DocumentBreadcrumb: React.FC<Props> = ({
output.push(category);
}
output.push(collectionNode);
if (collectionNode) {
output.push(collectionNode);
}
path.forEach((node: NavigationNode) => {
output.push({
+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;
+408
View File
@@ -0,0 +1,408 @@
import FuzzySearch from "fuzzy-search";
import { includes, difference, concat, filter, map, fill } from "lodash";
import { observer } from "mobx-react";
import { StarredIcon, DocumentIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeList as List } from "react-window";
import scrollIntoView from "smooth-scroll-into-view-if-needed";
import styled, { useTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { NavigationNode } from "@shared/types";
import parseTitle from "@shared/utils/parseTitle";
import DocumentExplorerNode from "~/components/DocumentExplorerNode";
import DocumentExplorerSearchResult from "~/components/DocumentExplorerSearchResult";
import Flex from "~/components/Flex";
import CollectionIcon from "~/components/Icons/CollectionIcon";
import EmojiIcon from "~/components/Icons/EmojiIcon";
import { Outline } from "~/components/Input";
import InputSearch from "~/components/InputSearch";
import Text from "~/components/Text";
import useMobile from "~/hooks/useMobile";
import useStores from "~/hooks/useStores";
import { isModKey } from "~/utils/keyboard";
import { ancestors, descendants } from "~/utils/tree";
type Props = {
/** Action taken upon submission of selected item, could be publish, move etc. */
onSubmit: () => void;
/** A side-effect of item selection */
onSelect: (item: NavigationNode | null) => void;
/** Items to be shown in explorer */
items: NavigationNode[];
};
function DocumentExplorer({ onSubmit, onSelect, items }: Props) {
const isMobile = useMobile();
const { collections, documents } = useStores();
const { t } = useTranslation();
const theme = useTheme();
const [searchTerm, setSearchTerm] = React.useState<string>();
const [selectedNode, selectNode] = React.useState<NavigationNode | null>(
null
);
const [initialScrollOffset, setInitialScrollOffset] = React.useState<number>(
0
);
const [nodes, setNodes] = React.useState<NavigationNode[]>([]);
const [activeNode, setActiveNode] = React.useState<number>(0);
const [expandedNodes, setExpandedNodes] = React.useState<string[]>([]);
const [itemRefs, setItemRefs] = React.useState<
React.RefObject<HTMLSpanElement>[]
>([]);
const inputSearchRef = React.useRef<HTMLInputElement | HTMLTextAreaElement>(
null
);
const listRef = React.useRef<List<NavigationNode[]>>(null);
const VERTICAL_PADDING = 6;
const HORIZONTAL_PADDING = 24;
const searchIndex = React.useMemo(() => {
return new FuzzySearch(items, ["title"], {
caseSensitive: false,
});
}, [items]);
React.useEffect(() => {
if (searchTerm) {
selectNode(null);
setExpandedNodes([]);
}
setActiveNode(0);
}, [searchTerm]);
React.useEffect(() => {
let results;
if (searchTerm) {
results = searchIndex.search(searchTerm);
} else {
results = items.filter((item) => item.type === "collection");
}
setInitialScrollOffset(0);
setNodes(results);
}, [searchTerm, items, searchIndex]);
React.useEffect(() => {
setItemRefs((itemRefs) =>
map(
fill(Array(items.length), 0),
(_, i) => itemRefs[i] || React.createRef()
)
);
}, [items.length]);
React.useEffect(() => {
onSelect(selectedNode);
}, [selectedNode, onSelect]);
const scrollNodeIntoView = React.useCallback(
(node: number) => {
if (itemRefs[node] && itemRefs[node].current) {
scrollIntoView(itemRefs[node].current as HTMLSpanElement, {
behavior: "auto",
block: "center",
});
}
},
[itemRefs]
);
const handleSearch = (ev: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(ev.target.value);
};
const isExpanded = (node: number) => {
return includes(expandedNodes, nodes[node].id);
};
const calculateInitialScrollOffset = (itemCount: number) => {
if (listRef.current) {
const { height, itemSize } = listRef.current.props;
const { scrollOffset } = listRef.current.state as {
scrollOffset: number;
};
const itemsHeight = itemCount * itemSize;
return itemsHeight < height ? 0 : scrollOffset;
}
return 0;
};
const collapse = (node: number) => {
const descendantIds = descendants(nodes[node]).map((des) => des.id);
setExpandedNodes(
difference(expandedNodes, [...descendantIds, nodes[node].id])
);
// remove children
const newNodes = filter(nodes, (node) => !includes(descendantIds, node.id));
const scrollOffset = calculateInitialScrollOffset(newNodes.length);
setInitialScrollOffset(scrollOffset);
setNodes(newNodes);
};
const expand = (node: number) => {
setExpandedNodes(concat(expandedNodes, nodes[node].id));
// add children
const newNodes = nodes.slice();
newNodes.splice(node + 1, 0, ...descendants(nodes[node], 1));
const scrollOffset = calculateInitialScrollOffset(newNodes.length);
setInitialScrollOffset(scrollOffset);
setNodes(newNodes);
};
const isSelected = (node: number) => {
if (!selectedNode) {
return false;
}
const selectedNodeId = selectedNode.id;
const nodeId = nodes[node].id;
return selectedNodeId === nodeId;
};
const hasChildren = (node: number) => {
return nodes[node].children.length > 0;
};
const toggleCollapse = (node: number) => {
if (!hasChildren(node)) {
return;
}
if (isExpanded(node)) {
collapse(node);
} else {
expand(node);
}
};
const toggleSelect = (node: number) => {
if (isSelected(node)) {
selectNode(null);
} else {
selectNode(nodes[node]);
}
};
const ListItem = ({
index,
data,
style,
}: {
index: number;
data: NavigationNode[];
style: React.CSSProperties;
}) => {
const node = data[index];
const isCollection = node.type === "collection";
let icon, title, path;
if (isCollection) {
const col = collections.get(node.collectionId as string);
icon = col && (
<CollectionIcon collection={col} expanded={isExpanded(index)} />
);
title = node.title;
} else {
const doc = documents.get(node.id);
const { strippedTitle, emoji } = parseTitle(node.title);
title = strippedTitle;
if (emoji) {
icon = <EmojiIcon emoji={emoji} />;
} else if (doc?.isStarred) {
icon = <StarredIcon color={theme.yellow} />;
} else {
icon = <DocumentIcon />;
}
path = ancestors(node)
.map((a) => parseTitle(a.title).strippedTitle)
.join(" / ");
}
return searchTerm ? (
<DocumentExplorerSearchResult
selected={isSelected(index)}
active={activeNode === index}
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
icon={icon}
title={title}
path={path}
/>
) : (
<DocumentExplorerNode
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
onDisclosureClick={(ev) => {
ev.stopPropagation();
toggleCollapse(index);
}}
selected={isSelected(index)}
active={activeNode === index}
expanded={isExpanded(index)}
icon={icon}
title={title}
depth={node.depth as number}
hasChildren={hasChildren(index)}
ref={itemRefs[index]}
/>
);
};
const focusSearchInput = () => {
inputSearchRef.current?.focus();
};
const next = () => {
return Math.min(activeNode + 1, nodes.length - 1);
};
const prev = () => {
return Math.max(activeNode - 1, 0);
};
const handleKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>) => {
switch (ev.key) {
case "ArrowDown": {
ev.preventDefault();
setActiveNode(next());
scrollNodeIntoView(next());
break;
}
case "ArrowUp": {
ev.preventDefault();
if (activeNode === 0) {
focusSearchInput();
} else {
setActiveNode(prev());
scrollNodeIntoView(prev());
}
break;
}
case "ArrowLeft": {
if (!searchTerm && isExpanded(activeNode)) {
toggleCollapse(activeNode);
}
break;
}
case "ArrowRight": {
if (!searchTerm) {
toggleCollapse(activeNode);
// let the nodes re-render first and then scroll
setImmediate(() => scrollNodeIntoView(activeNode));
}
break;
}
case "Enter": {
if (isModKey(ev)) {
onSubmit();
} else {
toggleSelect(activeNode);
}
break;
}
}
};
const innerElementType = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ style, ...rest }, ref) => (
<div
ref={ref}
style={{
...style,
height: `${parseFloat(style?.height + "") + VERTICAL_PADDING * 2}px`,
}}
{...rest}
/>
));
return (
<Container tabIndex={-1} onKeyDown={handleKeyDown}>
<ListSearch
ref={inputSearchRef}
onChange={handleSearch}
placeholder={`${t("Search collections & documents")}`}
autoFocus
/>
<ListContainer>
{nodes.length ? (
<AutoSizer>
{({ width, height }: { width: number; height: number }) => (
<Flex role="listbox" column>
<List
ref={listRef}
key={nodes.length}
width={width}
height={height}
itemData={nodes}
itemCount={nodes.length}
itemSize={isMobile ? 48 : 32}
innerElementType={innerElementType}
initialScrollOffset={initialScrollOffset}
itemKey={(index, results) => results[index].id}
>
{ListItem}
</List>
</Flex>
)}
</AutoSizer>
) : (
<FlexContainer>
<Text type="secondary">{t("No results found")}.</Text>
</FlexContainer>
)}
</ListContainer>
</Container>
);
}
const Container = styled.div``;
const FlexContainer = styled(Flex)`
height: 100%;
align-items: center;
justify-content: center;
`;
const ListSearch = styled(InputSearch)`
${Outline} {
border-radius: 16px;
}
margin-bottom: 4px;
padding-left: 24px;
padding-right: 24px;
`;
const ListContainer = styled.div`
height: 65vh;
${breakpoint("tablet")`
height: 40vh;
`}
`;
export default observer(DocumentExplorer);
+134
View File
@@ -0,0 +1,134 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Flex from "~/components/Flex";
import Disclosure from "~/components/Sidebar/components/Disclosure";
import Text from "~/components/Text";
type Props = {
selected: boolean;
active: boolean;
style: React.CSSProperties;
expanded: boolean;
icon?: React.ReactNode;
title: string;
depth: number;
hasChildren: boolean;
onDisclosureClick: (ev: React.MouseEvent) => void;
onPointerMove: (ev: React.MouseEvent) => void;
onClick: (ev: React.MouseEvent) => void;
};
function DocumentExplorerNode(
{
selected,
active,
style,
expanded,
icon,
title,
depth,
hasChildren,
onDisclosureClick,
onPointerMove,
onClick,
}: Props,
ref: React.RefObject<HTMLSpanElement>
) {
const { t } = useTranslation();
const OFFSET = 12;
const ICON_SIZE = 24;
const width = depth ? depth * ICON_SIZE + OFFSET : ICON_SIZE;
return (
<Node
ref={ref}
selected={selected}
active={active}
onClick={onClick}
style={style}
onPointerMove={onPointerMove}
role="option"
>
<Spacer width={width}>
{hasChildren && (
<StyledDisclosure
expanded={expanded}
onClick={onDisclosureClick}
tabIndex={-1}
/>
)}
</Spacer>
{icon}
<Title>{title || t("Untitled")}</Title>
</Node>
);
}
const Title = styled(Text)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 4px 0 4px;
color: inherit;
`;
const StyledDisclosure = styled(Disclosure)`
position: relative;
left: auto;
margin-top: 2px;
`;
const Spacer = styled(Flex)<{ width: number }>`
flex-direction: row-reverse;
flex-shrink: 0;
width: ${(props) => props.width}px;
`;
export const Node = styled.span<{
active: boolean;
selected: boolean;
style: React.CSSProperties;
}>`
display: flex;
user-select: none;
overflow: hidden;
font-size: 16px;
width: ${(props) => props.style.width};
color: ${(props) => props.theme.text};
cursor: var(--pointer);
padding: 12px;
border-radius: 6px;
background: ${(props) =>
!props.selected && props.active && props.theme.listItemHoverBackground};
svg {
flex-shrink: 0;
}
&:focus {
outline: none;
}
${(props) =>
props.selected &&
`
background: ${props.theme.primary};
color: ${props.theme.white};
svg {
fill: ${props.theme.white};
}
`}
${breakpoint("tablet")`
padding: 4px;
font-size: 15px;
`}
`;
export default observer(React.forwardRef(DocumentExplorerNode));
@@ -0,0 +1,85 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import scrollIntoView from "smooth-scroll-into-view-if-needed";
import styled from "styled-components";
import { Node as SearchResult } from "~/components/DocumentExplorerNode";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
type Props = {
selected: boolean;
active: boolean;
style: React.CSSProperties;
icon?: React.ReactNode;
title: string;
path?: string;
onPointerMove: (ev: React.MouseEvent) => void;
onClick: (ev: React.MouseEvent) => void;
};
function DocumentExplorerSearchResult({
selected,
active,
style,
icon,
title,
path,
onPointerMove,
onClick,
}: Props) {
const { t } = useTranslation();
const ref = React.useCallback(
(node: HTMLSpanElement | null) => {
if (active && node) {
scrollIntoView(node, {
scrollMode: "if-needed",
behavior: "auto",
block: "nearest",
});
}
},
[active]
);
return (
<SearchResult
ref={ref}
selected={selected}
active={active}
onClick={onClick}
style={style}
onPointerMove={onPointerMove}
role="option"
>
{icon}
<Flex>
<Title>{title || t("Untitled")}</Title>
<Path $selected={selected} size="xsmall">
{path}
</Path>
</Flex>
</SearchResult>
);
}
const Title = styled(Text)`
flex-shrink: 0;
white-space: nowrap;
margin: 0 4px 0 4px;
color: inherit;
`;
const Path = styled(Text)<{ $selected: boolean }>`
padding-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 4px 0 8px;
color: ${(props) =>
props.$selected ? props.theme.white50 : props.theme.textTertiary};
`;
export default observer(DocumentExplorerSearchResult);
-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
/>
+38 -31
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,16 +26,11 @@ 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" */
"~/editor"
)
);
const LazyLoadedEditor = React.lazy(() => import("~/editor"));
export type Props = Optional<
EditorProps,
@@ -48,31 +44,33 @@ 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 [
activeLinkEvent,
setActiveLinkEvent,
] = React.useState<MouseEvent | null>(null);
const localRef = React.useRef<SharedEditor>();
const preferences = auth.user?.preferences;
const previousHeadings = React.useRef<Heading[] | null>(null);
const [
activeLinkElement,
setActiveLink,
] = React.useState<HTMLAnchorElement | null>(null);
const handleLinkActive = React.useCallback((event: MouseEvent) => {
setActiveLinkEvent(event);
const handleLinkActive = React.useCallback((element: HTMLAnchorElement) => {
setActiveLink(element);
return false;
}, []);
const handleLinkInactive = React.useCallback(() => {
setActiveLinkEvent(null);
setActiveLink(null);
}, []);
const handleSearchLink = React.useCallback(
@@ -131,6 +129,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 +158,16 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
}
}
if (shareId) {
navigateTo = `/share/${shareId}${navigateTo}`;
// Link to our own API should be opened in a new tab, not in the app
if (navigateTo.startsWith("/api/")) {
window.open(href, "_blank");
return;
}
// If we're navigating to an internal document link then prepend the
// share route to the URL so that the document is loaded in context
if (shareId && navigateTo.includes("/doc/")) {
navigateTo = sharedDocumentPath(shareId, navigateTo);
}
history.push(navigateTo);
@@ -172,8 +179,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 +188,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 +232,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
});
},
[
ref,
localRef,
props.onFileUploadStart,
props.onFileUploadStop,
dictionary,
@@ -246,7 +253,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 +263,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
onHeadingsChange(headings);
}
}
}, [ref, onHeadingsChange]);
}, [localRef, onHeadingsChange]);
const handleChange = React.useCallback(
(event) => {
@@ -268,7 +275,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 +286,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,18 +300,17 @@ 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 && (
{activeLinkElement && !shareId && (
<HoverPreview
node={activeLinkEvent.target as HTMLAnchorElement}
event={activeLinkEvent}
element={activeLinkElement}
onClose={handleLinkInactive}
/>
)}
+4 -2
View File
@@ -30,7 +30,7 @@ class ErrorBoundary extends React.Component<Props> {
if (
this.props.reloadOnChunkMissing &&
error.message &&
error.message.match(/chunk/)
error.message.match(/dynamically imported module/)
) {
// If the editor bundle fails to load then reload the entire window. This
// can happen if a deploy happens between the user loading the initial JS
@@ -60,7 +60,9 @@ class ErrorBoundary extends React.Component<Props> {
if (this.error) {
const error = this.error;
const isReported = !!env.SENTRY_DSN && isCloudHosted;
const isChunkError = this.error.message.match(/chunk/);
const isChunkError = this.error.message.match(
/dynamically imported module/
);
if (isChunkError) {
return (
+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);
+126
View File
@@ -0,0 +1,126 @@
import { observer } from "mobx-react";
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 Text from "~/components/Text";
import env from "~/env";
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();
const appName = env.APP_NAME;
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" });
};
const items = [
{
title: "Markdown",
description: t(
"A ZIP file containing the images, and documents in the Markdown format."
),
value: FileOperationFormat.MarkdownZip,
},
{
title: "HTML",
description: t(
"A ZIP file containing the images, and documents as HTML files."
),
value: FileOperationFormat.HTMLZip,
},
{
title: "JSON",
description: t(
"Structured data that can be used to transfer data to another compatible {{ appName }} instance.",
{
appName,
}
),
value: FileOperationFormat.JSON,
},
];
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>
{items.map((item) => (
<Option>
<input
type="radio"
name="format"
value={item.value}
checked={format === item.value}
onChange={handleFormatChange}
/>
<div>
<Text size="small" weight="bold">
{item.title}
</Text>
<Text size="small">{item.description}</Text>
</div>
</Option>
))}
</Flex>
</ConfirmationDialog>
);
}
const Option = styled.label`
display: flex;
align-items: center;
gap: 16px;
p {
margin: 0;
}
`;
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")`
+15 -16
View File
@@ -14,14 +14,13 @@ const DELAY_OPEN = 300;
const DELAY_CLOSE = 300;
type Props = {
node: HTMLAnchorElement;
event: MouseEvent;
element: HTMLAnchorElement;
onClose: () => void;
};
function HoverPreviewInternal({ node, onClose }: Props) {
function HoverPreviewInternal({ element, onClose }: Props) {
const { documents } = useStores();
const slug = parseDocumentSlug(node.href);
const slug = parseDocumentSlug(element.href);
const [isVisible, setVisible] = React.useState(false);
const timerClose = React.useRef<ReturnType<typeof setTimeout>>();
const timerOpen = React.useRef<ReturnType<typeof setTimeout>>();
@@ -68,13 +67,13 @@ function HoverPreviewInternal({ node, onClose }: Props) {
cardRef.current.addEventListener("mouseleave", startCloseTimer);
}
node.addEventListener("mouseout", startCloseTimer);
node.addEventListener("mouseover", stopCloseTimer);
node.addEventListener("mouseover", startOpenTimer);
element.addEventListener("mouseout", startCloseTimer);
element.addEventListener("mouseover", stopCloseTimer);
element.addEventListener("mouseover", startOpenTimer);
return () => {
node.removeEventListener("mouseout", startCloseTimer);
node.removeEventListener("mouseover", stopCloseTimer);
node.removeEventListener("mouseover", startOpenTimer);
element.removeEventListener("mouseout", startCloseTimer);
element.removeEventListener("mouseover", stopCloseTimer);
element.removeEventListener("mouseover", startOpenTimer);
if (cardRef.current) {
cardRef.current.removeEventListener("mouseenter", stopCloseTimer);
@@ -88,9 +87,9 @@ function HoverPreviewInternal({ node, onClose }: Props) {
clearTimeout(timerClose.current);
}
};
}, [node, slug]);
}, [element, slug]);
const anchorBounds = node.getBoundingClientRect();
const anchorBounds = element.getBoundingClientRect();
const cardBounds = cardRef.current?.getBoundingClientRect();
const left = cardBounds
? Math.min(anchorBounds.left, window.innerWidth - 16 - 350)
@@ -105,7 +104,7 @@ function HoverPreviewInternal({ node, onClose }: Props) {
aria-hidden
>
<div ref={cardRef}>
<HoverPreviewDocument url={node.href}>
<HoverPreviewDocument url={element.href}>
{(content: React.ReactNode) =>
isVisible ? (
<Animate>
@@ -124,18 +123,18 @@ function HoverPreviewInternal({ node, onClose }: Props) {
);
}
function HoverPreview({ node, ...rest }: Props) {
function HoverPreview({ element, ...rest }: Props) {
const isMobile = useMobile();
if (isMobile) {
return null;
}
// previews only work for internal doc links for now
if (isExternalUrl(node.href)) {
if (isExternalUrl(element.href)) {
return null;
}
return <HoverPreviewInternal {...rest} node={node} />;
return <HoverPreviewInternal {...rest} element={element} />;
}
const Animate = styled.div`
+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`
+25 -15
View File
@@ -51,12 +51,9 @@ const style = {
width: 30,
height: 30,
};
const TwitterPicker = React.lazy(
() =>
import(
/* webpackChunkName: "twitter-picker" */
"react-color/lib/components/twitter/Twitter"
)
() => import("react-color/lib/components/twitter/Twitter")
);
export const icons = {
@@ -241,7 +238,7 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
aria-label={t("Choose icon")}
>
<Icons>
{Object.keys(icons).map((name) => {
{Object.keys(icons).map((name, index) => {
return (
<MenuItem
key={name}
@@ -249,7 +246,15 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
{...menu}
>
{(props) => (
<IconButton style={style} {...props}>
<IconButton
style={
{
...style,
"--delay": `${index * 8}ms`,
} as React.CSSProperties
}
{...props}
>
<Icon as={icons[name].component} color={color} size={30} />
</IconButton>
)}
@@ -257,7 +262,7 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
);
})}
</Icons>
<Flex>
<Colors>
<React.Suspense fallback={<Loading>{t("Loading")}</Loading>}>
<ColorPicker
color={color}
@@ -266,6 +271,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 +288,7 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
}}
/>
</React.Suspense>
</Flex>
</Colors>
</ContextMenu>
</Wrapper>
);
@@ -287,6 +296,11 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
const Icon = styled.svg`
transition: fill 150ms ease-in-out;
transition-delay: var(--delay);
`;
const Colors = styled(Flex)`
padding: 8px;
`;
const Label = styled.label`
@@ -294,7 +308,7 @@ const Label = styled.label`
`;
const Icons = styled.div`
padding: 16px 8px 0 16px;
padding: 8px;
${breakpoint("tablet")`
width: 276px;
@@ -321,11 +335,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,6 +25,7 @@ const Span = styled.span<{ $size: number }>`
align-items: center;
justify-content: center;
text-align: center;
flex-shrink: 0;
width: ${(props) => props.$size}px;
height: ${(props) => props.$size}px;
text-indent: -0.15em;
+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>
);
}
+35
View File
@@ -0,0 +1,35 @@
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",
...rest
}: Props) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...rest}
>
<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 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]
);
-1
View File
@@ -18,7 +18,6 @@ const Key = styled.kbd<Props>`
border: solid 1px ${(props) => props.theme.slateLight};
border-bottom-color: ${(props) => props.theme.slate};
border-radius: 3px;
box-shadow: inset 0 -1px 0 ${(props) => props.theme.slate};
`;
export default Key;
+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: 500px;
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: () => {
//
},
+14 -5
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;
};
@@ -94,7 +99,7 @@ class PaginatedList<T extends PaginatedItem> extends React.Component<Props<T>> {
}
this.isFetching = true;
const counter = ++this.fetchCounter;
const limit = DEFAULT_PAGINATION_LIMIT;
const limit = this.props.options?.limit ?? DEFAULT_PAGINATION_LIMIT;
this.error = undefined;
try {
@@ -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 = "";
-122
View File
@@ -1,122 +0,0 @@
import { observer } from "mobx-react";
import { GoToIcon } from "outline-icons";
import * as React from "react";
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";
type Props = {
result: DocumentPath;
document?: Document | null | undefined;
collection: Collection | null | undefined;
onSuccess?: () => void;
style?: React.CSSProperties;
ref?: (element: React.ElementRef<"div"> | null | undefined) => void;
};
@observer
class PathToDocument extends React.Component<Props> {
handleClick = async (ev: React.SyntheticEvent) => {
ev.preventDefault();
const { document, result, onSuccess } = this.props;
if (!document) {
return;
}
if (result.type === "document") {
await document.move(result.collectionId, result.id);
} else {
await document.move(result.collectionId);
}
if (onSuccess) {
onSuccess();
}
};
render() {
const { result, collection, document, ref, style } = this.props;
const Component = document ? ResultWrapperLink : ResultWrapper;
if (!result) {
return <div />;
}
return (
// @ts-expect-error ts-migrate(2604) FIXME: JSX element type 'Component' does not have any con... Remove this comment to see the full error message
<Component
ref={ref}
onClick={this.handleClick}
href=""
style={style}
role="option"
selectable
>
{collection && <CollectionIcon collection={collection} />}
&nbsp;
{result.path
.map((doc) => <Title key={doc.id}>{doc.title}</Title>)
// @ts-expect-error ts-migrate(2739) FIXME: Type 'Element[]' is missing the following properti... Remove this comment to see the full error message
.reduce((prev, curr) => [prev, <StyledGoToIcon />, curr])}
{document && (
<DocumentTitle>
{" "}
<StyledGoToIcon /> <Title>{document.title}</Title>
</DocumentTitle>
)}
</Component>
);
}
}
const DocumentTitle = styled(Flex)``;
const Title = styled.span`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
const StyledGoToIcon = styled(GoToIcon)`
fill: ${(props) => props.theme.divider};
`;
const ResultWrapper = styled.div`
display: flex;
margin-bottom: 10px;
user-select: none;
color: ${(props) => props.theme.text};
cursor: default;
svg {
flex-shrink: 0;
}
`;
const ResultWrapperLink = styled(ResultWrapper.withComponent("a"))`
padding: 8px 4px;
${DocumentTitle} {
display: none;
}
svg {
flex-shrink: 0;
}
&:hover,
&:active,
&:focus {
background: ${(props) => props.theme.listItemHoverBackground};
outline: none;
${DocumentTitle} {
display: flex;
}
}
`;
export default PathToDocument;
+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;
+16 -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,11 @@ import TeamLogo from "../TeamLogo";
import Sidebar from "./Sidebar";
import ArchiveLink from "./components/ArchiveLink";
import Collections from "./components/Collections";
import DragPlaceholder from "./components/DragPlaceholder";
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 +59,27 @@ function AppSidebar() {
return (
<Sidebar ref={handleSidebarRef}>
<HistoryNavigation />
{dndArea && (
<DndProvider backend={HTML5Backend} options={html5Options}>
<DragPlaceholder />
<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 +148,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);

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