Compare commits

...

718 Commits

Author SHA1 Message Date
Tom Moor 2c9b01391f 0.75.0 2024-02-13 19:58:16 -05:00
dependabot[bot] 726d24c728 chore(deps-dev): bump @faker-js/faker from 8.0.2 to 8.4.1 (#6526)
Bumps [@faker-js/faker](https://github.com/faker-js/faker) from 8.0.2 to 8.4.1.
- [Release notes](https://github.com/faker-js/faker/releases)
- [Changelog](https://github.com/faker-js/faker/blob/next/CHANGELOG.md)
- [Commits](https://github.com/faker-js/faker/compare/v8.0.2...v8.4.1)

---
updated-dependencies:
- dependency-name: "@faker-js/faker"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 06:55:00 -08:00
dependabot[bot] 46e8b276c8 chore(deps): bump passport from 0.6.0 to 0.7.0 (#6527)
Bumps [passport](https://github.com/jaredhanson/passport) from 0.6.0 to 0.7.0.
- [Changelog](https://github.com/jaredhanson/passport/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jaredhanson/passport/compare/v0.6.0...v0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 13:04:21 -08:00
dependabot[bot] be0a7924f6 chore(deps-dev): bump @typescript-eslint/eslint-plugin (#6523)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 6.19.1 to 6.21.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.21.0/packages/eslint-plugin)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 13:03:20 -08:00
dependabot[bot] 82468ec39d chore(deps): bump koa and @types/koa (#6524)
Bumps [koa](https://github.com/koajs/koa) and [@types/koa](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/koa). These dependencies needed to be updated together.

Updates `koa` from 2.14.2 to 2.15.0
- [Changelog](https://github.com/koajs/koa/blob/2.15.0/History.md)
- [Commits](https://github.com/koajs/koa/compare/2.14.2...2.15.0)

Updates `@types/koa` from 2.13.6 to 2.14.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/koa)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 13:03:03 -08:00
dependabot[bot] 3dcac0492a chore(deps): bump prosemirror-markdown from 1.11.0 to 1.12.0 (#6525)
Bumps [prosemirror-markdown](https://github.com/prosemirror/prosemirror-markdown) from 1.11.0 to 1.12.0.
- [Changelog](https://github.com/ProseMirror/prosemirror-markdown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prosemirror/prosemirror-markdown/compare/1.11.0...1.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 13:02:54 -08:00
Tom Moor 93c32536f9 fix: Allow application/octet-stream mimetype to fall through to extension matching in DocumentConverter 2024-02-11 13:38:27 -05:00
Tom Moor 8da07fc118 Add sanity check of document length after image conversion, before ydoc conversion 2024-02-11 11:51:15 -05:00
Tom Moor de34f33058 fix: Do not automatically retry failed document import request 2024-02-11 11:28:57 -05:00
Tom Moor 2d6a7ed244 fix: Self-hosted grist embeds not matching
closes #6451
2024-02-10 15:58:40 -05:00
Tom Moor 012d8c2ae7 chore: Various importer improvements (#6519)
* Handle new Notion export format
Clear data on file operation delete

* fix: Don't restart development server on html upload

* fix: Do not send collection created notifications on bulk import

* fix: Avoid parellelizing all uploads at once
Move import into one transaction per-collection
2024-02-10 12:21:52 -08:00
Tom Moor e608de93fb fix: Unable to easily edit captions in Firefox, closes #6513 2024-02-09 19:38:05 -05:00
Tom Moor 329426d09f fix: comments.info endpoint not accessible to non-admins
closes #6516
2024-02-09 18:18:14 -05:00
Tom Moor 24ce661b7d fix: AttachmentHelper key includes double forward slash 2024-02-09 08:58:19 -05:00
Tom Moor 55005d4447 fix: Mobile shows menu button on shared documents without a menu 2024-02-08 20:44:14 -05:00
Tom Moor 84c97ae5ff fix: Missing padding on public share custom url 2024-02-08 19:56:13 -05:00
Tom Moor 18cf0856c6 Increased example FILE_STORAGE_UPLOAD_MAX_SIZE 2024-02-08 19:52:21 -05:00
Tom Moor a950422695 Merge branch 'main' of github.com:outline/outline 2024-02-08 19:03:00 -05:00
Tom Moor ec580d5bd4 fix: Drop styling on active sidebar item 2024-02-07 23:06:59 -05:00
github-actions[bot] 99d8934aae chore: Auto Compress Images (#6508)
Co-authored-by: tommoor <tommoor@users.noreply.github.com>
2024-02-07 19:42:19 -08:00
Tom Moor fc2ff691f7 feat: Add support SmartSuite embeds 2024-02-07 22:37:23 -05:00
Tom Moor 105c84b4e9 mammoth.convertToHtml tracing 2024-02-07 22:13:35 -05:00
Tom Moor 8e66354cce Add tracing to TextHelper, DocumentConverter 2024-02-07 22:09:21 -05:00
Tom Moor 140e685d67 Move in-app notifications to instant, keep emails delayed (#6506) 2024-02-07 05:05:51 -08:00
Tom Moor d8e59a32ee fix: Relative links in document HTML should become absolute in emailed snippets
closes #6480
2024-02-06 21:27:24 -05:00
Tom Moor 68d4041b1c feat: Cmd-A inside code block should select block contents, closes #6498 2024-02-06 20:44:57 -05:00
Tom Moor 7bf403356a fix: Unseen error on client action execution 2024-02-06 20:44:57 -05:00
Tom Moor 0ff0780869 fix: 'No matches' state appears when all matching non-pending users are filtered in document share popover 2024-02-06 20:44:57 -05:00
Tom Moor 8a2c710792 Remove excessive .babelrc (#6493)
* Remove excessive babelrc

* wip

* Restore styled-components plugin

* fix
2024-02-06 04:24:04 -08:00
Tom Moor fbc628e331 feat: Add copy button to code selection, closes #6499 2024-02-05 23:45:22 -05:00
Tom Moor c9e4a57ee3 fix: Update filename in attachment creator 2024-02-05 22:56:04 -05:00
Tom Moor d4d226e011 chore: Fix various warnings 2024-02-05 21:59:14 -05:00
Tom Moor 69665a42d7 chore: Improve language prompt, prep. 2024-02-05 21:36:13 -05:00
Tom Moor d6595c15ad chore: Automatically display errors as toast if uncaught in actions (#6482)
Reduces plumbing
2024-02-05 16:40:29 -08:00
dependabot[bot] 3a125beb9e chore(deps): bump mailparser from 3.6.6 to 3.6.7 (#6503)
Bumps [mailparser](https://github.com/nodemailer/mailparser) from 3.6.6 to 3.6.7.
- [Release notes](https://github.com/nodemailer/mailparser/releases)
- [Changelog](https://github.com/nodemailer/mailparser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/mailparser/compare/v3.6.6...v3.6.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 14:54:52 -08:00
dependabot[bot] 11ea9b370d chore(deps): bump y-protocols from 1.0.5 to 1.0.6 (#6504)
Bumps [y-protocols](https://github.com/yjs/y-protocols) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/yjs/y-protocols/releases)
- [Commits](https://github.com/yjs/y-protocols/compare/v1.0.5...v1.0.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 14:54:42 -08:00
dependabot[bot] 7b98de1afb chore(deps-dev): bump eslint-plugin-import from 2.28.1 to 2.29.1 (#6500)
Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.28.1 to 2.29.1.
- [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.28.1...v2.29.1)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 14:54:34 -08:00
dependabot[bot] d05bd86e7a chore(deps): bump prosemirror-tables from 1.3.4 to 1.3.5 (#6502)
Bumps [prosemirror-tables](https://github.com/prosemirror/prosemirror-tables) from 1.3.4 to 1.3.5.
- [Release notes](https://github.com/prosemirror/prosemirror-tables/releases)
- [Commits](https://github.com/prosemirror/prosemirror-tables/compare/v1.3.4...v1.3.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 08:56:11 -08:00
dependabot[bot] 8bb0520900 chore(deps): bump aws-sdk from 2.1540.0 to 2.1550.0 (#6501)
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.1540.0 to 2.1550.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Commits](https://github.com/aws/aws-sdk-js/compare/v2.1540.0...v2.1550.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 08:55:37 -08:00
Tom Moor 7005597aa9 chore: Simplify teamUpdater with changes from #6490 (#6492) 2024-02-04 15:53:14 -08:00
Tom Moor 930210e46d fix: Allow querying by email address in share popover 2024-02-04 18:27:06 -05:00
Tom Moor 8ee266f7b1 chore: Track lastActiveAt for teams (#6491) 2024-02-04 11:14:18 -08:00
Tom Moor 234613580d fix: User updates are not synced between clients (#6490)
* Add Model.changeset method to get minified changes since last update

* fix: Handle arrays

* Add changes column, types

* test
2024-02-04 10:36:43 -08:00
Tom Moor 06ab5e5f44 chore: Bump outline-icons 2024-02-04 10:02:07 -05:00
Tom Moor c2b7d01c7d feat: Add setting to allow users to send invites (#6488) 2024-02-03 17:37:39 -08:00
Tom Moor 9046892864 fix: Event bubbling on click outside IconPicker 2024-02-03 18:36:38 -05:00
Tom Moor 9b4f20df63 feat: Add truck, building, ice cream icons 2024-02-03 18:31:14 -05:00
Tom Moor ca7d919b94 feat: Add 'Rename…' option to collection menu 2024-02-03 16:53:39 -05:00
Tom Moor 176a0451fc chore: Update Sentry deps 2024-02-03 16:42:26 -05:00
Tom Moor c3b515f0e1 chore: Rename tooltip.tooltip prop to tooltip.content 2024-02-03 16:22:51 -05:00
Tom Moor 0726445135 feat: Add pending state in document share user picker 2024-02-03 16:09:58 -05:00
Tom Moor 02711c29e3 fix: Do not show suspended users in picker 2024-02-03 15:15:41 -05:00
Tom Moor e38796d14b fix: Retain fullscreen modal as default for those triggered outside of showModal 2024-02-03 15:06:54 -05:00
Tom Moor 21bb8d36ae Hide permissions on collection edit for now 2024-02-03 14:59:17 -05:00
Tom Moor 0a54227d97 Refactor collection creation UI (#6485)
* Iteration, before functional component

* Use react-hook-form, shared form for new and edit

* Avoid negative margin on input prefix

* Centered now default for modals
2024-02-03 11:23:25 -08:00
Tom Moor abaa56c8f1 feat: Badge documents in sidebar that have been newly shared with you 2024-02-02 23:21:12 -05:00
dependabot[bot] 1bf0788de6 chore(deps): bump nodemailer from 6.9.8 to 6.9.9 (#6481)
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.8 to 6.9.9.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.8...v6.9.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-02 19:44:15 -08:00
Tom Moor fdd4788012 Add 15 new collection icons 2024-02-02 22:41:35 -05:00
Savely Krasovsky a3ccb33099 feat: replace (--) with emdash (#6479) 2024-02-02 17:23:17 -08:00
Tom Moor 69ecda387e test 2024-02-02 09:01:18 -05:00
Tom Moor 391814a54e fix: Improve multi-partial word matching in search 2024-02-02 08:58:51 -05:00
Tom Moor 490a1b6009 Add missing integrations.info endpoint (#6474) 2024-02-02 09:48:55 +05:30
Tom Moor aecefc2c01 fix: Layout of notice 2024-02-01 22:49:16 -05:00
Translate-O-Tron 058e2c44e1 New Crowdin updates (#6420)
* fix: New French translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: New Dutch translations from Crowdin [ci skip]
2024-02-01 19:48:51 -08:00
Tom Moor 6f1d02dfad fix: Cannot import into parent document, regression from new permissions logic. closes #6471 2024-02-01 18:26:30 -05:00
Tom Moor a963a63ee1 fix: Prefer displaying user email on share popover if available, better to distinguish between users 2024-02-01 18:05:25 -05:00
Tom Moor f6e6187ff5 chore: Add error boundary around document notices 2024-02-01 18:03:59 -05:00
Tom Moor 554c3a87dc chore: Remove no longer neccessary useOnClickOutside (capture: true on InputSelect solved the same problem) 2024-02-01 18:03:03 -05:00
Tom Moor a21079a276 fix: Custom share url not appearing in popover, closes OLN-234 2024-02-01 11:44:10 -05:00
Tom Moor 2995d8ca29 Add native keyboard shortcuts for Find+Replace, closes #6468 2024-02-01 07:49:46 -05:00
Tom Moor 92d5a7ee89 fix: Delete my account code confirmation step missing 2024-02-01 07:39:17 -05:00
Tom Moor fde8a9fd88 fix: Cannot read properties of undefined (reading 'id') 2024-02-01 07:20:26 -05:00
Tom Moor 5c368f1433 fix: Allow querying users with latin extended chars 2024-02-01 00:04:25 -05:00
Tom Moor f40263cb0c fix: User name ordering doesn't take into account lowercase, closes OLN-227 2024-01-31 23:59:13 -05:00
Tom Moor 4d935ade80 Use capture on InputSelect click outside listener 2024-01-31 23:53:16 -05:00
Tom Moor 05f4fa90b8 fix: click outside select input in popover event bubbling 2024-01-31 22:40:10 -05:00
Tom Moor 8c65e40c7e fix: Flashing hand cursor in notifications popover 2024-01-31 21:03:21 -05:00
Tom Moor 8e3cec01f8 fix: Consistency of member/editor language
fix: 'Delete' option on collection menu should show as dangerous
2024-01-31 20:55:37 -05:00
Tom Moor 0f125886b7 Clarify nested document public access note 2024-01-31 20:49:58 -05:00
Tom Moor 7417514e7c chore: Deprecate collection.url on client 2024-01-31 20:08:01 -05:00
Tom Moor 4800b60825 fix: Path to collection from notification UI 2024-01-31 18:35:25 -05:00
Tom Moor fb711a1db8 Increase rate limit on documents.export endpoint 2024-01-31 18:34:14 -05:00
Tom Moor bd6e4c586e Add Camunda as replacement for Cawemo (#6457)
* Add Camunda as replacement for Cawemo (what are these product names even?)

* Optimised images with calibre/image-actions

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-31 15:01:51 -08:00
Tom Moor 47d168a29b Add notifications for document and collection access (#6460)
* Add notification for added to document

* Add notifications for document and collection access

* Add notification delay

* fix: Collection notifications not appearing

* Add notification settings
2024-01-31 15:01:27 -08:00
Tom Moor 5ce8827a8c chore: Convert <Text /> component to span by default 2024-01-30 22:49:31 -05:00
Tom Moor a960d8cee5 fix: framer-motion warning for missing key prop 2024-01-30 22:29:45 -05:00
Apoorv Mishra 1490c3a14b Individual document sharing with permissions (#5814)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2024-01-30 17:48:22 -08:00
Tom Moor 717c9b5d64 tsc 2024-01-30 19:48:18 -05:00
Tom Moor b52b12fcfd chore: Remove no-longer maintained Enzyme 2024-01-30 09:12:45 -05:00
dependabot[bot] 56d05a2595 chore(deps-dev): bump @types/react-helmet from 6.1.9 to 6.1.11 (#6441)
Bumps [@types/react-helmet](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-helmet) from 6.1.9 to 6.1.11.
- [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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 14:00:26 -08:00
dependabot[bot] 9d68c879aa chore(deps): bump bull from 4.11.5 to 4.12.2 (#6439)
Bumps [bull](https://github.com/OptimalBits/bull) from 4.11.5 to 4.12.2.
- [Release notes](https://github.com/OptimalBits/bull/releases)
- [Changelog](https://github.com/OptimalBits/bull/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/OptimalBits/bull/compare/v4.11.5...v4.12.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 13:39:01 -08:00
dependabot[bot] 1e3a3f554b chore(deps): bump randomstring from 1.2.3 to 1.3.0 (#6440)
Bumps [randomstring](https://github.com/klughammer/node-randomstring) from 1.2.3 to 1.3.0.
- [Changelog](https://github.com/klughammer/node-randomstring/blob/master/CHANGELOG.md)
- [Commits](https://github.com/klughammer/node-randomstring/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 13:38:40 -08:00
dependabot[bot] ff218ebf5e chore(deps): bump styled-normalize from 8.0.7 to 8.1.0 (#6442)
Bumps [styled-normalize](https://github.com/sergeysova/styled-normalize) from 8.0.7 to 8.1.0.
- [Release notes](https://github.com/sergeysova/styled-normalize/releases)
- [Commits](https://github.com/sergeysova/styled-normalize/compare/v8.0.7...v8.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 13:38:20 -08:00
Tom Moor 5d810ac02d chore: Hide sidebar section on empty 2024-01-28 21:51:43 -05:00
Tom Moor a7e519f026 chore: Add docs and onClick to List/Item component 2024-01-28 21:50:38 -05:00
Tom Moor 657ee2c6bd fix: Allow value prop to change select 2024-01-28 21:47:09 -05:00
Tom Moor 19cc5aee04 Allow ref passthrough on CopyToClipboard component 2024-01-28 21:46:11 -05:00
Tom Moor e62c734c41 Duplicative method cleanup (#6431) 2024-01-25 20:02:17 -08:00
Tom Moor cab9a1ec96 Misc fixes ported from #5814 2024-01-24 22:43:10 -05:00
dependabot[bot] df816c200f chore(deps): bump @dnd-kit/sortable from 7.0.1 to 7.0.2 (#6419)
Bumps [@dnd-kit/sortable](https://github.com/clauderic/dnd-kit/tree/HEAD/packages/sortable) from 7.0.1 to 7.0.2.
- [Release notes](https://github.com/clauderic/dnd-kit/releases)
- [Changelog](https://github.com/clauderic/dnd-kit/blob/master/packages/sortable/CHANGELOG.md)
- [Commits](https://github.com/clauderic/dnd-kit/commits/@dnd-kit/sortable@7.0.2/packages/sortable)

---
updated-dependencies:
- dependency-name: "@dnd-kit/sortable"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-24 15:58:18 -08:00
dependabot[bot] 5f655535ab chore(deps): bump aws-sdk from 2.1510.0 to 2.1540.0 (#6418)
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.1510.0 to 2.1540.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Commits](https://github.com/aws/aws-sdk-js/compare/v2.1510.0...v2.1540.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 20:12:23 -08:00
dependabot[bot] eb6d30483d chore(deps): bump react-virtualized-auto-sizer and @types/react-virtualized-auto-sizer (#6415)
Bumps [react-virtualized-auto-sizer](https://github.com/bvaughn/react-virtualized-auto-sizer) and [@types/react-virtualized-auto-sizer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-virtualized-auto-sizer). These dependencies needed to be updated together.

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

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

---
updated-dependencies:
- dependency-name: react-virtualized-auto-sizer
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/react-virtualized-auto-sizer"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 20:11:56 -08:00
Tom Moor 0a781b7d99 chore: Upgrade typescript/eslint, fix warnings 2024-01-23 09:07:52 -05:00
Tom Moor db2a66cb8a fix: Toggle shortcut for TOC not working 2024-01-23 09:00:09 -05:00
Tom Moor aadd916336 fix: Mismatch between route registered vs checked for custom rate limiters 2024-01-22 22:40:17 -05:00
Tom Moor 0d797d49e3 fix: signupQueryParams default to true, closes OLN-206 2024-01-22 21:30:45 -05:00
Translate-O-Tron 7329b10846 New Crowdin updates (#6388)
* fix: New Danish translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---
updated-dependencies:
- dependency-name: "@sentry/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 10:00:33 -08:00
dependabot[bot] 330691a8d9 chore(deps): bump @dnd-kit/modifiers from 6.0.0 to 6.0.1 (#6390)
Bumps [@dnd-kit/modifiers](https://github.com/clauderic/dnd-kit/tree/HEAD/packages/modifiers) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/clauderic/dnd-kit/releases)
- [Changelog](https://github.com/clauderic/dnd-kit/blob/master/packages/modifiers/CHANGELOG.md)
- [Commits](https://github.com/clauderic/dnd-kit/commits/@dnd-kit/modifiers@6.0.1/packages/modifiers)

---
updated-dependencies:
- dependency-name: "@dnd-kit/modifiers"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 09:59:58 -08:00
dependabot[bot] 96bee22951 chore(deps): bump rfc6902 from 5.0.1 to 5.1.1 (#6392)
Bumps [rfc6902](https://github.com/chbrown/rfc6902) from 5.0.1 to 5.1.1.
- [Commits](https://github.com/chbrown/rfc6902/compare/v5.0.1...v5.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 09:59:47 -08:00
dependabot[bot] e72850fa1a chore(deps-dev): bump eslint-import-resolver-typescript from 3.5.4 to 3.6.1 (#6393)
Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 3.5.4 to 3.6.1.
- [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-import-resolver-typescript/compare/v3.5.4...v3.6.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 09:59:38 -08:00
dependabot[bot] d3f5b6cbae chore(deps-dev): bump typescript from 5.1.6 to 5.3.3 (#6394)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.1.6 to 5.3.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.1.6...v5.3.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 09:59:26 -08:00
Apoorv Mishra 3561b79d65 Zod schemas for routes under /plugins (#6378)
* fix: schema for slack routes

* fix: slack.post

* fix: email
2024-01-13 10:55:30 +05:30
Apoorv Mishra 7e61a519f1 Type server models (#6326)
* fix: type server models

* fix: make ParanoidModel generic

* fix: ApiKey

* fix: Attachment

* fix: AuthenticationProvider

* fix: Backlink

* fix: Collection

* fix: Comment

* fix: Document

* fix: FileOperation

* fix: Group

* fix: GroupPermission

* fix: GroupUser

* fix: Integration

* fix: IntegrationAuthentication

* fix: Notification

* fix: Pin

* fix: Revision

* fix: SearchQuery

* fix: Share

* fix: Star

* fix: Subscription

* fix: TypeError

* fix: Imports

* fix: Team

* fix: TeamDomain

* fix: User

* fix: UserAuthentication

* fix: UserPermission

* fix: View

* fix: WebhookDelivery

* fix: WebhookSubscription

* Remove type duplication

---------

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2024-01-12 22:33:05 +05:30
Tom Moor 8360c2dec9 Update DesktopEventHandler.tsx (#6376) 2024-01-12 06:18:12 -08:00
Tom Moor 2505fea103 fix: Prevent duplicate documents in collection structure 2024-01-11 22:18:50 -05:00
Tom Moor 89931ca2f0 fix: Improve reliability of inter-linking documents through importer. closes OLN-156 2024-01-10 21:19:39 -05:00
Tom Moor 22c52f84c5 fix: Remove try/catch statements without error argument (#6370) 2024-01-10 08:02:44 -08:00
Tom Moor 870c623601 chore: Update checksums (#6369) 2024-01-10 06:02:03 -08:00
Tom Moor 6e1347c2a7 Add 'Find and replace' option to menu on mobile (#6368) 2024-01-10 05:07:05 -08:00
Tom Moor 7d7d0fd9ca fix: Improve logic for word import (#6361)
* Refactor DocumentConverter

* Support parsing images from Confluence exported .doc files

* fix: Bring across 2 fixes from enterprise codebase

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---
updated-dependencies:
- dependency-name: "@dnd-kit/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 14:05:38 -08:00
dependabot[bot] 21a4176b36 chore(deps-dev): bump @types/react-portal from 4.0.6 to 4.0.7 (#6358)
Bumps [@types/react-portal](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-portal) from 4.0.6 to 4.0.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-portal)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 14:05:05 -08:00
dependabot[bot] 413b39c473 chore(deps): bump @babel/core from 7.22.5 to 7.23.7 (#6359)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.22.5 to 7.23.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.7/packages/babel-core)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 14:04:31 -08:00
dependabot[bot] a0ef71a0c1 chore(deps): bump socket.io from 4.7.2 to 4.7.3 (#6360)
Bumps [socket.io](https://github.com/socketio/socket.io) from 4.7.2 to 4.7.3.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/4.7.2...4.7.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 14:04:19 -08:00
Tom Moor f511540770 fix: Disable 'dark reader' chrome extension on Outline.
- We have native dark mode
- With extension enabled it mutates the document causing unrecoverable render loops

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

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

* lint

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

* Update tests

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

* fix: default to Active

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

---
updated-dependencies:
- dependency-name: vite-plugin-pwa
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 06:22:26 -08:00
dependabot[bot] 95d9dda64d chore(deps): bump @babel/plugin-transform-destructuring from 7.22.5 to 7.23.3 (#6337)
Bumps [@babel/plugin-transform-destructuring](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-destructuring) from 7.22.5 to 7.23.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.3/packages/babel-plugin-transform-destructuring)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-destructuring"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 05:17:54 -08:00
dependabot[bot] 9e6339696d chore(deps): bump react-window from 1.8.9 to 1.8.10 (#6340)
Bumps [react-window](https://github.com/bvaughn/react-window) from 1.8.9 to 1.8.10.
- [Release notes](https://github.com/bvaughn/react-window/releases)
- [Changelog](https://github.com/bvaughn/react-window/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bvaughn/react-window/compare/1.8.9...1.8.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 05:17:38 -08:00
dependabot[bot] 923ed24843 chore(deps): bump katex from 0.16.8 to 0.16.9 (#6339)
Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.16.8 to 0.16.9.
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.16.8...v0.16.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 05:17:27 -08:00
dependabot[bot] 6f249630c2 chore(deps-dev): bump @types/fuzzy-search from 2.1.2 to 2.1.5 (#6341)
Bumps [@types/fuzzy-search](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/fuzzy-search) from 2.1.2 to 2.1.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/fuzzy-search)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 05:17:14 -08:00
Tom Moor f864bb2dbd Improved insights icon 2023-12-29 18:21:40 -05:00
Tom Moor ce88e0ea8d Move display option switches to the other side (It looks better, setting up for share dialog improvements) 2023-12-29 17:34:26 -05:00
Tom Moor cb40e285f4 chore: Remove RPCAction.Count as default valid action on frontend (Only available for users) 2023-12-29 10:18:34 -05:00
Tom Moor 5d2a75c8e9 feat: Add missing comments.info endpoint, fix misnamed types 2023-12-29 10:13:22 -05:00
Tom Moor 08a787082f chore: Automatically remove policy from memory when associated model is deleted 2023-12-29 09:22:23 -05:00
Tom Moor 8d74028f93 chore: Remove unused fetchDocumentComments method 2023-12-29 09:15:16 -05:00
Tom Moor 01c806d6ea fix: Comment form should not collapse with draft 2023-12-28 21:31:40 -05:00
Tom Moor 0419e7dc52 fix: usePolicy attempting to fetch policies for unsaved entity 2023-12-28 19:59:15 -05:00
Tom Moor 6f989ec327 chore: Missing runInAction in DocumentsStore 2023-12-28 19:34:11 -05:00
Tom Moor 55a55376c6 chore: Improve typings around model methods (#6324) 2023-12-28 16:11:27 -08:00
dependabot[bot] ed1f345326 chore(deps): bump msgpackr from 1.6.2 to 1.10.1 (#6323)
Bumps [msgpackr](https://github.com/kriszyp/msgpackr) from 1.6.2 to 1.10.1.
- [Release notes](https://github.com/kriszyp/msgpackr/releases)
- [Commits](https://github.com/kriszyp/msgpackr/commits/v1.10.1)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: New Thai translations from Crowdin [ci skip]
2023-12-27 14:26:18 -08:00
Tom Moor 551f569896 feat: Allow filtering searches by 'source'
fix: Do not show API searches in recent list in app
2023-12-27 16:56:27 -05:00
Tom Moor 820e4839d5 feat: Allow plugins to provide Email templates 2023-12-27 16:21:44 -05:00
Tom Moor e7fbec91fc fix: Missing permission on selector in permissions dialog 2023-12-27 12:41:53 -05:00
Apoorv Mishra 548a56e058 Accomodate membership id (#6221)
* fix: accomodate membership id

* fix: remove only

* fix: event handling

* fix: tests

* fix: use transaction

* Remove useless test

---------

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

* fix: fetchAll

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-25 08:59:52 -08:00
dependabot[bot] e1fdfa5f9b chore(deps-dev): bump @types/enzyme from 3.10.13 to 3.10.18 (#6313)
Bumps [@types/enzyme](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/enzyme) from 3.10.13 to 3.10.18.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/enzyme)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-25 08:58:17 -08:00
dependabot[bot] 7be3b833ed chore(deps): bump tiny-cookie from 2.4.1 to 2.5.1 (#6312)
Bumps [tiny-cookie](https://github.com/Alex1990/tiny-cookie) from 2.4.1 to 2.5.1.
- [Changelog](https://github.com/Alex1990/tiny-cookie/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Alex1990/tiny-cookie/compare/v2.4.1...v2.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-25 08:49:20 -08:00
dependabot[bot] 35371e0bbf chore(deps): bump fs-extra and @types/fs-extra (#6310)
Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) and [@types/fs-extra](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/fs-extra). These dependencies needed to be updated together.

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-25 08:48:43 -08:00
dependabot[bot] fe1b15e976 chore(deps): bump css-inline from 0.11.0 to 0.11.2 (#6311)
Bumps [css-inline](https://github.com/Stranger6667/css-inline) from 0.11.0 to 0.11.2.
- [Release notes](https://github.com/Stranger6667/css-inline/releases)
- [Changelog](https://github.com/Stranger6667/css-inline/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/css-inline/compare/ruby-v0.11.0...ruby-v0.11.2)

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

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

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

* fix: don't drop ext

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 18:07:32 -05:00
dependabot[bot] 9ddf31632f chore(deps-dev): bump @types/addressparser from 1.0.1 to 1.0.2 (#6124)
Bumps [@types/addressparser](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/addressparser) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/addressparser)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 18:06:51 -05:00
dependabot[bot] 067fd11663 chore(deps): bump core-js from 3.33.0 to 3.33.2 (#6125)
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.33.0 to 3.33.2.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.33.2/packages/core-js)

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

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

* fix: spread params

* fix: handle limit zero

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

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

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

* Allow commenting in code blocks

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 15:44:47 -07:00
dependabot[bot] 8e7dfdb6a0 chore(deps-dev): bump @types/react-window from 1.8.5 to 1.8.6 (#5915)
Bumps [@types/react-window](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-window) from 1.8.5 to 1.8.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-window)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 15:44:31 -07:00
dependabot[bot] c8acf96790 chore(deps-dev): bump @types/sequelize from 4.28.10 to 4.28.16 (#5917)
Bumps [@types/sequelize](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sequelize) from 4.28.10 to 4.28.16.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/sequelize)

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

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

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

* fix: use scope with collectionId not null

* fix: update model with documentId

* fix: rename to GroupPermission

* fix: rename collection_users to user_permissions

* fix: teamPermanentDeleter test

* fix: use scope with collectionId not null

* fix: update model with documentId

* fix: rename to UserPermission

* fix: create views upon table rename for zero downtime

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

* test

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

* restore

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

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

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

* tweaks

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

* fix: comment

* fix: tsc without previewText

* fix: goToAction

* fix: link to original template

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

* trigger ci

* trigger ci
2023-08-09 21:52:44 +05:30
Tom Moor ed5671209a New Crowdin updates (#5647) 2023-08-09 04:23:00 -07:00
Tom Moor c32cec7bff Add support for SSL in development (#5668) 2023-08-09 04:21:41 -07:00
1098 changed files with 43055 additions and 25622 deletions
+30 -15
View File
@@ -1,29 +1,28 @@
{
"presets": [
"@babel/preset-react",
"@babel/preset-typescript",
[
"@babel/preset-env",
{
"corejs": {
"version": "3",
"proposals": true
},
"useBuiltIns": "usage"
}
]
"@babel/preset-env",
"@babel/preset-typescript"
],
"plugins": [
"styled-components",
"babel-plugin-transform-typescript-metadata",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-transform-destructuring",
"@babel/plugin-transform-regenerator",
"transform-class-properties"
"@babel/plugin-proposal-class-properties",
[
"transform-inline-environment-variables",
{
"include": [
"SOURCE_COMMIT",
"SOURCE_VERSION"
]
}
],
"tsconfig-paths-module-resolver"
],
"env": {
"production": {
@@ -36,13 +35,29 @@
]
],
"ignore": [
"**/__mocks__",
"**/*.test.ts"
]
},
"development": {
"ignore": [
"**/__mocks__",
"**/*.test.ts"
]
},
"test": {
"presets": [
[
"@babel/preset-env",
{
"corejs": {
"version": "3",
"proposals": true
},
"useBuiltIns": "usage"
}
]
]
}
}
}
+21 -14
View File
@@ -3,7 +3,7 @@ version: 2.1
defaults: &defaults
working_directory: ~/outline
docker:
- image: cimg/node:18.12
- image: cimg/node:20.10
- image: cimg/redis:5.0
- image: cimg/postgres:14.2
environment:
@@ -36,12 +36,12 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: install-deps
command: yarn install --frozen-lockfile
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
paths:
- ./node_modules
lint:
@@ -49,7 +49,7 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: lint
command: yarn lint
@@ -58,7 +58,7 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: typescript
command: yarn tsc
@@ -67,7 +67,7 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: test
command: yarn test:app
@@ -76,22 +76,25 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: test
command: yarn test:shared
test-server:
<<: *defaults
parallelism: 3
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: migrate
command: ./node_modules/.bin/sequelize db:migrate --url $DATABASE_URL_TEST
- run:
name: test
command: yarn test:server --forceExit
command: |
TESTFILES=$(circleci tests glob "server/**/*.test.ts" | circleci tests split)
yarn test --maxWorkers=2 $TESTFILES
bundle-size:
<<: *defaults
environment:
@@ -99,7 +102,7 @@ jobs:
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: dependency-cache-v1-{{ checksum "package.json" }}
- run:
name: build-vite
command: yarn vite:build
@@ -142,7 +145,12 @@ jobs:
command: docker push $BASE_IMAGE_NAME:latest
- run:
name: Build and push Docker image
command: docker buildx build -t $IMAGE_NAME:latest -t $IMAGE_NAME:${CIRCLE_TAG/v/''} --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --push .
command: |
if [[ "$CIRCLE_TAG" == *"-"* ]]; then
docker buildx build -t $IMAGE_NAME:${CIRCLE_TAG/v/''} --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --push .
else
docker buildx build -t $IMAGE_NAME:latest -t $IMAGE_NAME:${CIRCLE_TAG/v/''} --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --push .
fi
workflows:
version: 2
@@ -166,9 +174,8 @@ workflows:
- build
- bundle-size:
requires:
- test-app
- test-shared
- test-server
- build
- types
build-docker:
jobs:
-1
View File
@@ -13,5 +13,4 @@ app.json
crowdin.yml
build
docker-compose.yml
fakes3
node_modules
+19 -4
View File
@@ -30,7 +30,7 @@ REDIS_URL=redis://localhost:6379
# URL should point to the fully qualified, publicly accessible URL. If using a
# proxy the port in URL and PORT may be different.
URL=http://localhost:3000
URL=https://app.outline.dev:3000
PORT=3000
# See [documentation](docs/SERVICES.md) on running a separate collaboration
@@ -51,10 +51,20 @@ AWS_REGION=xx-xxxx-x
AWS_S3_ACCELERATE_URL=
AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569
AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here
AWS_S3_UPLOAD_MAX_SIZE=26214400
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private
# Specify what storage system to use. Possible value is one of "s3" or "local".
# For "local", the avatar images and document attachments will be saved on local disk.
FILE_STORAGE=local
# If "local" is configured for FILE_STORAGE above, then this sets the parent directory under
# which all attachments/images go. Make sure that the process has permissions to create
# this path and also to write files to it.
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
# Maximum allowed size for the uploaded attachment.
FILE_STORAGE_UPLOAD_MAX_SIZE=262144000
# –––––––––––––– AUTHENTICATION ––––––––––––––
@@ -183,5 +193,10 @@ RATE_LIMITER_REQUESTS=1000
RATE_LIMITER_DURATION_WINDOW=60
# Iframely API config
IFRAMELY_URL=
IFRAMELY_API_KEY=
# IFRAMELY_URL=
# IFRAMELY_API_KEY=
# Enable unsafe-inline in script-src CSP directive
# Setting it to true allows React dev tools add-on in
# Firefox to successfully detect the project
DEVELOPMENT_UNSAFE_INLINE_CSP=false
+3 -1
View File
@@ -21,7 +21,7 @@
"eslint-plugin-import",
"eslint-plugin-node",
"eslint-plugin-react",
"import"
"eslint-plugin-lodash"
],
"rules": {
"eqeqeq": 2,
@@ -37,6 +37,7 @@
"component": true,
"html": true
}],
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-misused-promises": [
@@ -55,6 +56,7 @@
],
"padding-line-between-statements": ["error", { "blankLine": "always", "prev": "*", "next": "export" }],
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
"lodash/import-scope": ["warn", "method"],
"import/no-named-as-default": "off",
"import/no-named-as-default-member": "off",
"import/newline-after-import": 2,
+1 -1
View File
@@ -7,7 +7,7 @@ node_modules/*
npm-debug.log
stats.json
.DS_Store
fakes3/*
data/*
.idea
*.pem
*.key
+5 -6
View File
@@ -1,5 +1,6 @@
{
"workerIdleMemoryLimit": "0.75",
"maxWorkers": "50%",
"projects": [
{
"displayName": "server",
@@ -8,13 +9,11 @@
"^@server/(.*)$": "<rootDir>/server/$1",
"^@shared/(.*)$": "<rootDir>/shared/$1"
},
"setupFiles": [
"<rootDir>/__mocks__/console.js",
"<rootDir>/server/test/env.ts"
],
"setupFiles": ["<rootDir>/__mocks__/console.js", "<rootDir>/server/test/env.ts"],
"setupFilesAfterEnv": ["<rootDir>/server/test/setup.ts"],
"testEnvironment": "node",
"runner": "@getoutline/jest-runner-serial"
"globalSetup": "<rootDir>/server/test/globalSetup.js",
"globalTeardown": "<rootDir>/server/test/globalTeardown.js",
"testEnvironment": "node"
},
{
"displayName": "app",
+11 -2
View File
@@ -5,7 +5,7 @@ ARG APP_PATH
WORKDIR $APP_PATH
# ---
FROM node:18-alpine AS runner
FROM node:20-alpine AS runner
RUN apk update && apk add --no-cache curl && apk add --no-cache ca-certificates
@@ -24,7 +24,16 @@ COPY --from=base $APP_PATH/package.json ./package.json
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs $APP_PATH/build
chown -R nodejs:nodejs $APP_PATH/build && \
mkdir -p /var/lib/outline && \
chown -R nodejs:nodejs /var/lib/outline
ENV FILE_STORAGE_LOCAL_ROOT_DIR /var/lib/outline/data
RUN mkdir -p "$FILE_STORAGE_LOCAL_ROOT_DIR" && \
chown -R nodejs:nodejs "$FILE_STORAGE_LOCAL_ROOT_DIR" && \
chmod 1777 "$FILE_STORAGE_LOCAL_ROOT_DIR"
VOLUME /var/lib/outline/data
USER nodejs
+1 -1
View File
@@ -1,5 +1,5 @@
ARG APP_PATH=/opt/outline
FROM node:18-alpine AS deps
FROM node:20-alpine AS deps
ARG APP_PATH
WORKDIR $APP_PATH
+2 -2
View File
@@ -3,7 +3,7 @@ Business Source License 1.1
Parameters
Licensor: General Outline, Inc.
Licensed Work: Outline 0.64.0
Licensed Work: Outline 0.71.0
The Licensed Work is (c) 2020 General Outline, Inc.
Additional Use Grant: You may make use of the Licensed Work, provided that
you may not use the Licensed Work for a Document
@@ -15,7 +15,7 @@ Additional Use Grant: You may make use of the Licensed Work, provided that
Licensed Work by creating teams and documents
controlled by such third parties.
Change Date: 2026-05-23
Change Date: 2027-08-18
Change License: Apache License, Version 2.0
+6 -5
View File
@@ -1,5 +1,6 @@
up:
docker-compose up -d redis postgres s3
docker-compose up -d redis postgres
yarn install-local-ssl
yarn install --pure-lockfile
yarn dev:watch
@@ -7,17 +8,17 @@ build:
docker-compose build --pull outline
test:
docker-compose up -d redis postgres s3
docker-compose up -d redis postgres
yarn sequelize db:drop --env=test
yarn sequelize db:create --env=test
yarn sequelize db:migrate --env=test
NODE_ENV=test yarn sequelize db:migrate --env=test
yarn test
watch:
docker-compose up -d redis postgres s3
docker-compose up -d redis postgres
yarn sequelize db:drop --env=test
yarn sequelize db:create --env=test
yarn sequelize db:migrate --env=test
NODE_ENV=test yarn sequelize db:migrate --env=test
yarn test:watch
destroy:
+5 -1
View File
@@ -96,6 +96,10 @@ Or to run migrations on test database:
yarn sequelize db:migrate --env test
```
## License
# Activity
![Alt](https://repobeats.axiom.co/api/embed/ff2e4e6918afff1acf9deb72d1ba6b071d586178.svg "Repobeats analytics image")
# License
Outline is [BSL 1.1 licensed](LICENSE).
+5 -5
View File
@@ -128,11 +128,6 @@
"description": "Live web link to your bucket. For CNAMEs, https://yourbucket.example.com",
"required": false
},
"AWS_S3_UPLOAD_MAX_SIZE": {
"description": "Maximum file upload size in bytes",
"value": "26214400",
"required": false
},
"AWS_S3_FORCE_PATH_STYLE": {
"description": "Use path-style URL's for connecting to S3 instead of subdomain. This is useful for S3-compatible storage.",
"value": "true",
@@ -148,6 +143,11 @@
"description": "S3 canned ACL for document attachments",
"required": false
},
"FILE_STORAGE_UPLOAD_MAX_SIZE": {
"description": "Maximum file upload size in bytes",
"value": "26214400",
"required": false
},
"SMTP_HOST": {
"description": "smtp.example.com (optional)",
"required": false
+2 -2
View File
@@ -2,10 +2,10 @@
"extends": [
"../.eslintrc",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:react-hooks/recommended"
],
"plugins": [
"eslint-plugin-react-hooks",
"eslint-plugin-react-hooks"
],
"env": {
"jest": true,
+7 -6
View File
@@ -10,9 +10,9 @@ import {
import * as React from "react";
import stores from "~/stores";
import Collection from "~/models/Collection";
import CollectionEdit from "~/scenes/CollectionEdit";
import CollectionNew from "~/scenes/CollectionNew";
import CollectionPermissions from "~/scenes/CollectionPermissions";
import { CollectionEdit } from "~/components/Collection/CollectionEdit";
import { CollectionNew } from "~/components/Collection/CollectionNew";
import CollectionDeleteDialog from "~/components/CollectionDeleteDialog";
import DynamicCollectionIcon from "~/components/Icons/CollectionIcon";
import { createAction } from "~/actions";
@@ -34,11 +34,11 @@ export const openCollection = createAction({
return collections.map((collection) => ({
// Note: using url which includes the slug rather than id here to bust
// cache if the collection is renamed
id: collection.url,
id: collection.path,
name: collection.name,
icon: <ColorCollectionIcon collection={collection} />,
section: CollectionSection,
perform: () => history.push(collection.url),
perform: () => history.push(collection.path),
}));
},
});
@@ -103,6 +103,7 @@ export const editCollectionPermissions = createAction({
stores.dialogs.openModal({
title: t("Collection permissions"),
fullscreen: true,
content: <CollectionPermissions collectionId={activeCollectionId} />,
});
},
@@ -161,9 +162,10 @@ export const unstarCollection = createAction({
});
export const deleteCollection = createAction({
name: ({ t }) => t("Delete"),
name: ({ t }) => `${t("Delete")}`,
analyticsName: "Delete collection",
section: CollectionSection,
dangerous: true,
icon: <TrashIcon />,
visible: ({ activeCollectionId, stores }) => {
if (!activeCollectionId) {
@@ -182,7 +184,6 @@ export const deleteCollection = createAction({
}
stores.dialogs.openModal({
isCentered: true,
title: t("Delete collection"),
content: (
<CollectionDeleteDialog
+90 -12
View File
@@ -1,6 +1,7 @@
import { ToolsIcon, TrashIcon, UserIcon } from "outline-icons";
import copy from "copy-to-clipboard";
import { CopyIcon, ToolsIcon, TrashIcon, UserIcon } from "outline-icons";
import * as React from "react";
import stores from "~/stores";
import { toast } from "sonner";
import { createAction } from "~/actions";
import { DeveloperSection } from "~/actions/sections";
import env from "~/env";
@@ -8,6 +9,71 @@ import { client } from "~/utils/ApiClient";
import Logger from "~/utils/Logger";
import { deleteAllDatabases } from "~/utils/developer";
export const copyId = createAction({
name: ({ t }) => t("Copy ID"),
icon: <CopyIcon />,
keywords: "uuid",
section: DeveloperSection,
children: ({
currentTeamId,
currentUserId,
activeCollectionId,
activeDocumentId,
}) => {
function copyAndToast(text: string | null | undefined) {
if (text) {
copy(text);
toast.success("Copied to clipboard");
}
}
return [
createAction({
name: "Copy User ID",
section: DeveloperSection,
icon: <CopyIcon />,
visible: () => !!currentUserId,
perform: () => copyAndToast(currentUserId),
}),
createAction({
name: "Copy Team ID",
section: DeveloperSection,
icon: <CopyIcon />,
visible: () => !!currentTeamId,
perform: () => copyAndToast(currentTeamId),
}),
createAction({
name: "Copy Collection ID",
icon: <CopyIcon />,
section: DeveloperSection,
visible: () => !!activeCollectionId,
perform: () => copyAndToast(activeCollectionId),
}),
createAction({
name: "Copy Document ID",
icon: <CopyIcon />,
section: DeveloperSection,
visible: () => !!activeDocumentId,
perform: () => copyAndToast(activeDocumentId),
}),
createAction({
name: "Copy Team ID",
icon: <CopyIcon />,
section: DeveloperSection,
visible: () => !!currentTeamId,
perform: () => copyAndToast(currentTeamId),
}),
createAction({
name: "Copy Release ID",
icon: <CopyIcon />,
section: DeveloperSection,
visible: () => !!env.RELEASE,
perform: () => copyAndToast(env.RELEASE),
}),
];
},
});
export const clearIndexedDB = createAction({
name: ({ t }) => t("Delete IndexedDB cache"),
icon: <TrashIcon />,
@@ -15,24 +81,30 @@ export const clearIndexedDB = createAction({
section: DeveloperSection,
perform: async ({ t }) => {
await deleteAllDatabases();
stores.toasts.showToast(t("IndexedDB cache deleted"));
toast.message(t("IndexedDB cache deleted"));
},
});
export const createTestUsers = createAction({
name: "Create test users",
name: "Create 10 test users",
icon: <UserIcon />,
section: DeveloperSection,
visible: () => env.ENVIRONMENT === "development",
perform: async () => {
const count = 10;
await client.post("/developer.create_test_users", { count });
toast.message(`${count} test users created`);
},
});
try {
await client.post("/developer.create_test_users", { count });
stores.toasts.showToast(`${count} test users created`);
} catch (err) {
stores.toasts.showToast(err.message, { type: "error" });
}
export const createToast = createAction({
name: "Create toast",
section: DeveloperSection,
visible: () => env.ENVIRONMENT === "development",
perform: async () => {
toast.message("Hello world", {
duration: 30000,
});
},
});
@@ -42,7 +114,7 @@ export const toggleDebugLogging = createAction({
section: DeveloperSection,
perform: async ({ t }) => {
Logger.debugLoggingEnabled = !Logger.debugLoggingEnabled;
stores.toasts.showToast(
toast.message(
Logger.debugLoggingEnabled
? t("Debug logging enabled")
: t("Debug logging disabled")
@@ -56,7 +128,13 @@ export const developer = createAction({
icon: <ToolsIcon />,
iconInContextMenu: false,
section: DeveloperSection,
children: [clearIndexedDB, toggleDebugLogging, createTestUsers],
children: [
copyId,
clearIndexedDB,
toggleDebugLogging,
createToast,
createTestUsers,
],
});
export const rootDeveloperActions = [developer];
+193 -80
View File
@@ -1,3 +1,4 @@
import copy from "copy-to-clipboard";
import invariant from "invariant";
import {
DownloadIcon,
@@ -19,19 +20,25 @@ import {
ArchiveIcon,
ShuffleIcon,
HistoryIcon,
LightBulbIcon,
GraphIcon,
UnpublishIcon,
PublishIcon,
CommentIcon,
GlobeIcon,
CopyIcon,
} from "outline-icons";
import * as React from "react";
import { toast } from "sonner";
import { ExportContentType, TeamPreference } from "@shared/types";
import MarkdownHelper from "@shared/utils/MarkdownHelper";
import { getEventFiles } from "@shared/utils/files";
import DocumentDelete from "~/scenes/DocumentDelete";
import DocumentMove from "~/scenes/DocumentMove";
import DocumentPermanentDelete from "~/scenes/DocumentPermanentDelete";
import DocumentPublish from "~/scenes/DocumentPublish";
import DocumentTemplatizeDialog from "~/components/DocumentTemplatizeDialog";
import DuplicateDialog from "~/components/DuplicateDialog";
import SharePopover from "~/components/Sharing";
import { createAction } from "~/actions";
import { DocumentSection } from "~/actions/sections";
import env from "~/env";
@@ -42,6 +49,8 @@ import {
homePath,
newDocumentPath,
searchPath,
documentPath,
urlify,
} from "~/utils/routeHelpers";
export const openDocument = createAction({
@@ -86,6 +95,48 @@ export const createDocument = createAction({
}),
});
export const createDocumentFromTemplate = createAction({
name: ({ t }) => t("New from template"),
analyticsName: "New document",
section: DocumentSection,
icon: <NewDocumentIcon />,
keywords: "create",
visible: ({ currentTeamId, activeDocumentId, stores }) =>
!!currentTeamId &&
!!activeDocumentId &&
!!stores.documents.get(activeDocumentId)?.template &&
stores.policies.abilities(currentTeamId).createDocument,
perform: ({ activeCollectionId, activeDocumentId, inStarredSection }) =>
history.push(
newDocumentPath(activeCollectionId, { templateId: activeDocumentId }),
{
starred: inStarredSection,
}
),
});
export const createNestedDocument = createAction({
name: ({ t }) => t("New nested document"),
analyticsName: "New document",
section: DocumentSection,
icon: <NewDocumentIcon />,
keywords: "create",
visible: ({ currentTeamId, activeDocumentId, stores }) =>
!!currentTeamId &&
!!activeDocumentId &&
stores.policies.abilities(currentTeamId).createDocument &&
stores.policies.abilities(activeDocumentId).createChildDocument,
perform: ({ activeCollectionId, activeDocumentId, inStarredSection }) =>
history.push(
newDocumentPath(activeCollectionId, {
parentDocumentId: activeDocumentId,
}),
{
starred: inStarredSection,
}
),
});
export const starDocument = createAction({
name: ({ t }) => t("Star"),
analyticsName: "Star document",
@@ -148,7 +199,7 @@ export const publishDocument = createAction({
}
const document = stores.documents.get(activeDocumentId);
return (
!!document?.isDraft && stores.policies.abilities(activeDocumentId).update
!!document?.isDraft && stores.policies.abilities(activeDocumentId).publish
);
},
perform: async ({ activeDocumentId, stores, t }) => {
@@ -165,13 +216,14 @@ export const publishDocument = createAction({
await document.save(undefined, {
publish: true,
});
stores.toasts.showToast(t("Document published"), {
type: "success",
});
toast.success(
t("Published {{ documentName }}", {
documentName: document.noun,
})
);
} else if (document) {
stores.dialogs.openModal({
title: t("Publish document"),
isCentered: true,
content: <DocumentPublish document={document} />,
});
}
@@ -195,12 +247,17 @@ export const unpublishDocument = createAction({
}
const document = stores.documents.get(activeDocumentId);
if (!document) {
return;
}
await document?.unpublish();
await document.unpublish();
stores.toasts.showToast(t("Document unpublished"), {
type: "success",
});
toast.success(
t("Unpublished {{ documentName }}", {
documentName: document.noun,
})
);
},
});
@@ -227,12 +284,8 @@ export const subscribeDocument = createAction({
}
const document = stores.documents.get(activeDocumentId);
await document?.subscribe();
stores.toasts.showToast(t("Subscribed to document notifications"), {
type: "success",
});
toast.success(t("Subscribed to document notifications"));
},
});
@@ -262,8 +315,38 @@ export const unsubscribeDocument = createAction({
await document?.unsubscribe(currentUserId);
stores.toasts.showToast(t("Unsubscribed from document notifications"), {
type: "success",
toast.success(t("Unsubscribed from document notifications"));
},
});
export const shareDocument = createAction({
name: ({ t }) => t("Share"),
analyticsName: "Share document",
section: DocumentSection,
icon: <GlobeIcon />,
perform: async ({ activeDocumentId, stores, currentUserId, t }) => {
if (!activeDocumentId || !currentUserId) {
return;
}
const document = stores.documents.get(activeDocumentId);
const share = stores.shares.getByDocumentId(activeDocumentId);
const sharedParent = stores.shares.getByDocumentParents(activeDocumentId);
if (!document) {
return;
}
stores.dialogs.openModal({
title: t("Share this document"),
content: (
<SharePopover
document={document}
share={share}
sharedParent={sharedParent}
onRequestClose={stores.dialogs.closeAllModals}
visible
/>
),
});
},
});
@@ -303,15 +386,11 @@ export const downloadDocumentAsPDF = createAction({
return;
}
const id = stores.toasts.showToast(`${t("Exporting")}`, {
type: "loading",
timeout: 30 * 1000,
});
const id = toast.loading(`${t("Exporting")}`);
const document = stores.documents.get(activeDocumentId);
document
return document
?.download(ExportContentType.Pdf)
.finally(() => id && stores.toasts.hideToast(id));
.finally(() => id && toast.dismiss(id));
},
});
@@ -348,6 +427,47 @@ export const downloadDocument = createAction({
],
});
export const copyDocumentAsMarkdown = createAction({
name: ({ t }) => t("Copy as Markdown"),
section: DocumentSection,
keywords: "clipboard",
visible: ({ activeDocumentId }) => !!activeDocumentId,
perform: ({ stores, activeDocumentId, t }) => {
const document = activeDocumentId
? stores.documents.get(activeDocumentId)
: undefined;
if (document) {
copy(MarkdownHelper.toMarkdown(document));
toast.success(t("Markdown copied to clipboard"));
}
},
});
export const copyDocumentLink = createAction({
name: ({ t }) => t("Copy link"),
section: DocumentSection,
keywords: "clipboard",
visible: ({ activeDocumentId }) => !!activeDocumentId,
perform: ({ stores, activeDocumentId, t }) => {
const document = activeDocumentId
? stores.documents.get(activeDocumentId)
: undefined;
if (document) {
copy(urlify(documentPath(document)));
toast.success(t("Link copied to clipboard"));
}
},
});
export const copyDocument = createAction({
name: ({ t }) => t("Copy"),
analyticsName: "Copy document",
section: DocumentSection,
icon: <CopyIcon />,
keywords: "clipboard",
children: [copyDocumentLink, copyDocumentAsMarkdown],
});
export const duplicateDocument = createAction({
name: ({ t, isContextMenu }) =>
isContextMenu ? t("Duplicate") : t("Duplicate document"),
@@ -356,7 +476,7 @@ export const duplicateDocument = createAction({
icon: <DuplicateIcon />,
keywords: "copy",
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).update,
!!activeDocumentId && stores.policies.abilities(activeDocumentId).duplicate,
perform: async ({ activeDocumentId, t, stores }) => {
if (!activeDocumentId) {
return;
@@ -364,11 +484,18 @@ export const duplicateDocument = createAction({
const document = stores.documents.get(activeDocumentId);
invariant(document, "Document must exist");
const duped = await document.duplicate();
// when duplicating, go straight to the duplicated document content
history.push(duped.url);
stores.toasts.showToast(t("Document duplicated"), {
type: "success",
stores.dialogs.openModal({
title: t("Copy document"),
content: (
<DuplicateDialog
document={document}
onSubmit={(response) => {
stores.dialogs.closeAllModals();
history.push(documentPath(response[0]));
}}
/>
),
});
},
});
@@ -407,19 +534,13 @@ export const pinDocumentToCollection = createAction({
return;
}
try {
const document = stores.documents.get(activeDocumentId);
await document?.pin(document.collectionId);
const document = stores.documents.get(activeDocumentId);
await document?.pin(document.collectionId);
const collection = stores.collections.get(activeCollectionId);
const collection = stores.collections.get(activeCollectionId);
if (!collection || !location.pathname.startsWith(collection?.url)) {
stores.toasts.showToast(t("Pinned to collection"));
}
} catch (err) {
stores.toasts.showToast(err.message, {
type: "error",
});
if (!collection || !location.pathname.startsWith(collection?.url)) {
toast.success(t("Pinned to collection"));
}
},
});
@@ -452,16 +573,10 @@ export const pinDocumentToHome = createAction({
}
const document = stores.documents.get(activeDocumentId);
try {
await document?.pin();
await document?.pin();
if (location.pathname !== homePath()) {
stores.toasts.showToast(t("Pinned to team home"));
}
} catch (err) {
stores.toasts.showToast(err.message, {
type: "error",
});
if (location.pathname !== homePath()) {
toast.success(t("Pinned to home"));
}
},
});
@@ -504,7 +619,7 @@ export const importDocument = createAction({
return false;
},
perform: ({ activeCollectionId, activeDocumentId, stores }) => {
const { documents, toasts } = stores;
const { documents } = stores;
const input = document.createElement("input");
input.type = "file";
input.accept = documents.importFileTypes.join(", ");
@@ -512,23 +627,16 @@ export const importDocument = createAction({
input.onchange = async (ev) => {
const files = getEventFiles(ev);
try {
const file = files[0];
const document = await documents.import(
file,
activeDocumentId,
activeCollectionId,
{
publish: true,
}
);
history.push(document.url);
} catch (err) {
toasts.showToast(err.message, {
type: "error",
});
throw err;
}
const file = files[0];
const document = await documents.import(
file,
activeDocumentId,
activeCollectionId,
{
publish: true,
}
);
history.push(document.url);
};
input.click();
@@ -562,7 +670,6 @@ export const createTemplate = createAction({
stores.dialogs.openModal({
title: t("Create template"),
isCentered: true,
content: <DocumentTemplatizeDialog documentId={activeDocumentId} />,
});
},
@@ -621,7 +728,6 @@ export const moveDocument = createAction({
title: t("Move {{ documentType }}", {
documentType: document.noun,
}),
isCentered: true,
content: <DocumentMove document={document} />,
});
}
@@ -647,15 +753,13 @@ export const archiveDocument = createAction({
}
await document.archive();
stores.toasts.showToast(t("Document archived"), {
type: "success",
});
toast.success(t("Document archived"));
}
},
});
export const deleteDocument = createAction({
name: ({ t }) => t("Delete"),
name: ({ t }) => `${t("Delete")}`,
analyticsName: "Delete document",
section: DocumentSection,
icon: <TrashIcon />,
@@ -677,7 +781,6 @@ export const deleteDocument = createAction({
title: t("Delete {{ documentName }}", {
documentName: document.noun,
}),
isCentered: true,
content: (
<DocumentDelete
document={document}
@@ -712,7 +815,6 @@ export const permanentlyDeleteDocument = createAction({
title: t("Permanently delete {{ documentName }}", {
documentName: document.noun,
}),
isCentered: true,
content: (
<DocumentPermanentDelete
document={document}
@@ -733,8 +835,7 @@ export const openDocumentComments = createAction({
const can = stores.policies.abilities(activeDocumentId ?? "");
return (
!!activeDocumentId &&
can.read &&
!can.restore &&
can.comment &&
!!stores.auth.team?.getPreference(TeamPreference.Commenting)
);
},
@@ -772,10 +873,19 @@ export const openDocumentInsights = createAction({
name: ({ t }) => t("Insights"),
analyticsName: "Open document insights",
section: DocumentSection,
icon: <LightBulbIcon />,
icon: <GraphIcon />,
visible: ({ activeDocumentId, stores }) => {
const can = stores.policies.abilities(activeDocumentId ?? "");
return !!activeDocumentId && can.read;
const document = activeDocumentId
? stores.documents.get(activeDocumentId)
: undefined;
return (
!!activeDocumentId &&
can.read &&
!document?.isTemplate &&
!document?.isDeleted
);
},
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
@@ -797,6 +907,8 @@ export const rootDocumentActions = [
deleteDocument,
importDocument,
downloadDocument,
copyDocumentLink,
copyDocumentAsMarkdown,
starDocument,
unstarDocument,
publishDocument,
@@ -813,4 +925,5 @@ export const rootDocumentActions = [
openDocumentComments,
openDocumentHistory,
openDocumentInsights,
shareDocument,
];
+12 -14
View File
@@ -6,14 +6,15 @@ import {
EditIcon,
OpenIcon,
SettingsIcon,
ShapesIcon,
KeyboardIcon,
EmailIcon,
LogoutIcon,
ProfileIcon,
BrowserIcon,
ShapesIcon,
} from "outline-icons";
import * as React from "react";
import { isMac } from "@shared/utils/browser";
import {
developersUrl,
changelogUrl,
@@ -26,14 +27,12 @@ import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { createAction } from "~/actions";
import { NavigationSection, RecentSearchesSection } from "~/actions/sections";
import Desktop from "~/utils/Desktop";
import { isMac } from "~/utils/browser";
import history from "~/utils/history";
import isCloudHosted from "~/utils/isCloudHosted";
import {
homePath,
searchPath,
draftsPath,
templatesPath,
archivePath,
trashPath,
settingsPath,
@@ -67,15 +66,6 @@ export const navigateToDrafts = createAction({
visible: ({ location }) => location.pathname !== draftsPath(),
});
export const navigateToTemplates = createAction({
name: ({ t }) => t("Templates"),
analyticsName: "Navigate to templates",
section: NavigationSection,
icon: <ShapesIcon />,
perform: () => history.push(templatesPath()),
visible: ({ location }) => location.pathname !== templatesPath(),
});
export const navigateToArchive = createAction({
name: ({ t }) => t("Archive"),
analyticsName: "Navigate to archive",
@@ -103,7 +93,7 @@ export const navigateToSettings = createAction({
icon: <SettingsIcon />,
visible: ({ stores }) =>
stores.policies.abilities(stores.auth.team?.id || "").update,
perform: () => history.push(settingsPath("details")),
perform: () => history.push(settingsPath()),
});
export const navigateToProfileSettings = createAction({
@@ -115,6 +105,15 @@ export const navigateToProfileSettings = createAction({
perform: () => history.push(settingsPath()),
});
export const navigateToTemplateSettings = createAction({
name: ({ t }) => t("Templates"),
analyticsName: "Navigate to template settings",
section: NavigationSection,
iconInContextMenu: false,
icon: <ShapesIcon />,
perform: () => history.push(settingsPath("templates")),
});
export const navigateToNotificationSettings = createAction({
name: ({ t }) => t("Notifications"),
analyticsName: "Navigate to notification settings",
@@ -216,7 +215,6 @@ export const logout = createAction({
export const rootNavigationActions = [
navigateToHome,
navigateToDrafts,
navigateToTemplates,
navigateToArchive,
navigateToTrash,
downloadApp,
+2 -3
View File
@@ -2,6 +2,7 @@ import copy from "copy-to-clipboard";
import { LinkIcon, RestoreIcon } from "outline-icons";
import * as React from "react";
import { matchPath } from "react-router-dom";
import { toast } from "sonner";
import stores from "~/stores";
import { createAction } from "~/actions";
import { RevisionSection } from "~/actions/sections";
@@ -68,9 +69,7 @@ export const copyLinkToRevision = createAction({
copy(url, {
format: "text/plain",
onCopy: () => {
stores.toasts.showToast(t("Link copied"), {
type: "info",
});
toast.message(t("Link copied"));
},
});
},
+1
View File
@@ -60,6 +60,7 @@ export const createTeam = createAction({
user &&
stores.dialogs.openModal({
title: t("Create a workspace"),
fullscreen: true,
content: <TeamNew user={user} />,
});
},
+1 -1
View File
@@ -17,6 +17,7 @@ export const inviteUser = createAction({
perform: ({ t }) => {
stores.dialogs.openModal({
title: t("Invite people"),
fullscreen: true,
content: <Invite onSubmit={stores.dialogs.closeAllModals} />,
});
},
@@ -38,7 +39,6 @@ export const deleteUserActionFactory = (userId: string) =>
stores.dialogs.openModal({
title: t("Delete user"),
isCentered: true,
content: (
<UserDeleteDialog
user={user}
+21 -12
View File
@@ -1,5 +1,6 @@
import { flattenDeep } from "lodash";
import flattenDeep from "lodash/flattenDeep";
import * as React from "react";
import { toast } from "sonner";
import { Optional } from "utility-types";
import { v4 as uuidv4 } from "uuid";
import {
@@ -73,15 +74,7 @@ export function actionToMenuItem(
icon,
visible,
dangerous: action.dangerous,
onClick: () => {
try {
action.perform?.(context);
} catch (err) {
context.stores.toasts.showToast(err.message, {
type: "error",
});
}
},
onClick: () => performAction(action, context),
selected: action.selected?.(context),
};
}
@@ -115,8 +108,24 @@ export function actionToKBar(
keywords: action.keywords ?? "",
shortcut: action.shortcut || [],
icon: resolvedIcon,
perform: action.perform ? () => action.perform?.(context) : undefined,
perform: action.perform
? () => performAction(action, context)
: undefined,
},
].concat(
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
].concat(children.map((child) => ({ ...child, parent: action.id })));
children.map((child) => ({ ...child, parent: child.parent ?? action.id }))
);
}
export async function performAction(action: Action, context: ActionContext) {
const result = action.perform?.(context);
if (result instanceof Promise) {
return result.catch((err: Error) => {
toast.error(err.message);
});
}
return result;
}
+7 -2
View File
@@ -1,6 +1,8 @@
/* eslint-disable react/prop-types */
import * as React from "react";
import Tooltip, { Props as TooltipProps } from "~/components/Tooltip";
import { performAction } from "~/actions";
import useIsMounted from "~/hooks/useIsMounted";
import { Action, ActionContext } from "~/types";
export type Props = React.HTMLAttributes<HTMLButtonElement> & {
@@ -24,6 +26,7 @@ const ActionButton = React.forwardRef<HTMLButtonElement, Props>(
{ action, context, tooltip, hideOnActionDisabled, ...rest }: Props,
ref: React.Ref<HTMLButtonElement>
) {
const isMounted = useIsMounted();
const [executing, setExecuting] = React.useState(false);
const disabled = rest.disabled;
@@ -60,10 +63,12 @@ const ActionButton = React.forwardRef<HTMLButtonElement, Props>(
? (ev) => {
ev.preventDefault();
ev.stopPropagation();
const response = action.perform?.(actionContext);
const response = performAction(action, actionContext);
if (response?.finally) {
setExecuting(true);
response.finally(() => setExecuting(false));
void response.finally(
() => isMounted() && setExecuting(false)
);
}
}
: rest.onClick
+1 -1
View File
@@ -1,6 +1,6 @@
/* eslint-disable prefer-rest-params */
/* global ga */
import { escape } from "lodash";
import escape from "lodash/escape";
import * as React from "react";
import { IntegrationService } from "@shared/types";
import env from "~/env";
-41
View File
@@ -1,41 +0,0 @@
import * as React from "react";
type Props = {
size?: number;
fill?: string;
className?: string;
};
function SlackLogo({ size = 34, fill = "#FFF", className }: Props) {
return (
<svg
fill={fill}
width={size}
height={size}
viewBox="0 0 34 34"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g stroke="none" strokeWidth="1" fillRule="evenodd">
<g transform="translate(0.000000, 17.822581)">
<path d="M7.23870968,3.61935484 C7.23870968,5.56612903 5.6483871,7.15645161 3.7016129,7.15645161 C1.75483871,7.15645161 0.164516129,5.56612903 0.164516129,3.61935484 C0.164516129,1.67258065 1.75483871,0.0822580645 3.7016129,0.0822580645 L7.23870968,0.0822580645 L7.23870968,3.61935484 Z" />
<path d="M9.02096774,3.61935484 C9.02096774,1.67258065 10.6112903,0.0822580645 12.5580645,0.0822580645 C14.5048387,0.0822580645 16.0951613,1.67258065 16.0951613,3.61935484 L16.0951613,12.4758065 C16.0951613,14.4225806 14.5048387,16.0129032 12.5580645,16.0129032 C10.6112903,16.0129032 9.02096774,14.4225806 9.02096774,12.4758065 C9.02096774,12.4758065 9.02096774,3.61935484 9.02096774,3.61935484 Z" />
</g>
<g>
<path d="M12.5580645,7.23870968 C10.6112903,7.23870968 9.02096774,5.6483871 9.02096774,3.7016129 C9.02096774,1.75483871 10.6112903,0.164516129 12.5580645,0.164516129 C14.5048387,0.164516129 16.0951613,1.75483871 16.0951613,3.7016129 L16.0951613,7.23870968 L12.5580645,7.23870968 Z" />
<path d="M12.5580645,9.02096774 C14.5048387,9.02096774 16.0951613,10.6112903 16.0951613,12.5580645 C16.0951613,14.5048387 14.5048387,16.0951613 12.5580645,16.0951613 L3.7016129,16.0951613 C1.75483871,16.0951613 0.164516129,14.5048387 0.164516129,12.5580645 C0.164516129,10.6112903 1.75483871,9.02096774 3.7016129,9.02096774 C3.7016129,9.02096774 12.5580645,9.02096774 12.5580645,9.02096774 Z" />
</g>
<g transform="translate(17.822581, 0.000000)">
<path d="M8.93870968,12.5580645 C8.93870968,10.6112903 10.5290323,9.02096774 12.4758065,9.02096774 C14.4225806,9.02096774 16.0129032,10.6112903 16.0129032,12.5580645 C16.0129032,14.5048387 14.4225806,16.0951613 12.4758065,16.0951613 L8.93870968,16.0951613 L8.93870968,12.5580645 Z" />
<path d="M7.15645161,12.5580645 C7.15645161,14.5048387 5.56612903,16.0951613 3.61935484,16.0951613 C1.67258065,16.0951613 0.0822580645,14.5048387 0.0822580645,12.5580645 L0.0822580645,3.7016129 C0.0822580645,1.75483871 1.67258065,0.164516129 3.61935484,0.164516129 C5.56612903,0.164516129 7.15645161,1.75483871 7.15645161,3.7016129 L7.15645161,12.5580645 Z" />
</g>
<g transform="translate(17.822581, 17.822581)">
<path d="M3.61935484,8.93870968 C5.56612903,8.93870968 7.15645161,10.5290323 7.15645161,12.4758065 C7.15645161,14.4225806 5.56612903,16.0129032 3.61935484,16.0129032 C1.67258065,16.0129032 0.0822580645,14.4225806 0.0822580645,12.4758065 L0.0822580645,8.93870968 L3.61935484,8.93870968 Z" />
<path d="M3.61935484,7.15645161 C1.67258065,7.15645161 0.0822580645,5.56612903 0.0822580645,3.61935484 C0.0822580645,1.67258065 1.67258065,0.0822580645 3.61935484,0.0822580645 L12.4758065,0.0822580645 C14.4225806,0.0822580645 16.0129032,1.67258065 16.0129032,3.61935484 C16.0129032,5.56612903 14.4225806,7.15645161 12.4758065,7.15645161 L3.61935484,7.15645161 Z" />
</g>
</g>
</svg>
);
}
export default SlackLogo;
+5 -3
View File
@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Redirect } from "react-router-dom";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import { changeLanguage } from "~/utils/language";
import LoadingIndicator from "./LoadingIndicator";
@@ -13,10 +14,11 @@ type Props = {
const Authenticated = ({ children }: Props) => {
const { auth } = useStores();
const { i18n } = useTranslation();
const language = auth.user?.language;
const user = useCurrentUser({ rejectOnEmpty: false });
const language = user?.language;
// Watching for language changes here as this is the earliest point we have
// the user available and means we can start loading translations faster
// Watching for language changes here as this is the earliest point we might have the user
// available and means we can start loading translations faster
React.useEffect(() => {
void changeLanguage(language, i18n);
}, [i18n, language]);
+23 -15
View File
@@ -12,6 +12,7 @@ import Sidebar from "~/components/Sidebar";
import SidebarRight from "~/components/Sidebar/Right";
import SettingsSidebar from "~/components/Sidebar/Settings";
import type { Editor as TEditor } from "~/editor";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import history from "~/utils/history";
@@ -25,6 +26,7 @@ import {
matchDocumentInsights,
} from "~/utils/routeHelpers";
import Fade from "./Fade";
import { PortalContext } from "./Portal";
const DocumentComments = lazyWithRetry(
() => import("~/scenes/Document/components/Comments")
@@ -44,8 +46,9 @@ type Props = {
const AuthenticatedLayout: React.FC = ({ children }: Props) => {
const { ui, auth } = useStores();
const location = useLocation();
const layoutRef = React.useRef<HTMLDivElement>(null);
const can = usePolicy(ui.activeCollectionId);
const { user, team } = auth;
const team = useCurrentTeam();
const documentContext = useLocalStore<DocumentContextValue>(() => ({
editor: null,
setEditor: (editor: TEditor) => {
@@ -76,16 +79,14 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
return <ErrorSuspended />;
}
const showSidebar = auth.authenticated && user && team;
const sidebar = showSidebar ? (
const sidebar = (
<Fade>
<Switch>
<Route path={settingsPath()} component={SettingsSidebar} />
<Route component={Sidebar} />
</Switch>
</Fade>
) : undefined;
);
const showHistory = !!matchPath(location.pathname, {
path: matchDocumentHistory,
@@ -98,7 +99,7 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
!showHistory &&
ui.activeDocumentId &&
ui.commentsExpanded.includes(ui.activeDocumentId) &&
team?.getPreference(TeamPreference.Commenting);
team.getPreference(TeamPreference.Commenting);
const sidebarRight = (
<AnimatePresence
@@ -121,15 +122,22 @@ const AuthenticatedLayout: React.FC = ({ children }: Props) => {
return (
<DocumentContext.Provider value={documentContext}>
<Layout title={team?.name} sidebar={sidebar} sidebarRight={sidebarRight}>
<RegisterKeyDown trigger="n" handler={goToNewDocument} />
<RegisterKeyDown trigger="t" handler={goToSearch} />
<RegisterKeyDown trigger="/" handler={goToSearch} />
{children}
<React.Suspense fallback={null}>
<CommandBar />
</React.Suspense>
</Layout>
<PortalContext.Provider value={layoutRef.current}>
<Layout
title={team.name}
sidebar={sidebar}
sidebarRight={sidebarRight}
ref={layoutRef}
>
<RegisterKeyDown trigger="n" handler={goToNewDocument} />
<RegisterKeyDown trigger="t" handler={goToSearch} />
<RegisterKeyDown trigger="/" handler={goToSearch} />
{children}
<React.Suspense fallback={null}>
<CommandBar />
</React.Suspense>
</Layout>
</PortalContext.Provider>
</DocumentContext.Provider>
);
};
+1
View File
@@ -5,6 +5,7 @@ import Initials from "./Initials";
export enum AvatarSize {
Small = 16,
Toast = 18,
Medium = 24,
Large = 32,
XLarge = 48,
+1 -1
View File
@@ -34,7 +34,7 @@ function AvatarWithPresence({
return (
<>
<Tooltip
tooltip={
content={
<Centered>
<strong>{user.name}</strong> {isCurrentUser && `(${t("You")})`}
{status && (
+6 -5
View File
@@ -34,16 +34,17 @@ const Link = styled.a`
fill: ${s("text")};
}
&:hover {
background: ${s("sidebarBackground")};
}
${breakpoint("tablet")`
z-index: ${depths.sidebar + 1};
background: ${s("sidebarBackground")};
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
&:hover {
background: ${s("sidebarControlHoverBackground")};
}
`};
`;
+1 -1
View File
@@ -171,7 +171,7 @@ const Button = <T extends React.ElementType = "button">(
danger,
...rest
} = props;
const hasText = children !== undefined || value !== undefined;
const hasText = !!children || value !== undefined;
const ic = hideIcon ? undefined : action?.icon ?? icon;
const hasIcon = ic !== undefined;
+12 -5
View File
@@ -4,6 +4,7 @@ import breakpoint from "styled-components-breakpoint";
type Props = {
children?: React.ReactNode;
maxWidth?: string;
withStickyHeader?: boolean;
};
@@ -18,18 +19,24 @@ const Container = styled.div<Props>`
`};
`;
const Content = styled.div`
max-width: 46em;
type ContentProps = { $maxWidth?: string };
const Content = styled.div<ContentProps>`
max-width: ${(props) => props.$maxWidth ?? "46em"};
margin: 0 auto;
${breakpoint("desktopLarge")`
max-width: 52em;
max-width: ${(props: ContentProps) => props.$maxWidth ?? "52em"};
`};
`;
const CenteredContent: React.FC<Props> = ({ children, ...rest }: Props) => (
const CenteredContent: React.FC<Props> = ({
children,
maxWidth,
...rest
}: Props) => (
<Container {...rest}>
<Content>{children}</Content>
<Content $maxWidth={maxWidth}>{children}</Content>
</Container>
);
+17
View File
@@ -0,0 +1,17 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { changeLanguage } from "~/utils/language";
type Props = {
locale: string;
};
export default function ChangeLanguage({ locale }: Props) {
const { i18n } = useTranslation();
React.useEffect(() => {
void changeLanguage(locale, i18n);
}, [locale, i18n]);
return null;
}
+4 -1
View File
@@ -1,4 +1,7 @@
import { sortBy, filter, uniq, isEqual } from "lodash";
import filter from "lodash/filter";
import isEqual from "lodash/isEqual";
import sortBy from "lodash/sortBy";
import uniq from "lodash/uniq";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
@@ -0,0 +1,32 @@
import { observer } from "mobx-react";
import * as React from "react";
import { toast } from "sonner";
import useStores from "~/hooks/useStores";
import { CollectionForm, FormData } from "./CollectionForm";
type Props = {
collectionId: string;
onSubmit: () => void;
};
export const CollectionEdit = observer(function CollectionEdit_({
collectionId,
onSubmit,
}: Props) {
const { collections } = useStores();
const collection = collections.get(collectionId);
const handleSubmit = React.useCallback(
async (data: FormData) => {
try {
await collection?.save(data);
onSubmit?.();
} catch (error) {
toast.error(error.message);
}
},
[collection, onSubmit]
);
return <CollectionForm collection={collection} handleSubmit={handleSubmit} />;
});
@@ -0,0 +1,168 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Controller, useForm } from "react-hook-form";
import { Trans, useTranslation } from "react-i18next";
import styled from "styled-components";
import { randomElement } from "@shared/random";
import { CollectionPermission } from "@shared/types";
import { colorPalette } from "@shared/utils/collections";
import { CollectionValidation } from "@shared/validations";
import Collection from "~/models/Collection";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import IconPicker from "~/components/IconPicker";
import { IconLibrary } from "~/components/Icons/IconLibrary";
import Input from "~/components/Input";
import InputSelectPermission from "~/components/InputSelectPermission";
import Switch from "~/components/Switch";
import Text from "~/components/Text";
import useBoolean from "~/hooks/useBoolean";
import useCurrentTeam from "~/hooks/useCurrentTeam";
export interface FormData {
name: string;
icon: string;
color: string;
sharing: boolean;
permission: CollectionPermission | undefined;
}
export const CollectionForm = observer(function CollectionForm_({
handleSubmit,
collection,
}: {
handleSubmit: (data: FormData) => void;
collection?: Collection;
}) {
const team = useCurrentTeam();
const { t } = useTranslation();
const [hasOpenedIconPicker, setHasOpenedIconPicker] = useBoolean(false);
const {
register,
handleSubmit: formHandleSubmit,
formState,
watch,
control,
setValue,
setFocus,
} = useForm<FormData>({
mode: "all",
defaultValues: {
name: collection?.name ?? "",
icon: collection?.icon,
sharing: collection?.sharing ?? true,
permission: collection?.permission,
color: collection?.color ?? randomElement(colorPalette),
},
});
const values = watch();
React.useEffect(() => {
// If the user hasn't picked an icon yet, go ahead and suggest one based on
// the name of the collection. It's the little things sometimes.
if (!hasOpenedIconPicker) {
setValue(
"icon",
IconLibrary.findIconByKeyword(values.name) ??
values.icon ??
"collection"
);
}
}, [values.name]);
const handleIconPickerChange = React.useCallback(
(color: string, icon: string) => {
if (icon !== values.icon) {
setFocus("name");
}
setValue("color", color);
setValue("icon", icon);
},
[setFocus, setValue, values.icon]
);
return (
<form onSubmit={formHandleSubmit(handleSubmit)}>
<Text as="p">
<Trans>
Collections are used to group documents and choose permissions
</Trans>
.
</Text>
<Flex gap={8}>
<Input
type="text"
placeholder={t("Name")}
{...register("name", {
required: true,
maxLength: CollectionValidation.maxNameLength,
})}
prefix={
<StyledIconPicker
onOpen={setHasOpenedIconPicker}
onChange={handleIconPickerChange}
initial={values.name[0]}
color={values.color}
icon={values.icon}
/>
}
autoFocus
flex
/>
</Flex>
{/* Following controls are available in create flow, but moved elsewhere for edit */}
{!collection && (
<Controller
control={control}
name="permission"
render={({ field }) => (
<InputSelectPermission
ref={field.ref}
value={field.value}
onChange={(value: CollectionPermission) => {
field.onChange(value);
}}
note={t(
"The default access for workspace members, you can share with more users or groups later."
)}
/>
)}
/>
)}
{team.sharing && !collection && (
<Switch
id="sharing"
label={t("Public document sharing")}
note={t(
"Allow documents within this collection to be shared publicly on the internet."
)}
{...register("sharing")}
/>
)}
<Flex justify="flex-end">
<Button
type="submit"
disabled={formState.isSubmitting || !formState.isValid}
>
{collection
? formState.isSubmitting
? `${t("Saving")}`
: t("Save")
: formState.isSubmitting
? `${t("Creating")}`
: t("Create")}
</Button>
</Flex>
</form>
);
});
const StyledIconPicker = styled(IconPicker)`
margin-left: 4px;
margin-right: 4px;
`;
@@ -0,0 +1,32 @@
import { observer } from "mobx-react";
import * as React from "react";
import { toast } from "sonner";
import Collection from "~/models/Collection";
import useStores from "~/hooks/useStores";
import history from "~/utils/history";
import { CollectionForm, FormData } from "./CollectionForm";
type Props = {
onSubmit: () => void;
};
export const CollectionNew = observer(function CollectionNew_({
onSubmit,
}: Props) {
const { collections } = useStores();
const handleSubmit = React.useCallback(
async (data: FormData) => {
try {
const collection = new Collection(data, collections);
await collection.save();
onSubmit?.();
history.push(collection.path);
} catch (error) {
toast.error(error.message);
}
},
[collections, onSubmit]
);
return <CollectionForm handleSubmit={handleSubmit} />;
});
+8 -4
View File
@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import Collection from "~/models/Collection";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Text from "~/components/Text";
@@ -22,11 +23,14 @@ function CollectionDeleteDialog({ collection, onSubmit }: Props) {
const handleSubmit = async () => {
const redirect = collection.id === ui.activeCollectionId;
await collection.delete();
onSubmit();
if (redirect) {
history.push(homePath());
}
await collection.delete();
onSubmit();
toast.success(t("Collection deleted"));
};
return (
@@ -37,7 +41,7 @@ function CollectionDeleteDialog({ collection, onSubmit }: Props) {
danger
>
<>
<Text type="secondary">
<Text as="p" type="secondary">
<Trans
defaults="Are you sure about that? Deleting the <em>{{collectionName}}</em> collection is permanent and cannot be restored, however all published documents within will be moved to the trash."
values={{
@@ -49,7 +53,7 @@ function CollectionDeleteDialog({ collection, onSubmit }: Props) {
/>
</Text>
{team.defaultCollectionId === collection.id ? (
<Text type="secondary">
<Text as="p" type="secondary">
<Trans
defaults="Also, <em>{{collectionName}}</em> is being used as the start view deleting it will reset the start view to the Home page."
values={{
+16 -9
View File
@@ -3,7 +3,9 @@ import { observer } from "mobx-react";
import { transparentize } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { richExtensions } from "@shared/editor/nodes";
import { s } from "@shared/styles";
import Collection from "~/models/Collection";
import Arrow from "~/components/Arrow";
@@ -11,9 +13,18 @@ import ButtonLink from "~/components/ButtonLink";
import Editor from "~/components/Editor";
import LoadingIndicator from "~/components/LoadingIndicator";
import NudeButton from "~/components/NudeButton";
import BlockMenuExtension from "~/editor/extensions/BlockMenu";
import EmojiMenuExtension from "~/editor/extensions/EmojiMenu";
import HoverPreviewsExtension from "~/editor/extensions/HoverPreviews";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
const extensions = [
...richExtensions,
BlockMenuExtension,
EmojiMenuExtension,
HoverPreviewsExtension,
];
type Props = {
collection: Collection;
@@ -21,7 +32,6 @@ type Props = {
function CollectionDescription({ collection }: Props) {
const { collections } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const [isExpanded, setExpanded] = React.useState(false);
const [isEditing, setEditing] = React.useState(false);
@@ -59,15 +69,11 @@ function CollectionDescription({ collection }: Props) {
});
setDirty(false);
} catch (err) {
showToast(
t("Sorry, an error occurred saving the collection", {
type: "error",
})
);
toast.error(t("Sorry, an error occurred saving the collection"));
throw err;
}
}, 1000),
[collection, showToast, t]
[collection, t]
);
const handleChange = React.useCallback(
@@ -109,6 +115,7 @@ function CollectionDescription({ collection }: Props) {
readOnly={!isEditing}
autoFocus={isEditing}
onBlur={handleStopEditing}
extensions={extensions}
maxLength={1000}
embedsDisabled
canUpdate
@@ -170,7 +177,7 @@ const MaxHeight = styled.div`
position: relative;
max-height: 25vh;
overflow: hidden;
margin: -12px -8px -8px;
margin: 8px -8px -8px;
padding: 8px;
&[data-editing="true"],
+6 -18
View File
@@ -11,39 +11,26 @@ import SearchActions from "~/components/SearchActions";
import rootActions from "~/actions/root";
import useCommandBarActions from "~/hooks/useCommandBarActions";
import useSettingsActions from "~/hooks/useSettingsActions";
import { CommandBarAction } from "~/types";
import useTemplateActions from "~/hooks/useTemplateActions";
function CommandBar() {
const { t } = useTranslation();
const settingsActions = useSettingsActions();
const templateActions = useTemplateActions();
const commandBarActions = React.useMemo(
() => [...rootActions, settingsActions],
[settingsActions]
() => [...rootActions, templateActions, settingsActions],
[settingsActions, templateActions]
);
useCommandBarActions(commandBarActions);
const { rootAction } = useKBar((state) => ({
rootAction: state.currentRootActionId
? (state.actions[
state.currentRootActionId
] as unknown as CommandBarAction)
: undefined,
}));
return (
<>
<KBarPortal>
<Positioner>
<Animator>
<SearchActions />
<SearchInput
placeholder={`${
rootAction?.placeholder ||
rootAction?.name ||
t("Type a command or search")
}`}
/>
<SearchInput defaultPlaceholder={t("Type a command or search")} />
<CommandBarResults />
</Animator>
</Positioner>
@@ -83,6 +70,7 @@ const SearchInput = styled(KBarSearch)`
&:disabled,
&::placeholder {
color: ${s("placeholder")};
opacity: 1;
}
`;
+1 -1
View File
@@ -62,7 +62,7 @@ function CommandBarItem(
{index > 0 ? (
<>
{" "}
<Text size="xsmall" as="span" type="secondary">
<Text size="xsmall" type="secondary">
then
</Text>{" "}
</>
+3 -4
View File
@@ -1,11 +1,11 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import Comment from "~/models/Comment";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
comment: Comment;
@@ -14,7 +14,6 @@ type Props = {
function CommentDeleteDialog({ comment, onSubmit }: Props) {
const { comments } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const hasChildComments = comments.inThread(comment.id).length > 1;
@@ -23,7 +22,7 @@ function CommentDeleteDialog({ comment, onSubmit }: Props) {
await comment.delete();
onSubmit?.();
} catch (err) {
showToast(err.message, { type: "error" });
toast.error(err.message);
}
};
@@ -34,7 +33,7 @@ function CommentDeleteDialog({ comment, onSubmit }: Props) {
savingText={`${t("Deleting")}`}
danger
>
<Text type="secondary">
<Text as="p" type="secondary">
{hasChildComments ? (
<Trans>
Are you sure you want to permanently delete this entire comment
+20 -18
View File
@@ -1,10 +1,11 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
/** Callback when the dialog is submitted */
@@ -29,8 +30,8 @@ const ConfirmationDialog: React.FC<Props> = ({
disabled = false,
}: Props) => {
const [isSaving, setIsSaving] = React.useState(false);
const { t } = useTranslation();
const { dialogs } = useStores();
const { showToast } = useToasts();
const handleSubmit = React.useCallback(
async (ev: React.SyntheticEvent) => {
@@ -40,30 +41,31 @@ const ConfirmationDialog: React.FC<Props> = ({
await onSubmit();
dialogs.closeAllModals();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}
},
[onSubmit, dialogs, showToast]
[onSubmit, dialogs]
);
return (
<Flex column>
<form onSubmit={handleSubmit}>
<form onSubmit={handleSubmit}>
<Flex gap={12} column>
<Text type="secondary">{children}</Text>
<Button
type="submit"
disabled={isSaving || disabled}
danger={danger}
autoFocus
>
{isSaving && savingText ? savingText : submitText}
</Button>
</form>
</Flex>
<Flex justify="flex-end">
<Button
type="submit"
disabled={isSaving || disabled}
danger={danger}
autoFocus
>
{isSaving && savingText ? savingText : submitText ?? t("Confirm")}
</Button>
</Flex>
</Flex>
</form>
);
};
+39 -6
View File
@@ -14,15 +14,48 @@ function ConnectionStatus() {
const theme = useTheme();
const { t } = useTranslation();
const codeToMessage = {
1009: {
title: t("Document is too large"),
body: t(
"This document has reached the maximum size and can no longer be edited"
),
},
4401: {
title: t("Authentication failed"),
body: t("Please try logging out and back in again"),
},
4403: {
title: t("Authorization failed"),
body: t("You may have lost access to this document, try reloading"),
},
4503: {
title: t("Too many users connected to document"),
body: t("Your edits will sync once other users leave the document"),
},
};
const message = ui.multiplayerErrorCode
? codeToMessage[ui.multiplayerErrorCode]
: undefined;
return ui.multiplayerStatus === "connecting" ||
ui.multiplayerStatus === "disconnected" ? (
<Tooltip
tooltip={
<Centered>
<strong>{t("Server connection lost")}</strong>
<br />
{t("Edits you make will sync once youre online")}
</Centered>
content={
message ? (
<Centered>
<strong>{message.title}</strong>
<br />
{message.body}
</Centered>
) : (
<Centered>
<strong>{t("Server connection lost")}</strong>
<br />
{t("Edits you make will sync once youre online")}
</Centered>
)
}
placement="bottom"
>
+5 -2
View File
@@ -9,6 +9,7 @@ type Props = Omit<React.HTMLAttributes<HTMLSpanElement>, "ref" | "onChange"> & {
readOnly?: boolean;
onClick?: React.MouseEventHandler<HTMLDivElement>;
onChange?: (text: string) => void;
onFocus?: React.FocusEventHandler<HTMLSpanElement> | undefined;
onBlur?: React.FocusEventHandler<HTMLSpanElement> | undefined;
onInput?: React.FormEventHandler<HTMLSpanElement> | undefined;
onKeyDown?: React.KeyboardEventHandler<HTMLSpanElement> | undefined;
@@ -35,6 +36,7 @@ const ContentEditable = React.forwardRef(function _ContentEditable(
disabled,
onChange,
onInput,
onFocus,
onBlur,
onKeyDown,
value,
@@ -143,11 +145,13 @@ const ContentEditable = React.forwardRef(function _ContentEditable(
);
return (
<div className={className} dir={dir} onClick={onClick}>
<div className={className} dir={dir} onClick={onClick} tabIndex={-1}>
{children}
<Content
ref={contentRef}
contentEditable={!disabled && !readOnly}
onInput={wrappedEvent(onInput)}
onFocus={wrappedEvent(onFocus)}
onBlur={wrappedEvent(onBlur)}
onKeyDown={wrappedEvent(onKeyDown)}
onPaste={handlePaste}
@@ -158,7 +162,6 @@ const ContentEditable = React.forwardRef(function _ContentEditable(
>
{innerValue}
</Content>
{children}
</div>
);
});
@@ -1,11 +1,13 @@
import styled from "styled-components";
import { s } from "@shared/styles";
const MenuIconWrapper = styled.span`
width: 24px;
height: 24px;
margin-right: 6px;
margin-left: -4px;
color: ${({ theme }) => theme.textSecondary};
color: ${s("textSecondary")};
flex-shrink: 0;
`;
export default MenuIconWrapper;
+1 -1
View File
@@ -5,7 +5,7 @@ import { mergeRefs } from "react-merge-refs";
import { MenuItem as BaseMenuItem } from "reakit/Menu";
import styled, { css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import MenuIconWrapper from "../MenuIconWrapper";
import MenuIconWrapper from "./MenuIconWrapper";
type Props = {
id?: string;
+6 -6
View File
@@ -2,17 +2,17 @@ import * as React from "react";
import { useMousePosition } from "~/hooks/useMousePosition";
type Positions = {
/* Sub-menu x */
/** Sub-menu x */
x: number;
/* Sub-menu y */
/** Sub-menu y */
y: number;
/* Sub-menu height */
/** Sub-menu height */
h: number;
/* Sub-menu width */
/** Sub-menu width */
w: number;
/* Mouse x */
/** Mouse x */
mouseX: number;
/* Mouse y */
/** Mouse y */
mouseY: number;
};
+2 -2
View File
@@ -9,8 +9,8 @@ import {
MenuStateReturn,
} from "reakit/Menu";
import styled, { useTheme } from "styled-components";
import MenuIconWrapper from "~/components/ContextMenu/MenuIconWrapper";
import Flex from "~/components/Flex";
import MenuIconWrapper from "~/components/MenuIconWrapper";
import { actionToMenuItem } from "~/actions";
import useActionContext from "~/hooks/useActionContext";
import {
@@ -201,7 +201,7 @@ function Template({ items, actions, context, ...menu }: Props) {
}
if (item.type === "heading") {
return <Header>{item.title}</Header>;
return <Header key={index}>{item.title}</Header>;
}
const _exhaustiveCheck: never = item;
+92 -61
View File
@@ -46,6 +46,8 @@ type Props = MenuStateReturn & {
onClose?: () => void;
/** Called when the context menu is clicked. */
onClick?: (ev: React.MouseEvent) => void;
/** The maximum width of the context menu. */
maxWidth?: number;
children?: React.ReactNode;
};
@@ -57,11 +59,6 @@ const ContextMenu: React.FC<Props> = ({
...rest
}: Props) => {
const previousVisible = usePrevious(rest.visible);
const maxHeight = useMenuHeight({
visible: rest.visible,
elementRef: rest.unstable_disclosureRef,
});
const backgroundRef = React.useRef<HTMLDivElement>(null);
const { ui } = useStores();
const { t } = useTranslation();
const { setIsMenuOpen } = useMenuContext();
@@ -99,21 +96,6 @@ const ContextMenu: React.FC<Props> = ({
t,
]);
// We must manually manage scroll lock for iOS support so that the scrollable
// element can be passed into body-scroll-lock. See:
// https://github.com/ariakit/ariakit/issues/469
React.useEffect(() => {
const scrollElement = backgroundRef.current;
if (rest.visible && scrollElement && !isSubMenu) {
disableBodyScroll(scrollElement, {
reserveScrollBarGap: true,
});
}
return () => {
scrollElement && !isSubMenu && enableBodyScroll(scrollElement);
};
}, [isSubMenu, rest.visible]);
// Perf win don't render anything until the menu has been opened
if (!rest.visible && !previousVisible) {
return null;
@@ -124,51 +106,98 @@ const ContextMenu: React.FC<Props> = ({
return (
<>
<Menu hideOnClickOutside={!isMobile} preventBodyScroll={false} {...rest}>
{(props) => {
// kind of hacky, but this is an effective way of telling which way
// the menu will _actually_ be placed when taking into account screen
// positioning.
const topAnchor = props.style?.top === "0";
// @ts-expect-error ts-migrate(2339) FIXME: Property 'placement' does not exist on type 'Extra... Remove this comment to see the full error message
const rightAnchor = props.placement === "bottom-end";
return (
<>
{isMobile && (
<Backdrop
onClick={(ev) => {
ev.preventDefault();
ev.stopPropagation();
rest.hide?.();
}}
/>
)}
<Position {...props}>
<Background
dir="auto"
topAnchor={topAnchor}
rightAnchor={rightAnchor}
ref={backgroundRef}
hiddenScrollbars
style={
topAnchor
? {
maxHeight,
}
: undefined
}
>
{rest.visible || rest.animating ? children : null}
</Background>
</Position>
</>
);
}}
{(props) => (
<InnerContextMenu
// eslint-disable-next-line @typescript-eslint/no-explicit-any
menuProps={props as any}
{...rest}
isSubMenu={isSubMenu}
>
{children}
</InnerContextMenu>
)}
</Menu>
</>
);
};
type InnerContextMenuProps = MenuStateReturn & {
isSubMenu: boolean;
menuProps: { style?: React.CSSProperties; placement: string };
children: React.ReactNode;
maxWidth?: number;
};
/**
* Inner context menu allows deferring expensive window measurement hooks etc
* until the menu is actually opened.
*/
const InnerContextMenu = (props: InnerContextMenuProps) => {
const { menuProps } = props;
// kind of hacky, but this is an effective way of telling which way
// the menu will _actually_ be placed when taking into account screen
// positioning.
const topAnchor =
menuProps.style?.top === "0" || menuProps.style?.position === "fixed";
const rightAnchor = menuProps.placement === "bottom-end";
const backgroundRef = React.useRef<HTMLDivElement>(null);
const isMobile = useMobile();
const maxHeight = useMenuHeight({
visible: props.visible,
elementRef: props.unstable_disclosureRef,
});
// We must manually manage scroll lock for iOS support so that the scrollable
// element can be passed into body-scroll-lock. See:
// https://github.com/ariakit/ariakit/issues/469
React.useEffect(() => {
const scrollElement = backgroundRef.current;
if (props.visible && scrollElement && !props.isSubMenu) {
disableBodyScroll(scrollElement, {
reserveScrollBarGap: true,
});
}
return () => {
scrollElement && !props.isSubMenu && enableBodyScroll(scrollElement);
};
}, [props.isSubMenu, props.visible]);
const style =
topAnchor && !isMobile
? {
maxHeight,
}
: undefined;
return (
<>
{isMobile && (
<Backdrop
onClick={(ev) => {
ev.preventDefault();
ev.stopPropagation();
props.hide?.();
}}
/>
)}
<Position {...menuProps}>
<Background
dir="auto"
maxWidth={props.maxWidth}
topAnchor={topAnchor}
rightAnchor={rightAnchor}
ref={backgroundRef}
hiddenScrollbars
style={style}
>
{props.visible || props.animating ? props.children : null}
</Background>
</Position>
</>
);
};
export default ContextMenu;
export const Backdrop = styled.div`
@@ -203,6 +232,7 @@ export const Position = styled.div`
type BackgroundProps = {
topAnchor?: boolean;
rightAnchor?: boolean;
maxWidth?: number;
theme: DefaultTheme;
};
@@ -228,7 +258,8 @@ export const Background = styled(Scrollable)<BackgroundProps>`
props.topAnchor ? fadeAndSlideDown : fadeAndSlideUp} 200ms ease;
transform-origin: ${(props: BackgroundProps) =>
props.rightAnchor ? "75%" : "25%"} 0;
max-width: 276px;
max-width: ${(props: BackgroundProps) => props.maxWidth ?? 276}px;
max-height: 100vh;
background: ${(props: BackgroundProps) => props.theme.menuBackground};
box-shadow: ${(props: BackgroundProps) => props.theme.menuShadow};
`};
+30 -21
View File
@@ -1,5 +1,6 @@
import copy from "copy-to-clipboard";
import * as React from "react";
import { mergeRefs } from "react-merge-refs";
import env from "~/env";
type Props = {
@@ -9,32 +10,40 @@ type Props = {
onCopy?: () => void;
};
class CopyToClipboard extends React.PureComponent<Props> {
onClick = (ev: React.SyntheticEvent) => {
const { text, onCopy, children } = this.props;
const elem = React.Children.only(children);
function CopyToClipboard(props: Props, ref: React.Ref<HTMLElement>) {
const { text, onCopy, children, ...rest } = props;
copy(text, {
debug: env.ENVIRONMENT !== "production",
format: "text/plain",
});
const onClick = React.useCallback(
(ev: React.MouseEvent<HTMLElement>) => {
const elem = React.Children.only(children);
onCopy?.();
copy(text, {
debug: env.ENVIRONMENT !== "production",
format: "text/plain",
});
if (elem && elem.props && typeof elem.props.onClick === "function") {
elem.props.onClick(ev);
}
};
onCopy?.();
render() {
const { text, onCopy, children, ...rest } = this.props;
const elem = React.Children.only(children);
if (!elem) {
return null;
}
if (elem && elem.props && typeof elem.props.onClick === "function") {
elem.props.onClick(ev);
}
},
[children, onCopy, text]
);
return React.cloneElement(elem, { ...rest, onClick: this.onClick });
const elem = React.Children.only(children);
if (!elem) {
return null;
}
return React.cloneElement(elem, {
...rest,
ref:
"ref" in elem
? mergeRefs([elem.ref as React.MutableRefObject<HTMLElement>, ref])
: ref,
onClick,
});
}
export default CopyToClipboard;
export default React.forwardRef(CopyToClipboard);
@@ -1,13 +1,13 @@
import { HomeIcon } from "outline-icons";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Optional } from "utility-types";
import Flex from "~/components/Flex";
import CollectionIcon from "~/components/Icons/CollectionIcon";
import InputSelect from "~/components/InputSelect";
import { IconWrapper } from "~/components/Sidebar/components/SidebarLink";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type DefaultCollectionInputSelectProps = Optional<
React.ComponentProps<typeof InputSelect>
@@ -25,7 +25,6 @@ const DefaultCollectionInputSelect = ({
const { collections } = useStores();
const [fetching, setFetching] = useState(false);
const [fetchError, setFetchError] = useState();
const { showToast } = useToasts();
React.useEffect(() => {
async function fetchData() {
@@ -36,11 +35,8 @@ const DefaultCollectionInputSelect = ({
limit: 100,
});
} catch (error) {
showToast(
t("Collections could not be loaded, please reload the app"),
{
type: "error",
}
toast.error(
t("Collections could not be loaded, please reload the app")
);
setFetchError(error);
} finally {
@@ -49,7 +45,7 @@ const DefaultCollectionInputSelect = ({
}
}
void fetchData();
}, [showToast, fetchError, t, fetching, collections]);
}, [fetchError, t, fetching, collections]);
const options = React.useMemo(
() =>
+12 -7
View File
@@ -1,10 +1,10 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { useDesktopTitlebar } from "~/hooks/useDesktopTitlebar";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import Desktop from "~/utils/Desktop";
export default function DesktopEventHandler() {
@@ -12,7 +12,7 @@ export default function DesktopEventHandler() {
const { t } = useTranslation();
const history = useHistory();
const { dialogs } = useStores();
const { showToast } = useToasts();
const hasDisabledUpdateMessage = React.useRef(false);
React.useEffect(() => {
Desktop.bridge?.redirect((path: string, replace = false) => {
@@ -24,11 +24,16 @@ export default function DesktopEventHandler() {
});
Desktop.bridge?.updateDownloaded(() => {
showToast("An update is ready to install.", {
type: "info",
timeout: Infinity,
if (hasDisabledUpdateMessage.current) {
return;
}
hasDisabledUpdateMessage.current = true;
toast.message("An update is ready to install.", {
duration: Infinity,
dismissible: true,
action: {
text: "Install now",
label: t("Install now"),
onClick: () => {
void Desktop.bridge?.restartAndInstall();
},
@@ -50,7 +55,7 @@ export default function DesktopEventHandler() {
content: <KeyboardShortcuts />,
});
});
}, [t, history, dialogs, showToast]);
}, [t, history, dialogs]);
return null;
}
+1 -1
View File
@@ -22,7 +22,7 @@ function Dialogs() {
<Modal
key={id}
isOpen={modal.isOpen}
isCentered={modal.isCentered}
fullscreen={modal.fullscreen ?? false}
onRequestClose={() => dialogs.closeModal(id)}
title={modal.title}
>
+21 -14
View File
@@ -12,9 +12,10 @@ import { MenuInternalLink } from "~/types";
import {
archivePath,
collectionPath,
templatesPath,
settingsPath,
trashPath,
} from "~/utils/routeHelpers";
import EmojiIcon from "./Icons/EmojiIcon";
type Props = {
children?: React.ReactNode;
@@ -43,12 +44,12 @@ function useCategory(document: Document): MenuInternalLink | null {
};
}
if (document.isTemplate) {
if (document.template) {
return {
type: "route",
icon: <ShapesIcon />,
title: t("Templates"),
to: templatesPath(),
to: settingsPath("templates"),
};
}
@@ -67,6 +68,10 @@ const DocumentBreadcrumb: React.FC<Props> = ({
? collections.get(document.collectionId)
: undefined;
React.useEffect(() => {
void document.loadRelations();
}, [document]);
let collectionNode: MenuInternalLink | undefined;
if (collection) {
@@ -74,22 +79,18 @@ const DocumentBreadcrumb: React.FC<Props> = ({
type: "route",
title: collection.name,
icon: <CollectionIcon collection={collection} expanded />,
to: collectionPath(collection.url),
to: collectionPath(collection.path),
};
} else if (document.collectionId && !collection) {
} else if (document.isCollectionDeleted) {
collectionNode = {
type: "route",
title: t("Deleted Collection"),
icon: undefined,
to: collectionPath("deleted-collection"),
to: "",
};
}
const path = React.useMemo(
() => collection?.pathToDocument(document.id).slice(0, -1) || [],
// eslint-disable-next-line react-hooks/exhaustive-deps
[collection, document, document.collectionId, document.parentDocumentId]
);
const path = document.pathTo;
const items = React.useMemo(() => {
const output = [];
@@ -102,10 +103,16 @@ const DocumentBreadcrumb: React.FC<Props> = ({
output.push(collectionNode);
}
path.forEach((node: NavigationNode) => {
path.slice(0, -1).forEach((node: NavigationNode) => {
output.push({
type: "route",
title: node.title,
title: node.emoji ? (
<>
<EmojiIcon emoji={node.emoji} /> {node.title}
</>
) : (
node.title
),
to: node.url,
});
});
@@ -120,7 +127,7 @@ const DocumentBreadcrumb: React.FC<Props> = ({
return (
<>
{collection?.name}
{path.map((node: NavigationNode) => (
{path.slice(0, -1).map((node: NavigationNode) => (
<React.Fragment key={node.id}>
<SmallSlash />
{node.title}
+5 -4
View File
@@ -111,11 +111,12 @@ function DocumentCard(props: Props) {
{document.emoji ? (
<Squircle color={theme.slateLight}>
<EmojiIcon emoji={document.emoji} size={26} />
<EmojiIcon emoji={document.emoji} size={24} />
</Squircle>
) : (
<Squircle color={collection?.color}>
{collection?.icon &&
collection?.icon !== "letter" &&
collection?.icon !== "collection" &&
!pin?.collectionId ? (
<CollectionIcon collection={collection} color="white" />
@@ -144,7 +145,7 @@ function DocumentCard(props: Props) {
{canUpdatePin && (
<Actions dir={document.dir} gap={4}>
{!isDragging && pin && (
<Tooltip tooltip={t("Unpin")}>
<Tooltip content={t("Unpin")}>
<PinButton onClick={handleUnpin} aria-label={t("Unpin")}>
<CloseIcon />
</PinButton>
@@ -279,8 +280,8 @@ const Heading = styled.h3`
overflow: hidden;
color: ${s("text")};
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-family: ${s("fontFamily")};
font-weight: 500;
`;
export default observer(DocumentCard);
+18
View File
@@ -1,5 +1,6 @@
import * as React from "react";
import { Editor } from "~/editor";
import useIdle from "~/hooks/useIdle";
export type DocumentContextValue = {
/** The current editor instance for this document. */
@@ -16,4 +17,21 @@ const DocumentContext = React.createContext<DocumentContextValue>({
export const useDocumentContext = () => React.useContext(DocumentContext);
const activityEvents = [
"click",
"mousemove",
"DOMMouseScroll",
"mousewheel",
"mousedown",
"touchstart",
"touchmove",
"focus",
];
export const useEditingFocus = () => {
const { editor } = useDocumentContext();
const isIdle = useIdle(3000, activityEvents);
return isIdle && !!editor?.view.hasFocus();
};
export default DocumentContext;
+84 -76
View File
@@ -1,5 +1,10 @@
import FuzzySearch from "fuzzy-search";
import { includes, difference, concat, filter, map, fill } from "lodash";
import concat from "lodash/concat";
import difference from "lodash/difference";
import fill from "lodash/fill";
import filter from "lodash/filter";
import includes from "lodash/includes";
import map from "lodash/map";
import { observer } from "mobx-react";
import { StarredIcon, DocumentIcon } from "outline-icons";
import * as React from "react";
@@ -10,7 +15,6 @@ import scrollIntoView from "smooth-scroll-into-view-if-needed";
import styled, { useTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { NavigationNode } from "@shared/types";
import parseTitle from "@shared/utils/parseTitle";
import DocumentExplorerNode from "~/components/DocumentExplorerNode";
import DocumentExplorerSearchResult from "~/components/DocumentExplorerSearchResult";
import Flex from "~/components/Flex";
@@ -200,84 +204,86 @@ function DocumentExplorer({ onSubmit, onSelect, items }: Props) {
}
};
const ListItem = ({
index,
data,
style,
}: {
index: number;
data: NavigationNode[];
style: React.CSSProperties;
}) => {
const node = data[index];
const isCollection = node.type === "collection";
let icon, title, path;
const ListItem = observer(
({
index,
data,
style,
}: {
index: number;
data: NavigationNode[];
style: React.CSSProperties;
}) => {
const node = data[index];
const isCollection = node.type === "collection";
let icon, title: string, emoji: string | undefined, path;
if (isCollection) {
const col = collections.get(node.collectionId as string);
icon = col && (
<CollectionIcon collection={col} expanded={isExpanded(index)} />
);
title = node.title;
} else {
const doc = documents.get(node.id);
const { strippedTitle, emoji } = parseTitle(node.title);
title = strippedTitle;
if (emoji) {
icon = <EmojiIcon emoji={emoji} />;
} else if (doc?.isStarred) {
icon = <StarredIcon color={theme.yellow} />;
if (isCollection) {
const col = collections.get(node.collectionId as string);
icon = col && (
<CollectionIcon collection={col} expanded={isExpanded(index)} />
);
title = node.title;
} else {
icon = <DocumentIcon color={theme.textSecondary} />;
const doc = documents.get(node.id);
emoji = doc?.emoji ?? node.emoji;
title = doc?.title ?? node.title;
if (emoji) {
icon = <EmojiIcon emoji={emoji} />;
} else if (doc?.isStarred) {
icon = <StarredIcon color={theme.yellow} />;
} else {
icon = <DocumentIcon color={theme.textSecondary} />;
}
path = ancestors(node)
.map((a) => a.title)
.join(" / ");
}
path = ancestors(node)
.map((a) => parseTitle(a.title).strippedTitle)
.join(" / ");
return searchTerm ? (
<DocumentExplorerSearchResult
selected={isSelected(index)}
active={activeNode === index}
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
icon={icon}
title={title}
path={path}
/>
) : (
<DocumentExplorerNode
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
onDisclosureClick={(ev) => {
ev.stopPropagation();
toggleCollapse(index);
}}
selected={isSelected(index)}
active={activeNode === index}
expanded={isExpanded(index)}
icon={icon}
title={title}
depth={node.depth as number}
hasChildren={hasChildren(index)}
ref={itemRefs[index]}
/>
);
}
return searchTerm ? (
<DocumentExplorerSearchResult
selected={isSelected(index)}
active={activeNode === index}
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
icon={icon}
title={title}
path={path}
/>
) : (
<DocumentExplorerNode
style={{
...style,
top: (style.top as number) + VERTICAL_PADDING,
left: (style.left as number) + HORIZONTAL_PADDING,
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
}}
onPointerMove={() => setActiveNode(index)}
onClick={() => toggleSelect(index)}
onDisclosureClick={(ev) => {
ev.stopPropagation();
toggleCollapse(index);
}}
selected={isSelected(index)}
active={activeNode === index}
expanded={isExpanded(index)}
icon={icon}
title={title}
depth={node.depth as number}
hasChildren={hasChildren(index)}
ref={itemRefs[index]}
/>
);
};
);
const focusSearchInput = () => {
inputSearchRef.current?.focus();
@@ -383,7 +389,9 @@ function DocumentExplorer({ onSubmit, onSelect, items }: Props) {
</AutoSizer>
) : (
<FlexContainer>
<Text type="secondary">{t("No results found")}.</Text>
<Text as="p" type="secondary">
{t("No results found")}.
</Text>
</FlexContainer>
)}
</ListContainer>
+14 -33
View File
@@ -1,5 +1,4 @@
import { observer } from "mobx-react";
import { PlusIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
@@ -9,7 +8,6 @@ import breakpoint from "styled-components-breakpoint";
import { s } from "@shared/styles";
import Document from "~/models/Document";
import Badge from "~/components/Badge";
import Button from "~/components/Button";
import DocumentMeta from "~/components/DocumentMeta";
import EventBoundary from "~/components/EventBoundary";
import Flex from "~/components/Flex";
@@ -18,12 +16,11 @@ import NudeButton from "~/components/NudeButton";
import StarButton, { AnimatedStar } from "~/components/Star";
import Tooltip from "~/components/Tooltip";
import useBoolean from "~/hooks/useBoolean";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useCurrentUser from "~/hooks/useCurrentUser";
import usePolicy from "~/hooks/usePolicy";
import DocumentMenu from "~/menus/DocumentMenu";
import { hover } from "~/styles";
import { newDocumentPath } from "~/utils/routeHelpers";
import { documentPath } from "~/utils/routeHelpers";
import EmojiIcon from "./Icons/EmojiIcon";
type Props = {
document: Document;
@@ -51,7 +48,6 @@ function DocumentListItem(
) {
const { t } = useTranslation();
const user = useCurrentUser();
const team = useCurrentTeam();
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
const {
@@ -71,8 +67,6 @@ function DocumentListItem(
!!document.title.toLowerCase().includes(highlight.toLowerCase());
const canStar =
!document.isDraft && !document.isArchived && !document.isTemplate;
const can = usePolicy(team);
const canCollection = usePolicy(document.collectionId);
return (
<CompositeItem
@@ -83,7 +77,7 @@ function DocumentListItem(
$isStarred={document.isStarred}
$menuOpen={menuOpen}
to={{
pathname: document.url,
pathname: documentPath(document),
state: {
title: document.titleWithDefault,
},
@@ -92,6 +86,12 @@ function DocumentListItem(
>
<Content>
<Heading dir={document.dir}>
{document.emoji && (
<>
<EmojiIcon emoji={document.emoji} size={24} />
&nbsp;
</>
)}
<Title
text={document.titleWithDefault}
highlight={highlight}
@@ -107,7 +107,7 @@ function DocumentListItem(
)}
{document.isDraft && showDraft && (
<Tooltip
tooltip={t("Only visible to you")}
content={t("Only visible to you")}
delay={500}
placement="top"
>
@@ -135,25 +135,6 @@ function DocumentListItem(
/>
</Content>
<Actions>
{document.isTemplate &&
!document.isArchived &&
!document.isDeleted &&
can.createDocument &&
canCollection.update && (
<>
<Button
as={Link}
to={newDocumentPath(document.collectionId, {
templateId: document.id,
})}
icon={<PlusIcon />}
neutral
>
{t("New doc")}
</Button>
&nbsp;
</>
)}
<DocumentMenu
document={document}
showPin={showPin}
@@ -262,8 +243,8 @@ const Heading = styled.h3<{ rtl?: boolean }>`
margin-bottom: 0.25em;
white-space: nowrap;
color: ${s("text")};
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-family: ${s("fontFamily")};
font-weight: 500;
`;
const StarPositioner = styled(Flex)`
@@ -279,8 +260,8 @@ const Title = styled(Highlight)`
const ResultContext = styled(Highlight)`
display: block;
color: ${s("textTertiary")};
font-size: 14px;
color: ${s("textSecondary")};
font-size: 15px;
margin-top: -0.25em;
margin-bottom: 0.25em;
`;
+3 -6
View File
@@ -3,9 +3,9 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { documentPath } from "~/utils/routeHelpers";
type Props = {
@@ -14,7 +14,6 @@ type Props = {
function DocumentTemplatizeDialog({ documentId }: Props) {
const history = useHistory();
const { showToast } = useToasts();
const { t } = useTranslation();
const { documents } = useStores();
const document = documents.get(documentId);
@@ -24,11 +23,9 @@ function DocumentTemplatizeDialog({ documentId }: Props) {
const template = await document?.templatize();
if (template) {
history.push(documentPath(template));
showToast(t("Template created, go ahead and customize it"), {
type: "info",
});
toast.success(t("Template created, go ahead and customize it"));
}
}, [document, showToast, history, t]);
}, [document, history, t]);
return (
<ConfirmationDialog
+16 -7
View File
@@ -1,13 +1,15 @@
import { sortBy } from "lodash";
import compact from "lodash/compact";
import sortBy from "lodash/sortBy";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { dateToRelative } from "@shared/utils/date";
import { dateLocale, dateToRelative } from "@shared/utils/date";
import Document from "~/models/Document";
import User from "~/models/User";
import Avatar from "~/components/Avatar";
import ListItem from "~/components/List/Item";
import PaginatedList from "~/components/PaginatedList";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
type Props = {
@@ -18,6 +20,9 @@ type Props = {
function DocumentViews({ document, isOpen }: Props) {
const { t } = useTranslation();
const { views, presence } = useStores();
const user = useCurrentUser();
const locale = dateLocale(user.language);
const documentPresence = presence.get(document.id);
const documentPresenceArray = documentPresence
? Array.from(documentPresence.values())
@@ -31,10 +36,10 @@ function DocumentViews({ document, isOpen }: Props) {
const documentViews = views.inDocument(document.id);
const sortedViews = sortBy(
documentViews,
(view) => !presentIds.includes(view.user.id)
(view) => !presentIds.includes(view.userId)
);
const users = React.useMemo(
() => sortedViews.map((v) => v.user),
() => compact(sortedViews.map((v) => v.user)),
[sortedViews]
);
@@ -45,16 +50,20 @@ function DocumentViews({ document, isOpen }: Props) {
aria-label={t("Viewers")}
items={users}
renderItem={(model: User) => {
const view = documentViews.find((v) => v.user.id === model.id);
const view = documentViews.find((v) => v.userId === model.id);
const isPresent = presentIds.includes(model.id);
const isEditing = editingIds.includes(model.id);
const subtitle = isPresent
? isEditing
? t("Currently editing")
: t("Currently viewing")
: t("Viewed {{ timeAgo }} ago", {
: t("Viewed {{ timeAgo }}", {
timeAgo: dateToRelative(
view ? Date.parse(view.lastViewedAt) : new Date()
view ? Date.parse(view.lastViewedAt) : new Date(),
{
addSuffix: true,
locale,
}
),
});
return (
+73
View File
@@ -0,0 +1,73 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { DocumentValidation } from "@shared/validations";
import Document from "~/models/Document";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Input from "./Input";
import Switch from "./Switch";
import Text from "./Text";
type Props = {
/** The original document to duplicate */
document: Document;
onSubmit: (documents: Document[]) => void;
};
function DuplicateDialog({ document, onSubmit }: Props) {
const { t } = useTranslation();
const defaultTitle = t(`Copy of {{ documentName }}`, {
documentName: document.title,
});
const [recursive, setRecursive] = React.useState<boolean>(true);
const [title, setTitle] = React.useState<string>(defaultTitle);
const handleRecursiveChange = React.useCallback(
(ev: React.ChangeEvent<HTMLInputElement>) => {
setRecursive(ev.target.checked);
},
[]
);
const handleTitleChange = React.useCallback(
(ev: React.ChangeEvent<HTMLInputElement>) => {
setTitle(ev.target.value);
},
[]
);
const handleSubmit = async () => {
const result = await document.duplicate({
recursive,
title,
});
onSubmit(result);
};
return (
<ConfirmationDialog onSubmit={handleSubmit} submitText={t("Duplicate")}>
<Input
autoFocus
autoSelect
name="title"
label={t("Title")}
onChange={handleTitleChange}
maxLength={DocumentValidation.maxTitleLength}
defaultValue={defaultTitle}
/>
{document.publishedAt && !document.isTemplate && (
<Text size="small">
<Switch
name="recursive"
label={t("Include nested documents")}
labelPosition="right"
checked={recursive}
onChange={handleRecursiveChange}
/>
</Text>
)}
</ConfirmationDialog>
);
}
export default observer(DuplicateDialog);
+12 -79
View File
@@ -1,10 +1,11 @@
import { deburr, difference, sortBy } from "lodash";
import deburr from "lodash/deburr";
import difference from "lodash/difference";
import sortBy from "lodash/sortBy";
import { observer } from "mobx-react";
import { DOMParser as ProsemirrorDOMParser } from "prosemirror-model";
import { TextSelection } from "prosemirror-state";
import * as React from "react";
import { mergeRefs } from "react-merge-refs";
import { useHistory } from "react-router-dom";
import { Optional } from "utility-types";
import insertFiles from "@shared/editor/commands/insertFiles";
import { AttachmentPreset } from "@shared/types";
@@ -14,22 +15,18 @@ import { getDataTransferFiles } from "@shared/utils/files";
import parseDocumentSlug from "@shared/utils/parseDocumentSlug";
import { isInternalUrl } from "@shared/utils/urls";
import { AttachmentValidation } from "@shared/validations";
import Document from "~/models/Document";
import ClickablePadding from "~/components/ClickablePadding";
import ErrorBoundary from "~/components/ErrorBoundary";
import HoverPreview from "~/components/HoverPreview";
import type { Props as EditorProps, Editor as SharedEditor } from "~/editor";
import useCurrentUser from "~/hooks/useCurrentUser";
import useDictionary from "~/hooks/useDictionary";
import useEditorClickHandlers from "~/hooks/useEditorClickHandlers";
import useEmbeds from "~/hooks/useEmbeds";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import useUserLocale from "~/hooks/useUserLocale";
import { NotFoundError } from "~/utils/errors";
import { uploadFile } from "~/utils/files";
import { isModKey } from "~/utils/keyboard";
import lazyWithRetry from "~/utils/lazyWithRetry";
import { sharedDocumentPath } from "~/utils/routeHelpers";
import { isHash } from "~/utils/urls";
import DocumentBreadcrumb from "./DocumentBreadcrumb";
const LazyLoadedEditor = lazyWithRetry(() => import("~/editor"));
@@ -41,14 +38,13 @@ export type Props = Optional<
| "onClickLink"
| "embeds"
| "dictionary"
| "onShowToast"
| "extensions"
> & {
shareId?: string | undefined;
embedsDisabled?: boolean;
onHeadingsChange?: (headings: Heading[]) => void;
onSynced?: () => Promise<void>;
onPublish?: (event: React.MouseEvent) => any;
onPublish?: (event: React.MouseEvent) => void;
editorStyle?: React.CSSProperties;
};
@@ -63,27 +59,14 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
} = props;
const userLocale = useUserLocale();
const locale = dateLocale(userLocale);
const { auth, comments, documents } = useStores();
const { showToast } = useToasts();
const { comments, documents } = useStores();
const dictionary = useDictionary();
const embeds = useEmbeds(!shareId);
const history = useHistory();
const localRef = React.useRef<SharedEditor>();
const preferences = auth.user?.preferences;
const preferences = useCurrentUser({ rejectOnEmpty: false })?.preferences;
const previousHeadings = React.useRef<Heading[] | null>(null);
const [activeLinkElement, setActiveLink] =
React.useState<HTMLAnchorElement | null>(null);
const previousCommentIds = React.useRef<string[]>();
const handleLinkActive = React.useCallback((element: HTMLAnchorElement) => {
setActiveLink(element);
return false;
}, []);
const handleLinkInactive = React.useCallback(() => {
setActiveLink(null);
}, []);
const handleSearchLink = React.useCallback(
async (term: string) => {
if (isInternalUrl(term)) {
@@ -120,7 +103,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
const results = await documents.searchTitles(term);
return sortBy(
results.map((document: Document) => ({
results.map(({ document }) => ({
title: document.title,
subtitle: <DocumentBreadcrumb document={document} onlyText />,
url: document.url,
@@ -133,7 +116,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
: 1
);
},
[documents]
[locale, documents]
);
const handleUploadFile = React.useCallback(
@@ -147,47 +130,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
[id]
);
const handleClickLink = React.useCallback(
(href: string, event: MouseEvent) => {
// on page hash
if (isHash(href)) {
window.location.href = href;
return;
}
if (isInternalUrl(href) && !isModKey(event) && !event.shiftKey) {
// relative
let navigateTo = href;
// probably absolute
if (href[0] !== "/") {
try {
const url = new URL(href);
navigateTo = url.pathname + url.hash;
} catch (err) {
navigateTo = href;
}
}
// Link to our own API should be opened in a new tab, not in the app
if (navigateTo.startsWith("/api/")) {
window.open(href, "_blank");
return;
}
// If we're navigating to an internal document link then prepend the
// share route to the URL so that the document is loaded in context
if (shareId && navigateTo.includes("/doc/")) {
navigateTo = sharedDocumentPath(shareId, navigateTo);
}
history.push(navigateTo);
} else if (href) {
window.open(href, "_blank");
}
},
[history, shareId]
);
const { handleClickLink } = useEditorClickHandlers({ shareId });
const focusAtEnd = React.useCallback(() => {
localRef?.current?.focusAtEnd();
@@ -233,11 +176,10 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
(file) => !AttachmentValidation.imageContentTypes.includes(file.type)
);
insertFiles(view, event, pos, files, {
return insertFiles(view, event, pos, files, {
uploadFile: handleUploadFile,
onFileUploadStart: props.onFileUploadStart,
onFileUploadStop: props.onFileUploadStop,
onShowToast: showToast,
dictionary,
isAttachment,
});
@@ -248,7 +190,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
props.onFileUploadStop,
dictionary,
handleUploadFile,
showToast,
]
);
@@ -332,12 +273,10 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
<LazyLoadedEditor
ref={mergeRefs([ref, localRef, handleRefChanged])}
uploadFile={handleUploadFile}
onShowToast={showToast}
embeds={embeds}
userPreferences={preferences}
dictionary={dictionary}
{...props}
onHoverLink={handleLinkActive}
onClickLink={handleClickLink}
onSearchLink={handleSearchLink}
onChange={handleChange}
@@ -352,12 +291,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
minHeight={props.editorStyle.paddingBottom}
/>
)}
{activeLinkElement && !shareId && (
<HoverPreview
element={activeLinkElement}
onClose={handleLinkInactive}
/>
)}
</>
</ErrorBoundary>
);
+23
View File
@@ -0,0 +1,23 @@
import styled from "styled-components";
import Button from "~/components/Button";
import { hover } from "~/styles";
import Flex from "../Flex";
export const EmojiButton = styled(Button)`
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
&: ${hover},
&:active,
&[aria-expanded= "true"] {
opacity: 1 !important;
}
`;
export const Emoji = styled(Flex)<{ size?: number }>`
line-height: 1.6;
${(props) => (props.size ? `font-size: ${props.size}px` : "")}
`;
+269
View File
@@ -0,0 +1,269 @@
import data from "@emoji-mart/data";
import Picker from "@emoji-mart/react";
import { SmileyIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { usePopoverState, PopoverDisclosure } from "reakit/Popover";
import styled, { useTheme } from "styled-components";
import { depths, s } from "@shared/styles";
import { toRGB } from "@shared/utils/color";
import Button from "~/components/Button";
import Popover from "~/components/Popover";
import useStores from "~/hooks/useStores";
import useUserLocale from "~/hooks/useUserLocale";
import { Emoji, EmojiButton } from "./components";
/* Locales supported by emoji-mart */
const supportedLocales = [
"en",
"ar",
"be",
"cs",
"de",
"es",
"fa",
"fi",
"fr",
"hi",
"it",
"ja",
"kr",
"nl",
"pl",
"pt",
"ru",
"sa",
"tr",
"uk",
"vi",
"zh",
];
/**
* React hook to derive emoji picker's theme from UI theme
*
* @returns {string} Theme to use for emoji picker
*/
function usePickerTheme(): string {
const { ui } = useStores();
const { theme } = ui;
if (theme === "system") {
return "auto";
}
return theme;
}
type Props = {
/** The selected emoji, if any */
value?: string | null;
/** Callback when an emoji is selected */
onChange: (emoji: string | null) => void | Promise<void>;
/** Callback when the picker is opened */
onOpen?: () => void;
/** Callback when the picker is closed */
onClose?: () => void;
/** Callback when the picker is clicked outside of */
onClickOutside: () => void;
/** Whether to auto focus the search input on open */
autoFocus?: boolean;
/** Class name to apply to the trigger button */
className?: string;
};
function EmojiPicker({
value,
onOpen,
onClose,
onChange,
onClickOutside,
autoFocus,
className,
}: Props) {
const { t } = useTranslation();
const pickerTheme = usePickerTheme();
const theme = useTheme();
const locale = useUserLocale(true) ?? "en";
const popover = usePopoverState({
placement: "bottom-start",
modal: true,
unstable_offset: [0, 0],
});
const [emojisPerLine, setEmojisPerLine] = React.useState(9);
const pickerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (popover.visible) {
onOpen?.();
} else {
onClose?.();
}
}, [popover.visible, onOpen, onClose]);
React.useEffect(() => {
if (popover.visible && pickerRef.current) {
// 28 is picker's observed width when perLine is set to 0
// and 36 is the default emojiButtonSize
// Ref: https://github.com/missive/emoji-mart#options--props
setEmojisPerLine(Math.floor((pickerRef.current.clientWidth - 28) / 36));
}
}, [popover.visible]);
const handleEmojiChange = React.useCallback(
async (emoji) => {
popover.hide();
await onChange(emoji ? emoji.native : null);
},
[popover, onChange]
);
const handleClick = React.useCallback(
(ev: React.MouseEvent) => {
ev.stopPropagation();
if (popover.visible) {
popover.hide();
} else {
popover.show();
}
},
[popover]
);
const handleClickOutside = React.useCallback(() => {
// It was observed that onClickOutside got triggered
// even when the picker wasn't open or opened at all.
// Hence, this guard here...
if (popover.visible) {
onClickOutside();
}
}, [popover.visible, onClickOutside]);
// Auto focus search input when picker is opened
React.useLayoutEffect(() => {
if (autoFocus && popover.visible) {
requestAnimationFrame(() => {
const searchInput = pickerRef.current
?.querySelector("em-emoji-picker")
?.shadowRoot?.querySelector(
"input[type=search]"
) as HTMLInputElement | null;
searchInput?.focus();
});
}
}, [autoFocus, popover.visible]);
return (
<>
<PopoverDisclosure {...popover}>
{(props) => (
<EmojiButton
{...props}
className={className}
onClick={handleClick}
icon={
value ? (
<Emoji size={32} align="center" justify="center">
{value}
</Emoji>
) : (
<StyledSmileyIcon size={32} color={theme.textTertiary} />
)
}
neutral
borderOnHover
/>
)}
</PopoverDisclosure>
<PickerPopover
{...popover}
tabIndex={0}
// This prevents picker from closing when any of its
// children are focused, e.g, clicking on search bar or
// a click on skin tone button
onClick={(e) => e.stopPropagation()}
width={352}
aria-label={t("Emoji Picker")}
>
{popover.visible && (
<>
{value && (
<RemoveButton neutral onClick={() => handleEmojiChange(null)}>
{t("Remove")}
</RemoveButton>
)}
<PickerStyles ref={pickerRef}>
<Picker
// https://github.com/missive/emoji-mart/issues/800
locale={
locale === "ko"
? "kr"
: supportedLocales.includes(locale)
? locale
: "en"
}
data={data}
onEmojiSelect={handleEmojiChange}
theme={pickerTheme}
previewPosition="none"
perLine={emojisPerLine}
onClickOutside={handleClickOutside}
/>
</PickerStyles>
</>
)}
</PickerPopover>
</>
);
}
const StyledSmileyIcon = styled(SmileyIcon)`
flex-shrink: 0;
@media print {
display: none;
}
`;
const RemoveButton = styled(Button)`
margin-left: -12px;
margin-bottom: 8px;
border-radius: 6px;
height: 24px;
font-size: 13px;
> :first-child {
min-height: unset;
line-height: unset;
}
`;
const PickerPopover = styled(Popover)`
z-index: ${depths.popover};
> :first-child {
padding-top: 8px;
padding-bottom: 0;
max-height: 488px;
overflow: unset;
}
`;
const PickerStyles = styled.div`
margin-left: -24px;
margin-right: -24px;
em-emoji-picker {
--shadow: none;
--font-family: ${s("fontFamily")};
--rgb-background: ${(props) => toRGB(props.theme.menuBackground)};
--rgb-accent: ${(props) => toRGB(props.theme.accent)};
--border-radius: 6px;
margin-left: auto;
margin-right: auto;
min-height: 443px;
}
`;
export default EmojiPicker;
+4 -4
View File
@@ -82,7 +82,7 @@ class ErrorBoundary extends React.Component<Props> {
</h1>
</>
)}
<Text type="secondary">
<Text as="p" type="secondary">
<Trans>
Sorry, part of the application failed to load. This may be
because it was updated since you opened the tab or because of a
@@ -106,7 +106,7 @@ class ErrorBoundary extends React.Component<Props> {
</h1>
</>
)}
<Text type="secondary">
<Text as="p" type="secondary">
<Trans
defaults="Sorry, an unrecoverable error occurred{{notified}}. Please try reloading the page, it may have been a temporary glitch."
values={{
@@ -121,11 +121,11 @@ class ErrorBoundary extends React.Component<Props> {
<Button onClick={this.handleReload}>{t("Reload")}</Button>{" "}
{this.showDetails ? (
<Button onClick={this.handleReportBug} neutral>
<Trans>Report a Bug</Trans>
<Trans>Report a bug</Trans>
</Button>
) : (
<Button onClick={this.handleShowDetails} neutral>
<Trans>Show Detail</Trans>
<Trans>Show detail</Trans>
</Button>
)}
</p>
+18 -6
View File
@@ -1,6 +1,7 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { FileOperationFormat, NotificationEventType } from "@shared/types";
import Collection from "~/models/Collection";
@@ -10,7 +11,8 @@ import Text from "~/components/Text";
import env from "~/env";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import history from "~/utils/history";
import { settingsPath } from "~/utils/routeHelpers";
type Props = {
collection?: Collection;
@@ -24,7 +26,6 @@ function ExportDialog({ collection, onSubmit }: Props) {
const [includeAttachments, setIncludeAttachments] =
React.useState<boolean>(true);
const user = useCurrentUser();
const { showToast } = useToasts();
const { collections } = useStores();
const { t } = useTranslation();
const appName = env.APP_NAME;
@@ -46,11 +47,22 @@ function ExportDialog({ collection, onSubmit }: Props) {
const handleSubmit = async () => {
if (collection) {
await collection.export(format, includeAttachments);
toast.success(t("Export started"), {
description: t(`Your file will be available in {{ location }} soon`, {
location: `"${t("Settings")} > ${t("Export")}"`,
}),
action: {
label: t("View"),
onClick: () => {
history.push(settingsPath("export"));
},
},
});
} else {
await collections.export(format, includeAttachments);
toast.success(t("Export started"));
}
onSubmit();
showToast(t("Export started"), { type: "success" });
};
const items = [
@@ -83,7 +95,7 @@ function ExportDialog({ collection, onSubmit }: Props) {
return (
<ConfirmationDialog onSubmit={handleSubmit} submitText={t("Export")}>
{collection && (
<Text>
<Text as="p">
<Trans
defaults="Exporting the collection <em>{{collectionName}}</em> may take some time."
values={{
@@ -108,7 +120,7 @@ function ExportDialog({ collection, onSubmit }: Props) {
onChange={handleFormatChange}
/>
<div>
<Text size="small" weight="bold">
<Text as="p" size="small" weight="bold">
{item.title}
</Text>
<Text size="small">{item.description}</Text>
@@ -125,7 +137,7 @@ function ExportDialog({ collection, onSubmit }: Props) {
onChange={handleIncludeAttachmentsChange}
/>
<div>
<Text size="small" weight="bold">
<Text as="p" size="small" weight="bold">
{t("Include attachments")}
</Text>
<Text size="small">
+6 -3
View File
@@ -32,9 +32,12 @@ function Facepile({
</span>
</More>
)}
{users.slice(0, limit).map((user) => (
<AvatarWrapper key={user.id}>{renderAvatar(user)}</AvatarWrapper>
))}
{users
.filter(Boolean)
.slice(0, limit)
.map((user) => (
<AvatarWrapper key={user.id}>{renderAvatar(user)}</AvatarWrapper>
))}
</Avatars>
);
}
+1 -1
View File
@@ -80,7 +80,7 @@ const Note = styled(Text)`
margin-bottom: 0;
line-height: 1.2em;
font-size: 14px;
font-weight: 400;
font-weight: 500;
color: ${s("textTertiary")};
`;
+5 -3
View File
@@ -1,4 +1,4 @@
import { throttle } from "lodash";
import throttle from "lodash/throttle";
import { observer } from "mobx-react";
import { MenuIcon } from "outline-icons";
import { transparentize } from "polished";
@@ -6,6 +6,7 @@ import * as React from "react";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { depths, s } from "@shared/styles";
import { supportsPassiveListener } from "@shared/utils/browser";
import Button from "~/components/Button";
import Fade from "~/components/Fade";
import Flex from "~/components/Flex";
@@ -14,16 +15,16 @@ import useMobile from "~/hooks/useMobile";
import useStores from "~/hooks/useStores";
import { draggableOnDesktop, fadeOnDesktopBackgrounded } from "~/styles";
import Desktop from "~/utils/Desktop";
import { supportsPassiveListener } from "~/utils/browser";
type Props = {
left?: React.ReactNode;
title: React.ReactNode;
actions?: React.ReactNode;
hasSidebar?: boolean;
className?: string;
};
function Header({ left, title, actions, hasSidebar }: Props) {
function Header({ left, title, actions, hasSidebar, className }: Props) {
const { ui } = useStores();
const isMobile = useMobile();
const hasMobileSidebar = hasSidebar && isMobile;
@@ -54,6 +55,7 @@ function Header({ left, title, actions, hasSidebar }: Props) {
<Wrapper
align="center"
shrink={false}
className={className}
$passThrough={passThrough}
$insetTitleAdjust={ui.sidebarIsClosed && Desktop.hasInsetTitlebar()}
>
+2 -1
View File
@@ -1,9 +1,10 @@
import styled from "styled-components";
const Heading = styled.h1<{ centered?: boolean }>`
const Heading = styled.h1<{ as?: string; centered?: boolean }>`
display: flex;
align-items: center;
user-select: none;
${(props) => (props.as ? "" : "margin-top: 6vh; font-weight: 600;")}
${(props) => (props.centered ? "text-align: center;" : "")}
`;
+4 -5
View File
@@ -1,8 +1,7 @@
import { escapeRegExp } from "lodash";
import escapeRegExp from "lodash/escapeRegExp";
import * as React from "react";
import replace from "string-replace-to-array";
import styled from "styled-components";
import { s } from "@shared/styles";
type Props = React.HTMLAttributes<HTMLSpanElement> & {
highlight: (string | null | undefined) | RegExp;
@@ -44,9 +43,9 @@ function Highlight({
}
export const Mark = styled.mark`
background: ${s("searchHighlight")};
border-radius: 2px;
padding: 0 2px;
color: inherit;
background: transparent;
font-weight: 600;
`;
export default Highlight;
+2 -2
View File
@@ -4,7 +4,7 @@ import styled, { css } from "styled-components";
import { s } from "@shared/styles";
import Text from "~/components/Text";
export const CARD_MARGIN = 16;
export const CARD_MARGIN = 10;
const NUMBER_OF_LINES = 10;
@@ -17,7 +17,7 @@ const StyledText = styled(Text)`
`;
export const Preview = styled(Link)`
cursor: ${(props: any) =>
cursor: ${(props: { as?: string }) =>
props.as === "div" ? "default" : "var(--pointer)"};
border-radius: 4px;
box-shadow: 0 30px 90px -20px rgba(0, 0, 0, 0.3),
+195 -149
View File
@@ -2,13 +2,14 @@ import { m } from "framer-motion";
import * as React from "react";
import { Portal } from "react-portal";
import styled from "styled-components";
import { depths, s } from "@shared/styles";
import { depths } from "@shared/styles";
import { UnfurlType } from "@shared/types";
import LoadingIndicator from "~/components/LoadingIndicator";
import useEventListener from "~/hooks/useEventListener";
import useKeyDown from "~/hooks/useKeyDown";
import useMobile from "~/hooks/useMobile";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import usePrevious from "~/hooks/usePrevious";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import { client } from "~/utils/ApiClient";
@@ -17,131 +18,77 @@ import HoverPreviewDocument from "./HoverPreviewDocument";
import HoverPreviewLink from "./HoverPreviewLink";
import HoverPreviewMention from "./HoverPreviewMention";
const DELAY_OPEN = 300;
const DELAY_CLOSE = 600;
const POINTER_HEIGHT = 22;
const POINTER_WIDTH = 22;
type Props = {
/* The HTML element that is being hovered over */
element: HTMLAnchorElement;
/* A callback on close of the hover preview */
/** The HTML element that is being hovered over, or null if none. */
element: HTMLElement | null;
/** A callback on close of the hover preview. */
onClose: () => void;
};
function HoverPreviewInternal({ element, onClose }: Props) {
const url = element.href || element.dataset.url;
enum Direction {
UP,
DOWN,
}
function HoverPreviewDesktop({ element, onClose }: Props) {
const url = element?.getAttribute("href") || element?.dataset.url;
const previousUrl = usePrevious(url, true);
const [isVisible, setVisible] = React.useState(false);
const timerClose = React.useRef<ReturnType<typeof setTimeout>>();
const timerOpen = React.useRef<ReturnType<typeof setTimeout>>();
const cardRef = React.useRef<HTMLDivElement>(null);
const stores = useStores();
const [cardLeft, setCardLeft] = React.useState(0);
const [cardTop, setCardTop] = React.useState(0);
const [pointerOffset, setPointerOffset] = React.useState(0);
React.useLayoutEffect(() => {
if (isVisible && cardRef.current) {
const elem = element.getBoundingClientRect();
const card = cardRef.current.getBoundingClientRect();
const top = elem.bottom + window.scrollY;
setCardTop(top);
let left = elem.left;
let pointerOffset = elem.width / 2;
if (left + card.width > window.innerWidth) {
// shift card leftwards by the amount it went out of screen
let shiftBy = left + card.width - window.innerWidth;
// shift a littler further to leave some margin between card and window boundary
shiftBy += CARD_MARGIN;
left -= shiftBy;
// shift pointer rightwards by same amount so as to position it back correctly
pointerOffset += shiftBy;
}
setCardLeft(left);
setPointerOffset(pointerOffset);
}
}, [isVisible, element]);
const { data, request, loading } = useRequest(
React.useCallback(
() =>
client.post("/urls.unfurl", {
url,
documentId: stores.ui.activeDocumentId,
}),
[url, stores.ui.activeDocumentId]
)
);
React.useEffect(() => {
if (url) {
stopOpenTimer();
setVisible(false);
void request();
}
}, [url, request]);
const stopOpenTimer = () => {
if (timerOpen.current) {
clearTimeout(timerOpen.current);
timerOpen.current = undefined;
}
};
const { cardLeft, cardTop, pointerLeft, pointerTop, pointerDir } =
useHoverPosition({
cardRef,
element,
isVisible,
});
const closePreview = React.useCallback(() => {
if (isVisible) {
stopOpenTimer();
setVisible(false);
onClose();
}
}, [isVisible, onClose]);
setVisible(false);
onClose();
}, [onClose]);
useOnClickOutside(cardRef, closePreview);
useKeyDown("Escape", closePreview);
useEventListener("scroll", closePreview, window, { capture: true });
const stopCloseTimer = () => {
const stopCloseTimer = React.useCallback(() => {
if (timerClose.current) {
clearTimeout(timerClose.current);
timerClose.current = undefined;
}
};
const startOpenTimer = () => {
if (!timerOpen.current) {
timerOpen.current = setTimeout(() => setVisible(true), DELAY_OPEN);
}
};
}, []);
const startCloseTimer = React.useCallback(() => {
stopOpenTimer();
timerClose.current = setTimeout(closePreview, DELAY_CLOSE);
}, [closePreview]);
// Open and close the preview when the element changes.
React.useEffect(() => {
if (element) {
setVisible(true);
} else {
startCloseTimer();
}
}, [startCloseTimer, element]);
// Close the preview on Escape, scroll, or click outside.
useOnClickOutside(cardRef, closePreview);
useKeyDown("Escape", closePreview);
useEventListener("scroll", closePreview, window, { capture: true });
// Ensure that the preview stays open while the user is hovering over the card.
React.useEffect(() => {
const card = cardRef.current;
if (data) {
startOpenTimer();
if (isVisible) {
if (card) {
card.addEventListener("mouseenter", stopCloseTimer);
card.addEventListener("mouseleave", startCloseTimer);
}
element.addEventListener("mouseout", startCloseTimer);
element.addEventListener("mouseover", stopCloseTimer);
element.addEventListener("mouseover", startOpenTimer);
}
return () => {
element.removeEventListener("mouseout", startCloseTimer);
element.removeEventListener("mouseover", stopCloseTimer);
element.removeEventListener("mouseover", startOpenTimer);
if (card) {
card.removeEventListener("mouseenter", stopCloseTimer);
card.removeEventListener("mouseleave", startCloseTimer);
@@ -149,7 +96,83 @@ function HoverPreviewInternal({ element, onClose }: Props) {
stopCloseTimer();
};
}, [element, startCloseTimer, data]);
}, [element, startCloseTimer, isVisible, stopCloseTimer]);
const displayUrl = url ?? previousUrl;
if (!isVisible || !displayUrl) {
return null;
}
return (
<Portal>
<Position top={cardTop} left={cardLeft} ref={cardRef} aria-hidden>
<DataLoader url={displayUrl}>
{(data) => (
<Animate
initial={{ opacity: 0, y: -20, pointerEvents: "none" }}
animate={{ opacity: 1, y: 0, pointerEvents: "auto" }}
>
{data.type === UnfurlType.Mention ? (
<HoverPreviewMention
url={data.thumbnailUrl}
title={data.title}
info={data.meta.info}
color={data.meta.color}
/>
) : data.type === UnfurlType.Document ? (
<HoverPreviewDocument
id={data.meta.id}
url={data.url}
title={data.title}
description={data.description}
info={data.meta.info}
/>
) : (
<HoverPreviewLink
url={data.url}
thumbnailUrl={data.thumbnailUrl}
title={data.title}
description={data.description}
/>
)}
<Pointer
top={pointerTop}
left={pointerLeft}
direction={pointerDir}
/>
</Animate>
)}
</DataLoader>
</Position>
</Portal>
);
}
function DataLoader({
url,
children,
}: {
url: string;
children: (data: any) => React.ReactNode;
}) {
const { ui } = useStores();
const { data, request, loading } = useRequest(
React.useCallback(
() =>
client.post("/urls.unfurl", {
url,
documentId: ui.activeDocumentId,
}),
[url, ui.activeDocumentId]
)
);
React.useEffect(() => {
if (url) {
void request();
}
}, [url, request]);
if (loading) {
return <LoadingIndicator />;
@@ -159,46 +182,7 @@ function HoverPreviewInternal({ element, onClose }: Props) {
return null;
}
return (
<Portal>
<Position top={cardTop} left={cardLeft} aria-hidden>
{isVisible ? (
<Animate
initial={{ opacity: 0, y: -20, pointerEvents: "none" }}
animate={{ opacity: 1, y: 0, pointerEvents: "auto" }}
>
{data.type === UnfurlType.Mention ? (
<HoverPreviewMention
ref={cardRef}
url={data.thumbnailUrl}
title={data.title}
info={data.meta.info}
color={data.meta.color}
/>
) : data.type === UnfurlType.Document ? (
<HoverPreviewDocument
ref={cardRef}
id={data.meta.id}
url={data.url}
title={data.title}
description={data.description}
info={data.meta.info}
/>
) : (
<HoverPreviewLink
ref={cardRef}
url={data.url}
thumbnailUrl={data.thumbnailUrl}
title={data.title}
description={data.description}
/>
)}
<Pointer offset={pointerOffset} />
</Animate>
) : null}
</Position>
</Portal>
);
return <>{children(data)}</>;
}
function HoverPreview({ element, ...rest }: Props) {
@@ -207,7 +191,64 @@ function HoverPreview({ element, ...rest }: Props) {
return null;
}
return <HoverPreviewInternal {...rest} element={element} />;
return <HoverPreviewDesktop {...rest} element={element} />;
}
function useHoverPosition({
cardRef,
element,
isVisible,
}: {
cardRef: React.RefObject<HTMLDivElement>;
element: HTMLElement | null;
isVisible: boolean;
}) {
const [cardLeft, setCardLeft] = React.useState(0);
const [cardTop, setCardTop] = React.useState(0);
const [pointerLeft, setPointerLeft] = React.useState(0);
const [pointerTop, setPointerTop] = React.useState(0);
const [pointerDir, setPointerDir] = React.useState(Direction.UP);
React.useLayoutEffect(() => {
if (isVisible && element && cardRef.current) {
const elem = element.getBoundingClientRect();
const card = cardRef.current.getBoundingClientRect();
let cTop = elem.bottom + window.scrollY + CARD_MARGIN;
let pTop = -POINTER_HEIGHT;
let pDir = Direction.UP;
if (cTop + card.height > window.innerHeight + window.scrollY) {
// shift card upwards if it goes out of screen
const bottom = elem.top + window.scrollY;
cTop = bottom - card.height;
// shift a little further to leave some margin between card and element boundary
cTop -= CARD_MARGIN;
// pointer should be shifted downwards to align with card's bottom
pTop = card.height;
pDir = Direction.DOWN;
}
setCardTop(cTop);
setPointerTop(pTop);
setPointerDir(pDir);
let cLeft = elem.left;
let pLeft = elem.width / 2;
if (cLeft + card.width > window.innerWidth) {
// shift card leftwards by the amount it went out of screen
let shiftBy = cLeft + card.width - window.innerWidth;
// shift a little further to leave some margin between card and window boundary
shiftBy += CARD_MARGIN;
cLeft -= shiftBy;
// shift pointer rightwards by same amount so as to position it back correctly
pLeft += shiftBy;
}
setCardLeft(cLeft);
setPointerLeft(pLeft);
}
}, [isVisible, cardRef, element]);
return { cardLeft, cardTop, pointerLeft, pointerTop, pointerDir };
}
const Animate = styled(m.div)`
@@ -217,7 +258,6 @@ const Animate = styled(m.div)`
`;
const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
margin-top: 10px;
position: ${({ fixed }) => (fixed ? "fixed" : "absolute")};
z-index: ${depths.hoverPreview};
display: flex;
@@ -227,11 +267,11 @@ const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
${({ left }) => (left !== undefined ? `left: ${left}px` : "")};
`;
const Pointer = styled.div<{ offset: number }>`
top: -22px;
left: ${(props) => props.offset}px;
width: 22px;
height: 22px;
const Pointer = styled.div<{ top: number; left: number; direction: Direction }>`
top: ${(props) => props.top}px;
left: ${(props) => props.left}px;
width: ${POINTER_WIDTH}px;
height: ${POINTER_HEIGHT}px;
position: absolute;
transform: translateX(-50%);
pointer-events: none;
@@ -241,20 +281,26 @@ const Pointer = styled.div<{ offset: number }>`
content: "";
display: inline-block;
position: absolute;
bottom: 0;
right: 0;
${({ direction }) => (direction === Direction.UP ? "bottom: 0" : "top: 0")};
${({ direction }) => (direction === Direction.UP ? "right: 0" : "left: 0")};
}
&:before {
border: 8px solid transparent;
border-bottom-color: ${(props) =>
props.theme.menuBorder || "rgba(0, 0, 0, 0.1)"};
right: -1px;
${({ direction, theme }) =>
direction === Direction.UP
? `border-bottom-color: ${theme.menuBorder || "rgba(0, 0, 0, 0.1)"}`
: `border-top-color: ${theme.menuBorder || "rgba(0, 0, 0, 0.1)"}`};
${({ direction }) =>
direction === Direction.UP ? "right: -1px" : "left: -1px"};
}
&:after {
border: 7px solid transparent;
border-bottom-color: ${s("menuBackground")};
${({ direction, theme }) =>
direction === Direction.UP
? `border-bottom-color: ${theme.menuBackground}`
: `border-top-color: ${theme.menuBackground}`};
}
`;
@@ -26,9 +26,9 @@ const HoverPreviewLink = React.forwardRef(function _HoverPreviewLink(
) {
return (
<Preview as="a" href={url} target="_blank" rel="noopener noreferrer">
<Flex column>
<Flex column ref={ref}>
{thumbnailUrl ? <Thumbnail src={thumbnailUrl} alt={""} /> : null}
<Card ref={ref}>
<Card>
<CardContent>
<Flex column>
<Title>{title}</Title>
+95 -252
View File
@@ -1,263 +1,140 @@
import {
BookmarkedIcon,
BicycleIcon,
CollectionIcon,
CoinsIcon,
AcademicCapIcon,
BeakerIcon,
BuildingBlocksIcon,
CameraIcon,
CloudIcon,
CodeIcon,
EditIcon,
EmailIcon,
EyeIcon,
GlobeIcon,
InfoIcon,
ImageIcon,
LeafIcon,
LightBulbIcon,
MathIcon,
MoonIcon,
NotepadIcon,
PadlockIcon,
PaletteIcon,
PromoteIcon,
QuestionMarkIcon,
SportIcon,
SunIcon,
TargetIcon,
TerminalIcon,
ToolsIcon,
VehicleIcon,
WarningIcon,
DatabaseIcon,
SmileyIcon,
LightningIcon,
} from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useMenuState, MenuButton, MenuItem } from "reakit/Menu";
import { PopoverDisclosure, usePopoverState } from "reakit";
import { MenuItem } from "reakit/Menu";
import styled, { useTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { s } from "@shared/styles";
import { colorPalette } from "@shared/utils/collections";
import ContextMenu from "~/components/ContextMenu";
import Flex from "~/components/Flex";
import { LabelText } from "~/components/Input";
import NudeButton from "~/components/NudeButton";
import Text from "~/components/Text";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import lazyWithRetry from "~/utils/lazyWithRetry";
import DelayedMount from "./DelayedMount";
import { IconLibrary } from "./Icons/IconLibrary";
import Popover from "./Popover";
const style = {
width: 30,
height: 30,
};
const icons = IconLibrary.mapping;
const TwitterPicker = lazyWithRetry(
() => import("react-color/lib/components/twitter/Twitter")
);
export const icons = {
academicCap: {
component: AcademicCapIcon,
keywords: "learn teach lesson guide tutorial onboarding training",
},
bicycle: {
component: BicycleIcon,
keywords: "bicycle bike cycle",
},
beaker: {
component: BeakerIcon,
keywords: "lab research experiment test",
},
buildingBlocks: {
component: BuildingBlocksIcon,
keywords: "app blocks product prototype",
},
bookmark: {
component: BookmarkedIcon,
keywords: "bookmark",
},
collection: {
component: CollectionIcon,
keywords: "collection",
},
coins: {
component: CoinsIcon,
keywords: "coins money finance sales income revenue cash",
},
camera: {
component: CameraIcon,
keywords: "photo picture",
},
cloud: {
component: CloudIcon,
keywords: "cloud service aws infrastructure",
},
code: {
component: CodeIcon,
keywords: "developer api code development engineering programming",
},
database: {
component: DatabaseIcon,
keywords: "server ops database",
},
email: {
component: EmailIcon,
keywords: "email at",
},
eye: {
component: EyeIcon,
keywords: "eye view",
},
globe: {
component: GlobeIcon,
keywords: "world translate",
},
info: {
component: InfoIcon,
keywords: "info information",
},
image: {
component: ImageIcon,
keywords: "image photo picture",
},
leaf: {
component: LeafIcon,
keywords: "leaf plant outdoors nature ecosystem climate",
},
lightbulb: {
component: LightBulbIcon,
keywords: "lightbulb idea",
},
lightning: {
component: LightningIcon,
keywords: "lightning fast zap",
},
math: {
component: MathIcon,
keywords: "math formula",
},
moon: {
component: MoonIcon,
keywords: "night moon dark",
},
notepad: {
component: NotepadIcon,
keywords: "journal notepad write notes",
},
padlock: {
component: PadlockIcon,
keywords: "padlock private security authentication authorization auth",
},
palette: {
component: PaletteIcon,
keywords: "design palette art brand",
},
pencil: {
component: EditIcon,
keywords: "copy writing post blog",
},
promote: {
component: PromoteIcon,
keywords: "marketing promotion",
},
question: {
component: QuestionMarkIcon,
keywords: "question help support faq",
},
sun: {
component: SunIcon,
keywords: "day sun weather",
},
sport: {
component: SportIcon,
keywords: "sport outdoor racket game",
},
smiley: {
component: SmileyIcon,
keywords: "emoji smiley happy",
},
target: {
component: TargetIcon,
keywords: "target goal sales",
},
terminal: {
component: TerminalIcon,
keywords: "terminal code",
},
tools: {
component: ToolsIcon,
keywords: "tool settings",
},
vehicle: {
component: VehicleIcon,
keywords: "truck car travel transport",
},
warning: {
component: WarningIcon,
keywords: "warning alert error",
},
};
type Props = {
onOpen?: () => void;
onClose?: () => void;
onChange: (color: string, icon: string) => void;
initial: string;
icon: string;
color: string;
className?: string;
};
function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
function IconPicker({
onOpen,
onClose,
icon,
initial,
color,
onChange,
className,
}: Props) {
const { t } = useTranslation();
const theme = useTheme();
const menu = useMenuState({
const popover = usePopoverState({
gutter: 0,
placement: "bottom",
modal: true,
placement: "bottom-end",
});
React.useEffect(() => {
if (popover.visible) {
onOpen?.();
} else {
onClose?.();
}
}, [onOpen, onClose, popover.visible]);
const styles = React.useMemo(
() => ({
default: {
body: {
padding: 0,
marginRight: -8,
},
hash: {
color: theme.text,
background: theme.inputBorder,
},
swatch: {
cursor: "var(--cursor-pointer)",
},
input: {
color: theme.text,
boxShadow: `inset 0 0 0 1px ${theme.inputBorder}`,
background: "transparent",
},
},
}),
[theme]
);
// Custom click outside handling rather than using `hideOnClickOutside` from reakit so that we can
// prevent event bubbling.
useOnClickOutside(
popover.unstable_popoverRef,
(event) => {
if (popover.visible) {
event.stopPropagation();
event.preventDefault();
popover.hide();
}
},
{ capture: true }
);
return (
<Wrapper>
<Label>
<LabelText>{t("Icon")}</LabelText>
</Label>
<MenuButton {...menu}>
<>
<PopoverDisclosure {...popover}>
{(props) => (
<Button aria-label={t("Show menu")} {...props}>
<NudeButton
aria-label={t("Show menu")}
className={className}
{...props}
>
<Icon
as={icons[icon || "collection"].component}
as={IconLibrary.getComponent(icon || "collection")}
color={color}
size={30}
/>
</Button>
>
{initial}
</Icon>
</NudeButton>
)}
</MenuButton>
<ContextMenu
{...menu}
onOpen={onOpen}
onClose={onClose}
</PopoverDisclosure>
<Popover
{...popover}
width={388}
aria-label={t("Choose icon")}
hideOnClickOutside={false}
>
<Icons>
{Object.keys(icons).map((name, index) => (
<MenuItem
key={name}
onClick={() => onChange(color, name)}
{...menu}
>
<MenuItem key={name} onClick={() => onChange(color, name)}>
{(props) => (
<IconButton
style={
{
...style,
"--delay": `${index * 8}ms`,
} as React.CSSProperties
}
{...props}
>
<Icon as={icons[name].component} color={color} size={30} />
<Icon
as={IconLibrary.getComponent(name)}
color={color}
size={30}
>
{initial}
</Icon>
</IconButton>
)}
</MenuItem>
@@ -276,28 +153,12 @@ function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) {
onChange={(color) => onChange(color.hex, icon)}
colors={colorPalette}
triangle="hide"
styles={{
default: {
body: {
padding: 0,
marginRight: -8,
},
hash: {
color: theme.text,
background: theme.inputBorder,
},
input: {
color: theme.text,
boxShadow: `inset 0 0 0 1px ${theme.inputBorder}`,
background: "transparent",
},
},
}}
styles={styles}
/>
</React.Suspense>
</Colors>
</ContextMenu>
</Wrapper>
</Popover>
</>
);
}
@@ -310,25 +171,12 @@ const Colors = styled(Flex)`
padding: 8px;
`;
const Label = styled.label`
display: block;
`;
const Icons = styled.div`
padding: 8px;
${breakpoint("tablet")`
width: 276px;
`};
`;
const Button = styled(NudeButton)`
border: 1px solid ${s("inputBorder")};
width: 32px;
height: 32px;
`;
const IconButton = styled(NudeButton)`
vertical-align: top;
border-radius: 4px;
margin: 0px 6px 6px 0px;
width: 30px;
@@ -341,9 +189,4 @@ const ColorPicker = styled(TwitterPicker)`
width: 100% !important;
`;
const Wrapper = styled("div")`
display: inline-block;
position: relative;
`;
export default IconPicker;
+7 -3
View File
@@ -3,9 +3,9 @@ import { CollectionIcon } from "outline-icons";
import { getLuminance } from "polished";
import * as React from "react";
import Collection from "~/models/Collection";
import { icons } from "~/components/IconPicker";
import useStores from "~/hooks/useStores";
import Logger from "~/utils/Logger";
import { IconLibrary } from "./IconLibrary";
type Props = {
/** The collection to show an icon for */
@@ -38,8 +38,12 @@ function ResolvedCollectionIcon({
if (collection.icon && collection.icon !== "collection") {
try {
const Component = icons[collection.icon].component;
return <Component color={color} size={size} />;
const Component = IconLibrary.getComponent(collection.icon);
return (
<Component color={color} size={size}>
{collection.initial}
</Component>
);
} catch (error) {
Logger.warn("Failed to render custom icon", {
icon: collection.icon,
+3 -3
View File
@@ -2,9 +2,9 @@ import * as React from "react";
import styled from "styled-components";
type Props = {
/* The emoji to render */
/** The emoji to render */
emoji: string;
/* The size of the emoji, 24px is default to match standard icons */
/** The size of the emoji, 24px is default to match standard icons */
size?: number;
};
@@ -29,5 +29,5 @@ const Span = styled.span<{ $size: number }>`
width: ${(props) => props.$size}px;
height: ${(props) => props.$size}px;
text-indent: -0.15em;
font-size: 14px;
font-size: ${(props) => props.$size - 10}px;
`;
+315
View File
@@ -0,0 +1,315 @@
import intersection from "lodash/intersection";
import {
BookmarkedIcon,
BicycleIcon,
AcademicCapIcon,
BeakerIcon,
BuildingBlocksIcon,
BrowserIcon,
CollectionIcon,
CoinsIcon,
CameraIcon,
CarrotIcon,
FlameIcon,
HashtagIcon,
GraphIcon,
InternetIcon,
LibraryIcon,
PlaneIcon,
RamenIcon,
CloudIcon,
CodeIcon,
EditIcon,
EmailIcon,
EyeIcon,
GlobeIcon,
InfoIcon,
IceCreamIcon,
ImageIcon,
LeafIcon,
LightBulbIcon,
MathIcon,
MoonIcon,
NotepadIcon,
TeamIcon,
PadlockIcon,
PaletteIcon,
PromoteIcon,
QuestionMarkIcon,
SportIcon,
SunIcon,
ShapesIcon,
TargetIcon,
TerminalIcon,
ToolsIcon,
VehicleIcon,
WarningIcon,
DatabaseIcon,
SmileyIcon,
LightningIcon,
ClockIcon,
DoneIcon,
FeedbackIcon,
ServerRackIcon,
ThumbsUpIcon,
TruckIcon,
} from "outline-icons";
import LetterIcon from "./LetterIcon";
export class IconLibrary {
/**
* Get the component for a given icon name
*
* @param icon The name of the icon
* @returns The component for the icon
*/
public static getComponent(icon: string) {
return this.mapping[icon].component;
}
/**
* Find an icon by keyword. This is useful for searching for an icon based on a user's input.
*
* @param keyword The keyword to search for
* @returns The name of the icon that matches the keyword, or undefined if no match is found
*/
public static findIconByKeyword(keyword: string) {
const keys = Object.keys(this.mapping);
for (const key of keys) {
const icon = this.mapping[key];
const keywords = icon.keywords.split(" ");
const namewords = keyword.toLocaleLowerCase().split(" ");
const matches = intersection(namewords, keywords);
if (matches.length > 0) {
return key;
}
}
return undefined;
}
/**
* A map of all icons available to end users in the app. This does not include icons that are used
* internally only, which can be imported from `outline-icons` directly.
*/
public static mapping = {
academicCap: {
component: AcademicCapIcon,
keywords: "learn teach lesson guide tutorial onboarding training",
},
bicycle: {
component: BicycleIcon,
keywords: "bicycle bike cycle",
},
beaker: {
component: BeakerIcon,
keywords: "lab research experiment test",
},
buildingBlocks: {
component: BuildingBlocksIcon,
keywords: "app blocks product prototype",
},
bookmark: {
component: BookmarkedIcon,
keywords: "bookmark",
},
browser: {
component: BrowserIcon,
keywords: "browser web app",
},
collection: {
component: CollectionIcon,
keywords: "collection",
},
coins: {
component: CoinsIcon,
keywords: "coins money finance sales income revenue cash",
},
camera: {
component: CameraIcon,
keywords: "photo picture",
},
carrot: {
component: CarrotIcon,
keywords: "food vegetable produce",
},
clock: {
component: ClockIcon,
keywords: "time",
},
cloud: {
component: CloudIcon,
keywords: "cloud service aws infrastructure",
},
code: {
component: CodeIcon,
keywords: "developer api code development engineering programming",
},
database: {
component: DatabaseIcon,
keywords: "server ops database",
},
done: {
component: DoneIcon,
keywords: "checkmark success complete finished",
},
email: {
component: EmailIcon,
keywords: "email at",
},
eye: {
component: EyeIcon,
keywords: "eye view",
},
feedback: {
component: FeedbackIcon,
keywords: "faq help support",
},
flame: {
component: FlameIcon,
keywords: "fire flame hot",
},
graph: {
component: GraphIcon,
keywords: "chart analytics data",
},
globe: {
component: GlobeIcon,
keywords: "world translate",
},
hashtag: {
component: HashtagIcon,
keywords: "social media tag",
},
info: {
component: InfoIcon,
keywords: "info information",
},
icecream: {
component: IceCreamIcon,
keywords: "food dessert cone scoop",
},
image: {
component: ImageIcon,
keywords: "image photo picture",
},
internet: {
component: InternetIcon,
keywords: "network global globe world",
},
leaf: {
component: LeafIcon,
keywords: "leaf plant outdoors nature ecosystem climate",
},
library: {
component: LibraryIcon,
keywords: "library collection archive",
},
lightbulb: {
component: LightBulbIcon,
keywords: "lightbulb idea",
},
lightning: {
component: LightningIcon,
keywords: "lightning fast zap",
},
letter: {
component: LetterIcon,
keywords: "letter",
},
math: {
component: MathIcon,
keywords: "math formula",
},
moon: {
component: MoonIcon,
keywords: "night moon dark",
},
notepad: {
component: NotepadIcon,
keywords: "journal notepad write notes",
},
padlock: {
component: PadlockIcon,
keywords: "padlock private security authentication authorization auth",
},
palette: {
component: PaletteIcon,
keywords: "design palette art brand",
},
pencil: {
component: EditIcon,
keywords: "copy writing post blog",
},
plane: {
component: PlaneIcon,
keywords: "airplane travel flight trip vacation",
},
promote: {
component: PromoteIcon,
keywords: "marketing promotion",
},
ramen: {
component: RamenIcon,
keywords: "soup food noodle bowl meal",
},
question: {
component: QuestionMarkIcon,
keywords: "question help support faq",
},
server: {
component: ServerRackIcon,
keywords: "ops infra server",
},
sun: {
component: SunIcon,
keywords: "day sun weather",
},
shapes: {
component: ShapesIcon,
keywords: "blocks toy",
},
sport: {
component: SportIcon,
keywords: "sport outdoor racket game",
},
smiley: {
component: SmileyIcon,
keywords: "emoji smiley happy",
},
target: {
component: TargetIcon,
keywords: "target goal sales",
},
team: {
component: TeamIcon,
keywords: "team building organization office",
},
terminal: {
component: TerminalIcon,
keywords: "terminal code",
},
thumbsup: {
component: ThumbsUpIcon,
keywords: "like social favorite upvote",
},
truck: {
component: TruckIcon,
keywords: "truck transport vehicle",
},
tools: {
component: ToolsIcon,
keywords: "tool settings",
},
vehicle: {
component: VehicleIcon,
keywords: "truck car travel transport",
},
warning: {
component: WarningIcon,
keywords: "warning alert error",
},
};
}
+32
View File
@@ -0,0 +1,32 @@
import * as React from "react";
export function LanguageIcon({ className }: { className?: string }) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M21 18H16L14 16V6C14 4.89543 14.8954 4 16 4H28C29.1046 4 30 4.89543 30 6V16C30 17.1046 29.1046 18 28 18H27L25.4142 19.5858C24.6332 20.3668 23.3668 20.3668 22.5858 19.5858L21 18ZM16 15.1716V6H28V16H27H26.1716L25.5858 16.5858L24 18.1716L22.4142 16.5858L21.8284 16H21H16.8284L16 15.1716Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 13H4C2.89543 13 2 13.8954 2 15V25C2 26.1046 2.89543 27 4 27H5L6.58579 28.5858C7.36684 29.3668 8.63316 29.3668 9.41421 28.5858L11 27H16C17.1046 27 18 26.1046 18 25V15C18 13.8954 17.1046 13 16 13ZM9 17L6 16.9681C6 16.9681 5 17.016 5 18C5 18.984 6 19 6 19H8.5H10C10 19 9.57627 20.1885 8.38983 21.0831C7.20339 21.9777 5.7197 23 5.7197 23C5.7197 23 4.99153 23.6054 5.5 24.5C6.00847 25.3946 7 24.8403 7 24.8403L9.74576 22.8722L11.9492 24.6614C11.9492 24.6614 12.6271 25.3771 13.3051 24.4825C13.9831 23.5879 13.3051 23.0512 13.3051 23.0512L11.1017 21.262C11.1017 21.262 11.5 21 12 20L12.5 19H14C14 19 15 19.0319 15 18C15 16.9681 14 16.9681 14 16.9681L11 17V16C11 16 11.0169 15 10 15C8.98305 15 9 16 9 16V17Z"
fill="currentColor"
/>
<path
d="M23.6672 12.5221L23.5526 12.1816H23.1934H20.8818H20.5215L20.4075 12.5235L20.082 13.5H19.2196L21.2292 8.10156H21.8774L21.5587 9.06116L20.7633 11.4562L20.5449 12.1138H21.2378H22.8374H23.5327L23.3114 11.4546L22.5072 9.05959L22.1855 8.10156H22.768L24.7887 13.5H23.9964L23.6672 12.5221Z"
fill="currentColor"
stroke="currentColor"
/>
</svg>
);
}
+35
View File
@@ -0,0 +1,35 @@
import * as React from "react";
import styled from "styled-components";
import { s } from "@shared/styles";
import Squircle from "../Squircle";
type Props = {
/** The width and height of the icon, including standard padding. */
size?: number;
children: React.ReactNode;
};
/**
* A squircle shaped icon with a letter inside, used for collections.
*/
const LetterIcon = ({ children, size = 24, ...rest }: Props) => (
<LetterIconWrapper $size={size}>
<Squircle size={Math.round(size * 0.66)} {...rest}>
{children}
</Squircle>
</LetterIconWrapper>
);
const LetterIconWrapper = styled.div<{ $size: number }>`
display: inline-flex;
align-items: center;
justify-content: center;
width: ${({ $size }) => $size}px;
height: ${({ $size }) => $size}px;
font-weight: 700;
font-size: ${({ $size }) => $size / 2}px;
color: ${s("background")};
`;
export default LetterIcon;
+1 -1
View File
@@ -5,7 +5,7 @@ type Props = {
size?: number;
/** The color of the icon, defaults to the current text color */
color?: string;
/* Whether the safe area should be removed and have graphic across full size */
/** Whether the safe area should be removed and have graphic across full size */
cover?: boolean;
};
+51 -20
View File
@@ -1,4 +1,5 @@
import * as React from "react";
import { mergeRefs } from "react-merge-refs";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
@@ -7,10 +8,14 @@ import Flex from "~/components/Flex";
import Text from "~/components/Text";
import { undraggableOnDesktop } from "~/styles";
const RealTextarea = styled.textarea<{ hasIcon?: boolean }>`
export const NativeTextarea = styled.textarea<{
hasIcon?: boolean;
hasPrefix?: boolean;
}>`
border: 0;
flex: 1;
padding: 8px 12px 8px ${(props) => (props.hasIcon ? "8px" : "12px")};
padding: 8px 12px 8px
${(props) => (props.hasPrefix ? 0 : props.hasIcon ? "8px" : "12px")};
outline: none;
background: none;
color: ${s("text")};
@@ -18,13 +23,18 @@ const RealTextarea = styled.textarea<{ hasIcon?: boolean }>`
&:disabled,
&::placeholder {
color: ${s("placeholder")};
opacity: 1;
}
`;
const RealInput = styled.input<{ hasIcon?: boolean }>`
export const NativeInput = styled.input<{
hasIcon?: boolean;
hasPrefix?: boolean;
}>`
border: 0;
flex: 1;
padding: 8px 12px 8px ${(props) => (props.hasIcon ? "8px" : "12px")};
padding: 8px 12px 8px
${(props) => (props.hasPrefix ? 0 : props.hasIcon ? "8px" : "12px")};
outline: none;
background: none;
color: ${s("text")};
@@ -38,6 +48,7 @@ const RealInput = styled.input<{ hasIcon?: boolean }>`
&:disabled,
&::placeholder {
color: ${s("placeholder")};
opacity: 1;
}
&:-webkit-autofill,
@@ -55,7 +66,7 @@ const RealInput = styled.input<{ hasIcon?: boolean }>`
`};
`;
const Wrapper = styled.div<{
export const Wrapper = styled.div<{
flex?: boolean;
short?: boolean;
minHeight?: number;
@@ -110,9 +121,11 @@ export const LabelText = styled.div`
display: inline-block;
`;
export type Props = React.InputHTMLAttributes<
HTMLInputElement | HTMLTextAreaElement
> & {
export interface Props
extends Omit<
React.InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement>,
"prefix"
> {
type?: "text" | "email" | "checkbox" | "search" | "textarea";
labelHidden?: boolean;
label?: string;
@@ -120,19 +133,25 @@ export type Props = React.InputHTMLAttributes<
short?: boolean;
margin?: string | number;
error?: string;
/** Optional component that appears inside the input before the textarea and any icon */
prefix?: React.ReactNode;
/** Optional icon that appears inside the input before the textarea */
icon?: React.ReactNode;
/* Callback is triggered with the CMD+Enter keyboard combo */
/** Like autoFocus, but also select any text in the input */
autoSelect?: boolean;
/** Callback is triggered with the CMD+Enter keyboard combo */
onRequestSubmit?: (
ev: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>
) => unknown;
onFocus?: (ev: React.SyntheticEvent) => unknown;
onBlur?: (ev: React.SyntheticEvent) => unknown;
};
}
function Input(
props: Props,
ref: React.RefObject<HTMLInputElement | HTMLTextAreaElement>
) {
const internalRef = React.useRef<HTMLInputElement | HTMLTextAreaElement>();
const [focused, setFocused] = React.useState(false);
const handleBlur = (ev: React.SyntheticEvent) => {
@@ -165,6 +184,12 @@ function Input(
}
};
React.useEffect(() => {
if (props.autoSelect && internalRef.current) {
internalRef.current.select();
}
}, [props.autoSelect, internalRef]);
const {
type = "text",
icon,
@@ -174,6 +199,7 @@ function Input(
className,
short,
flex,
prefix,
labelHidden,
onFocus,
onBlur,
@@ -194,23 +220,32 @@ function Input(
wrappedLabel
))}
<Outline focused={focused} margin={margin}>
{prefix}
{icon && <IconWrapper>{icon}</IconWrapper>}
{type === "textarea" ? (
<RealTextarea
ref={ref as React.RefObject<HTMLTextAreaElement>}
<NativeTextarea
ref={mergeRefs([
internalRef,
ref as React.RefObject<HTMLTextAreaElement>,
])}
onBlur={handleBlur}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
hasIcon={!!icon}
hasPrefix={!!prefix}
{...rest}
/>
) : (
<RealInput
ref={ref as React.RefObject<HTMLInputElement>}
<NativeInput
ref={mergeRefs([
internalRef,
ref as React.RefObject<HTMLInputElement>,
])}
onBlur={handleBlur}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
hasIcon={!!icon}
hasPrefix={!!prefix}
type={type}
{...rest}
/>
@@ -220,9 +255,9 @@ function Input(
</label>
{error && (
<TextWrapper>
<StyledText type="danger" size="xsmall">
<Text type="danger" size="xsmall">
{error}
</StyledText>
</Text>
</TextWrapper>
)}
</Wrapper>
@@ -235,8 +270,4 @@ export const TextWrapper = styled.span`
margin-top: -16px;
`;
export const StyledText = styled(Text)`
margin-bottom: 0;
`;
export default React.forwardRef(Input);
@@ -2,31 +2,18 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { s } from "@shared/styles";
import { CollectionPermission } from "@shared/types";
import InputSelect, { Props as SelectProps } from "~/components/InputSelect";
import { Permission } from "~/types";
export default function InputMemberPermissionSelect(
props: Partial<SelectProps>
props: Partial<SelectProps> & { permissions: Permission[] }
) {
const { t } = useTranslation();
return (
<Select
label={t("Permissions")}
options={[
{
label: t("View only"),
value: CollectionPermission.Read,
},
{
label: t("View and edit"),
value: CollectionPermission.ReadWrite,
},
{
label: t("Admin"),
value: CollectionPermission.Admin,
},
]}
options={props.permissions}
ariaLabel={t("Permissions")}
labelHidden
nude
-63
View File
@@ -1,63 +0,0 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Trans } from "react-i18next";
import styled from "styled-components";
import Editor from "~/components/Editor";
import { LabelText, Outline } from "~/components/Input";
import Text from "~/components/Text";
type Props = {
label: string;
minHeight?: number;
maxHeight?: number;
readOnly?: boolean;
};
function InputRich({ label, minHeight, maxHeight, ...rest }: Props) {
const [focused, setFocused] = React.useState<boolean>(false);
const handleBlur = React.useCallback(() => {
setFocused(false);
}, []);
const handleFocus = React.useCallback(() => {
setFocused(true);
}, []);
return (
<>
<LabelText>{label}</LabelText>
<StyledOutline
maxHeight={maxHeight}
minHeight={minHeight}
focused={focused}
>
<React.Suspense
fallback={
<Text type="secondary">
<Trans>Loading editor</Trans>
</Text>
}
>
<Editor onBlur={handleBlur} onFocus={handleFocus} grow {...rest} />
</React.Suspense>
</StyledOutline>
</>
);
}
const StyledOutline = styled(Outline)<{
minHeight?: number;
maxHeight?: number;
focused?: boolean;
}>`
display: block;
padding: 8px 12px;
min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : "0")};
max-height: ${({ maxHeight }) => (maxHeight ? `${maxHeight}px` : "auto")};
overflow-y: auto;
> * {
display: block;
}
`;
export default observer(InputRich);
+56 -16
View File
@@ -14,6 +14,7 @@ import Button, { Inner } from "~/components/Button";
import Text from "~/components/Text";
import useMenuHeight from "~/hooks/useMenuHeight";
import useMobile from "~/hooks/useMobile";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import { fadeAndScaleIn } from "~/styles/animations";
import {
Position,
@@ -42,14 +43,25 @@ export type Props = {
labelHidden?: boolean;
icon?: React.ReactNode;
options: Option[];
/** @deprecated Removing soon, do not use. */
note?: React.ReactNode;
onChange?: (value: string | null) => void;
};
export interface InputSelectRef {
value: string | null;
focus: () => void;
blur: () => void;
}
interface InnerProps extends React.HTMLAttributes<HTMLDivElement> {
placement: Placement;
}
const getOptionFromValue = (options: Option[], value: string | null) =>
options.find((option) => option.value === value);
const InputSelect = (props: Props) => {
const InputSelect = (props: Props, ref: React.RefObject<InputSelectRef>) => {
const {
value = null,
label,
@@ -62,6 +74,7 @@ const InputSelect = (props: Props) => {
disabled,
note,
icon,
nude,
...rest
} = props;
@@ -71,9 +84,9 @@ const InputSelect = (props: Props) => {
selectedValue: value,
});
const popOver = useSelectPopover({
const popover = useSelectPopover({
...select,
hideOnClickOutside: true,
hideOnClickOutside: false,
preventBodyScroll: true,
disabled,
});
@@ -102,6 +115,35 @@ const InputSelect = (props: Props) => {
(option) => option.value === select.selectedValue
);
// Custom click outside handling rather than using `hideOnClickOutside` from reakit so that we can
// prevent event bubbling.
useOnClickOutside(
contentRef,
(event) => {
if (select.visible) {
event.stopPropagation();
event.preventDefault();
select.hide();
}
},
{ capture: true }
);
React.useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current?.focus();
},
blur: () => {
buttonRef.current?.blur();
},
value: select.selectedValue,
}));
React.useEffect(() => {
previousValue.current = value;
select.setSelectedValue(value);
}, [value]);
React.useEffect(() => {
if (previousValue.current === select.selectedValue) {
return;
@@ -138,6 +180,7 @@ const InputSelect = (props: Props) => {
disclosure
className={className}
icon={icon}
$nude={nude}
{...props}
>
{getOptionFromValue(options, select.selectedValue)?.label || (
@@ -146,12 +189,8 @@ const InputSelect = (props: Props) => {
</StyledButton>
)}
</Select>
<SelectPopover {...select} {...popOver} aria-label={ariaLabel}>
{(
props: React.HTMLAttributes<HTMLDivElement> & {
placement: Placement;
}
) => {
<SelectPopover {...select} {...popover} aria-label={ariaLabel}>
{(props: InnerProps) => {
const topAnchor = props.style?.top === "0";
const rightAnchor = props.placement === "bottom-end";
@@ -163,6 +202,7 @@ const InputSelect = (props: Props) => {
topAnchor={topAnchor}
rightAnchor={rightAnchor}
hiddenScrollbars
maxWidth={400}
style={
maxHeight && topAnchor
? {
@@ -200,7 +240,7 @@ const InputSelect = (props: Props) => {
</SelectPopover>
</Wrapper>
{note && (
<Text type="secondary" size="small">
<Text as="p" type="secondary" size="small">
{note}
</Text>
)}
@@ -223,7 +263,7 @@ const Spacer = styled.div`
flex-shrink: 0;
`;
const StyledButton = styled(Button)<{ nude?: boolean }>`
const StyledButton = styled(Button)<{ $nude?: boolean }>`
font-weight: normal;
text-transform: none;
margin-bottom: 16px;
@@ -236,7 +276,7 @@ const StyledButton = styled(Button)<{ nude?: boolean }>`
}
${(props) =>
props.nude &&
props.$nude &&
css`
border-color: transparent;
box-shadow: none;
@@ -244,8 +284,8 @@ const StyledButton = styled(Button)<{ nude?: boolean }>`
${Inner} {
line-height: 28px;
padding-left: 16px;
padding-right: 8px;
padding-left: 12px;
padding-right: 4px;
}
svg {
@@ -267,7 +307,7 @@ const Wrapper = styled.label<{ short?: boolean }>`
max-width: ${(props) => (props.short ? "350px" : "100%")};
`;
const Positioner = styled(Position)`
export const Positioner = styled(Position)`
&.focus-visible {
${StyledSelectOption} {
&[aria-selected="true"] {
@@ -284,4 +324,4 @@ const Positioner = styled(Position)`
}
`;
export default InputSelect;
export default React.forwardRef(InputSelect);
+13 -12
View File
@@ -2,36 +2,35 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { $Diff } from "utility-types";
import { CollectionPermission } from "@shared/types";
import InputSelect, { Props, Option } from "./InputSelect";
import { EmptySelectValue } from "~/types";
import InputSelect, { Props, Option, InputSelectRef } from "./InputSelect";
export default function InputSelectPermission(
function InputSelectPermission(
props: $Diff<
Props,
{
options: Array<Option>;
ariaLabel: string;
}
>
>,
ref: React.RefObject<InputSelectRef>
) {
const { value, onChange, ...rest } = props;
const { t } = useTranslation();
const handleChange = React.useCallback(
(value) => {
if (value === "no_access") {
value = "";
}
onChange?.(value);
onChange?.(value === EmptySelectValue ? null : value);
},
[onChange]
);
return (
<InputSelect
label={t("Default access")}
ref={ref}
label={t("Permission")}
options={[
{
label: t("View and edit"),
label: t("Can edit"),
value: CollectionPermission.ReadWrite,
},
{
@@ -40,13 +39,15 @@ export default function InputSelectPermission(
},
{
label: t("No access"),
value: "no_access",
value: EmptySelectValue,
},
]}
ariaLabel={t("Default access")}
value={value || "no_access"}
value={value || EmptySelectValue}
onChange={handleChange}
{...rest}
/>
);
}
export default React.forwardRef(InputSelectPermission);
-39
View File
@@ -1,39 +0,0 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { $Diff } from "utility-types";
import InputSelect, { Props, Option } from "~/components/InputSelect";
const InputSelectRole = (
props: $Diff<
Props,
{
options: Array<Option>;
ariaLabel: string;
}
>
) => {
const { t } = useTranslation();
return (
<InputSelect
label={t("Role")}
options={[
{
label: t("Member"),
value: "member",
},
{
label: t("Viewer"),
value: "viewer",
},
{
label: t("Admin"),
value: "admin",
},
]}
ariaLabel={t("Role")}
{...props}
/>
);
};
export default InputSelectRole;
+1 -1
View File
@@ -1,7 +1,7 @@
import styled from "styled-components";
type Props = {
/* Set to true if displaying a single symbol character to disable monospace */
/** Set to true if displaying a single symbol character to disable monospace */
symbol?: boolean;
};
+32 -63
View File
@@ -1,6 +1,7 @@
import { find } from "lodash";
import { m } from "framer-motion";
import find from "lodash/find";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { languages, languageOptions } from "@shared/i18n";
import ButtonLink from "~/components/ButtonLink";
@@ -9,49 +10,20 @@ import env from "~/env";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import { detectLanguage } from "~/utils/language";
function Icon({ className }: { className?: string }) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M21 18H16L14 16V6C14 4.89543 14.8954 4 16 4H28C29.1046 4 30 4.89543 30 6V16C30 17.1046 29.1046 18 28 18H27L25.4142 19.5858C24.6332 20.3668 23.3668 20.3668 22.5858 19.5858L21 18ZM16 15.1716V6H28V16H27H26.1716L25.5858 16.5858L24 18.1716L22.4142 16.5858L21.8284 16H21H16.8284L16 15.1716Z"
fill="#2B2F35"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 13H4C2.89543 13 2 13.8954 2 15V25C2 26.1046 2.89543 27 4 27H5L6.58579 28.5858C7.36684 29.3668 8.63316 29.3668 9.41421 28.5858L11 27H16C17.1046 27 18 26.1046 18 25V15C18 13.8954 17.1046 13 16 13ZM9 17L6 16.9681C6 16.9681 5 17.016 5 18C5 18.984 6 19 6 19H8.5H10C10 19 9.57627 20.1885 8.38983 21.0831C7.20339 21.9777 5.7197 23 5.7197 23C5.7197 23 4.99153 23.6054 5.5 24.5C6.00847 25.3946 7 24.8403 7 24.8403L9.74576 22.8722L11.9492 24.6614C11.9492 24.6614 12.6271 25.3771 13.3051 24.4825C13.9831 23.5879 13.3051 23.0512 13.3051 23.0512L11.1017 21.262C11.1017 21.262 11.5 21 12 20L12.5 19H14C14 19 15 19.0319 15 18C15 16.9681 14 16.9681 14 16.9681L11 17V16C11 16 11.0169 15 10 15C8.98305 15 9 16 9 16V17Z"
fill="#2B2F35"
/>
<path
d="M23.6672 12.5221L23.5526 12.1816H23.1934H20.8818H20.5215L20.4075 12.5235L20.082 13.5H19.2196L21.2292 8.10156H21.8774L21.5587 9.06116L20.7633 11.4562L20.5449 12.1138H21.2378H22.8374H23.5327L23.3114 11.4546L22.5072 9.05959L22.1855 8.10156H22.768L24.7887 13.5H23.9964L23.6672 12.5221Z"
fill="#2B2F35"
stroke="#2B2F35"
/>
</svg>
);
}
import { LanguageIcon } from "./Icons/LanguageIcon";
import Text from "./Text";
export default function LanguagePrompt() {
const { auth, ui } = useStores();
const { ui } = useStores();
const { t } = useTranslation();
const user = useCurrentUser();
const language = detectLanguage();
if (language === "en_US" || language === user.language) {
return null;
}
if (!languages.includes(language)) {
if (
language === "en_US" ||
language === user.language ||
!languages.includes(language)
) {
return null;
}
@@ -60,24 +32,23 @@ export default function LanguagePrompt() {
const appName = env.APP_NAME;
return (
<Wrapper>
<Flex align="center">
<Wrapper
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.2 }}
>
<Flex align="center" gap={12}>
<LanguageIcon />
<span>
<Trans>
{{ appName }} is available in your language{" "}
{{
optionLabel,
}}
, would you like to change?
</Trans>
<Text>
{appName} is available in your language {optionLabel}, would you
like to change?
</Text>
<br />
<Link
onClick={async () => {
ui.setLanguagePromptDismissed();
await auth.updateUser({
language,
});
await user.save({ language });
}}
>
{t("Change Language")}
@@ -90,16 +61,17 @@ export default function LanguagePrompt() {
);
}
const Wrapper = styled.p`
background: ${(props) => props.theme.brand.marine};
color: ${(props) => props.theme.almostBlack};
padding: 10px 12px;
margin-top: 24px;
border-radius: 4px;
const Wrapper = styled(m.p)`
color: ${(props) => props.theme.text};
border: 1px solid ${(props) => props.theme.divider};
padding: 20px;
margin-top: 12px;
border-radius: 8px;
position: relative;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
a {
color: ${(props) => props.theme.almostBlack};
color: ${(props) => props.theme.text};
font-weight: 500;
}
@@ -109,14 +81,11 @@ const Wrapper = styled.p`
`;
const Link = styled(ButtonLink)`
color: ${(props) => props.theme.almostBlack};
cursor: var(--cursor-pointer);
color: ${(props) => props.theme.text};
font-weight: 500;
&:hover {
text-decoration: underline;
}
`;
const LanguageIcon = styled(Icon)`
margin-right: 12px;
`;
+6 -8
View File
@@ -22,12 +22,10 @@ type Props = {
sidebarRight?: React.ReactNode;
};
const Layout: React.FC<Props> = ({
title,
children,
sidebar,
sidebarRight,
}: Props) => {
const Layout = React.forwardRef(function Layout_(
{ title, children, sidebar, sidebarRight }: Props,
ref: React.RefObject<HTMLDivElement>
) {
const { ui } = useStores();
const sidebarCollapsed = !sidebar || ui.sidebarIsClosed;
@@ -40,7 +38,7 @@ const Layout: React.FC<Props> = ({
});
return (
<Container column auto>
<Container column auto ref={ref}>
<Helmet>
<title>{title ? title : env.APP_NAME}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -75,7 +73,7 @@ const Layout: React.FC<Props> = ({
</Container>
</Container>
);
};
});
const Container = styled(Flex)`
background: ${s("background")};
+20 -1
View File
@@ -6,13 +6,23 @@ import Flex from "~/components/Flex";
import NavLink from "~/components/NavLink";
export type Props = Omit<React.HTMLAttributes<HTMLAnchorElement>, "title"> & {
/** An icon or image to display to the left of the list item */
image?: React.ReactNode;
/** An internal location to navigate to on click, if provided the list item will have hover styles */
to?: LocationDescriptor;
/** An optional click handler, if provided the list item will have hover styles */
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
/** Whether to match the location exactly */
exact?: boolean;
/** The title of the list item */
title: React.ReactNode;
/** The subtitle of the list item, displayed below the title */
subtitle?: React.ReactNode;
/** Actions to display to the right of the list item */
actions?: React.ReactNode;
/** Whether to display a border below the list item */
border?: boolean;
/** Whether to display the list item in a compact style */
small?: boolean;
};
@@ -74,10 +84,12 @@ const ListItem = (
const Wrapper = styled.a<{
$small?: boolean;
$border?: boolean;
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
to?: LocationDescriptor;
}>`
display: flex;
padding: ${(props) => (props.$border === false ? 0 : "8px 0")};
min-height: 32px;
margin: ${(props) =>
props.$border === false ? (props.$small ? "8px 0" : "16px 0") : 0};
border-bottom: 1px solid
@@ -88,7 +100,13 @@ const Wrapper = styled.a<{
border-bottom: 0;
}
cursor: ${({ to }) => (to ? "var(--pointer)" : "default")};
&:hover {
background: ${(props) =>
props.onClick ? props.theme.secondaryBackground : "inherit"};
}
cursor: ${(props) =>
props.to || props.onClick ? "var(--pointer)" : "default"};
`;
const Image = styled(Flex)`
@@ -126,6 +144,7 @@ const Subtitle = styled.p<{ $small?: boolean; $selected?: boolean }>`
export const Actions = styled(Flex)<{ $selected?: boolean }>`
align-self: center;
justify-content: center;
flex-shrink: 0;
color: ${(props) =>
props.$selected ? props.theme.white : props.theme.textSecondary};
`;
+1 -1
View File
@@ -1,4 +1,4 @@
import { times } from "lodash";
import times from "lodash/times";
import * as React from "react";
import styled from "styled-components";
import Fade from "~/components/Fade";
+2 -2
View File
@@ -20,7 +20,7 @@ function eachMinute(fn: () => void) {
};
}
type Props = {
export type Props = {
children?: React.ReactNode;
dateTime: string;
tooltipDelay?: number;
@@ -79,7 +79,7 @@ const LocaleTime: React.FC<Props> = ({
});
return (
<Tooltip tooltip={tooltipContent} delay={tooltipDelay} placement="bottom">
<Tooltip content={tooltipContent} delay={tooltipDelay} placement="bottom">
<time dateTime={dateTime}>{children || content}</time>
</Tooltip>
);
+21
View File
@@ -0,0 +1,21 @@
import * as React from "react";
import styled from "styled-components";
import useMobile from "~/hooks/useMobile";
type Props = {
children: React.ReactNode;
};
const MobileWrapper = styled.div`
width: 100vw;
height: 100vh;
overflow: auto;
-webkit-overflow-scrolling: touch;
`;
const MobileScrollWrapper = ({ children }: Props) => {
const isMobile = useMobile();
return isMobile ? <MobileWrapper>{children}</MobileWrapper> : <>{children}</>;
};
export default MobileScrollWrapper;
+35 -37
View File
@@ -23,7 +23,7 @@ let openModals = 0;
type Props = {
children?: React.ReactNode;
isOpen: boolean;
isCentered?: boolean;
fullscreen?: boolean;
title?: React.ReactNode;
onRequestClose: () => void;
};
@@ -31,7 +31,7 @@ type Props = {
const Modal: React.FC<Props> = ({
children,
isOpen,
isCentered,
fullscreen = true,
title = "Untitled",
onRequestClose,
}: Props) => {
@@ -68,41 +68,17 @@ const Modal: React.FC<Props> = ({
return (
<DialogBackdrop {...dialog}>
{(props) => (
<Backdrop $isCentered={isCentered} {...props}>
<Backdrop $fullscreen={fullscreen} {...props}>
<Dialog
{...dialog}
aria-label={typeof title === "string" ? title : undefined}
preventBodyScroll
hideOnEsc
hideOnClickOutside={!!isCentered}
hideOnClickOutside={!fullscreen}
hide={onRequestClose}
>
{(props) =>
isCentered && !isMobile ? (
<Small {...props}>
<Centered
onClick={(ev) => ev.stopPropagation()}
column
reverse
>
<SmallContent shadow>
<ErrorBoundary component="div">{children}</ErrorBoundary>
</SmallContent>
<Header>
{title && (
<Text as="span" size="large">
{title}
</Text>
)}
<Text as="span" size="large">
<NudeButton onClick={onRequestClose}>
<CloseIcon />
</NudeButton>
</Text>
</Header>
</Centered>
</Small>
) : (
fullscreen || isMobile ? (
<Fullscreen
$nested={!!depth}
style={
@@ -116,7 +92,11 @@ const Modal: React.FC<Props> = ({
>
<Content>
<Centered onClick={(ev) => ev.stopPropagation()} column>
{title && <h1>{title}</h1>}
{title && (
<Text size="xlarge" weight="bold">
{title}
</Text>
)}
<ErrorBoundary>{children}</ErrorBoundary>
</Centered>
</Content>
@@ -125,9 +105,27 @@ const Modal: React.FC<Props> = ({
</Close>
<Back onClick={onRequestClose}>
<BackIcon size={32} />
<Text as="span">{t("Back")} </Text>
<Text>{t("Back")} </Text>
</Back>
</Fullscreen>
) : (
<Small {...props}>
<Centered
onClick={(ev) => ev.stopPropagation()}
column
reverse
>
<SmallContent shadow>
<ErrorBoundary component="div">{children}</ErrorBoundary>
</SmallContent>
<Header>
{title && <Text size="large">{title}</Text>}
<NudeButton onClick={onRequestClose}>
<CloseIcon />
</NudeButton>
</Header>
</Centered>
</Small>
)
}
</Dialog>
@@ -137,16 +135,16 @@ const Modal: React.FC<Props> = ({
);
};
const Backdrop = styled(Flex)<{ $isCentered?: boolean }>`
const Backdrop = styled(Flex)<{ $fullscreen?: boolean }>`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: ${(props) =>
props.$isCentered
? props.theme.modalBackdrop
: transparentize(0.25, props.theme.background)} !important;
props.$fullscreen
? transparentize(0.25, props.theme.background)
: props.theme.modalBackdrop} !important;
z-index: ${depths.modalOverlay};
transition: opacity 50ms ease-in-out;
opacity: 0;
@@ -190,7 +188,7 @@ const Fullscreen = styled.div<FullscreenProps>`
const Content = styled(Scrollable)`
width: 100%;
padding: 8vh 32px;
padding: 8vh 12px;
${breakpoint("tablet")`
padding: 13vh 2rem 2rem;
@@ -259,7 +257,7 @@ const Small = styled.div`
margin: auto auto;
width: 30vw;
min-width: 350px;
max-width: 500px;
max-width: 450px;
z-index: ${depths.modal};
display: flex;
justify-content: center;
+16 -3
View File
@@ -1,4 +1,4 @@
import { LocationDescriptor } from "history";
import { LocationDescriptor, LocationDescriptorObject } from "history";
import * as React from "react";
import { match, NavLink, Route } from "react-router-dom";
@@ -9,10 +9,20 @@ type Props = React.ComponentProps<typeof NavLink> & {
[x: string]: string | undefined;
}>
| boolean
| null
| null,
location: LocationDescriptorObject
) => React.ReactNode;
/**
* If true, the tab will only be active if the path matches exactly.
*/
exact?: boolean;
/**
* CSS properties to apply to the link when it is active.
*/
activeStyle?: React.CSSProperties;
/**
* The path to match against the current location.
*/
to: LocationDescriptor;
};
@@ -25,7 +35,10 @@ function NavLinkWithChildrenFunc(
{({ match, location }) => (
<NavLink {...rest} to={to} exact={exact} ref={ref}>
{children
? children(rest.isActive ? rest.isActive(match, location) : match)
? children(
rest.isActive ? rest.isActive(match, location) : match,
location
)
: null}
</NavLink>
)}
+1 -1
View File
@@ -11,7 +11,7 @@ type Props = {
};
const Notice: React.FC<Props> = ({ children, icon, description }: Props) => (
<Container>
<Container as="div">
<Flex as="span" gap={8}>
{icon}
<span>
@@ -1,19 +1,18 @@
import { observer } from "mobx-react";
import { SubscribeIcon } from "outline-icons";
import * as React from "react";
import styled, { useTheme } from "styled-components";
import styled from "styled-components";
import { s } from "@shared/styles";
import useStores from "~/hooks/useStores";
import Relative from "../Sidebar/components/Relative";
const NotificationIcon = () => {
const { notifications } = useStores();
const theme = useTheme();
const count = notifications.approximateUnreadCount;
return (
<Relative style={{ height: 24 }}>
<SubscribeIcon color={theme.textTertiary} />
<SubscribeIcon />
{count > 0 && <Badge />}
</Relative>
);
@@ -14,6 +14,7 @@ import { AvatarSize } from "../Avatar/Avatar";
import Flex from "../Flex";
import Text from "../Text";
import Time from "../Time";
import { UnreadBadge } from "../UnreadBadge";
type Props = {
notification: Notification;
@@ -40,20 +41,18 @@ function NotificationListItem({ notification, onNavigate }: Props) {
};
return (
<Link to={notification.path} onClick={handleClick}>
<StyledLink to={notification.path ?? ""} onClick={handleClick}>
<Container gap={8} $unread={!notification.viewedAt}>
<StyledAvatar model={notification.actor} size={AvatarSize.Large} />
<Flex column>
<Text as="div" size="small">
<Text as="span" weight="bold">
<Text weight="bold">
{notification.actor?.name ?? t("Unknown")}
</Text>{" "}
{notification.eventText(t)}{" "}
<Text as="span" weight="bold">
{notification.subject}
</Text>
<Text weight="bold">{notification.subject}</Text>
</Text>
<Text as="span" type="tertiary" size="xsmall">
<Text type="tertiary" size="xsmall">
<Time
dateTime={notification.createdAt}
tooltipDelay={1000}
@@ -67,12 +66,18 @@ function NotificationListItem({ notification, onNavigate }: Props) {
/>
)}
</Flex>
{notification.viewedAt ? null : <Unread />}
{notification.viewedAt ? null : <UnreadBadge style={{ right: 20 }} />}
</Container>
</Link>
</StyledLink>
);
}
const StyledLink = styled(Link)`
display: block;
margin: 0 8px;
cursor: var(--pointer);
`;
const StyledCommentEditor = styled(CommentEditor)`
font-size: 0.9em;
margin-top: 4px;
@@ -87,24 +92,13 @@ const StyledAvatar = styled(Avatar)`
const Container = styled(Flex)<{ $unread: boolean }>`
position: relative;
padding: 8px 12px;
margin: 0 8px;
padding-right: 40px;
border-radius: 4px;
&:${hover},
&:active {
background: ${s("listItemHoverBackground")};
cursor: var(--pointer);
}
`;
const Unread = styled.div`
width: 8px;
height: 8px;
background: ${s("accent")};
border-radius: 8px;
align-self: center;
position: absolute;
right: 20px;
`;
export default observer(NotificationListItem);

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