Compare commits

...

738 Commits

Author SHA1 Message Date
Tom Moor 2aaad03270 refactor 2020-08-20 19:05:48 -07:00
Tom Moor 9252683260 fix: SocketPresence account for socket changing 2020-08-20 00:06:51 -07:00
Tom Moor f5748eb5e7 check connection on page visibility change 2020-08-19 22:51:18 -07:00
Tom Moor 0555fd2caa pref: JS bundling improvements (#1461)
* perf: Split only initial vendors
2020-08-17 22:09:12 -07:00
Tom Moor d885252fb0 fix: Mobile style fixes and improvements (#1459)
* fixes #1457 – check for matchMedia function before using it

* fixes: Depth issues
closes #1458

* fixes: Long breadcrumbs cause horizontal overflow

* fix: Improve tabs and overflow on mobile
2020-08-17 00:08:22 -07:00
Tom Moor df9b0bcf91 fix: Websocket reconnect when navigating from settings -> home 2020-08-14 17:47:12 -07:00
Tom Moor 31910f1628 Remove auto reconnect, increase reconnectionDelayMax 2020-08-14 17:25:55 -07:00
Tom Moor 14cb3a36c1 perf: Reduce initial bundle size / async bundle loading (#1456)
* feat: Move to React.lazy

* perf: Remove duplicate babel/runtime

* fix: Run yarn-deduplicate

* Further attempts to remove rich-markdown-editor from initial chunk

* perf: Lazy loading of authenticated routes

* perf: Move color picker to async loading
fix: Display placeholder when loading rich editor

* fix: Cache bust on auto reload
2020-08-14 17:23:58 -07:00
Tom Moor d3350c20b6 perf: Attempt websocket connection before polling 2020-08-14 13:37:11 -07:00
Tom Moor 174acfac32 fix: Unnecessary shares.info request when loading public share (#1453)
closes #1450
2020-08-13 16:48:03 -07:00
Tom Moor 9ef4e2b437 Update LICENSE 2020-08-12 20:04:48 -07:00
Tom Moor 8088da8cf3 0.46.0 2020-08-12 20:03:57 -07:00
Tom Moor 221ee48429 fix: Don't mangle class names in production 2020-08-12 19:28:15 -07:00
Tom Moor ffe8c046ef fix: Bump RME – Improves floating toolbar behavior 2020-08-12 17:01:27 -07:00
Tom Moor dbe8a10702 fix: Login to X should be centered when team name wraps to newline 2020-08-12 14:05:32 -07:00
Tom Moor 11f7e3a060 chore: Bundle Stats / Webpack v4 (#1448)
* chore: Experiment with bundle size monitoring service

* chore: Ensure build runs on CI, move lint and flow before test

* chore: Upgrade Webpack v3 -> v4

* chore: Add webpack-cli
Remove unused dep
Move deps to dev

* Move babel deps to production

* Move babel deps to production
2020-08-12 13:16:10 -07:00
Tom Moor 0f41a04e49 refactor: Remove centralized Modal management (#1444)
* refactor: Finally remove centralized Modals component

* chore: Cleanup related unused methods in UiStore
2020-08-12 10:49:15 -07:00
Tom Moor d055021ad4 chore: Remove all usage of collection.type (#1445)
* chore: Remove all usage of collection.type

* migration: Remove type column
2020-08-12 10:49:02 -07:00
Tom Moor 810dc5a061 feat: Clicking the last updated time should open document history sidebar
Ref #1285
2020-08-11 21:01:03 -07:00
ktfth 7abe375b3e refactor: Removed unusued index on the onSearchLink (#1420)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2020-08-11 19:59:11 -07:00
Tom Moor 63371d8f5b flow 2020-08-11 18:59:57 -07:00
Tom Moor 6e61df0729 fix: Improved loading jank fix, new DelayedMount component 2020-08-10 21:30:12 -07:00
Tom Moor 5ddc4000d0 fixes: Strange scroll behavior on long collection descriptions
closes #1391
2020-08-10 16:23:55 -07:00
Tom Moor 48b61559cc fixes: JS error when attempting to show toast messages from collection description editor 2020-08-10 16:04:23 -07:00
Tom Moor 0cac5cfe51 fix: Prevent reload loop with error on editor load 2020-08-10 15:52:45 -07:00
Tom Moor e9ce80a3aa fixes: Case where websocket will not reconnect
closes #1384
2020-08-09 23:25:27 -07:00
Tom Moor 07d488c826 fix: GitHub Gist embed reliability, closes #1400 2020-08-09 21:53:57 -07:00
Tom Moor e2bd03494d chore: Update syntax, improve more typing (#1439)
* chore: <React.Fragment> to <>

* flow types
2020-08-09 09:48:04 -07:00
Tom Moor ead55442e0 flow: Restore lesser flowtype for styled-components
The current flow-typed def requires an insane amount of manual typing that just doesnt
make any sense. Restoring the old definition for now:
https://github.com/flow-typed/flow-typed/issues/3766
2020-08-08 23:41:02 -07:00
Tom Moor 449dc55aaa chore: Upgrade Babel, Jest, Eslint (#1437)
* chore: Upgrade Prettier 1.8 -> 2.0

* chore: Upgrade Babel 6 -> 7

* chore: Upgrade eslint plugins

* chore: Add eslint import/order rules

* chore: Update flow-typed deps
2020-08-08 22:53:59 -07:00
Tom Moor e312b264a6 chore: Upgrade Prettier 1.8 -> 2.0 (#1436) 2020-08-08 18:53:11 -07:00
Tom Moor 68dcb4de5f fix: Catch expected error when shares.info returns 404 2020-08-08 17:55:21 -07:00
Tom Moor d2b9a5c03f fix: Various React errors in console 2020-08-08 17:51:40 -07:00
Tom Moor 1b023fb6d7 fix: Remove flash of loading state for document lists 2020-08-08 17:39:30 -07:00
Tom Moor afe4553a7e chore: Resolve 2 open security alerts 2020-08-08 17:35:42 -07:00
Tom Moor 139e2e29d7 flow 2020-08-08 17:12:36 -07:00
Tom Moor 638418432a test 2020-08-08 16:32:12 -07:00
Tom Moor c6d2467fae chore: Upgrade Flow to v0.104.0 2020-08-08 16:26:20 -07:00
Tom Moor e9387db895 chore: Remove unused flow-typed 2020-08-08 16:05:32 -07:00
Tom Moor 065d04ec98 chore: Missing flow types 2020-08-08 15:58:34 -07:00
Tom Moor 869fc086d6 feat: Templates (#1399)
* Migrations
* New from template
* fix: Don't allow public share of template
* chore: Template badges
* fix: Collection active
* feat: New doc button on template list item
* feat: New template menu
* fix: Sorting
* feat: Templates onboarding notice
* fix: New doc button showing on archived/deleted templates
2020-08-08 15:18:37 -07:00
dependabot[bot] 59c24aba7c chore(deps): bump elliptic from 6.5.2 to 6.5.3 (#1406)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-30 19:23:44 -07:00
Tom Moor bc4806ac30 feat: Allow checkboxes to be toggled without going into 'edit' mode (#1349) 2020-07-28 20:43:34 -07:00
Tom Moor 169ad5b025 feat: Sharing improvements (#1388)
* add migrations

* first pass at API

* feat: Updated share dialog UI

* tests

* test

* styling tweaks

* feat: Show share state on document

* fix: Allow publishing share links for draft docs

* test: shares.info
2020-07-28 19:14:32 -07:00
Tom Moor 0b33b5bc05 company -> team 2020-07-25 11:33:04 -07:00
Tom Moor 109efcaa27 chore: Remove WEBSOCKETS_ENABLED flag (#1383)
* chore: Remove WEBSOCKETS_ENALBED flag

* lint
2020-07-22 22:44:24 -07:00
Nan Yu 2cc6d7add8 fix: added missing call to onOpen (#1378) 2020-07-21 21:35:27 -07:00
Joona Heikkilä 003d82fe8a refactor: Fix updater's use of UPDATES_KEY (#1376) 2020-07-21 15:05:09 -07:00
Tom Moor f75a07cb0d fix: Remove ugly blue cross 2020-07-20 19:53:13 -07:00
Tom Moor 0d6720e499 fix: Heading style regressions 2020-07-20 19:43:30 -07:00
Tom Moor a97a1df5f1 fix: Extra active outline around editor toolbar buttons 2020-07-20 19:37:50 -07:00
Tom Moor 822395c265 chore: Remove emojis from welcome docs 2020-07-20 19:14:15 -07:00
Tom Moor 96d6e9b85e fix: Breadcrumb spacing 2020-07-20 19:09:51 -07:00
Tom Moor 70e1194f90 feat: Notice blocks available as new editor options (#1371)
* feat: Notice blocks available as new editor options

* fix: styling tweak
feat: Add shortcut to keyboard modal

* add notices to welcome docs

* styling tweaks

* styling tweaks

* styling tweaks
2020-07-20 19:03:14 -07:00
Tom Moor 710fcc697c feat: Add login link to /create page, closes OLN-63 2020-07-19 10:58:35 -07:00
Nan Yu 58f9e95d2f feat: nicer gradient mask for hover previews (#1367)
* feat: nicer gradient mask for hover previews

* tweak the stops on gradient mask
2020-07-18 18:25:54 -07:00
Tom Moor bc128359ab chore: Remove Spectrum references (#1366)
* fix: knowledgebase -> knowledge base

* chore: Remove links and mentions to Spectrum community
2020-07-18 17:19:13 -07:00
Tom Moor af09713c8c fix: knowledgebase -> knowledge base 2020-07-18 13:17:10 -07:00
Tom Moor 35052ef38f 0.45.0 2020-07-18 11:52:32 -07:00
Tom Moor ec3adc6d1c fix: 2 missed process.env spots on frontend 2020-07-18 11:33:34 -07:00
Tom Moor 67981a351e chore: Remove env variables in webpack bundle (#1353)
* chore: Remove env variables in webpack bundle

* remove unused globals

* refactor: consolidate window.env calls to single file

* fix: Slack client side integration auth

* fix: developers url
2020-07-18 11:02:40 -07:00
Necmettin Karakaya 24448c7504 fix: common misspelling errors
https://github.com/outline/outline/blob/master/app/scenes/UserDelete.js#L40: corrected "destory" to "destroy"
https://github.com/outline/outline/blob/master/app/components/ScrollToTop.js#L16: corrected "postion" to "position"
https://github.com/outline/outline/blob/master/server/policies/document.js#L11: corrected "existance" to "existence"
https://github.com/outline/outline/blob/master/server/policies/document.js#L23: corrected "existance" to "existence"
https://github.com/outline/outline/blob/master/server/models/Document.js#L493: corrected "permanantly" to "permanently"
https://github.com/outline/outline/blob/master/shared/utils/domains.js#L12: corrected "unneccessarily" to "unnecessarily"
https://github.com/outline/outline/blob/master/server/api/documents.js#L34: corrected "compatablity" to "compatibility"
2020-07-18 09:33:27 -07:00
Tom Moor d5b5d4fc27 feat: Document hover cards (#1346)
* stash

* refactor

* refactor, styling

* tweaks

* pointer

* styling

* fi: Hide when printing

* fix: No hover cards on shared links

* remove suppressions no longer needed

* fix: Don't show hover cards when editing, they get in the way

* fix: Prevent hover card from going off rhs edge of screen

* fix: Remount hover card when changing between links

* fix: allow one part domains in links (#1350)

* allow one part domains in links

* no TLD when only one part domain

* return null for parseDomain of empty string

* fix fiddly hover preview behavior

* WIP

* refactor hover preview

* fix: Non-rounded bottom corners

* fix: Fixes an edgecase where mounting the nested editor in hovercard causesdocument to scroll if there is a hash in the url

* fix: Incorrect document preview rendering

* lint

Co-authored-by: Nan Yu <thenanyu@gmail.com>
Co-authored-by: Nan Yu <nan@getoutline.com>
2020-07-16 21:26:23 -07:00
dependabot[bot] d8603cc961 chore(deps): bump lodash from 4.17.15 to 4.17.19 (#1361)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-16 17:30:56 -07:00
Tom Moor 943a290b83 chore: Update flow just far enough to get hooks libdefs (#1348) 2020-07-13 20:23:15 -07:00
Tom Moor f4a4f034cf Merge branch 'MatheusRV-feat/change-title-based-on-teamName' into develop 2020-07-13 19:42:57 -07:00
Tom Moor a53934a5c9 fix 2020-07-13 19:41:42 -07:00
Tom Moor e859e3b9e0 Merge branch 'feat/change-title-based-on-teamName' of https://github.com/MatheusRV/outline into MatheusRV-feat/change-title-based-on-teamName 2020-07-13 19:13:59 -07:00
Tom Moor b51d818db3 feat: Adds documents.export endpoint to return cleaned up Markdown (#1343) 2020-07-13 18:23:15 -07:00
Tom Moor bfea742650 fix: Makefile test runner 2020-07-11 12:41:48 -07:00
Tom Moor 9951cbf194 fix: Unneccessary redirect to /home 2020-07-11 09:51:10 -07:00
Tom Moor 5cb04d7ac1 New login screen (#1331)
* wip

* feat: first draft of auth.config

* chore: auth methodS

* chore: styling

* styling, styling, styling

* feat: Auth notices

* chore: Remove server-rendered pages, move shared/components -> components

* lint

* cleanup

* cleanup

* fix: Remove unused component

* fix: Ensure env variables in prod too

* style tweaks

* fix: Entering SSO email into login form fails
fix: Tweak language around guest signin
2020-07-09 22:33:07 -07:00
Tom Moor 75561079eb closes: Lucidchart embed does not work for app subdomain
closes #1340
2020-07-09 21:07:35 -07:00
Matheus Rocha Vieira d2c7f3f166 Update app/components/PageTitle.js
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2020-07-08 14:48:14 -03:00
Matheus Rocha Vieira a077766bff Update app/components/Layout.js
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2020-07-08 14:48:05 -03:00
Matheus Breguêz a25e03d7cd Fix Name Mistake & Lint 2020-07-07 09:11:19 -03:00
Tom Moor 2953d09ee1 fix: Bump RME 2020-07-05 20:44:37 -07:00
Tom Moor 28d15b1573 Update LICENSE 2020-07-03 21:14:21 -07:00
Tom Moor 18108cb359 0.44.0 2020-07-03 13:11:01 -07:00
Tom Moor 5dfa6a71a4 chore: Remove Lato
closes #1295
2020-07-01 21:40:34 -07:00
Matheus Breguêz 35270cd104 add mobx observer and inject 2020-07-01 09:14:21 -03:00
Matheus Breguêz 19d01ed575 Add Outline in Title 2020-07-01 09:09:21 -03:00
Matheus Breguêz 2b7639c903 Improve Title when TeamName is empty 2020-07-01 08:56:49 -03:00
Matheus Breguêz c8269ca134 Remove Polices 2020-06-29 17:06:47 -03:00
Matheus Breguêz da9fc5bfdf New title based on TeamName 2020-06-29 17:01:34 -03:00
Tom Moor efcfda8398 fix: Port from hosted 2020-06-22 22:11:39 -07:00
Tom Moor ce2f69342c Fix: Port fix from hosted 2020-06-22 22:00:40 -07:00
Tom Moor 9be5597f4b chore: Lint shared directory 2020-06-22 21:29:21 -07:00
Tom Moor 26f6961c82 chore: Include new brand colors in icon color suggestions 2020-06-22 21:24:42 -07:00
Tom Moor bf5f83e97d Subtle styling adjustments to dropdown menus 2020-06-22 21:18:43 -07:00
Tom Moor 8db0260a1a Bump RME 2020-06-22 21:10:24 -07:00
Tom Moor edbae275ae Bump RME 2020-06-22 20:55:29 -07:00
Tom Moor f43deb7940 chore: Move to prettier standard double quotes (#1309) 2020-06-20 13:59:15 -07:00
Tom Moor 2a3b9e2104 chore: Make editor version comparison more lenient to reduce forced reloads 2020-06-20 12:35:32 -07:00
Tom Moor 64c3ff8d6b chore: Remove 'DEPLOYMENT' env option
Add 'Installation' section
2020-06-19 19:11:02 -07:00
Tom Moor 4f7e7ec853 Update package.json to match reality
This will be kept up to date from now on
2020-06-19 17:34:59 -07:00
Tom Moor d864e228e7 feat: Collection Icons (#1281)
* wip: Working for creation, and display

* feat: IconPicker

* fix

* feat: Invert collection icon color when dark in dark mode

* Improve readability of dropdown menus in dark mode
Suggest icon based on collection name

* Add additional icons
Tweaks and final polish

* fix: Write default icon as empty icon column

* feat: Improve icon selection logic
add more keywords
Improve icon coloring when selected and in dark mode

* lint

* lint
2020-06-19 17:18:03 -07:00
Tom Moor f3ea02fdd0 Bump RME, closes #1305 2020-06-18 23:54:02 -07:00
Tom Moor ed096013e4 fix: Cleanup print display 2020-06-18 22:10:20 -07:00
Tom Moor 2e376b32ec fix: sp mistake 2020-06-18 08:49:10 -07:00
Tom Moor f352b35e13 Bump RME 2020-06-16 21:11:01 -07:00
Tom Moor 0f8d503df8 chore: API Consistency (#1304)
* chore: Addressing API inconsistencies

* lint

* add: Missing sort to groups.list
fix: Documention issues

* test: fix

* feat: Add missing shares.info endpoint

* feat: Add sorting to users.list endpoint

* fix: Incorrect pagination parameters listed on user endpoints

* users.s3Upload -> attachments.create

* chore: exportAll -> export_all
2020-06-16 20:56:17 -07:00
Tom Moor 5010b08e83 Bump RME 2020-06-12 22:15:48 -07:00
Tom Moor 933bbdfb84 feat: Add ability to create docs from link editor (#1303)
* feat: Add ability to create docs from link editor

* fix: Handling of paste and click events

* fix: Filter untitled documents from search results

* refactor: Move onCreateLink to DataLoader

* bump rme
2020-06-12 00:19:03 -07:00
Tom Moor d25a9d56dc fix: updatedBy incorrect in documents.update response 2020-06-12 00:18:38 -07:00
Tom Moor 1d8c3f3faf fix: Suspended users showing in group and collection member management (#1302)
* fix: Suspended users showing in group and collection member management

* test
2020-06-11 08:48:47 -07:00
Tom Moor d7766280a9 fix: Possible fix for intermittent CI failures
https://github.com/facebook/flow/issues/8058
2020-06-09 21:50:25 -07:00
moekhalil ae5eff2914 Make MenuItem Links work (#1299)
Menu items pointed to anchors links that were non-existent. 
Adding id's to proper sections for anchor links in menu to work.
2020-06-09 20:39:22 -07:00
Tom Moor b444874944 fix: Shared documents with system in dark mode display partially on light background
closes #1300
2020-06-09 20:38:34 -07:00
Tom Moor 20efa82ad9 Add additional restricted subdomains 2020-06-05 23:46:32 -07:00
Tom Moor bd9d4b3d0d fix: backslash in search query not escaped 2020-06-03 23:59:59 -07:00
Tom Moor 4b4f4fd188 flow 2020-06-02 23:17:54 -07:00
Tom Moor 33815639f2 fix: Improved handling of simultaneous edits 2020-06-02 23:16:15 -07:00
Tom Moor 05e24df226 fix: Update value when saved elsewhere and viewing doc
closes #1103
2020-05-31 22:46:55 -07:00
Tom Moor d2d9164fa1 chore: Remove unused component 2020-05-31 21:19:44 -07:00
Tom Moor f2eb395e8d fix: regex -> startsWith 2020-05-31 16:43:51 -07:00
Tom Moor 133cb56cca fix: Use real icon on settings back arrow 2020-05-30 18:26:40 -07:00
Tom Moor e752dba566 fix: User profile should say 'invited' instead of 'joined' when user has yet to signin 2020-05-30 18:26:40 -07:00
Tom Moor c1a141d99f fix: Make it possible to downgrade permissions of suspended user
closes #1291
2020-05-30 18:26:40 -07:00
Tom Moor ccedea55d6 chore: Reduce loading jank in sidebar (#1294) 2020-05-30 12:50:08 -07:00
Tom Moor c929f83813 feat: Improved error filtering and reporting (#1293) 2020-05-29 07:22:09 -07:00
Tom Moor 1b25d12e2e fix: Regression with upgrade to styled-components@5 – new ServerStyleSheet needed per render 2020-05-28 20:49:07 -07:00
Tom Moor 0c254285a1 fix: Improved readability of input placeholders in dark mode 2020-05-28 20:08:02 -07:00
Tom Moor 8b274c3713 fix: Keyboard shortcuts dialog shortcut should not be active when editing a document
closes #1292
2020-05-28 19:44:52 -07:00
Tom Moor 5a20a35c8b Bump RME 2020-05-28 08:25:25 -07:00
Tom Moor 72635e98e6 Remove precommit hook 2020-05-28 08:25:25 -07:00
Tom Moor 7d55b7f69b fix: Server error when listing memberships for group with deleted user (#1288)
* fix: Server error when listing memberships for group with deleted user

* PR feedback: Filter before slice
2020-05-28 08:21:22 -07:00
Tom Moor ca1ba277ad fix: Gist embed error on load 2020-05-27 23:04:34 -07:00
Tom Moor 32562886aa fix: Bump RME, closes #1286 2020-05-26 20:18:07 -07:00
Tom Moor 7f07cb57a2 chore: Capture event data to error tracker when background jobs fail 2020-05-25 13:48:50 -07:00
Tom Moor 62dd1e41f9 fix: Invariant violation reported to error tracker when client is reloading due to editor update 2020-05-25 13:10:18 -07:00
Tom Moor 446a9ade8c fix: Imported documents should get a best-guess title 2020-05-24 23:10:55 -07:00
Tom Moor a281b1e5be fix: Documents with empty titles (hey, it can happen) are invisible in the sidebar) 2020-05-24 22:43:54 -07:00
Tom Moor 1b5600b025 chore: Cleaner Markdown output when exporting docs 2020-05-24 22:22:06 -07:00
Tom Moor f0e513542d fix: auto focus search input when adding users to a collection 2020-05-24 22:17:11 -07:00
Tom Moor 4797a5fc77 fix: Invalid regex error on Safari when filtering users with certain characters 2020-05-24 22:14:09 -07:00
Tom Moor f05035c0b7 fix: Incorrect overlay background when zooming images 2020-05-23 22:22:19 -07:00
Tom Moor 75cfbd8970 fix: More migration tweaks 2020-05-23 19:23:31 -07:00
Tom Moor c4837f1943 fix: Potential corruption migrating v1 docs to v2 when a mark is adjacent the same mark. This was a quirk of the old editor 2020-05-23 12:26:26 -07:00
Tom Moor 0b3166dab5 Bump yarn.lock 2020-05-23 11:06:37 -07:00
Tom Moor 603f7962a2 fix: An unknown language annotation in code fences will no longer throw an error. It was only possible to hit this if you imported markdown from another source.
closes #1284
2020-05-23 11:01:07 -07:00
Tom Moor 69d16dd1d4 fix: Tables with empty cells are corrupted when converted to v2 documents
fix: Marks with trailing or leading empty spaces are corrupted when converted to v2 documents
2020-05-23 10:58:46 -07:00
Tom Moor e2ffe06221 fix: Document title not centered once scrolled 2020-05-22 14:19:26 -07:00
Tom Moor 6e2ea3ac4b fix: overflow menu on history revision is incorrect color when selected
closes #1140
2020-05-21 22:22:04 -07:00
Tom Moor f4c4a11277 fix: Only transfer accessToken if matches root token 2020-05-21 21:42:46 -07:00
Tom Moor c28dc08f6a fix: Alignment of bullet list
closes #1277
2020-05-21 21:22:33 -07:00
Tom Moor 4ef82d5476 test 2020-05-20 23:48:37 -07:00
Tom Moor 3487eb7857 fix: Submenu auto close 2020-05-20 23:40:54 -07:00
Tom Moor 87020348ce lint 2020-05-20 23:20:05 -07:00
Tom Moor 672ffacc5b feat: Have theme follow system pref 2020-05-20 23:19:07 -07:00
Tom Moor 218b0ea76a fix: Unable to edit starred documents
closes #1275
2020-05-20 21:42:26 -07:00
Tom Moor 47ff6feaee fix: JS error on server 2020-05-20 21:03:53 -07:00
Tom Moor 8ac5a574f3 Merge branch 'master' of github.com:outline/outline 2020-05-20 21:01:10 -07:00
Ante Primorac aed76c7bcb fix revisions backup data type (#1274) 2020-05-20 10:50:52 -07:00
Tom Moor 7f9cd51f37 Update bug template to account for self hosted 2020-05-20 08:30:05 -07:00
Tom Moor 092b29ee63 fix: Breadcrumb separator color in dark mode 2020-05-19 22:34:04 -07:00
Tom Moor ee7e1959c9 fix: Toast color in dark mode 2020-05-19 21:55:26 -07:00
Tom Moor 092d9dce18 fix: Don't set cookie domain when not using multiple subdomains (#1145)
* fix: Don't set cookie domain when not using multiple subdomains

* wip logging domain

* wip logging domain

* wip logging domain

* wip logging domain

* Revert "wip logging domain"

This reverts commit 325907e749.

* Revert "wip logging domain"

This reverts commit 6ee095a49e.

* Revert "wip logging domain"

This reverts commit 813d8eb960.

* Revert "wip logging domain"

This reverts commit f1ca819276.

* Remove SUBDOMAINS_ENABLED from documented env variables, no-one self hosting should need this – it just adds confusion to those looking to host on a single subdomain
fix: Account for server/client process.env parsing

Co-authored-by: Nan Yu <nanyu@Nans-MBP-2.lan>
Co-authored-by: Nan Yu <nan@getoutline.com>
2020-05-19 21:05:57 -07:00
Tom Moor 9274005cbb feat: Upgrade editor (#1227)
* WIP

* document migration

* fix: Handle clashing keyboard events

* fix: convert getSummary

* fix: parseDocumentIds

* lint

* fix: Remove unused plugin

* Move editor version to header
Add editor version check for API endpoints

* fix: Editor update auto-reload
Bump RME

* test

* bump rme

* Remove slate flow types, improve themeing, bump rme

* bump rme

* fix: parseDocumentIds returning duplicate ID's, improved regression tests

* test

* fix: Missing code styles

* lint

* chore: Upgrade v2 migration to use AST

* Bump RME

* Update welcome doc

* add highlight to keyboard shortcuts ref

* theming improvements

* fix: Code comments show as headings, closes #1255

* loop

* fix: TOC highlighting

* lint

* add: Automated backup of docs before migration

* Update embeds to new format

* fix: React warning

* bump to final editor version 10.0.0

* test
2020-05-19 20:39:34 -07:00
Tom Moor 400a1c87bb lint 2020-05-18 20:22:58 -07:00
Tom Moor 93abbab44a feat: Show different icon in theme menu to switch to light mode 2020-05-17 19:15:55 -07:00
Tom Moor 18cf148bd1 chore: Improve performance in dev by running Node/Yarn outside of docker (#1271)
* Improve performance in dev by running Node/Yarn outside of docker

* Transpose exposed port numbers by 100, so less likely conflict with host processes
2020-05-17 18:12:48 -07:00
Tom Moor e0b33ee576 chore: Auto reload frontend of client is out of date (#1270)
* Move editor version to header
Add editor version check for API endpoints

* fix: Editor update auto-reload
Bump RME

* fix: Only redirect if editor header exists

* lint
2020-05-16 14:05:51 -07:00
Tom Moor 82749ffbd8 Remove Spectrum badge 2020-05-12 08:27:17 -07:00
Tom Moor 231aab6ef8 lint 2020-05-12 08:26:31 -07:00
Tom Moor f8077e2125 Add github template chooser 2020-05-12 08:25:11 -07:00
Tom Moor 9d9435cce5 fix: Server error in hooks.slack endpoint if team has no public collections 2020-05-10 18:49:57 -07:00
Tom Moor 5deec264bc chore: Add request-id to error tracking 2020-05-10 16:24:34 -07:00
Tom Moor 48c87a1902 fix: Long titles should wrap
closes #1249
2020-05-07 21:21:58 -07:00
Tom Moor 06a3258b99 fix: Allow empty document body to be saved
closes #1258
2020-05-07 20:52:02 -07:00
Tom Moor bd2837250b fix: Guard missing attachment, closes #1262 2020-05-07 20:37:36 -07:00
Tom Moor e1adb16c43 fix: Incompatible with node 14 2020-05-07 20:33:33 -07:00
Tom Moor d914ecb603 chore: Remove max listener warning in console 2020-04-26 14:08:39 -07:00
Tom Moor 187be4737e fix: Log errors to console when Sentry not installed 2020-04-25 19:53:24 -07:00
Tom Moor 870b91f17a fix: Various extra scrollbars when not using mac-style overlaying scrollbars (#1242)
* fix: Various extra scrollbars when not using mac-style overlaying scrollbars

* Sidebar z-index
2020-04-24 18:44:21 -07:00
Tom Moor 6db92c9f49 remove unused images 2020-04-24 05:33:13 -07:00
Tom Moor c41e6e0423 fix: Minor fixes in backlinks service (#1240) 2020-04-24 05:29:48 -07:00
Tom Moor 9f8e7be755 fix: Restore ability to disable embeds on a document (#1238)
closes #1237
2020-04-21 21:43:01 -07:00
Tom Moor cead37051e fix: test prevents server loading, add logs 2020-04-19 22:14:31 -07:00
Tom Moor fd99da96af chore: upgrade deps 2020-04-19 22:07:17 -07:00
Tom Moor c526adf292 feat: Auto update titles in linked documents (#1233)
* feat: Auto update titles in linked documents

* Add spec
2020-04-19 21:58:42 -07:00
Tom Moor ee5ae140c3 fix: Improve neutral button styling in dark mode 2020-04-11 09:47:52 -07:00
Tom Moor 083ac0d840 fix: Image cropper in dark mode
closes #1229
2020-04-11 09:34:33 -07:00
Tom Moor fbaaa08ec7 closes #1230 2020-04-11 09:27:14 -07:00
Tom Moor b536c682a2 fix: Escape characters visible in TOC
closes #1226
2020-04-06 09:01:03 -07:00
Tom Moor c94823dd59 fix: Failed editor chunk load should refresh page 2020-04-06 08:50:43 -07:00
Tom Moor 1a60f51460 fix: Attempt to focus readonly editor
fix: Non-grow clickable padding beneath editor regression
2020-04-05 22:48:48 -07:00
Tom Moor abf91a3a51 fix: Share link rendering 2020-04-05 22:42:55 -07:00
Tom Moor 383806d155 fix: Document shrinks if only content is embed 2020-04-05 18:44:05 -07:00
Tom Moor 7413e8bf7a Update LICENSE 2020-04-05 18:18:01 -07:00
Tom Moor 7c1aa7622a Update README screenshot 2020-04-05 17:01:54 -07:00
Tom Moor f94efaada8 Update README screenshot 2020-04-05 17:00:30 -07:00
Tom Moor 283a762a9c fix: Title index 2020-04-05 16:46:03 -07:00
Tom Moor cef687464a chore: Improved onboarding docs 2020-04-05 16:32:12 -07:00
Tom Moor 8287355261 CI 2020-04-05 16:11:16 -07:00
Tom Moor 02d33267cc fix: Document updated email does include team subdomain in url
fix: Send document updated emails to any collaborators
fix: Correct quotation marks in email subjects
2020-04-05 16:04:46 -07:00
Tom Moor b964bdbe90 lint 2020-04-05 15:53:27 -07:00
Tom Moor c832265e8a fix: Account for emoji-offset title 2020-04-05 15:50:37 -07:00
Tom Moor 8819a0836e fix: Initial welcome docs account for new title field 2020-04-05 15:41:29 -07:00
Tom Moor 9338a54fe0 feat: Separate title from body (#1216)
* first pass at updating all Time components each second

* fix a couple date variable typos

* use class style state management instead of hooks

* wip: Separate title from body

* address feedback

* test: Remove unused test

* feat: You in publishing info language
fix: Removal of secondary headings

* After much deliberation… a migration is needed for this to be reliable

* fix: Export to work with new title structure

* fix: Untitled

* fix: Consistent spacing of first editor node

* fix: Emoji in title handling

* fix: Time component not updating for new props

* chore: Add createdAt case

* fix: Conflict after merging new TOC

* PR feedback

* lint

* fix: Heading level adjustment

Co-authored-by: Taylor Lapeyre <taylorlapeyre@gmail.com>
2020-04-05 15:07:34 -07:00
Tom Moor a0e73bf4c2 fix: Add shortcut for toggling document contents to help 2020-04-05 13:33:50 -07:00
Tom Moor 8a0263093b fix: Back button on modals should not scroll off screen 2020-04-05 13:33:21 -07:00
Tom Moor 9d8e99400f fix: Various React errors in console 2020-04-05 13:27:11 -07:00
Tom Moor 98b5350c65 lint 2020-04-05 13:21:03 -07:00
Tom Moor 3bd1c1f047 fix: Doc history does not load when linked to directly (race condition) 2020-04-05 13:20:47 -07:00
Tom Moor 9712b8d205 fix: Document history should scroll separate to page content
closes #1225
2020-04-05 13:07:16 -07:00
Tom Moor 597c09d2bc fix: Non-toc horizontal heading alignment 2020-04-05 12:50:38 -07:00
Tom Moor d0606a72c3 feat: Improved table of contents (#1223)
* feat: New table of contents

* fix: Hide TOC in edit mode

* feat: Highlight follows scroll position

* scroll tracking

* UI

* fix: Unrelated css fix with long doc titles

* Improve responsiveness

* feat: Add keyboard shortcut access to TOC

* fix: Headings should reflect content correctly when viewing old document revision

* flow

* fix: Persist TOC choice between sessions
2020-04-05 12:22:26 -07:00
Nan Yu 0deecfac44 make the team logo a little friendlier for first timers (#1222) 2020-04-04 15:34:11 -07:00
Nan Yu f534203cbd Merge pull request #1218 from outline/thenanyu-patch-1
docs: minor ergonomic updates to readme
2020-03-30 16:43:27 -07:00
Nan Yu f521543b0a Update README.md 2020-03-30 08:57:21 -07:00
Nan Yu 7da0d7589e minor ergonomic updates to readme 2020-03-30 08:26:37 -07:00
Tom Moor 09dea295a2 fix: Cleanup S3 Attachments (#1217)
* fix: Server error if attempting to load an unknown attachment

* fix: Migration should cascade delete to document attachments

* fix: Delete S3 attachments along with documents
2020-03-28 15:56:01 -07:00
Taylor Lapeyre d3773dc943 fix: Update all Time components each second (#1214)
* first pass at updating all Time components each second

* fix a couple date variable typos

* use class style state management instead of hooks

* address feedback

* lint

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2020-03-23 22:31:17 -07:00
Tom Moor 5db43f2607 lint 2020-03-23 22:26:17 -07:00
Tom Moor 4b3ddf8769 fix: Account for no text param passed to hooks.slack
This will never happen in production, Slack always provides the param but prevents a possible 500 server error when messing with the API manually.
2020-03-22 16:41:15 -07:00
Tom Moor 7a6ed86c95 Update LICENSE 2020-03-16 23:19:16 -07:00
Tom Moor f0be9beeb4 feat: Ensure that editorVersion is saved with document/revisions (#1212) 2020-03-16 08:30:23 -07:00
Tom Moor 4851f51d8b docs: Add documentation for groups API endpoints (#1211)
* docs: Add documentation for groups API endpoints
fix: Remove useless permission query param

* restore permission filter on collections.group_memberships

* tweak language
2020-03-16 08:30:15 -07:00
David Miranda e611979a8d Absolute routes won't work when users have subdomains (#1208) 2020-03-15 21:04:46 -07:00
Bryan Joseph 05af318a1d feat: Split up check for Slack environment variables (#1207)
* Split up check for Slack environment variables

This allows for the Slack slash command to be used without
needing Slack sign in enabled.

* Remove conditional checking for slack app id

Co-authored-by: Bryan Joseph <bryanjos@users.noreply.github.com>
2020-03-15 20:53:15 -07:00
Nan Yu 142303b3de feat: Add groups and group permissions (#1204)
* WIP - got one API test to pass yay

* adds group update endpoint

* added group policies

* adds groups.list API

* adds groups.info

* remove comment

* WIP

* tests for delete

* adds group membership list

* adds tests for groups list

* add and remove user endpoints for group

* ask some questions

* fix up some issues around primary keys

* remove export from group permissions

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* remove random file

* only create events on actual updates, add tests to ensure

* adds uniqueness validation to group name

* throw validation errors on model and let it pass through the controller

* fix linting

* WIP

* WIP

* WIP

* WIP

* WIP basic edit and delete

* basic CRUD for groups and memberships in place

* got member counts working

* add member count and limit the number of users sent over teh wire to 6

* factor avatar with AvatarWithPresence into its own class

* wip

* WIP avatars in group lists

* WIP collection groups

* add and remove group endpoints

* wip add collection groups

* wip get group adding to collections to work

* wip get updating collection group memberships to work

* wip get new group modal working

* add tests for collection index

* include collection groups in the withmemberships scope

* tie permissions to group memberships

* remove unused import

* Update app/components/GroupListItem.js

update title copy

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update server/migrations/20191211044318-create-groups.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update server/api/groups.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update server/api/groups.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/menus/CollectionMenu.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update server/models/Group.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* minor fixes

* Update app/scenes/CollectionMembers/AddGroupsToCollection.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/menus/GroupMenu.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/menus/GroupMenu.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/menus/GroupMenu.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/scenes/Collection.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/scenes/CollectionMembers/CollectionMembers.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/scenes/GroupNew.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/scenes/GroupNew.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/scenes/Settings/Groups.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update server/api/documents.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* Update app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* address comments

* WIP - getting websocket stuff up and running

* socket event for group deletion

* wrapped up cascading deletes

* lint

* flow

* fix: UI feedback

* fix: Facepile size

* fix: Lots of missing await's

* Allow clicking facepile on group list item to open members

* remove unused route push, grammar

* fix: Remove bad analytics events
feat: Add group events to audit log

* collection. -> collections.

* Add groups to entity websocket events (sync create/update/delete) between clients

* fix: Users should not be able to see groups they are not a member of

* fix: Not caching errors in UI when changing group memberships

* fix: Hide unusable UI

* test

* fix: Tweak language

* feat: Automatically open 'add member' modal after creating group

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2020-03-14 20:48:32 -07:00
Tom Moor 6c451a34d4 Update README.md 2020-03-13 20:15:57 -07:00
Tom Moor fa4f1846ec Update LICENSE (#1197) 2020-03-07 10:10:42 -08:00
Tom Moor 4baf5ce99a test: Google embeds (#1202)
Update slides to only embed pub links
2020-03-07 10:10:26 -08:00
Tom Moor 533ec3bd9c add: Support for published google docs / sheets 2020-03-06 22:45:32 -08:00
Tom Moor 572127b830 fix: Point changelog link to public site
Related to #1195
2020-02-28 19:07:25 -08:00
Tom Moor f0afa67012 fix: Focus on empty document after creation
fix: Clicking in whitespace below document should focus
Remove unused component
2020-02-26 22:29:22 -08:00
Tom Moor dac2d43f55 dashboard -> home (#1194) 2020-02-26 21:10:20 -08:00
Tom Moor d06ec5ce0c fix: Nested document menu item appears where it shouldnt (#1193) 2020-02-26 21:10:10 -08:00
Tom Moor 148affb52e fix: Editing with document history open attempts to edit old revision
fix: Document history sidebar missing background
fix: 'Publish' action should not appear when viewing history of drafts
closes #1184
2020-02-26 21:08:36 -08:00
Tom Moor afba1edae4 feat: Adds visual stacking to nested modals (#1189)
Fixes various mobile styling/layout issues
2020-02-26 19:36:23 -08:00
Tom Moor f8c53f8a88 fix: Pagination of people list items (#1192) 2020-02-25 22:50:18 -08:00
Tom Moor 0b86714984 fix: Newly created private collections do not return correct policies (#1188)
closes #1185
2020-02-24 23:16:24 -08:00
Tom Moor 3e7acc377e fix: Rich embeds should work on public share links
closes #1182
2020-02-22 17:19:16 -08:00
Tom Moor 4cb48e7310 fix: Document gets 'stuck' when navigating between docs with similar slugs
closes #1175
2020-02-18 20:13:01 -08:00
Tom Moor 8daef8ebce fix: Reload app if error loading editor chunk 2020-02-17 11:29:58 -08:00
Tom Moor 760e2b2ce9 fix: Attempt to loadRevision with empty revisionId 2020-02-17 10:23:02 -08:00
Tom Moor 902e7a4772 fix: Handle private attachments without documentId (migration process) 2020-02-17 09:38:34 -08:00
Tom Moor 3b0b37628e fix: document.updated slack service hook 2020-02-17 09:29:48 -08:00
Tom Moor c973436870 chore: Disable Sentry breadcrumbs 2020-02-17 08:15:46 -08:00
Tom Moor 41fb2826b3 fix: Sentry CSP 2020-02-16 23:45:00 -08:00
Tom Moor c15cbd06a4 chore: Bugsnag -> Sentry (#1178)
* Bugsnag -> Sentry

* fix: Import style
2020-02-16 22:58:50 -08:00
Tom Moor 8fbd4a7463 fix: Tweak branding, OSS landing page 2020-02-16 19:49:24 -08:00
Tom Moor e18d0fdc77 fix: import 2020-02-16 19:27:40 -08:00
Tom Moor 5a20f6322f remove changelog from OSS 2020-02-16 19:25:12 -08:00
Tom Moor 0556e2a32c add: AWS_S3_ACL to app.json 2020-02-16 19:15:04 -08:00
Tom Moor 908b457dec fix: Use alternative redirect method (#1176) 2020-02-16 17:28:24 -08:00
Tom Moor 71ed0844b9 fix: Pass github authorization via header instead of query string 2020-02-12 21:12:14 -08:00
Tom Moor 394be7ba74 fix: remove unused files 2020-02-12 21:12:00 -08:00
Tom Moor 8225a924c1 fix: CSP for development AWS assets 2020-02-12 19:42:29 -08:00
Huss 8e2b19dc7a feat: private content (#1137)
* save images as private and serve via signed url from images.info api

* download private images to directory on export

* fix lint errors

* private s3 default, AWS.s3 module level scope, default s3 url expiry

* combine regex to one, and only replace when there are matches

* fix lint

* code not needed anymore, remove

* updates after pulling master

* revert the uploadToS3FromUrl url return

* use model gettr to compact code, rename to attachments api

* basic checking of document read permission to allow attachment viewing

* fix: Continue to upload avatars as public
fix: Allow redirect for non-private attachments

* add support for publicly shared documents

* catch errors which crash the app during zip export and user creation

* add tests

* enable AWS signature v4 for s3

* switch to use factories to build models for testing

* add isDocker flag for local serving of attachment redirect url

* fix redirect tests

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2020-02-12 19:40:44 -08:00
Tom Moor 064d8cea44 fix #1174 – Allow _~ in url slugs 2020-02-12 09:14:42 -08:00
Mark Cabanero 241d557c90 fix: Add UTILS_SECRET to .env.sample (#1166) 2020-01-23 18:38:50 -08:00
Tom Moor 8e5a5a57a9 feat: Store image uploads as attachments in database (#1144)
* First pass

* Documentation

* Added optional documentId relationship

* name -> key

* cleanup: No need for separate documentId prop
2020-01-16 09:42:42 -08:00
Tom Moor 22230c25e5 perf: Use progressive rendering on PaginatedList component (#1156)
* Use progressive rendering on PaginatedList component
Move drafts and starred views to paginated

* heading
2020-01-13 18:17:56 -08:00
Tom Moor 5b78cb8963 perf: Reuse redis connections where possible (#1157)
* reuse redis connections where possible

* redis -> ioredis
2020-01-13 18:17:41 -08:00
Tom Moor f231c664e6 fix: ColorPicker preview height 2020-01-11 19:36:52 -08:00
Tom Moor 6d14dd5028 fix: missing autoComplete values, input types 2020-01-11 19:35:26 -08:00
Tom Moor f776741e77 Update copyright 2020-01-11 15:54:47 -08:00
Tom Moor aa61c37442 fix: View does not appear for first ever view
closes #1152
2020-01-11 13:54:49 -08:00
Tom Moor e38f4996ae feat: Return view model from views.create (#1153)
* feat: Return view model from views.create
Towards #1152

* fix: presenter data
2020-01-11 13:46:13 -08:00
Tom Moor cd3035a692 feat: Add search input to collection and home (#1149)
* feat: Add search input to collection and home

* Tweak spacing

* Add input to drafts/starred too
2020-01-09 19:14:34 -08:00
Tom Moor 0ccbc6126b fix: Drafts appear in document insert search (#1148)
* fix: Drafts appear in document insert search

* test
2020-01-05 17:24:57 -08:00
Tom Moor 9214be5645 fix: Documents not removed from local store when collection is deleted 2020-01-04 17:31:26 -08:00
Tom Moor c1c316b379 fix: Error loading share link view.
closes #1143
2020-01-04 14:02:54 -08:00
Tom Moor 7bbddcaebf fix: Unable to focus into description in collection edit 2020-01-02 22:49:40 -08:00
Tom Moor 9c66a14fec fix: API key create/delete should create audit events
closes #1101
2020-01-02 22:20:13 -08:00
Tom Moor 321e5c9cbd Update LICENSE 2020-01-02 21:46:16 -08:00
Tom Moor 146e4da73b feat: Document presence indicator (#1114)
* Update websockets to allow joining document-based rooms

* dynamic websocket joining

* emit user.join/leave events when entering and exiting document rooms

* presence storage

* feat: frontend presence store

* lint

* UI updates

* First pass editing state

* refactoring

* Timeout per user/doc
lint

* Document data loading refactor to keep Socket mounted

* restore: Mark as viewed functionality
Add display of 'you' to collaborators

* fix: Socket/document remount when document slug changes due to title change

* Revert unneccessary package update

* Move editing ping interval to a shared constant

* fix: Flash of sidebar when loading page directly on editing mode

* separate document and revision loading

* add comments for socket events

* fix: Socket events getting bound multiple times on reconnect

* fix: Clear client side presence state on disconnect

* fix: Don't ignore server side error
Improved documentation

* More comments / why comments

* rename Socket -> SocketPresence

* fix: Handle redis is down
remove unneccessary join

* fix: PR feedback
2020-01-02 21:17:59 -08:00
Tom Moor 541e4ebe37 feat: Show drafts to author in collections (#1134) 2020-01-02 21:17:29 -08:00
Tom Moor c7aa261863 chore: Upgrade react-router-dom dep 2020-01-01 20:54:05 -08:00
Tom Moor c484d15de6 chore: Upgrade react-waypoint dep 2020-01-01 20:36:42 -08:00
Tom Moor 93e4ad8c5e fix: Account for migrations ran on old versions of Sequelize (#1130) 2019-12-30 22:35:29 -08:00
Ezra Free 85e70e579a remove transparency from dark theme sidebar background (#1127) 2019-12-29 09:49:31 -08:00
Tom Moor 10e038be5b lint 2019-12-23 18:12:16 -08:00
Tom Moor 76365e8560 fix: Double scrollbars on search filter dropdowns
closes #1125
2019-12-23 18:08:40 -08:00
Tom Moor 595cb9cda5 fixes: Document menu on search results is empty if not previously loaded
closes #1124
2019-12-23 17:59:15 -08:00
Tom Moor b6fd9f4211 fix: Invites appearing in search author filter
closes #1123
2019-12-23 17:39:25 -08:00
Tom Moor 98dda567c2 fix: User records not written correctly on signin (#1119)
* Initial fix for #1116

* clarify logic
2019-12-22 20:14:06 -08:00
Tom Moor c20282de06 feat: Add child document preloading 2019-12-22 17:06:39 -08:00
Tom Moor d995f27736 feat: Add parentDocumentId option to documents.list endpoint 2019-12-22 17:06:29 -08:00
Tom Moor 6bf2069fa7 fixes #1115 (#1118) 2019-12-22 14:54:12 -08:00
Tom Moor adf323713e fix: Documentation spelling 2019-12-18 21:02:32 -08:00
Tom Moor 293c3b7b9c fix: Move references spacing directly below content (#1113)
* fix: Move references spacing directly below content

* Child document -> Nested document
2019-12-18 21:00:36 -08:00
Tom Moor 949dd296b4 fix: 1px heading misalignment 2019-12-17 23:16:16 -08:00
Tom Moor ca0de9fb5a fix: Incorrect sidebar positioning on mobile in edit mode
closes #1108
2019-12-17 19:55:23 -08:00
Tom Moor 89b87c5268 fix: Emails should be stored and processed in lowercase (#1109) 2019-12-17 19:44:50 -08:00
Tom Moor 4511fb7259 bump: RME 2019-12-16 22:44:15 -08:00
Tom Moor 6b378bf78f Remove link to old roadmap [ci skip] 2019-12-15 21:21:27 -08:00
Tom Moor 671fa9cc84 fix: Para floating on signin page when guest signin not enabled 2019-12-15 20:59:36 -08:00
Tom Moor c360d7cd8c fix: SocketProvider throws error when websockets disabled 2019-12-15 19:49:47 -08:00
Tom Moor 6d8216c54e feat: Guest email authentication (#1088)
* feat: API endpoints for email signin

* fix: After testing

* Initial signin flow working

* move shared middleware

* feat: Add guest signin toggle, obey on endpoints

* feat: Basic email signin when enabled

* Improve guest signin email
Disable double signin with JWT

* fix: Simple rate limiting

* create placeholder users in db

* fix: Give invited users default avatar
add invited users to people settings

* test

* add transaction

* tmp: test CI

* derp

* md5

* urgh

* again

* test: pass

* test

* fix: Remove usage of data values

* guest signin page

* Visually separator 'Invited' from other people tabs

* fix: Edge case attempting SSO signin for guest email account

* fix: Correctly set email auth method to cookie

* Improve rate limit error display

* lint: cleanup / comments

* Improve invalid token error display

* style tweaks

* pass guest value to subdomain

* Restore copy link option

* feat: Allow invite revoke from people management

* fix: Incorrect users email schema does not allow for user deletion

* lint

* fix: avatarUrl for deleted user failure

* change default to off for guest invites

* fix: Changing security settings wipes subdomain

* fix: user delete permissioning

* test: Add user.invite specs
2019-12-15 18:46:08 -08:00
Tom Moor 5731ff34a4 fix: Archived and then deleted documents appear in archived tab after loading trash 2019-12-07 12:39:49 -08:00
Tom Moor cbd9ff2dd9 fixes #1093 – account for no previous revision in backlinks service 2019-12-07 11:40:15 -08:00
Tom Moor 504aa9f7bb Add AWS_REGION to heroku install 2019-12-06 16:48:36 -08:00
Tom Moor a2a7f5ef5c Update docs to recommend against using .env in prod [ci skip] 2019-12-05 16:57:17 -08:00
Tom Moor 9427f06031 Added minimum Redis version. [ci skip] 2019-12-05 16:52:32 -08:00
Tom Moor dd11bb9079 feat: Add warning for self-hosted installations that don't auth auth method configured 2019-12-05 13:22:06 -08:00
Tom Moor 828ce086cc fix: Account for unset S3 url when starting application 2019-12-05 12:55:46 -08:00
Tom Moor 682151554b fix: Additional SQL cascades required 2019-11-18 20:14:55 -08:00
Tom Moor 3ea79dd31a fix: Additional SQL cascades required 2019-11-18 19:49:33 -08:00
Tom Moor e404955394 feat: Trash (#1082)
* wip: trash

* Enable restoration of deleted documents

* update Trash icon

* Add endpoint to trigger garbage collection

* fix: account for drafts

* fix: Archived documents should be deletable

* fix: Missing delete cascade

* bump: upgrade rich-markdown-editor
2019-11-18 18:51:32 -08:00
Tom Moor 14f6e6abad feat: Add support for InVision live images
closes #946
2019-11-03 15:32:43 -08:00
Himanshu Agarwal f06097d9e8 chore: Remove marketing material from OSS project (#941)
* changes to support Plainhome

* changes to env sample

* changes to env variable names

* formatter fixes

* remove the content pages

* test fix

* lint fixes

* minor fixes

* removed unnesscary routes

* Apply suggestions from code review

Co-Authored-By: Tom Moor <tom.moor@gmail.com>

* removed team name from env
2019-11-03 15:01:46 -08:00
Tom Moor dce0c4ac73 yarn upgrade
closes #1066
2019-11-03 12:46:47 -08:00
Tom Moor ae6d24c343 upgrade: set-value 2019-11-03 12:26:43 -08:00
Tom Moor 3c794ec5b6 fix: Bump RME, closes #1077 2019-10-30 19:33:57 -07:00
Tom Moor f7a9152ee3 fix: Refreshing search page with special character in query does not reflect
fix: Search query that looks like regex causes JS error
2019-10-27 22:34:11 -07:00
Tom Moor 77153a2529 fix: More space for new empty search state 2019-10-27 22:12:18 -07:00
Tom Moor 2e61bb7d88 feat: Add publish shortcut to keyboard shortcuts overlay 2019-10-27 22:07:01 -07:00
Tom Moor 9ef9c75c6b feat: Add keyboard shortcut to publish document
closes #1073
2019-10-27 18:04:45 -07:00
Tom Moor 98cd93c99c Delete yarn-error.log 2019-10-27 17:02:13 -07:00
Tom Moor 3a8cd2bb35 fix: File upload input must stopPropagation of click events otherwise click event is triggered on sidebar menu item and file choose dialog never appears
closes #1065
2019-10-27 16:59:36 -07:00
Tom Moor e72608938a chore: upgrade flow-typed 2019-10-26 23:50:02 -07:00
Tom Moor e024b1b042 Create SECURITY.md 2019-10-26 22:14:46 -07:00
Tom Moor 3099eaaf12 chore: upgrade micromatch 2019-10-26 21:59:07 -07:00
Tom Moor b4d6c70b29 chore: upgrade google-auth-library 2019-10-26 21:44:45 -07:00
Tom Moor 4080216fe3 chore: upgrade koa-x 2019-10-26 21:35:50 -07:00
Tom Moor 1b415ac19a chore: upgrade html-webpack-plugin 2019-10-26 21:25:09 -07:00
Tom Moor a9f851897d chore: upgrade sequelize 2019-10-26 21:16:36 -07:00
Tom Moor db94a95001 chore: upgrade nodemon 2019-10-26 20:41:50 -07:00
Tom Moor 2d2ad83469 fix: Cannot move a document to nested child in private collection 2019-10-16 08:45:21 -07:00
Tom Moor b95e1cdef3 fix: Keep document menu open when toggling starred status to provide better feedback 2019-10-15 23:14:14 -07:00
Tom Moor 66197a967a fix: Document history menu item should toggle history sidebar 2019-10-15 21:42:07 -07:00
Tom Moor 5263d8a315 fix: Collaborator avatars should overlap 2019-10-15 21:30:33 -07:00
Tom Moor 97c8bfc27f fix: Protect against redirecting back to the same doc
closes #975
2019-10-15 20:59:00 -07:00
Tom Moor b093acd94f fix: closes #1064 – only show active users in collection members 2019-10-15 20:19:08 -07:00
Tom Moor 21af0bd8be fix: Sidebar links unexpand
closes #1044
2019-10-13 16:15:42 -07:00
Tom Moor 766a52f10e flow 2019-10-13 09:32:58 -07:00
Tom Moor 84c1cfea14 lint 2019-10-12 23:08:14 -07:00
Tom Moor 01f672fac9 fix: Drop to import 2019-10-12 23:03:58 -07:00
Tom Moor 5eb384b2c8 refactor, aria props 2019-10-12 21:08:04 -07:00
Mateusz Sapielak 8ea1323a7c fix: Ensure menus are always kept on the screen (#1036)
* ensuring dropdowns fit on the screen

* refactoring

* fix flow types

* no longer fixing the elements which should resolve scrolling issues

* fix menus that should be fixed

* styled-components syntax was wrong

* account for fixed dropdowns when handling overflowing menus

* Update app/components/DropdownMenu/DropdownMenu.js

Co-Authored-By: Tom Moor <tom.moor@gmail.com>
2019-10-12 20:21:48 -07:00
Tom Moor 00d5b58850 fix: Collection membership list does not update after change to private
closes #1059
2019-10-12 20:14:18 -07:00
Tom Moor 65b8fb40f3 fix: Empty collection causes export failure
closes #1043
2019-10-12 19:31:15 -07:00
Tom Moor ec4d4fb20f fix: Show error message when signing in to suspended account
closes #1056
2019-10-12 19:16:17 -07:00
Tom Moor fc201663c6 fix: closes #1058 – orphaned archived headig 2019-10-12 13:40:05 -07:00
Tom Moor d4347b6f4b fix: Restore option missing in archived documents 2019-10-12 13:24:48 -07:00
Tom Moor 2d913e3766 fix: Event activity endpoint with deleted actor
closes #1061
2019-10-12 13:03:50 -07:00
Tom Moor e33aaec469 fix: Remove nonsense relationship 2019-10-10 08:25:23 -07:00
Tom Moor 24231053af feat: Child document references (#1054)
* feat: Child document references

* refactor: naming

* lint: flow
2019-10-08 08:01:30 -07:00
Tom Moor 927e0b3fb8 Update LICENSE 2019-10-07 20:45:45 -07:00
Tom Moor b0ceae5af0 fix: Restore 'Publish' action in drafts 2019-10-06 21:09:49 -07:00
Tom Moor 6fde4e2ec5 feat: Add new doc button when search results are empty
closes #1022
2019-10-05 20:06:48 -07:00
Tom Moor b42e9737b6 feat: Memberships (#1032)
* WIP

* feat: Add collection.memberships endpoint

* feat: Add ability to filter collection.memberships with query

* WIP

* Merge stashed work

* feat: Add ability to filter memberships by permission

* continued refactoring

* paginated list component

* Collection member management

* fix: Incorrect policy data sent down after collection.update

* Reduce duplication, add empty state

* cleanup

* fix: Modal close should be a real button

* fix: Allow opening edit from modal

* fix: remove unused methods

* test: fix

* Passing test suite

* Refactor

* fix: Flow UI errors

* test: Add collections.update tests

* lint

* test: moar tests

* fix: Missing scopes, more missing tests

* fix: Handle collection privacy change over socket

* fix: More membership scopes

* fix: view endpoint permissions

* fix: respond to privacy change on socket event

* policy driven menus

* fix: share endpoint policies

* chore: Use policies to drive documents UI

* alignment

* fix: Header height

* fix: Correct behavior when collection becomes private

* fix: Header height for read-only collection

* send id's over socket instead of serialized objects

* fix: Remote policy change

* fix: reduce collection fetching

* More websocket efficiencies

* fix: Document collection pinning

* fix: Restored ability to edit drafts
fix: Removed ability to star drafts

* fix: Require write permissions to pin doc to collection

* fix: Header title overlaying document actions at small screen sizes

* fix: Jank on load caused by previous commit

* fix: Double collection fetch post-publish

* fix: Hide publish button if draft is in no longer accessible collection

* fix: Always allow deleting drafts
fix: Improved handling of deleted documents

* feat: Show collections in drafts view
feat: Show more obvious 'draft' badge on documents

* fix: incorrect policies after publish to private collection

* fix: Duplicating a draft publishes it
2019-10-05 18:42:03 -07:00
Tom Moor 4164fc178c fix: First auto-save unfocuses document (#1046)
* fix: Autosave unfocuses document

* Revert unneeded change

* test: le fix

* fix: Handle offline state
fix: Untitled documents appear with empty titles

* fix: Draft bubble roundness (yes, it doesnt belong here but see it, fix it)
2019-09-22 17:09:11 -07:00
Tom Moor b1a1d24f9c fix: Unknown Slack users should be able to search team accessible docs (#1049)
* fix: Unknown Slack users should be able to search team accessible docs

* test: fix flaky test

* test: remove obsolete snapshot

* lint

* flow

* fix: Spelling mistake
2019-09-22 11:52:15 -07:00
Tom Moor d46530a4a0 Merge branch 'rylxes-master' 2019-09-21 13:27:18 -07:00
Tom Moor 3f7d4f7873 fix: tweak wording 2019-09-21 13:27:00 -07:00
Tom Moor b20d41a047 test: Update snapshots 2019-09-21 13:26:28 -07:00
Tom Moor 1797a0e90c Merge branch 'master' of https://github.com/rylxes/outline into rylxes-master 2019-09-21 13:11:31 -07:00
Tom Moor dd5e30f414 fix: Account for missing localStorage (browser security settings?) 2019-09-21 12:42:28 -07:00
Tom Moor 690299ac6b Redirect unknown integration to integrations home, rather than 404 2019-09-21 12:28:34 -07:00
Tom Moor 7ff0a1d820 tidy env sample 2019-09-21 12:23:47 -07:00
Juncheol Cho 3292d95d8b chore: add env parameter for enforce https (#1042)
* env parameter for enforce https

* Update app.js

fix format for multi-line condition

* Update app.js

fix code format

* Update app.js
2019-09-18 23:26:27 -07:00
Tom Moor 18658e354a Update Editor.js 2019-09-15 21:18:31 -07:00
Sherriff Agboola 02f2868d06 re-added the .env.sample 2019-09-15 23:30:41 +01:00
Sherriff Agboola 4ea4bd41cd changed lastActiveAt boolean to string 2019-09-15 23:28:11 +01:00
Sherriff Agboola 593aa80abf Show last active date on people list instead of joined date 2019-09-15 23:07:43 +01:00
Tom Moor f43643f43b fix: Image and horizontal highlight on selected 2019-09-15 14:53:27 -07:00
Tom Moor e2453b5b2a fix: Editor headings offset 2019-09-15 14:42:32 -07:00
Tom Moor 439ae1e832 fix: CSP for client-side editor uploads 2019-09-15 14:41:34 -07:00
Agboola Sherriff 228c0c45e7 Merge pull request #1 from outline/master
merge
2019-09-14 21:43:17 +01:00
Tom Moor 6520a501e3 fix: accessiblity improvements, focus states, real buttons 2019-08-30 00:27:40 -07:00
Tom Moor 140f009b4d fix: Improve accessibility of buttons / tab indexes with tooltips 2019-08-29 00:06:21 -07:00
Tom Moor 579eaf325b feat: Add keyboard shortcuts to tooltips 2019-08-28 23:30:27 -07:00
Tom Moor b56f8e7870 feat: add padlock next to private collections in lists 2019-08-28 21:41:40 -07:00
Tom Moor 79f8a41b5b fix: Loading state interrupts infinite scrolling 2019-08-28 21:01:35 -07:00
dependabot[bot] abf5b79de6 chore(deps): bump mixin-deep from 1.3.1 to 1.3.2 (#1031)
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-28 16:53:05 -07:00
Tom Moor c60295fcca fix: Slack notification not sent on publish, reported via Spectrum 2019-08-28 00:22:22 -07:00
Tom Moor 5bd2409e39 Revert "WIP"
This reverts commit ccfad1d800.
2019-08-28 00:03:11 -07:00
Tom Moor 780c5c1129 fix: Add github gist styles to CSP 2019-08-27 23:26:32 -07:00
Tom Moor b98c908568 fix: Add github gist to CSP
closes #1028
2019-08-27 23:12:09 -07:00
Tom Moor f1e8633623 fix: Add blob protocol to imgSrc 2019-08-27 09:21:53 -07:00
Tom Moor 39de7c0aee Merge branch 'master' of github.com:outline/outline 2019-08-27 09:19:07 -07:00
dependabot[bot] 8b942cf202 chore(deps): bump eslint-utils from 1.3.1 to 1.4.2 (#1026)
Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.3.1 to 1.4.2.
- [Release notes](https://github.com/mysticatea/eslint-utils/releases)
- [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.3.1...v1.4.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-08-26 13:11:43 -07:00
Tom Moor ccfad1d800 WIP 2019-08-25 15:20:49 -07:00
Tom Moor 347015cf86 feat: improve physicality of draft bubble (it's the little details) 2019-08-24 17:43:23 -07:00
Tom Moor d96afad6c0 fix: Document heading vertical alignment 2019-08-24 10:22:58 -07:00
Tom Moor d066468bc0 chore: Add additional env variable checks for self-hosted installations 2019-08-24 10:19:21 -07:00
Tom Moor b6dd55fbea chore: Add additional env variable checks for self-hosted installations 2019-08-23 22:43:41 -07:00
Tom Moor 8f2d31876d feat: Port new color picker from Journals branch 2019-08-23 22:24:06 -07:00
Tom Moor 8e76c4e8f1 fix: Add missing audit log events UI 2019-08-23 21:51:05 -07:00
Tom Moor 468fd792ed fix: Allow iframes in CSP 2019-08-23 19:39:40 -07:00
Tom Moor c1bef2db59 fix: additional domains in CSP, dont send headers with API responses 2019-08-23 19:21:16 -07:00
Tom Moor 53cc69a413 fix: additional security headers by default 2019-08-23 19:00:38 -07:00
Tom Moor 7e62b3b9aa chore: Upgrade Node to v12 (#1023)
* chore: Upgrade Node to v12

* test: Upgrade test node image
2019-08-23 18:14:51 -07:00
Tom Moor 54565fff74 fix: Handle doc not found / no permission
closes #1021
2019-08-21 22:11:54 -07:00
Tom Moor e2b28dfeb7 refactor: Policies Architecture (#1016)
* add policy serialize method

* Add policies to collection responses

* wip

* test: remove .only

* refactor: Return policies with team and document requests

* store policies on the client

* refactor: drive admin UI from policies
2019-08-21 21:41:37 -07:00
Tom Moor cf18b952a4 chore: Move Dockerfile to Alpine
closes #1020
2019-08-21 08:19:27 -07:00
Tom Moor 929722e1c1 fix: double load on mount. closes #1015 2019-08-20 22:13:36 -07:00
Tom Moor c0ec349ad2 bump RME 2019-08-18 20:16:49 -07:00
Tom Moor 4d2eda6750 fix: Embed does not show selected highlight 2019-08-18 12:45:54 +01:00
Tom Moor db08263e5c fix: Nested lists 2019-08-15 14:05:15 +02:00
Tom Moor 33320ba13d perf: Improve speed of rendering tables
fix: Issue rendering tables with certain cells empty of content
2019-08-14 19:17:10 +01:00
Tom Moor 4dad3f3287 fix: Bump RME, more editor fixes 2019-08-12 20:17:55 -07:00
Tom Moor f87b561685 feat: Allow export of collections as sync zip (#1013)
* feat: Allow export of collections as sync zip

* test: Add spec
2019-08-09 20:37:51 -07:00
Tom Moor d024d31f66 refactor: flow typing (#1012)
* fix: padding

* fix: Minor button alignment issues

* feat: Add icon to invite people button

* WIP
2019-08-08 23:09:09 -07:00
Tom Moor 7b2eea0009 feat: Add icon to invite people button 2019-08-08 22:10:11 -07:00
Tom Moor 2ce11365ab fix: Minor button alignment issues 2019-08-08 22:07:29 -07:00
Tom Moor e936aa82c9 fix: padding 2019-08-08 21:25:51 -07:00
Tom Moor a26ae119fe feat: Keyboard shortcut reference inside editor 2019-08-08 21:13:58 -07:00
Tom Moor 789a1acea1 feat: Add 'edit' item to document menu 2019-08-08 20:00:56 -07:00
Tom Moor dd95c9cba9 feat: Opensearch tags / descriptor 2019-08-08 19:52:29 -07:00
Tom Moor ae1cf2d00c fix: Upgrade RME (fix link toolbar blur) 2019-08-07 23:22:06 -07:00
Tom Moor 978eda3ad2 fix: Deleted collections not showing in audit log
feat: Show titles of objects in audit log
fix: modelId not saved with share events
fix: List item squashes avatar at small screen sizes
2019-08-07 20:52:59 -07:00
Tom Moor 0f028812e1 fix: Flash of documents on home if drafts load before main request
fix: Drafts loading placeholder misplaced
2019-08-06 23:46:27 -07:00
Tom Moor c18e4cd43e feat: separate draft count and icon in sidebar 2019-08-05 23:17:41 -07:00
Tom Moor 7f10fe728f fix: Logout -> Log out 2019-08-05 22:43:24 -07:00
Tom Moor 5c99116898 feat: redirect to requested doc after authentication 2019-08-05 22:25:19 -07:00
Tom Moor 38a67b1f9e fix: Avatars should overlap in document header 2019-08-05 21:10:44 -07:00
Tom Moor fb4f6822a4 feat: Events / audit log (#1008)
* feat: Record events in DB

* feat: events API

* First pass, hacky activity feed

* WIP

* Reset dashboard

* feat: audit log UI
feat: store ip address

* chore: Document events.list api

* fix: command specs

* await event create

* fix: backlinks service

* tidy

* fix: Hide audit log menu item if not admin
2019-08-05 20:38:31 -07:00
Tom Moor 75b03fdba2 Bump RME 2019-08-04 12:59:14 -07:00
Tom Moor 60a31992d0 chore: disable typescript detection in VSCode 2019-08-01 21:34:17 -07:00
Tom Moor be09b6a3bd Merge pull request #1004 from shyim/patch-1
chore: add missing EXPOSE rule
2019-07-30 08:08:09 -07:00
Tom Moor 92a18159b5 feat: Export collection as direct download instead of emailing (#1001)
* feat: Export collection as zip instead of emailing

* Flow typing download.js
2019-07-29 22:35:34 -07:00
Shyim 7c01904cec Add missing expose 2019-07-29 20:48:26 +02:00
Douglas Gadêlha c9b86ec2e7 Fix document duplicate command (#1003) 2019-07-27 10:33:38 -07:00
Tom Moor 466ba6ec1f fix: don't echo request back for unknown endpoints
closes #998
2019-07-25 23:17:45 -07:00
Tom Moor 9cbc9aaad6 chore: RealtimeBoard -> Miro
closes #996
2019-07-25 22:56:20 -07:00
Tom Moor 5ec2c3c7a0 fix: Add support for mm.tt mindmeister links 2019-07-25 22:42:23 -07:00
Tom Moor a8d8fecd15 Update DropdownMenu.js 2019-07-13 12:56:04 -07:00
Tom Moor be09ffea7b feat: sharing drafts (#991)
closes #988
2019-07-13 12:40:25 -07:00
Tom Moor 18d104218e fix: public share links attempting to load backlinks 2019-07-13 11:33:32 -07:00
Tom Moor a515631e21 feat: document menu available in sidebar (#986)
* feat: document menu available in sidebar

* fix: more accessible blue

* feat: accessible blue
feat: clearer new doc button
closes #983

* lint
2019-07-13 10:15:38 -07:00
dependabot[bot] 28954a19af chore(deps): bump lodash from 4.17.11 to 4.17.13 (#987)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.13.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.13)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-12 13:32:56 -07:00
Tom Moor b3f847a371 fix: User profiles dont open from document collaborator facepile 2019-07-11 22:32:37 -07:00
Tom Moor c9da515d4c fix: force resolution of js-yaml (WS-2019-0063) 2019-07-09 21:32:42 -07:00
Tom Moor 3d805d5fe7 chore: tooltip update / remove css loader (#985)
* fix: Add suspended filter to people management #984

* chore: removing css loader

* pui-react-tooltip -> tippy
closes #980

* remove extract-text-plugin
2019-07-09 21:17:25 -07:00
Tom Moor 7db0be0a6a fix: 2px misalignment
feat: Improved readability of tabs and subheadings
2019-07-07 21:40:34 -07:00
Tom Moor 091e542406 feat: Backlinks (#979)
* feat: backlinks

* feat: add backlinkDocumentId to documents.list

* chore: refactor
fix: create and delete backlink handling

* fix: guard against self links

* feat: basic frontend
fix: race condition

* styling

* test: fix parse ids

* self review

* linting

* feat: Improved link styling

* fix: Increase clickable area at bottom of doc / between references

* perf: global styles are SLOW
2019-07-07 19:25:45 -07:00
Tom Moor 599e5c8f5d Merge branch 'thenanyu-refactor-doc-dirty-logic' 2019-07-07 19:20:04 -07:00
Tom Moor 14b746c676 Update documentation 2019-07-07 19:19:21 -07:00
Tom Moor 3e3db7435f lint 2019-07-07 19:18:51 -07:00
Tom Moor 2ef1d3f95c fix: email link in onboarding support doc 2019-07-07 16:13:56 -07:00
Tom Moor 2cfdf7043b fix: zIndex of toolbar buttons 2019-07-07 11:30:15 -07:00
Tom Moor 8ac47074fe Update README.md 2019-07-07 09:29:56 -07:00
Nan Yu beb3a80d3d fix up the readme and sample .env to smooth out first dev experience (#976)
* chore: fix up the readme and sample .env to smooth out first dev experience

* fix indentation
2019-07-07 09:14:27 -07:00
thenanyu dea6085a11 refactor document dirty and empty logic 2019-07-06 21:45:50 -07:00
Tom Moor ccc0906b0a feat: Improved onboarding documents (#970)
* feat: New onboarding documents

* Images -> blocks

* Add tips

* test: removes assumptions of welcome documents

this actually results in the tests being much more understandable too

* add db flag when document was created from welcome flow
2019-07-04 10:33:00 -07:00
dependabot[bot] eb3a1dd673 Bump axios from 0.18.0 to 0.18.1 (#974)
Bumps [axios](https://github.com/axios/axios) from 0.18.0 to 0.18.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.18.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.18.0...v0.18.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-04 00:09:30 -07:00
dependabot[bot] b12f15de52 Bump handlebars from 4.1.0 to 4.1.2 (#973)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.0 to 4.1.2.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.0...v4.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-04 00:04:43 -07:00
dependabot[bot] e1e4c006d2 Bump fstream from 1.0.11 to 1.0.12 (#972)
Bumps [fstream](https://github.com/npm/fstream) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/npm/fstream/releases)
- [Commits](https://github.com/npm/fstream/compare/v1.0.11...v1.0.12)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-04 00:00:41 -07:00
Tom Moor d3abbcf9d5 feat: Added tooltips to editor controls 2019-07-03 21:32:21 -07:00
Tom Moor 34f011d99f Bump RME (fixes special chars in code blocks) 2019-06-29 00:04:26 -07:00
Tom Moor 232216193f fix: Correctly detect Slack team 2019-06-27 22:35:49 -07:00
Tom Moor 5a6b9caabc fix: bring local and remote delete inline 2019-06-26 22:18:18 -07:00
Tom Moor ce675a7fe2 fix: Remove collections/document when removed elsewhere 2019-06-26 22:10:24 -07:00
Tom Moor 3d7eb11a49 fix: react warning with DropToImport 2019-06-26 22:10:07 -07:00
Tom Moor 8200e36b89 fix: Accessibility warning with react-modal 2019-06-26 22:09:52 -07:00
Tom Moor 1ed257de62 fix: Race condition fetching collection after doc create 2019-06-26 20:24:20 -07:00
Tom Moor f0de382367 fix: Deeply nested document breadcrumb menu 2019-06-25 23:21:04 -07:00
Tom Moor 5f8956e5c6 fix: more language cleanup 2019-06-25 22:06:20 -07:00
Tom Moor b93824915d fix: Settings screen cleanup 2019-06-25 21:53:23 -07:00
Tom Moor 7aea6458ce fix: Update email in auth service should update email in Outline 2019-06-25 21:44:46 -07:00
Tom Moor 424af9e72c feat: Show account email address on notification settings 2019-06-25 21:38:23 -07:00
Tom Moor c9c5e43389 fix: add support for help slack command 2019-06-25 20:43:59 -07:00
Tom Moor 46ad1feb96 chore: Remove welcome email for Slack users (against TOS) 2019-06-25 20:28:45 -07:00
Tom Moor 8faed5de6f chore: update about page 2019-06-25 00:22:27 -07:00
Tom Moor 5c39287a59 Merge branch 'mogita-update-installation-guide' 2019-06-24 23:08:06 -07:00
Tom Moor b9e48e86c1 Remove ap- from AWS_REGION example 2019-06-24 23:07:54 -07:00
Tom Moor d39260c5c7 Merge branch 'update-installation-guide' of https://github.com/mogita/outline into mogita-update-installation-guide 2019-06-24 22:59:43 -07:00
Tom Moor d5192acabf feat: invites (#967)
* stub invite endpoint

* feat: First pass invite UI

* feat: allow removing invite rows

* First pass: sending logic

* fix: label accessibility

* fix: add button submits
incorrect permissions
middleware flow error

* 💚

* Error handling, email filtering, tests

* Flow

* Add Invite to people page
Remove old Tip

* Add copy link to subdomain
2019-06-24 22:14:59 -07:00
mogita 1c8e074662 fix: remove redundant line breaks 2019-06-24 13:27:22 +08:00
mogita 4bea33eae0 Add port number to test db 2019-06-24 11:31:14 +08:00
mogita c278172290 Merge from upstream master 2019-06-24 11:29:21 +08:00
mogita fefb9d0c13 Prefer yarn for building 2019-06-24 11:22:56 +08:00
mogita c865c57f92 Add port number 2019-06-24 11:22:16 +08:00
Tom Moor f406faf08e chore: remove asyncLock
closes #928
2019-06-23 17:57:37 -07:00
Tom Moor 0a8a685c12 fix: improved pagination validation 2019-06-23 16:11:15 -07:00
Tom Moor 32f83311f6 chore: upgrade sequelize (#965)
* 0.18.0

* chore: Upgrade sequelize 4 -> 5

* fix: migrations v5 support

* fix: Majority of test failures

* fix: the rest of v5 tests
2019-06-23 15:49:45 -07:00
Tom Moor 595adeb55f 0.18.0 2019-06-19 00:35:13 -07:00
Himanshu Agarwal 0079593446 Fix: Now supports AWS signature version 4. (#949)
* intial commiy

* cleaning code

* added makeCredential to s3.js and removed extra module dependecy

* lint fixes

* minor fix

* minor fixes

* changed encoding type from string to any

* added new env var to env.sample
2019-06-19 00:33:38 -07:00
Tom Moor ebd9535cb4 Flow 2019-06-19 00:06:24 -07:00
Tom Moor 318ad18894 closes #944 2019-06-18 23:45:29 -07:00
Tom Moor e9e21f280e closes #964 2019-06-18 23:39:29 -07:00
Tom Moor 86b2dbf5c8 docs: Add append flag to documentation 2019-06-13 23:38:57 -07:00
arpitsingh94 7dfe8785a2 Added ability to append to documents. Added test cases for the same (#963)
* added ability to append to documents. Added test cases for the same

* made changes required for lint test

* updated snapshot for document.test.js to reflect new test cases added

* append should not enforce newline character
2019-06-13 23:36:24 -07:00
mogita 4448ea8e4b Update placeholders 2019-05-31 11:27:01 +08:00
mogita cb0da79be3 Update installation guide 2019-05-31 11:26:26 +08:00
Tom Moor 18a5cd8765 Bump RME 2019-05-25 22:30:03 -07:00
Tom Moor ad51ac28b1 Table editing (#955)
* WIP

* Up deps
2019-05-25 11:26:51 -07:00
Tom Moor c6ae18503a Closes #903 - Added more syntax highlighters 2019-05-19 18:45:42 -07:00
Tom Moor 2db8cdc7d1 Fixes #952 – Page jump 2019-05-19 17:49:51 -07:00
Tom Moor 8942c7afe8 Add integration pages for Abstract integration 2019-05-07 20:02:23 -07:00
Tom Moor 402638ca54 Added: Abstract public share embed support 2019-05-07 19:46:02 -07:00
Tom Moor d5f6311bbc Fixed: Added support for new loom domain name 2019-05-07 19:32:03 -07:00
Tom Moor 3eb67eaecf Fixes: Calm notifications when hitting CMD+S while editing 2019-04-23 20:54:24 -07:00
Tom Moor bc918b7bf5 Fixes: Search filter dropdown should close on selection 2019-04-23 20:30:40 -07:00
Tom Moor 6d03257cc1 Bump RME – More editor fixes 2019-04-23 20:22:49 -07:00
Tom Moor a35a047cc2 Fixes: Loading indicator on search screen load
Fixes: Unscrollable dropdowns with large datasets
2019-04-23 07:51:02 -07:00
Tom Moor da7fdfef0a Improved search filtering (#940)
* Filter search by collectionId

* Improve spec, remove recursive import

* Add userId filter for documents.search

* 💚

* Search filter UI

* WIP UI

* Date filtering
Prevent dupe menu

* Refactor

* button

* Added year option, improved hover states

* Add new indexes

* Remove manual string interpolation in SQL construction

* Move dateFilter validation to controller

* Fixes: Double query when changing filter
Fixes: Visual jump between filters in dropdown

* Add option to clear filters

* More clearly define dropdowns in dark mode

* Checkbox -> Checkmark
2019-04-23 07:31:20 -07:00
Tom Moor a256eba856 Fixes #936 2019-04-20 15:31:51 -07:00
Tom Moor 8f276731ed Fixes: Socket reconnecting when changing theme 2019-04-20 15:19:35 -07:00
Tom Moor eb638ba68d Fixed: Page component naming 2019-04-20 15:07:25 -07:00
Tom Moor b54583f438 👕 2019-04-20 12:01:09 -07:00
Tom Moor 8ba27762d1 Fix: Race condition in websocket emit 2019-04-20 11:40:14 -07:00
Tom Moor d552d1e34d Prefix socket names 2019-04-18 20:37:46 -07:00
Tom Moor 56a6db7d2a Cleanup 2019-04-18 18:51:16 -07:00
Tom Moor f491029c21 Missing divider on document header 2019-04-17 23:13:25 -07:00
Tom Moor 0bc6662366 Update RME: Fixes horizontal rules being interpreted as headings 2019-04-17 23:09:43 -07:00
Tom Moor 5b34a4f076 Fix: Don't send notifications for autosave events 2019-04-17 21:55:11 -07:00
Tom Moor 77f28584d4 Merge branch 'master' of github.com:outline/outline 2019-04-17 21:49:25 -07:00
Tom Moor 07a941a65d Websocket Support (#937)
* Atom / RSS meta link

* Spike

* Feeling good about this spike now

* Remove document.collection

* Remove koa.ctx from all presenters to make them portable outside requests

* Remove full serialized model from events
Move events.add to controllers for now, will eventually be in commands

* collections.create event
parentDocument -> parentDocumentId

* Fix up deprecated tests

* Fixed: Doc creation

* documents.move

* Handle collection deleted

* 💚

* Authorize room join requests

* Move starred data structure
Account for documents with no context on sockets

* Add socket.io-redis

* Add WEBSOCKETS_ENABLED env variable to disable websockets entirely for self hosted
New installations will default to true, existing installations to false

* 💚 No need for promise response here

* Reload notice
2019-04-17 19:11:23 -07:00
Tom Moor 2ab35e23f3 Atom / RSS meta link 2019-04-11 19:53:26 -07:00
Tom Moor 4a571a088e Closes #930 – Don't allow launch of move dialog from drafts 2019-04-10 21:13:21 -07:00
Tom Moor 0c1bf1586d Closes outline/issues#926
Closes outline/issues#888
Added native lazy loading in prep for Chrome release
2019-04-10 21:06:53 -07:00
Tom Moor c1256c61aa Search archived documents (#932)
* POC

* Improved styling

* Test
2019-04-09 09:20:30 -07:00
Tom Moor 57e051d62b Clarify available options in document move
closes #852
2019-04-08 22:27:10 -07:00
Tom Moor a3ca3447d1 Closes #929 – Clarify pin feature 2019-04-08 21:47:27 -07:00
Tom Moor 763f57a3dc Move document improvements (#927)
* Show all collections in UI

* Introduce command pattern

* Actually remove from previous collection

* Stash

* Fixes: Promises resolved outside of response lifecycle

* 💚

* 💚

* documentMover tests

* Transaction

* Perf. More in transactions
2019-04-08 21:25:13 -07:00
Tom Moor 16066c0b24 Add websocket reserved domains 2019-04-07 17:05:05 -07:00
Tom Moor 705938e622 Bump RME, fix tab to indent lists 2019-04-07 15:57:05 -07:00
Tom Moor d668bd5646 Remove duplicate save in archive
Improve documents.archive test
2019-04-07 09:56:09 -07:00
Tom Moor 135d035eb5 Fixes: Unneccessary refresh navigating to settings 2019-04-06 17:59:15 -07:00
Tom Moor ea3e81acc4 Closes #922 - Dark mode fixes 2019-04-06 17:07:14 -07:00
Tom Moor 1fc5578349 Fixes: Ensure publishing info does not wrap 2019-04-06 16:46:20 -07:00
André Glatzl e33d447a0d Issue #919 : Show DocumentPath in Breadcrumbs at Publishing Info, instead of only showing collection's name (#920)
closes https://github.com/outline/outline/issues/919
2019-04-06 16:45:08 -07:00
Tom Moor bf685c7703 Update routeHelpers.js 2019-04-06 16:29:23 -07:00
Tom Moor 642c11ff7d Document Archive (#921)
* WIP: Archive

* WIP

* Finishing up archive endpoints

* WIP

* Update docs

* Flow

* Stash

* Add toast message confirmations

* Redirect handling, fixed publishhing info for archived docs

* Redirect to collection instead of home, remove unused pub info

* Account for deleted parent

* Trash -> Archive
Allow reading of archived docs

* Dont overload deletedAt

* Fixes

* 💚

* ParentDocumentId wipe for unarchived sub docs

* Fix: CMD+S exits editing
Fix: Duplicate user name on published but unedited docs

* Improve jank on paginated lists

* Prevent editing when archived

* 💚
Separate lint / flow steps
2019-04-06 16:20:27 -07:00
Tom Moor 76957865bb Ensure normalize on load, imported documents arent safe 2019-03-31 14:39:55 -07:00
Tom Moor 1883e77d5c Bump RME, improve perf 2019-03-30 23:36:02 -07:00
Tom Moor 20a54bd2e9 Don't publish by default 2019-03-30 23:35:53 -07:00
Tom Moor 76bb6c4341 Change dev ports to reduce clashes 2019-03-30 23:35:09 -07:00
Tom Moor 49e5748a4f Change dev ports to reduce clashes 2019-03-30 23:34:56 -07:00
Tom Moor 52a029a657 👕 2019-03-13 23:50:52 -07:00
Tom Moor 1ef528bbd7 Bump RME - closes #915 2019-03-13 23:49:54 -07:00
Tom Moor f80c3c6877 Fixed: Return error when Slack auth fails to help with debugging 2019-03-13 23:22:12 -07:00
Tom Moor a7d49e9042 Bump production dependencies with open CVEs
closes #916
2019-03-13 23:11:03 -07:00
Tom Moor 3fcfae257f Small flow issues 2019-03-13 23:00:41 -07:00
Tom Moor 3ef507c137 Fixes #918 - Add extra at bottom of document 2019-03-13 22:59:25 -07:00
Tom Moor 12ea37e71e Fixes: Text color on disabled buttons 2019-03-13 22:45:49 -07:00
Tom Moor aba3d25700 logger.warn does not exist 2019-03-13 09:03:51 -07:00
Tom Moor 7b7ec52eee Bump production dependencies with open CVEs 2019-03-12 23:54:35 -07:00
Tom Moor 680a9245bd More text color tweaking 2019-03-12 22:33:24 -07:00
Tom Moor 0c2d9f2f9c Tweak subheading colors 2019-03-12 22:11:33 -07:00
Tom Moor 59c82f1f06 Dark Mode (#912)
closes #704
2019-03-12 21:35:35 -07:00
Tom Moor 6445da33db Update Flex.js 2019-03-10 14:32:42 -07:00
Tom Moor a2749a752a ESC key should go back when editing a document 2019-03-09 20:12:43 -08:00
Tom Moor 38f4e6b9a2 Closes #909 2019-03-09 20:02:46 -08:00
Tom Moor fc7c485ba9 Add 'n' shortcut for new doc
Fixed cmd+enter shortcut to publish doc
Fixed keyboard shortcut display on non-mac
Fixed heading alignment
Fixed documents smaller than page should not scroll
2019-03-09 20:00:45 -08:00
Tom Moor f75783c2f1 Added empty state on user profiles when no documents edited 2019-03-09 18:55:54 -08:00
Tom Moor f11bba6b63 Remove logging 2019-03-09 18:43:40 -08:00
Tom Moor 24bf3766bf Add shortcut tip when searching 2019-03-09 16:10:53 -08:00
Tom Moor e3cb7f9055 Update index.js 2019-03-07 20:47:37 -08:00
Tom Moor 3db1a6679a Bump RME 2019-03-07 08:44:57 -08:00
Tom Moor 222f164247 Fixes: Don't send client connection issues to error tracker 2019-03-02 21:57:26 -08:00
Tom Moor d91f4045c9 Add favicon to serverside html 2019-03-02 21:43:07 -08:00
Tom Moor 8b639682ff Guard against empty error messages 2019-03-02 21:17:26 -08:00
Tom Moor 67ed017122 Fixes: toString of undefined 2019-03-02 21:11:36 -08:00
Tom Moor f1c14f943e Bump RME 2019-03-02 20:35:28 -08:00
Tom Moor 18aa3f3787 Fix specs, thanks for the breaking minor change jest 2019-03-02 16:16:46 -08:00
Tom Moor 4a90c57dfe More dependency upgrades 2019-03-02 16:02:44 -08:00
Tom Moor 565a0006c9 Remove lint-staged dependency 2019-03-02 15:56:41 -08:00
Tom Moor 78ee921244 Upgrade slug dependency 2019-03-02 15:51:02 -08:00
Tom Moor 78606e892a Merge master 2019-03-02 15:41:46 -08:00
Tom Moor f705da4f3b Update dependencies 2019-03-02 15:39:18 -08:00
Tom Moor fa38ab60eb Fixes: Unexpected token parsing sessions cookie (#905) 2019-03-02 14:58:56 -08:00
Tom Moor ad2e869dea 👕 2019-03-02 14:55:39 -08:00
Tom Moor b4796e5b35 Fixes: Cookie encoding issues 2019-02-28 23:23:44 -08:00
Tom Moor e3b105d1c0 Update Slack logo, closes #866 2019-02-20 21:08:54 -08:00
Tom Moor 1d93acefeb Bump RME – Fixes scroll to anchor
closes #902
2019-02-20 21:00:40 -08:00
Tom Moor 19fc99944a Adds 'post to channel' functionality. (#901)
* Adds 'post to channel' functionality. Closes #613

* Add specs
Update Slack integration marketing page

* Fix specs

* 💚
2019-02-19 22:42:13 -08:00
Tom Moor e283d15d7e Add mindmeister content pages 2019-02-18 11:40:26 -08:00
Tom Moor fbd311d352 Add mindmeister support 2019-02-18 11:32:21 -08:00
Tom Moor 15bc0e7629 Bump RME – Improves keyboard behavior around deleting inline code 2019-02-17 00:39:06 -08:00
Tom Moor 8af7c3c264 Closes #894 - Sidebar jumping regression 2019-02-17 00:38:35 -08:00
Tom Moor 0b1a4c6184 Fixes: Server error when validating UUID against empty value 2019-02-15 21:52:34 -08:00
Tom Moor 346ea4df89 Fixes: Error when user is suspended by deleted user
Fixes: Suspended user interface never displayed
2019-02-15 21:49:48 -08:00
Tom Moor fdb49cf153 Bump RME: Includes fix for addition marks disappearing 2019-02-15 21:48:54 -08:00
Tom Moor 188f70b676 Fixes #893 – Allow links to different subdomains on same domain as wiki 2019-02-09 16:17:54 +07:00
Tom Moor 721332e87a Update RME – Fixes: Individual code lines are scrollable 2019-02-09 16:09:02 +07:00
Tom Moor be85e8a6e0 Closes #895 - Chrome 72 changed flexbox behavior 2019-02-08 10:29:19 +07:00
Tom Moor 945e7ffb7b Fixes: Imported tables are normalized out of document 2019-02-08 10:23:02 +07:00
Tom Moor b597226eeb Merge pull request #887 from outline/issue-726
Fixes: Multiline blockquotes collapse
2019-01-27 22:59:05 +00:00
Tom Moor c2e58898d8 Fixes: Multiline blockquotes collapse 2019-01-27 22:43:40 +00:00
Tom Moor 476bab9333 Fixes: Welcome email dashboard location (#886)
* Fixes: Welcome email dashboard location
Updated logo in email

* 💚
2019-01-27 12:30:53 +00:00
Tom Moor 1a6a7d04e5 Closes #866 - Updated Slack Icons 2019-01-27 00:21:22 +00:00
Tom Moor a2434988b4 Fixes: Overflow of long titles in move dialog 2019-01-26 18:05:58 +00:00
Tom Moor 70d30e31b9 Fixes: Quick fix for document move not working 2019-01-26 18:01:13 +00:00
Tom Moor b46db25553 Fixes: No redirect after doc import 2019-01-26 17:28:08 +00:00
Tom Moor e7e94cdef2 Bump RME 2019-01-26 17:08:00 +00:00
Tom Moor 0ce50781d7 Fixes: Redirects do not push into history stack 2019-01-26 13:01:33 +00:00
Tom Moor 1768a1921d Fixes: Issue with embeds disappearing 🙈 2019-01-22 12:43:54 +00:00
Tom Moor 39a61d8559 Upgrade Dependencies (#873)
* Loading placeholder

* Upgrade dependencies, reduce warnings

* 👕

* Remove more unused deps

* Bump RME
2019-01-21 11:06:44 -08:00
Tom Moor 4f4e55d120 Fixes: New document creation 2019-01-19 18:14:10 -08:00
Tom Moor 5525730272 Refactor history sidebar, reduce thrashing on doc render 2019-01-19 17:28:32 -08:00
Tom Moor babcf4a3f3 More withRouter removal 2019-01-19 15:25:46 -08:00
Tom Moor 9360cd0014 Merge pull request #871 from outline/less-with-router
Reduce withRouter usage
2019-01-19 13:33:12 -08:00
Tom Moor 2d48eb46a9 Closes #868 2019-01-19 13:11:11 -08:00
Tom Moor 258b5464a2 Boop 2019-01-19 01:52:43 -08:00
Tom Moor bfd32843ff Added caching of loaded editor instance 2019-01-19 01:31:34 -08:00
Tom Moor 72d8abe069 Canonical check 2019-01-19 00:51:25 -08:00
Tom Moor 77a8f54973 Even less history passing, fix global styles warning in dev 2019-01-19 00:44:16 -08:00
Tom Moor 13501b6d76 Moving redirects to declarative method 2019-01-19 00:23:39 -08:00
Tom Moor d21dd710bb Upgrade Editor – Slate 0.44 / RME 8.0.0 (#863)
* Upgrade Slate

* Normalization

* Remove dupe dep

* Fixes placeholders

* Image uploading fixed

* Verbose but solid placeholder handling

* RME 8.0.0

* Fixes: Ability to delete embeds
2019-01-18 22:44:31 -08:00
Tom Moor 0be5aef1c6 Merge pull request #865 from satyadeepk/patch-3
Fixed broken License link in Readme
2019-01-16 09:33:15 -08:00
Satyadeep 4015b19484 Fixed broken License link in Readme
The previous link was giving 404. Changed the relative URL to absolute URL.
2019-01-16 15:47:18 +05:30
Boris Mann d081b64ce2 Heroku deploy support (#861)
* Adding app.json for Heroku -- Procfile already exists

* Removed required false and changed values to strings

* Required is required!

* Tested and working install

* Apply suggestions from code review

Thanks to @tommoor for updating the descriptions for items I wasn't sure about. Added!

Co-Authored-By: bmann <boris@bmannconsulting.com>

* missing coma
2019-01-15 19:04:03 -08:00
Tom Moor 8d3dc3a92e User Profile (#858)
* First pass user profile

* 💚

* More collaborator tooltip improvements
2019-01-12 19:41:45 -08:00
Tom Moor ef583314e0 Remove parse-domain dependency (#856)
* Remove parse-domain dependency

* Remove only, add commentary

* Update lockfile
2019-01-12 13:50:30 -08:00
Tom Moor 394adf97f8 Minor grammar and mobile style fixes 2019-01-11 21:46:58 -08:00
Tom Moor 5ba1522ada Add Prezi to integrations page 2019-01-11 21:40:01 -08:00
Tom Moor 6c055810ad Merge pull request #855 from outline/issue-854
Fixes: Regex-like search queries error
2019-01-11 21:16:03 -08:00
Tom Moor 3a260037cd Regex-like search queries from Slack should not cause a server error 2019-01-11 19:41:19 -08:00
Tom Moor a7c669f90b Hide pricing page on self hosted installations 2019-01-10 19:15:36 -08:00
Tom Moor 449f4f7a26 Fixes: Realtimeboard and Vimeo embed should be secure 2019-01-10 19:12:36 -08:00
Tom Moor 6d769a738d Merge pull request #853 from rylxes/slack-document-update-fix
document update fix for slack
2019-01-10 09:29:05 -08:00
sherriff f82a3fa32b document update fix for slack 2019-01-10 14:24:10 +01:00
Tom Moor 6fb51eb7bb 💚 2019-01-09 22:59:39 -08:00
Tom Moor 23b227c352 Closes #842 - Toast messages hanging 2019-01-09 22:41:06 -08:00
Tom Moor c54c3d963e Increase accessibility of search input 2019-01-09 22:15:09 -08:00
Tom Moor 4ba10fc5f7 Implements local search cache
Results no longer disappear when searching something previously searched
Navigating from a document back to results is now instant
Search item in left nav no longer unhighlights
2019-01-09 21:57:17 -08:00
Tom Moor e6fd7276fc Fixed: Viewers for current doc 2019-01-08 23:06:02 -08:00
Tom Moor c78bf3c4bf Display document views (#849)
* Display who has viewed a document in the header

* Add overflow, display of WHEN last viewed
Cleanup old document attributes
Add firstViewedAt, lastViewedAt to API response

* Cleanup

* Added: API documentation for views endpoints

* Include views for deleted users
2019-01-08 22:49:20 -08:00
Tom Moor 11b0ac0c66 Fix new document creation 2019-01-08 09:41:03 -08:00
Tom Moor 4ddd7d4dfe Merge pull request #843 from outline/collection-sort
Add new document sort options to collections
2019-01-08 09:19:51 -08:00
Tom Moor 70c93fcc86 Refactor, add alphabetical sort 2019-01-07 23:42:55 -08:00
Tom Moor 4cd482c80e Merge branch 'master' of github.com:outline/outline into collection-sort 2019-01-07 23:14:50 -08:00
Tom Moor e6e89dc243 Added: Recently published view to collection
Added: Infinite scroll to collection
2019-01-07 23:14:43 -08:00
Tom Moor 3bca0ed9c2 Merge pull request #839 from outline/wildcard-search
Improve partial word matching
2019-01-07 21:53:35 -08:00
Tom Moor 74515e0b19 Improve partial word matching 2019-01-07 21:44:33 -08:00
Tom Moor a68ad856a3 Move draft bubble inline with menu icons 2019-01-06 16:25:05 -08:00
Tom Moor b9765fb59e Improved sidebar selected styling, simplified logic and css 2019-01-06 16:12:07 -08:00
Tom Moor 12e324d34c Fixed: React warning 2019-01-06 14:19:08 -08:00
Tom Moor c970cddd14 Consistent page titles 2019-01-06 14:14:44 -08:00
Tom Moor 56bda12192 WIP: Pricing page 2019-01-06 13:09:22 -08:00
Tom Moor 58e31a9d3d Fixes #831 - Link from Slack notification does not work for teams with subdomains 2019-01-06 00:05:50 -08:00
Tom Moor 44e04c57a9 Add Prezi embed support
Increase size of video embeds
2019-01-05 23:43:51 -08:00
Tom Moor accba7614c Add Google Slides embed support 2019-01-05 23:29:14 -08:00
Tom Moor 028160d9ad Fixes: Share links in private collections visible in share listing 2019-01-05 23:13:58 -08:00
Tom Moor 70287de6d7 Fixes: Cannot load document in private collection from share link when signed in with user that cannot access 2019-01-05 23:04:09 -08:00
Tom Moor 18859bec3d Add real buttons to document header
Fixed: Incorrect page title when starting a new doc
Fixed: Linting error
2019-01-05 18:45:19 -08:00
Tom Moor f80e4ab04c Improved button styling
Added toast when collection permissions are saved
Removed usage of setState (old habits die hard)
2019-01-05 18:23:57 -08:00
Tom Moor 713473b7d2 Revert node version due to sequelize incompat 2019-01-05 13:55:24 -08:00
Tom Moor 8c02b0028c Collection Permissions (#829)
see https://github.com/outline/outline/issues/668
2019-01-05 13:37:33 -08:00
Tom Moor 8978915423 Fix Lucidchart embeds
closes https://github.com/outline/outline/issues/836
2019-01-01 11:58:57 -08:00
Tom Moor d045b8975c Only for admins 2018-12-25 17:18:20 -08:00
Tom Moor 96b1d6257e Added: Dashboard help tip 2018-12-25 17:12:37 -08:00
Tom Moor e08cb10b7f Closes #827 – Add optional github access token 2018-12-24 10:36:17 -08:00
Tom Moor 80a16c8af2 Closes #825 2018-12-23 10:42:34 -08:00
Tom Moor b654ba37f5 Add non-mac typeface fallbacks for headings 2018-12-23 09:45:31 -08:00
Tom Moor b673ec88b2 Merge pull request #824 from outline/content-pages
Content pages
2018-12-22 20:08:48 -08:00
Tom Moor ab82d76332 Optimize images 2018-12-22 19:57:01 -08:00
Tom Moor 4599e03121 Update API docs to new layout 2018-12-22 18:24:49 -08:00
Tom Moor 122fdfa34b Minimal developer page improves to bring inline 2018-12-21 00:35:40 -08:00
Tom Moor 47ea3c343c Figma, framer, invision, marvel pages 2018-12-20 23:53:28 -08:00
Tom Moor ac814abda4 Fixes: Oauth error on subdomains authenticating slack post 2018-12-20 20:25:13 -08:00
Tom Moor b7bea4941e Content pages 2018-12-20 20:00:58 -08:00
Tom Moor d1b352963f Optimize images 2018-12-20 07:19:48 -08:00
Tom Moor 6ebb652481 Adding content pages [wip] 2018-12-20 07:19:05 -08:00
Tom Moor 3a9a5f5ed3 Closes #819 2018-12-17 22:52:06 -08:00
Tom Moor 29630e68d2 Closes #815 2018-12-16 17:57:41 -08:00
Tom Moor 3330359c4c Fixes: Misalignment of signin buttons on mobile 2018-12-16 17:26:38 -08:00
Tom Moor 3c51b38672 Upgrade RME, fixes block insert menu appearing on embeds and block toolbar not hiding correctly on outside click 2018-12-16 14:18:56 -08:00
Tom Moor 8653338f4e Fixes: Connecting slack commands from subdomain 2018-12-15 19:05:22 -08:00
Tom Moor 044b4f16bc Editor embeds (#680)
- [x] Make deleting an embed easier
- [x] Add document level ability to disable embeds
- [x] Add team level ability to disable embeds
- [x] GitHub
- [x] Numeracy
- [x] Mode Analytics
- [x] Figma
- [x] Airtable
- [x] Vimeo
- [x] RealtimeBoard
- [x] Loom
- [x] Lucidcharts
- [x] Framer
- [x] InVision
- [x] Typeform
- [x] Marvel
- [x] Spotify
- [x] Codepen
- [x] Trello
2018-12-15 14:06:29 -08:00
Tom Moor 836f9a88a2 Fixes: Teams with non-latin characters annot signin 2018-12-12 10:31:08 -08:00
Tom Moor 05fe573974 Remove duplicate documents.update hook 2018-12-08 08:59:15 -08:00
Tom Moor b068ba9f02 Customize route for first signin for tracking 2018-12-08 08:57:34 -08:00
Tom Moor d3911b9e20 Fixes: Slack integrations page needs to ensure collections are loaded 2018-12-06 19:18:34 -08:00
Tom Moor 06e7ab84cd Cascade notification setting deletion on user account deletion 2018-12-06 19:14:43 -08:00
Tom Moor 9ca0038d39 Fixes: Collection creation notification email
Added: Unsubscribe option to notification email footers
Added: Two new notification types (emails not written yet)
Fixed: Validation added to notification setting events
2018-12-05 23:44:41 -08:00
Tom Moor cc8dacba32 Preserve function names in production code 2018-12-04 22:36:13 -08:00
Tom Moor 8cbcb77486 Base model refactor (#810)
* Big upgrades

* WIP: Stash

* Stash, 30 flow errors left

* Downgrade mobx

* WIP

* When I understand the difference between class and instance methods

* 💚

* Fixes: File import
Model saving edge cases
pinning and starring docs
Collection editing
Upgrade mobx devtools

* Notification settings saving works

* Disabled settings

* Document mailer

* Working notifications

* Colletion created notification
Ensure not notified for own actions

* Tidy up

* Document updated event only for document creation
Add indexes
Notification setting on user creation

* Commentary

* Fixed: Notification setting on signup

* Fix document move / duplicate stale data
Add BaseModel.refresh method

* Fixes: Title in sidebar not updated after editing document

* 💚

* Improve / restore error handling
Better handle offline errors

* 👕
2018-12-04 22:24:30 -08:00
Tom Moor 67cd250316 Remove diff summary from document history sidebar.
Cause of occassional memory leaks and downtime :(
2018-11-23 11:12:14 -08:00
Tom Moor 36fd81d190 Improve styling of images in Changelog 2018-11-22 21:40:17 -08:00
Tom Moor c5cd2223f7 Bump RME: Fixes deleting images now smoother
Adds code language selection to code blocks
2018-11-22 14:57:03 -08:00
Tom Moor 085c452d77 Add subtle branding to shared documents 2018-11-20 21:12:17 -08:00
Tom Moor 76924e70f5 Fix ordering of recently updated documents
Edited -> Updated
2018-11-20 20:18:24 -08:00
Tom Moor f20f197930 Add ability to create description on collection create 2018-11-19 23:04:55 -08:00
Tom Moor ecc7ba0e9d Added: Collection to starred view
Alphabetical sort to starred view
2018-11-19 23:01:49 -08:00
Tom Moor 2c55f94d39 Move session tracking 2018-11-19 21:28:15 -08:00
Tom Moor 8aaab2fc76 Add ga dimension 2018-11-19 21:05:43 -08:00
Tom Moor 85da275b06 Merge branch 'collection-descriptions' 2018-11-18 22:03:27 -08:00
Tom Moor e251c77f3c Fixes: Error loading new document in Editor 2018-11-18 22:03:10 -08:00
Tom Moor 4845331d70 Merge pull request #807 from outline/collection-descriptions
Collection descriptions
2018-11-18 21:34:05 -08:00
Tom Moor 45445606b4 Improve loading state 2018-11-18 21:27:50 -08:00
Tom Moor 8208a6128a Upgrade RME - z-index fix 2018-11-18 18:59:09 -08:00
Tom Moor 032d843f5b Fix isInternalUrl for subdomain support
Refactor / reduce plumbing
2018-11-18 18:43:11 -08:00
Tom Moor cd1956b971 Split editor 2018-11-18 18:28:22 -08:00
Tom Moor c308a2378f stash 2018-11-18 13:12:46 -08:00
Tom Moor 919bca6769 Collections got descriptions now 2018-11-18 13:12:46 -08:00
Tom Moor 3718a9609d Closes #805 - Unable to signin on self-hosted installations with non-www subdomain 2018-11-18 12:07:11 -08:00
Tom Moor 3c563e3001 Ensure team authentication matches subdomain 2018-11-17 23:30:05 -08:00
Tom Moor 5d6dca0faa Fixes: SPA page tracking 2018-11-17 19:44:57 -08:00
Tom Moor 1b0ac340c2 Best practice for DefinePlugin 2018-11-17 19:09:48 -08:00
Tom Moor 6e32f292c2 Allow filtering of people in admin 2018-11-17 18:43:46 -08:00
Tom Moor d74b99635e Fixes: Save profile picture automatically after upload
Fix jank when cropping large photos
2018-11-17 18:25:10 -08:00
Tom Moor d3834d2dc5 Fixes: Redis keys related to queue jobs should be removed after completion 2018-11-17 16:09:42 -08:00
Tom Moor 11a411447c Bump RME - closes #803 2018-11-17 14:06:21 -08:00
Tom Moor 0a73048f0f Fixed height for keyboard shortcuts UI
closes #792
2018-11-15 00:28:59 -08:00
Tom Moor e3b0b55065 CRUSH pngs on homepage 2018-11-15 00:21:24 -08:00
Tom Moor e73c25e3e3 Include missing doctype (lighthouse feedback) 2018-11-15 00:16:15 -08:00
Tom Moor 26036ad92c Fixes: handling of www subdomain in production
Hanging sessions when subdomain changes
2018-11-13 23:08:27 -08:00
Tom Moor 400d0f264e Closes #801 2018-11-13 20:54:18 -08:00
Tom Moor ac21d4b6e8 v0.17.0 2018-11-12 23:33:03 -08:00
Tom Moor 414cb1d79c Fix specs 2018-11-12 23:17:50 -08:00
Tom Moor a95632b9de Fix infinite redirect on www subdomain
Add first route tests
2018-11-12 22:45:51 -08:00
Tom Moor 1d906c7f68 Merge pull request #791 from outline/subdomains
Custom Subdomains
2018-11-12 22:01:49 -08:00
Tom Moor 2e3737c6f5 Cleanup, fix header links on subdomains 2018-11-12 20:22:01 -08:00
Tom Moor bad8718a6b 💚 2018-11-12 14:53:32 -08:00
Tom Moor c60bd4260f Provision subdomain for ALL new teams, add tests 2018-11-12 14:00:23 -08:00
Tom Moor b3a8d34af3 Add support for SUBDOMAINS_ENABLED=false 2018-11-11 22:06:50 -08:00
Tom Moor 61138ff4fa Cleanup 2018-11-11 21:17:03 -08:00
Tom Moor c81135c09e animations 2018-11-11 21:08:28 -08:00
Tom Moor cc9f32cdc9 Team switcher 2018-11-11 16:24:05 -08:00
Tom Moor 10e1f0231c Iterate, iterate 2018-11-11 14:23:31 -08:00
Tom Moor e24a187699 accessToken per subdomain 2018-11-09 23:40:33 -08:00
Tom Moor c323de4807 Attempt to provision subdomain on team create 2018-11-08 21:05:07 -08:00
Tom Moor 6391474d14 getUrl -> url consistency
test improvements
2018-11-08 21:05:07 -08:00
Tom Moor 8de074b275 Allow removing subdomain 2018-11-08 21:05:07 -08:00
Tom Moor 1be8e13828 Fix localhost
Handle automatic subdomain redirect
2018-11-08 21:05:07 -08:00
Tom Moor 6418712c47 Improve error handling 2018-11-08 21:05:07 -08:00
Tom Moor 21b1c0747c Basic functionality in place, need improved errors and logged in redirect 2018-11-08 21:05:07 -08:00
Tom Moor 07e61bd347 First pass, can create and update 2018-11-08 21:02:36 -08:00
Tom Moor 4260b5e664 datetime -> dateTime [ci skip] 2018-11-06 23:07:07 -08:00
Tom Moor ebc0346ff9 Merge pull request #798 from outline/upgrade-deps
Upgrade dependencies
2018-11-06 22:27:51 -08:00
Tom Moor 8d569fd46d Upgrade dependencies 2018-11-06 21:58:32 -08:00
Tom Moor 690feb6040 Fixes: Opening document history menu 2018-11-05 23:39:10 -08:00
Tom Moor 5f97897418 SSLify 2018-11-04 10:15:37 -08:00
Tom Moor 5f2d4e416c Upgrade RME 2018-10-29 21:05:07 -07:00
Tom Moor 464eda1d82 Merge pull request #785 from JonathanUsername/master
Make isHistory regex slightly more specific
2018-10-19 20:07:54 -07:00
Tom Moor ffe7b5a41c Merge pull request #788 from Polychain/roman/s3-content-length-range
server: fix content-length-range s3 uplaod policy
2018-10-19 20:06:54 -07:00
Roman Shtylman aadea856c3 server: fix content-length-range s3 uplaod policy
The max size should be an integer rather than a string (environment variables
are strings). More strict s3 implementations reject the upload because they do
not consider the policy valid if a string is used rather than an integer.
2018-10-19 18:09:44 -07:00
Jonathan King 91d79491f4 Make isHistory regex slightly more specific 2018-10-16 13:01:17 +01:00
Tom Moor 61cce88ef8 Add note on Google Auth to README [ci skip] 2018-09-30 23:32:06 -07:00
Tom Moor 1356e35ad1 Closes #686 - null does not trigger default param 2018-09-30 18:42:27 -07:00
Tom Moor d54750ef19 Closes #769 2018-09-30 18:31:42 -07:00
Tom Moor 283fad5f87 Merge pull request #781 from outline/zapier
Zapier
2018-09-30 18:11:22 -07:00
Tom Moor b02e5184fd Closes #764 - Visual feedback that avatar is uploading 2018-09-30 18:09:35 -07:00
Tom Moor e8ef1145a1 Add integration icons
Add link to public zapier invite
2018-09-30 17:56:20 -07:00
Tom Moor f284dd1ebf Merge branch 'master' of github.com:outline/outline into zapier 2018-09-30 17:37:35 -07:00
Tom Moor 3f0202f660 Closes #780 2018-09-30 08:58:27 -07:00
Tom Moor b36977cf80 Stash 2018-09-30 08:51:14 -07:00
Tom Moor 7515bd2275 Upgrade styled-components
Upgrade styled-components-grid
2018-09-29 23:19:46 -07:00
Tom Moor ed4013e7ee Fixes #772 - Cursor jumps in search field 2018-09-29 23:10:07 -07:00
841 changed files with 63362 additions and 41102 deletions
+25 -14
View File
@@ -1,18 +1,29 @@
{
"presets": ["react", "env"],
"presets": [
"@babel/preset-react",
"@babel/preset-flow",
[
"@babel/preset-env",
{
"corejs": {
"version": "2",
"proposals": true
},
"useBuiltIns": "usage"
}
]
],
"plugins": [
"lodash",
"styled-components",
"transform-decorators-legacy",
"transform-es2015-destructuring",
"transform-object-rest-spread",
"transform-regenerator",
"transform-class-properties",
"syntax-dynamic-import"
],
"env": {
"development": {
"presets": ["react-hmre"]
}
}
}
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-transform-destructuring",
"@babel/plugin-transform-regenerator",
"transform-class-properties"
]
}
+9 -3
View File
@@ -3,7 +3,7 @@ jobs:
build:
working_directory: ~/outline
docker:
- image: circleci/node:8.11
- image: circleci/node:12
- image: circleci/redis:latest
- image: circleci/postgres:9.6.5-alpine-ram
environment:
@@ -29,9 +29,15 @@ jobs:
- run:
name: migrate
command: ./node_modules/.bin/sequelize db:migrate --url $DATABASE_URL_TEST
- run:
name: lint
command: yarn lint
- run:
name: flow
command: yarn flow check --max-workers 4
- run:
name: test
command: yarn test
- run:
name: lint
command: yarn lint
name: build
command: yarn build
+29 -14
View File
@@ -3,23 +3,30 @@
# keys (for auth) and the SECRET_KEY.
#
# Please use `openssl rand -hex 32` to create SECRET_KEY
SECRET_KEY=generate_a_new_key
UTILS_SECRET=generate_a_new_key
DATABASE_URL=postgres://user:pass@localhost:5532/outline
DATABASE_URL_TEST=postgres://user:pass@localhost:5532/outline-test
REDIS_URL=redis://localhost:6479
DATABASE_URL=postgres://user:pass@postgres:5432/outline
DATABASE_URL_TEST=postgres://user:pass@postgres:5432/outline-test
SECRET_KEY=F0E5AD933D7F6FD8F4DBB3E038C501C052DC0593C686D21ACB30AE205D2F634B
PORT=3000
REDIS_URL=redis://redis:6379
URL=http://localhost:3000
DEPLOYMENT=self
PORT=3000
# enforce (auto redirect to) https in production, (optional) default is true.
# set to false if your SSL is terminated at a loadbalancer, for example
FORCE_HTTPS=true
ENABLE_UPDATES=true
DEBUG=sql,cache,presenters,events
DEBUG=cache,presenters,events
# Third party signin credentials (at least one is required)
SLACK_KEY=71315967491.XXXXXXXXXX
SLACK_SECRET=d2dc414f9953226bad0a356cXXXXYYYY
SLACK_KEY=get_a_key_from_slack
SLACK_SECRET=get_the_secret_of_above_key
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Comma separated list of domains to be allowed (optional)
# If not set, all Google apps domains are allowed by default
GOOGLE_ALLOWED_DOMAINS=
@@ -27,15 +34,20 @@ GOOGLE_ALLOWED_DOMAINS=
# Third party credentials (optional)
SLACK_VERIFICATION_TOKEN=PLxk6OlXXXXXVj3YYYY
SLACK_APP_ID=A0XXXXXXX
SLACK_MESSAGE_ACTIONS=true
GOOGLE_ANALYTICS_ID=
BUGSNAG_KEY=
SENTRY_DSN=
# AWS credentials (optional in dev)
AWS_ACCESS_KEY_ID=notcheckedindev
AWS_SECRET_ACCESS_KEY=notcheckedindev
# AWS credentials (optional in development)
AWS_ACCESS_KEY_ID=get_a_key_from_aws
AWS_SECRET_ACCESS_KEY=get_the_secret_of_above_key
AWS_REGION=xx-xxxx-x
AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569
AWS_S3_UPLOAD_BUCKET_NAME=outline-dev
AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here
AWS_S3_UPLOAD_MAX_SIZE=26214400
# uploaded s3 objects permission level, default is private
# set to "public-read" to allow public access
AWS_S3_ACL=private
# Emails configuration (optional)
SMTP_HOST=
@@ -44,3 +56,6 @@ SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_FROM_EMAIL=
SMTP_REPLY_EMAIL=
# Custom logo that displays on the authentication screen, scaled to height: 60px
# TEAM_LOGO=https://example.com/images/logo.png
+60 -23
View File
@@ -6,23 +6,54 @@
"plugin:import/warnings",
"plugin:flowtype/recommended"
],
"plugins": ["prettier", "flowtype"],
"plugins": [
"prettier",
"flowtype"
],
"rules": {
"eqeqeq": 2,
"no-unused-vars": 2,
// // Bring back after we remove CSS Modules 100%
// "import/order": "warn",
// Prettier automatically uses the least amount of parens possible, so this
// does more harm than good.
"no-mixed-operators": "off",
// Temporary fix for a failing import lint
"import/no-unresolved": [
"import/order": [
"error",
{
"ignore": ["slate-drop-or-paste-images"]
"alphabetize": {
"order": "asc"
},
"pathGroups": [
{
"pattern": "shared/**",
"group": "external",
"position": "after"
},
{
"pattern": "stores",
"group": "external",
"position": "after"
},
{
"pattern": "stores/**",
"group": "external",
"position": "after"
},
{
"pattern": "models/**",
"group": "external",
"position": "after"
},
{
"pattern": "scenes/**",
"group": "external",
"position": "after"
},
{
"pattern": "components/**",
"group": "external",
"position": "after"
}
]
}
],
// Flow
"flowtype/require-valid-file-annotation": [
2,
"always",
@@ -30,22 +61,34 @@
"annotationStyle": "line"
}
],
"flowtype/space-after-type-colon": [2, "always"],
"flowtype/space-before-type-colon": [2, "never"],
// Enforce that code is formatted with prettier.
"flowtype/space-after-type-colon": [
2,
"always"
],
"flowtype/space-before-type-colon": [
2,
"never"
],
"prettier/prettier": [
"error",
{
"printWidth": 80,
"trailingComma": "es5",
"singleQuote": true
"trailingComma": "es5"
}
]
},
"settings": {
"react": {
"createClass": "createReactClass",
"pragma": "React",
"version": "detect"
},
"import/resolver": {
"node": {
"paths": ["app", "."]
"paths": [
"app",
"."
]
}
},
"flowtype": {
@@ -56,12 +99,6 @@
"jest": true
},
"globals": {
"__DEV__": true,
"SLACK_KEY": true,
"DEPLOYMENT": true,
"BASE_URL": true,
"BUGSNAG_KEY": true,
"afterAll": true,
"Bugsnag": true
"EDITOR_VERSION": true
}
}
}
+3 -6
View File
@@ -4,15 +4,14 @@
.*/shared/.*
[ignore]
.*/node_modules/tiny-cookie/flow/.*
.*/node_modules/styled-components/.*
.*/node_modules/polished/.*
.*/node_modules/mobx/.*.flow
.*/node_modules/react-side-effect/.*
.*/node_modules/fbjs/.*
.*/node_modules/slate-edit-code/example/.*
.*/node_modules/slate-edit-code/lib/.*
.*/node_modules/slate-edit-list/.*
.*/node_modules/slate-prism/.*
.*/node_modules/config-chain/.*
.*/server/scripts/.*
*.test.js
[libs]
@@ -23,12 +22,10 @@ emoji=true
module.system.node.resolve_dirname=node_modules
module.system.node.resolve_dirname=app
module.name_mapper='^\(.*\)\.s?css$' -> 'empty/object'
module.name_mapper='^\(.*\)\.md$' -> 'empty/object'
module.name_mapper='^shared\/\(.*\)$' -> '<PROJECT_ROOT>/shared/\1'
module.file_ext=.js
module.file_ext=.scss
module.file_ext=.md
module.file_ext=.json
-1
View File
@@ -1 +0,0 @@
yarn lint:flow
+37
View File
@@ -0,0 +1,37 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots or videos to help explain your problem.
**Outline (please complete the following information):**
- Install: [getoutline.com or self hosted]
- Version: [commit sha if self hosted]
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Mobile (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Feature request
url: https://github.com/outline/outline/discussions/new
about: Request a feature to be added to the project
- name: Self hosting questions
url: https://github.com/outline/outline/discussions/new
about: Ask questions and discuss running Outline with community members
+1
View File
@@ -1,5 +1,6 @@
dist
node_modules/*
server/scripts
.env
.log
npm-debug.log
+6
View File
@@ -0,0 +1,6 @@
{
"javascript.validate.enable": false,
"typescript.validate.enable": false,
"editor.formatOnSave": true,
"typescript.format.enable": false
}
+7 -3
View File
@@ -1,4 +1,4 @@
FROM node:8.11
FROM node:12-alpine
ENV PATH /opt/outline/node_modules/.bin:/opt/node_modules/.bin:$PATH
ENV NODE_PATH /opt/outline/node_modules:/opt/node_modules
@@ -7,7 +7,11 @@ RUN mkdir -p $APP_PATH
WORKDIR $APP_PATH
COPY . $APP_PATH
RUN yarn
RUN yarn install --pure-lockfile
RUN yarn build
RUN cp -r /opt/outline/node_modules /opt/node_modules
CMD yarn build && yarn start
CMD yarn start
EXPOSE 3000
+98 -14
View File
@@ -1,19 +1,103 @@
Copyright (c) 2017 Outline (https://www.getoutline.com/) and individual contributors.
All rights reserved.
Business Source License 1.1
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Parameters
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Licensor: General Outline, Inc.
Licensed Work: Outline 0.46.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
Service.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
A “Document Service” is a commercial offering that
allows third parties (other than your employees and
contractors) to access the functionality of the
Licensed Work by creating teams and documents
controlled by such third parties.
3. Neither the name of the Outline nor the names of its contributors may be used to endorse or promote products derived from this software
without specific prior written permission.
Change Date: 2023-08-12
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Change License: Apache License, Version 2.0
For information about alternative licensing arrangements for the Software,
please visit: https://www.getoutline.com
Notice
The Business Source License (this document, or the “License”) is not an Open
Source license. However, the Licensed Work will eventually be made available
under an Open Source License, as stated in this License.
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
“Business Source License” is a trademark of MariaDB Corporation Ab.
-----------------------------------------------------------------------------
Business Source License 1.1
Terms
The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited
production use.
Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.
If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.
All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.
You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.
MariaDB hereby grants you permission to use this Licenses text to license
your works, and to refer to it using the trademark “Business Source License”,
as long as you comply with the Covenants of Licensor below.
Covenants of Licensor
In consideration of the right to use this Licenses text and the “Business
Source License” name and trademark, Licensor covenants to MariaDB, and to all
other recipients of the licensed work to be provided by Licensor:
1. To specify as the Change License the GPL Version 2.0 or any later version,
or a license that is compatible with GPL Version 2.0 or a later version,
where “compatible” means that software provided under the Change License can
be included in a program with software provided under GPL Version 2.0 or a
later version. Licensor may specify additional Change Licenses without
limitation.
2. To either: (a) specify an additional grant of rights to use that does not
impose any additional restriction on the right granted in this License, as
the Additional Use Grant; or (b) insert the text “None”.
3. To specify a Change Date.
4. Not to modify this License in any other way.
+7 -4
View File
@@ -1,16 +1,19 @@
up:
docker-compose up -d redis postgres s3
docker-compose run --rm outline bash -c "yarn && yarn sequelize db:migrate"
docker-compose up outline
yarn install --pure-lockfile
yarn sequelize db:migrate
yarn dev
build:
docker-compose build --pull outline
test:
docker-compose run --rm outline yarn test
docker-compose up -d redis postgres s3
yarn test
watch:
docker-compose run --rm outline yarn test:watch
docker-compose up -d redis postgres s3
yarn test:watch
destroy:
docker-compose stop
+65 -20
View File
@@ -1,14 +1,15 @@
<p align="center">
<img src="https://user-images.githubusercontent.com/31465/34380645-bd67f474-eb0b-11e7-8d03-0151c1730654.png" height="29" />
</p>
<p align="center">
<i>An open, extensible, wiki for your team built using React and Node.js.<br/>Try out Outline using our hosted version at <a href="https://www.getoutline.com">www.getoutline.com</a>.</i>
<br/>
<img src="https://user-images.githubusercontent.com/31465/34456332-51e41eb0-ed9c-11e7-9fa9-20e7fa946494.jpg" alt="Outline" width="800" />
<img src="https://user-images.githubusercontent.com/380914/78513257-153ae080-775f-11ea-9b49-1e1939451a3e.png" alt="Outline" width="800" />
</p>
<p align="center">
<a href="https://circleci.com/gh/outline/outline" rel="nofollow"><img src="https://circleci.com/gh/outline/outline.svg?style=shield&amp;circle-token=c0c4c2f39990e277385d5c1ae96169c409eb887a"></a>
<a href="https://spectrum.chat/outline" rel="nofollow"><img src="https://withspectrum.github.io/badge/badge.svg" alt="Join the community on Spectrum"/></a>
<a href="https://github.com/prettier/prettier"><img src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat"></a>
<a href="https://github.com/styled-components/styled-components"><img src="https://img.shields.io/badge/style-%F0%9F%92%85%20styled--components-orange.svg"></a>
</p>
@@ -21,26 +22,68 @@ If you'd like to run your own copy of Outline or contribute to development then
Outline requires the following dependencies:
- Node.js >= 12
- Postgres >=9.5
- Redis
- Slack developer application
- Redis >= 4
- AWS S3 storage bucket for media and other attachments
- Slack or Google developer application for authentication
### Development
In development you can quickly get an environment running using Docker by following these steps:
1. Install [Docker for Desktop](https://www.docker.com) if you don't already have it.
1. Install these dependencies if you don't already have them
1. [Docker for Desktop](https://www.docker.com)
1. [Node.js](https://nodejs.org/) (v12 LTS preferred)
1. [Yarn](https://yarnpkg.com)
1. Clone this repo
1. Register a Slack app at https://api.slack.com/apps
1. Copy the file `.env.sample` to `.env` and fill out the Slack keys, everything
else should work well for development.
1. Run `make up`. This will download dependencies, build and launch a development version of Outline.
1. Copy the file `.env.sample` to `.env`
1. Fill out the following fields:
1. `SECRET_KEY` (follow instructions in the comments at the top of `.env`)
1. `SLACK_KEY` (this is called "Client ID" in Slack admin)
1. `SLACK_SECRET` (this is called "Client Secret" in Slack admin)
1. Configure your Slack app's Oauth & Permissions settings
1. Add `http://localhost:3000/auth/slack.callback` as an Oauth redirect URL
1. Ensure that the bot token scope contains at least `users:read`
1. Run `make up`. This will download dependencies, build and launch a development version of Outline
### Production
For a self-hosted production installation there is more flexibility, but these are the suggested steps:
1. Clone this repo and install dependencies with `yarn` or `npm install`
> Requires [Node.js](https://nodejs.org/) and [yarn](https://yarnpkg.com) installed
1. Build the web app with `yarn build:webpack` or `npm run build:webpack`
1. Using the `.env.sample` as a reference, set the required variables in your production environment. The following are required as a minimum:
1. `SECRET_KEY` (follow instructions in the comments at the top of `.env`)
1. `SLACK_KEY` (this is called "Client ID" in Slack admin)
1. `SLACK_SECRET` (this is called "Client Secret" in Slack admin)
1. `DATABASE_URL` (run your own local copy of Postgres, or use a cloud service)
1. `REDIS_URL` (run your own local copy of Redis, or use a cloud service)
1. `URL` (the public facing URL of your installation)
1. `AWS_` (all of the keys beginning with AWS)
1. Migrate database schema with `yarn sequelize:migrate` or `npm run sequelize:migrate `
1. Start the service with any daemon tools you prefer. Take PM2 for example, `NODE_ENV=production pm2 start index.js --name outline `
1. Visit http://you_server_ip:3000 and you should be able to see Outline page
> Port number can be changed in the `.env` file
1. (Optional) You can add an `nginx` reverse proxy to serve your instance of Outline for a clean URL without the port number, support SSL, etc.
## Development
### Server
To enable debugging statements, set the following env vars:
Outline uses [debug](https://www.npmjs.com/package/debug). To enable debugging output, the following categories are available:
```
DEBUG=sql,cache,presenters,events
DEBUG=sql,cache,presenters,events,logistics,emails,mailer
```
## Migrations
@@ -48,7 +91,7 @@ DEBUG=sql,cache,presenters,events
Sequelize is used to create and run migrations, for example:
```
yarn sequelize migration:create
yarn sequelize migration:generate --name my-migration
yarn sequelize db:migrate
```
@@ -60,13 +103,13 @@ yarn sequelize db:migrate --env test
## Structure
Outline is composed of separate backend and frontend application which are both driven by the same Node process. As both are written in Javascript, they share some code but are mostly separate. We utilize latest language features, including `async`/`await`, and [Flow](https://flow.org/) typing. Prettier and ESLint are ran as pre-commit hooks.
Outline is composed of separate backend and frontend application which are both driven by the same Node process. As both are written in Javascript, they share some code but are mostly separate. We utilize the latest language features, including `async`/`await`, and [Flow](https://flow.org/) typing. Prettier and ESLint are enforced by CI.
### Frontend
Outline's frontend is a React application compiled with [Webpack](https://webpack.js.org/). It uses [Mobx](https://mobx.js.org/) for state management and [Styled Components](https://www.styled-components.com/) for component styles. Unless global, state logic and styles are always co-located with React components together with their subcomponents to make the component tree easier to manage.
The editor itself is built ontop of [Slate](https://github.com/ianstormtaylor/slate) and hosted in a separate repository to encourage reuse: [rich-markdown-editor](https://github.com/outline/rich-markdown-editor)
The editor itself is built on [Prosemirror](https://github.com/prosemirror) and hosted in a separate repository to encourage reuse: [rich-markdown-editor](https://github.com/outline/rich-markdown-editor)
- `app/` - Frontend React application
- `app/scenes` - Full page views
@@ -80,15 +123,18 @@ The editor itself is built ontop of [Slate](https://github.com/ianstormtaylor/sl
Backend is driven by [Koa](http://koajs.com/) (API, web server), [Sequelize](http://docs.sequelizejs.com/) (database) and React for public pages and emails.
- `server/api` - API endpoints
- `server/commands` - Domain logic, currently being refactored from /models
- `server/emails` - React rendered email templates
- `server/models` - Database models (Sequelize)
- `server/pages` - Server-side rendered public pages (React)
- `server/models` - Database models
- `server/policies` - Authorization logic
- `server/presenters` - API responses for database models
- `server/test` - Test helps and support
- `server/utils` - Utility methods
- `shared` - Code shared between frontend and backend applications
## Tests
We aim to have sufficient test coverage for critical parts of the application and aren't aiming for 100% unit test coverage. All API endpoints and anything authentication related should be thoroughly tested, and it's generally good to add tests for backend features and code.
We aim to have sufficient test coverage for critical parts of the application and aren't aiming for 100% unit test coverage. All API endpoints and anything authentication related should be thoroughly tested.
To add new tests, write your tests with [Jest](https://facebook.github.io/jest/) and add a file with `.test.js` extension next to the tested code.
@@ -105,9 +151,9 @@ yarn test:app
## Contributing
Outline is still built and maintained by a small team we'd love your help to fix bugs and add features!
Outline is built and maintained by a small team we'd love your help to fix bugs and add features!
However, before working on a pull request please let the core team know by creating or commenting in an issue on [GitHub](https://www.github.com/outline/outline/issues), and we'd also love to hear from you in our [Spectrum community](https://spectrum.chat/outline). This way we can ensure that an approach is agreed on before code is written and will hopefully help to get your contributions integrated faster! Take a look at our [roadmap](https://www.getoutline.com/share/3e6cb2b5-d68b-4ad8-8900-062476820311).
However, before working on a pull request please let the core team know by creating or commenting in an issue on [GitHub](https://www.github.com/outline/outline/issues), and we'd also love to hear from you in the [Discussions](https://www.github.com/outline/outline/discussions). This way we can ensure that an approach is agreed on before code is written and will hopefully help to get your contributions integrated faster!
If youre looking for ways to get started, here's a list of ways to help us improve Outline:
@@ -115,8 +161,7 @@ If youre looking for ways to get started, here's a list of ways to help us im
* Performance improvements, both on server and frontend
* Developer happiness and documentation
* Bugs and other issues listed on GitHub
* Helping others on Spectrum
## License
Outline is [BSD licensed](/blob/master/LICENSE).
Outline is [BSL 1.1 licensed](https://github.com/outline/outline/blob/master/LICENSE).
+11
View File
@@ -0,0 +1,11 @@
# Security Policy
## Reporting a Vulnerability
The Outline team takes security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
To report a security issue, email [hello@getoutline.com](mailto:hello@getoutline.com) and include the word "SECURITY" in the subject line.
The Outline team will send a response indicating the next steps in handling your report. After the initial reply to your report you will be kept informed of the progress towards a fix and full announcement.
Report security bugs in third-party dependencies to the person or team maintaining the module. You can also report a vulnerability through the [Node Security Project](https://nodesecurity.io/report).
-2
View File
@@ -1,2 +0,0 @@
import idObj from 'identity-obj-proxy';
export default idObj;
+142
View File
@@ -0,0 +1,142 @@
{
"name": "Outline",
"description": "Open source wiki and knowledge base for growing teams",
"website": "https://www.getoutline.com/",
"repository": "https://github.com/outline/outline",
"keywords": [
"wiki",
"team",
"node",
"markdown",
"slack"
],
"success_url": "/",
"formation": {
"web": {
"quantity": 1,
"size": "Hobby"
}
},
"image": "heroku/node",
"addons": [
{
"plan": "heroku-redis"
},
{
"plan": "heroku-postgresql"
}
],
"scripts": {
"postdeploy": "yarn sequelize db:migrate"
},
"env": {
"SECRET_KEY": {
"description": "A secret key",
"generator": "secret",
"required": true
},
"ENABLE_UPDATES": {
"value": "true",
"required": true
},
"URL": {
"description": "https://{your app name}.herokuapp.com",
"required": true
},
"GOOGLE_CLIENT_ID": {
"description": "See https://developers.google.com/identity/protocols/OAuth2 to create a new Google OAuth client. You must configure at least one of Slack or Google to control login.",
"required": false
},
"GOOGLE_CLIENT_SECRET": {
"description": "",
"required": false
},
"GOOGLE_ALLOWED_DOMAINS": {
"description": "Comma separated list of domains to be allowed (optional). If not set, all Google apps domains are allowed by default",
"required": false
},
"SLACK_KEY": {
"description": "See https://api.slack.com/apps to create a new Slack app. You must configure at least one of Slack or Google to control login.",
"required": false
},
"SLACK_SECRET": {
"description": "Your Slack client secret - d2dc414f9953226bad0a356cXXXXYYYY",
"required": false
},
"SLACK_VERIFICATION_TOKEN": {
"description": "Your Slack verification token - PLxk6OlXXXXXVj3YYYY",
"required": false
},
"SLACK_APP_ID": {
"description": "A0XXXXXXXXX",
"required": false
},
"AWS_ACCESS_KEY_ID": {
"description": "Needed to save file uploads. Optional for development / testing",
"required": false
},
"AWS_SECRET_ACCESS_KEY": {
"description": "",
"required": false
},
"AWS_S3_UPLOAD_BUCKET_NAME": {
"description": "yourbucket.example.com",
"required": false
},
"AWS_S3_UPLOAD_BUCKET_URL": {
"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_REGION": {
"value": "us-east-1",
"description": "Region in which the above S3 bucket exists",
"required": false
},
"AWS_S3_ACL": {
"value": "private",
"description": "S3 canned ACL for document attachments",
"required": false
},
"SMTP_HOST": {
"description": "smtp.example.com (optional)",
"required": false
},
"SMTP_PORT": {
"description": "1234 (optional)",
"required": false
},
"SMTP_USERNAME": {
"description": "me@example.com (optional)",
"required": false
},
"SMTP_PASSWORD": {
"description": "(optional)",
"required": false
},
"SMTP_FROM_EMAIL": {
"description": "wiki@example.com (optional)",
"required": false
},
"SMTP_REPLY_EMAIL": {
"description": "wikireply@example.com (optional)",
"required": false
},
"GOOGLE_ANALYTICS_ID": {
"description": "UA-xxxx (optional)",
"required": false
},
"SENTRY_DSN": {
"description": "An API key for Sentry if you wish to collect error reporting (optional)",
"required": false
},
"TEAM_LOGO": {
"description": "A logo that will be displayed on the signed out home page",
"required": false
}
}
}
@@ -1,25 +1,32 @@
// @flow
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import Flex from 'shared/components/Flex';
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Flex from "components/Flex";
export const Action = styled(Flex)`
justify-content: center;
align-items: center;
padding: 0 0 0 12px;
height: 32px;
font-size: 15px;
flex-shrink: 0;
a {
color: ${props => props.theme.text};
color: ${(props) => props.theme.text};
height: 24px;
}
&:empty {
display: none;
}
`;
export const Separator = styled.div`
flex-shrink: 0;
margin-left: 12px;
width: 1px;
height: 20px;
background: ${props => props.theme.slateLight};
height: 28px;
background: ${(props) => props.theme.divider};
`;
const Actions = styled(Flex)`
@@ -28,7 +35,8 @@ const Actions = styled(Flex)`
right: 0;
left: 0;
border-radius: 3px;
background: rgba(255, 255, 255, 0.9);
background: ${(props) => props.theme.background};
transition: ${(props) => props.theme.backgroundTransition};
padding: 12px;
-webkit-backdrop-filter: blur(20px);
@@ -36,7 +44,7 @@ const Actions = styled(Flex)`
display: none;
}
${breakpoint('tablet')`
${breakpoint("tablet")`
left: auto;
padding: 24px;
`};
-4
View File
@@ -1,4 +0,0 @@
// @flow
import Actions from './Actions';
export { Action, Separator } from './Actions';
export default Actions;
-36
View File
@@ -1,36 +0,0 @@
// @flow
import * as React from 'react';
import { observer } from 'mobx-react';
import Flex from 'shared/components/Flex';
import styled from 'styled-components';
type Props = {
children: React.Node,
type?: 'info' | 'success' | 'warning' | 'danger' | 'offline',
};
@observer
class Alert extends React.Component<Props> {
defaultProps = {
type: 'info',
};
render() {
return (
<Container align="center" justify="center" type={this.props.type}>
{this.props.children}
</Container>
);
}
}
const Container = styled(Flex)`
height: $headerHeight;
color: #ffffff;
font-size: 14px;
line-height: 1;
background-color: ${({ theme, type }) => theme.color[type]};
`;
export default Alert;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import Alert from './Alert';
export default Alert;
+40
View File
@@ -0,0 +1,40 @@
// @flow
/* global ga */
import * as React from "react";
import env from "env";
type Props = {
children?: React.Node,
};
export default class Analytics extends React.Component<Props> {
componentDidMount() {
if (!env.GOOGLE_ANALYTICS_ID) return;
// standard Google Analytics script
window.ga =
window.ga ||
function () {
// $FlowIssue
(ga.q = ga.q || []).push(arguments);
};
// $FlowIssue
ga.l = +new Date();
ga("create", env.GOOGLE_ANALYTICS_ID, "auto");
ga("set", { dimension1: "true" });
ga("send", "pageview");
const script = document.createElement("script");
script.src = "https://www.google-analytics.com/analytics.js";
script.async = true;
if (document.body) {
document.body.appendChild(script);
}
}
render() {
return this.props.children || null;
}
}
-62
View File
@@ -1,62 +0,0 @@
// @flow
import * as React from 'react';
import { Provider, observer, inject } from 'mobx-react';
import stores from 'stores';
import AuthStore from 'stores/AuthStore';
import ApiKeysStore from 'stores/ApiKeysStore';
import UsersStore from 'stores/UsersStore';
import CollectionsStore from 'stores/CollectionsStore';
import IntegrationsStore from 'stores/IntegrationsStore';
import LoadingIndicator from 'components/LoadingIndicator';
type Props = {
auth: AuthStore,
children?: React.Node,
};
let authenticatedStores;
const Auth = observer(({ auth, children }: Props) => {
if (auth.authenticated) {
const { user, team } = auth;
if (!team || !user) {
return <LoadingIndicator />;
}
// Only initialize stores once. Kept in global scope because otherwise they
// will get overridden on route change
if (!authenticatedStores) {
// Stores for authenticated user
authenticatedStores = {
integrations: new IntegrationsStore({
ui: stores.ui,
}),
apiKeys: new ApiKeysStore(),
users: new UsersStore(),
collections: new CollectionsStore({
ui: stores.ui,
teamId: team.id,
}),
};
if (window.Bugsnag) {
Bugsnag.user = {
id: user.id,
name: user.name,
teamId: team.id,
team: team.name,
};
}
authenticatedStores.collections.fetchPage({ limit: 100 });
}
return <Provider {...authenticatedStores}>{children}</Provider>;
}
auth.logout();
return null;
});
export default inject('auth')(Auth);
+43
View File
@@ -0,0 +1,43 @@
// @flow
import { observer, inject } from "mobx-react";
import * as React from "react";
import { Redirect } from "react-router-dom";
import { isCustomSubdomain } from "shared/utils/domains";
import AuthStore from "stores/AuthStore";
import LoadingIndicator from "components/LoadingIndicator";
import env from "env";
type Props = {
auth: AuthStore,
children?: React.Node,
};
const Authenticated = observer(({ auth, children }: Props) => {
if (auth.authenticated) {
const { user, team } = auth;
const { hostname } = window.location;
if (!team || !user) {
return <LoadingIndicator />;
}
// If we're authenticated but viewing a subdomain that doesn't match the
// currently authenticated team then kick the user to the teams subdomain.
if (
env.SUBDOMAINS_ENABLED &&
team.subdomain &&
isCustomSubdomain(hostname) &&
!hostname.startsWith(`${team.subdomain}.`)
) {
window.location.href = `${team.url}${window.location.pathname}`;
return <LoadingIndicator />;
}
return children;
}
auth.logout(true);
return <Redirect to="/" />;
});
export default inject("auth")(Authenticated);
+35 -14
View File
@@ -1,13 +1,14 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import placeholder from './placeholder.png';
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import placeholder from "./placeholder.png";
type Props = {
src: string,
size: number,
icon?: React.Node,
};
@observer
@@ -23,23 +24,43 @@ class Avatar extends React.Component<Props> {
};
render() {
const { src, ...rest } = this.props;
const { src, icon, ...rest } = this.props;
return (
<CircleImg
onError={this.handleError}
src={this.error ? placeholder : src}
{...rest}
/>
<AvatarWrapper>
<CircleImg
onError={this.handleError}
src={this.error ? placeholder : src}
{...rest}
/>
{icon && <IconWrapper>{icon}</IconWrapper>}
</AvatarWrapper>
);
}
}
const AvatarWrapper = styled.div`
position: relative;
`;
const IconWrapper = styled.div`
display: flex;
position: absolute;
bottom: -2px;
right: -2px;
background: ${(props) => props.theme.primary};
border: 2px solid ${(props) => props.theme.background};
border-radius: 100%;
width: 20px;
height: 20px;
`;
const CircleImg = styled.img`
width: ${props => props.size}px;
height: ${props => props.size}px;
display: block;
width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
border-radius: 50%;
border: 2px solid ${props => props.theme.white};
border: 2px solid ${(props) => props.theme.background};
flex-shrink: 0;
`;
@@ -0,0 +1,86 @@
// @flow
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { EditIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import User from "models/User";
import UserProfile from "scenes/UserProfile";
import Avatar from "components/Avatar";
import Tooltip from "components/Tooltip";
type Props = {
user: User,
isPresent: boolean,
isEditing: boolean,
isCurrentUser: boolean,
lastViewedAt: string,
};
@observer
class AvatarWithPresence extends React.Component<Props> {
@observable isOpen: boolean = false;
handleOpenProfile = () => {
this.isOpen = true;
};
handleCloseProfile = () => {
this.isOpen = false;
};
render() {
const {
user,
lastViewedAt,
isPresent,
isEditing,
isCurrentUser,
} = this.props;
return (
<>
<Tooltip
tooltip={
<Centered>
<strong>{user.name}</strong> {isCurrentUser && "(You)"}
<br />
{isPresent
? isEditing
? "currently editing"
: "currently viewing"
: `viewed ${distanceInWordsToNow(new Date(lastViewedAt))} ago`}
</Centered>
}
placement="bottom"
>
<AvatarWrapper isPresent={isPresent}>
<Avatar
src={user.avatarUrl}
onClick={this.handleOpenProfile}
size={32}
icon={isEditing ? <EditIcon size={16} color="#FFF" /> : undefined}
/>
</AvatarWrapper>
</Tooltip>
<UserProfile
user={user}
isOpen={this.isOpen}
onRequestClose={this.handleCloseProfile}
/>
</>
);
}
}
const Centered = styled.div`
text-align: center;
`;
const AvatarWrapper = styled.div`
opacity: ${(props) => (props.isPresent ? 1 : 0.5)};
transition: opacity 250ms ease-in-out;
`;
export default AvatarWithPresence;
+4 -1
View File
@@ -1,3 +1,6 @@
// @flow
import Avatar from './Avatar';
import Avatar from "./Avatar";
import AvatarWithPresence from "./AvatarWithPresence";
export { AvatarWithPresence };
export default Avatar;
+17
View File
@@ -0,0 +1,17 @@
// @flow
import styled from "styled-components";
const Badge = styled.span`
margin-left: 10px;
padding: 2px 6px 3px;
background-color: ${({ primary, theme }) =>
primary ? theme.primary : theme.textTertiary};
color: ${({ primary, theme }) => (primary ? theme.white : theme.background)};
border-radius: 4px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
user-select: none;
`;
export default Badge;
+43
View File
@@ -0,0 +1,43 @@
// @flow
import * as React from "react";
import styled from "styled-components";
import OutlineLogo from "./OutlineLogo";
import env from "env";
type Props = {
href?: string,
};
function Branding({ href = env.URL }: Props) {
return (
<Link href={href}>
<OutlineLogo size={16} />
&nbsp;Outline
</Link>
);
}
const Link = styled.a`
position: fixed;
bottom: 0;
left: 0;
font-weight: 600;
font-size: 14px;
text-decoration: none;
border-top-right-radius: 2px;
color: ${(props) => props.theme.text};
display: flex;
align-items: center;
padding: 16px;
svg {
fill: ${(props) => props.theme.text};
}
&:hover {
background: ${(props) => props.theme.sidebarBackground};
}
`;
export default Branding;
+162
View File
@@ -0,0 +1,162 @@
// @flow
import { observer, inject } from "mobx-react";
import {
PadlockIcon,
GoToIcon,
MoreIcon,
ShapesIcon,
EditIcon,
} from "outline-icons";
import * as React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import CollectionsStore from "stores/CollectionsStore";
import Document from "models/Document";
import CollectionIcon from "components/CollectionIcon";
import Flex from "components/Flex";
import BreadcrumbMenu from "./BreadcrumbMenu";
import { collectionUrl } from "utils/routeHelpers";
type Props = {
document: Document,
collections: CollectionsStore,
onlyText: boolean,
};
const Breadcrumb = observer(({ document, collections, onlyText }: Props) => {
const collection = collections.get(document.collectionId);
if (!collection) return <div />;
const path = collection.pathToDocument(document).slice(0, -1);
if (onlyText === true) {
return (
<>
{collection.private && (
<>
<SmallPadlockIcon color="currentColor" size={16} />{" "}
</>
)}
{collection.name}
{path.map((n) => (
<React.Fragment key={n.id}>
<SmallSlash />
{n.title}
</React.Fragment>
))}
</>
);
}
const isTemplate = document.isTemplate;
const isDraft = !document.publishedAt && !isTemplate;
const isNestedDocument = path.length > 1;
const lastPath = path.length ? path[path.length - 1] : undefined;
const menuPath = isNestedDocument ? path.slice(0, -1) : [];
return (
<Wrapper justify="flex-start" align="center">
{isTemplate && (
<>
<CollectionName to="/templates">
<ShapesIcon color="currentColor" />
&nbsp;
<span>Templates</span>
</CollectionName>
<Slash />
</>
)}
{isDraft && (
<>
<CollectionName to="/drafts">
<EditIcon color="currentColor" />
&nbsp;
<span>Drafts</span>
</CollectionName>
<Slash />
</>
)}
<CollectionName to={collectionUrl(collection.id)}>
<CollectionIcon collection={collection} expanded />
&nbsp;
<span>{collection.name}</span>
</CollectionName>
{isNestedDocument && (
<>
<Slash /> <BreadcrumbMenu label={<Overflow />} path={menuPath} />
</>
)}
{lastPath && (
<>
<Slash />{" "}
<Crumb to={lastPath.url} title={lastPath.title}>
{lastPath.title}
</Crumb>
</>
)}
</Wrapper>
);
});
const Wrapper = styled(Flex)`
display: none;
${breakpoint("tablet")`
display: flex;
`};
`;
const SmallPadlockIcon = styled(PadlockIcon)`
display: inline-block;
vertical-align: sub;
`;
const SmallSlash = styled(GoToIcon)`
width: 15px;
height: 10px;
flex-shrink: 0;
opacity: 0.25;
`;
export const Slash = styled(GoToIcon)`
flex-shrink: 0;
fill: ${(props) => props.theme.divider};
`;
const Overflow = styled(MoreIcon)`
flex-shrink: 0;
opacity: 0.25;
transition: opacity 100ms ease-in-out;
&:hover,
&:active {
opacity: 1;
}
`;
const Crumb = styled(Link)`
color: ${(props) => props.theme.text};
font-size: 15px;
height: 24px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
&:hover {
text-decoration: underline;
}
`;
const CollectionName = styled(Link)`
display: flex;
flex-shrink: 0;
color: ${(props) => props.theme.text};
font-size: 15px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
`;
export default inject("collections")(Breadcrumb);
+25
View File
@@ -0,0 +1,25 @@
// @flow
import * as React from "react";
import { Link } from "react-router-dom";
import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu";
type Props = {
label: React.Node,
path: Array<any>,
};
export default class BreadcrumbMenu extends React.Component<Props> {
render() {
const { path } = this.props;
return (
<DropdownMenu label={this.props.label} position="center">
{path.map((item) => (
<DropdownMenuItem as={Link} to={item.url} key={item.id}>
{item.title}
</DropdownMenuItem>
))}
</DropdownMenu>
);
}
}
+160
View File
@@ -0,0 +1,160 @@
// @flow
import { ExpandedIcon } from "outline-icons";
import { darken, lighten } from "polished";
import * as React from "react";
import styled from "styled-components";
const RealButton = styled.button`
display: ${(props) => (props.fullwidth ? "block" : "inline-block")};
width: ${(props) => (props.fullwidth ? "100%" : "auto")};
margin: 0;
padding: 0;
border: 0;
background: ${(props) => props.theme.buttonBackground};
color: ${(props) => props.theme.buttonText};
box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
height: 32px;
text-decoration: none;
flex-shrink: 0;
outline: none;
cursor: pointer;
user-select: none;
svg {
fill: ${(props) => props.iconColor || props.theme.buttonText};
}
&::-moz-focus-inner {
padding: 0;
border: 0;
}
&:hover {
background: ${(props) => darken(0.05, props.theme.buttonBackground)};
}
&:focus {
transition-duration: 0.05s;
box-shadow: ${(props) => lighten(0.4, props.theme.buttonBackground)} 0px 0px
0px 3px;
outline: none;
}
&:disabled {
cursor: default;
pointer-events: none;
color: ${(props) => props.theme.white50};
}
${(props) =>
props.neutral &&
`
background: ${props.theme.buttonNeutralBackground};
color: ${props.theme.buttonNeutralText};
box-shadow: ${
props.borderOnHover ? "none" : "rgba(0, 0, 0, 0.07) 0px 1px 2px"
};
border: 1px solid ${
props.borderOnHover ? "transparent" : props.theme.buttonNeutralBorder
};
svg {
fill: ${props.iconColor || props.theme.buttonNeutralText};
}
&:hover {
background: ${darken(0.05, props.theme.buttonNeutralBackground)};
border: 1px solid ${props.theme.buttonNeutralBorder};
}
&:focus {
transition-duration: 0.05s;
border: 1px solid ${lighten(0.4, props.theme.buttonBackground)};
box-shadow: ${lighten(0.4, props.theme.buttonBackground)} 0px 0px
0px 2px;
}
&:disabled {
color: ${props.theme.textTertiary};
}
`} ${(props) =>
props.danger &&
`
background: ${props.theme.danger};
color: ${props.theme.white};
&:hover {
background: ${darken(0.05, props.theme.danger)};
}
&:focus {
transition-duration: 0.05s;
box-shadow: ${lighten(0.4, props.theme.danger)} 0px 0px
0px 3px;
}
`};
`;
const Label = styled.span`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
${(props) => props.hasIcon && "padding-left: 4px;"};
`;
export const Inner = styled.span`
display: flex;
padding: 0 8px;
padding-right: ${(props) => (props.disclosure ? 2 : 8)}px;
line-height: ${(props) => (props.hasIcon ? 24 : 32)}px;
justify-content: center;
align-items: center;
min-height: 30px;
${(props) => props.hasIcon && props.hasText && "padding-left: 4px;"};
${(props) => props.hasIcon && !props.hasText && "padding: 0 4px;"};
`;
export type Props = {
type?: string,
value?: string,
icon?: React.Node,
iconColor?: string,
className?: string,
children?: React.Node,
innerRef?: React.ElementRef<any>,
disclosure?: boolean,
fullwidth?: boolean,
borderOnHover?: boolean,
};
function Button({
type = "text",
icon,
children,
value,
disclosure,
innerRef,
...rest
}: Props) {
const hasText = children !== undefined || value !== undefined;
const hasIcon = icon !== undefined;
return (
<RealButton type={type} ref={innerRef} {...rest}>
<Inner hasIcon={hasIcon} hasText={hasText} disclosure={disclosure}>
{hasIcon && icon}
{hasText && <Label hasIcon={hasIcon}>{children || value}</Label>}
{disclosure && <ExpandedIcon />}
</Inner>
</RealButton>
);
}
export default React.forwardRef<Props, typeof Button>((props, ref) => (
<Button {...props} innerRef={ref} />
));
-116
View File
@@ -1,116 +0,0 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import { darken } from 'polished';
const RealButton = styled.button`
display: inline-block;
margin: 0;
padding: 0;
border: 0;
background: ${props => props.theme.primary};
color: ${props => props.theme.white};
border-radius: 4px;
font-size: 15px;
height: 36px;
text-decoration: none;
flex-shrink: 0;
outline: none;
cursor: pointer;
&::-moz-focus-inner {
padding: 0;
border: 0;
}
&:hover {
background: ${props => darken(0.05, props.theme.primary)};
}
svg {
position: relative;
top: 0.05em;
}
&:disabled {
opacity: 0.8;
cursor: default;
}
${props =>
props.light &&
`
color: ${props.theme.slate};
background: transparent;
border: 1px solid ${props.theme.slate};
&:hover {
background: transparent;
color: ${props.theme.slateDark};
border: 1px solid ${props.theme.slateDark};
}
`} ${props =>
props.neutral &&
`
background: ${props.theme.slate};
&:hover {
background: ${darken(0.05, props.theme.slate)};
}
`} ${props =>
props.danger &&
`
background: ${props.theme.danger};
&:hover {
background: ${darken(0.05, props.theme.danger)};
}
`};
`;
const Label = styled.span`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
${props => props.hasIcon && 'padding-left: 2px;'};
`;
const Inner = styled.span`
padding: 0 12px;
display: flex;
line-height: 28px;
justify-content: center;
align-items: center;
${props =>
props.hasIcon &&
(props.small ? 'padding-left: 6px;' : 'padding-left: 10px;')};
`;
export type Props = {
type?: string,
value?: string,
icon?: React.Node,
className?: string,
children?: React.Node,
};
export default function Button({
type = 'text',
icon,
children,
value,
...rest
}: Props) {
const hasText = children !== undefined || value !== undefined;
const hasIcon = icon !== undefined;
return (
<RealButton {...rest}>
<Inner hasIcon={hasIcon}>
{hasIcon && icon}
{hasText && <Label hasIcon={hasIcon}>{children || value}</Label>}
</Inner>
</RealButton>
);
}
-3
View File
@@ -1,3 +0,0 @@
// @flow
import Button from './Button';
export default Button;
+13
View File
@@ -0,0 +1,13 @@
// @flow
import styled from "styled-components";
import Button, { Inner } from "./Button";
const ButtonLarge = styled(Button)`
height: 40px;
${Inner} {
padding: 4px 16px;
}
`;
export default ButtonLarge;
@@ -1,7 +1,7 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import * as React from "react";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
type Props = {
children?: React.Node,
@@ -9,9 +9,10 @@ type Props = {
const Container = styled.div`
width: 100%;
max-width: 100vw;
padding: 60px 20px;
${breakpoint('tablet')`
${breakpoint("tablet")`
padding: 60px;
`};
`;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import CenteredContent from './CenteredContent';
export default CenteredContent;
+24 -8
View File
@@ -1,45 +1,61 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import HelpText from 'components/HelpText';
import * as React from "react";
import styled from "styled-components";
import HelpText from "components/HelpText";
import VisuallyHidden from "components/VisuallyHidden";
export type Props = {
checked?: boolean,
label?: string,
labelHidden?: boolean,
className?: string,
note?: string,
short?: boolean,
small?: boolean,
};
const LabelText = styled.span`
font-weight: 500;
margin-left: 10px;
margin-left: ${(props) => (props.small ? "6px" : "10px")};
${(props) => (props.small ? `color: ${props.theme.textSecondary}` : "")};
`;
const Wrapper = styled.div`
padding-bottom: 8px;
${(props) => (props.small ? "font-size: 14px" : "")};
`;
const Label = styled.label`
display: flex;
align-items: center;
user-select: none;
`;
export default function Checkbox({
label,
labelHidden,
note,
className,
small,
short,
...rest
}: Props) {
const wrappedLabel = <LabelText small={small}>{label}</LabelText>;
return (
<React.Fragment>
<Wrapper>
<>
<Wrapper small={small}>
<Label>
<input type="checkbox" {...rest} />
{label && <LabelText>{label}</LabelText>}
{label &&
(labelHidden ? (
<VisuallyHidden>{wrappedLabel}</VisuallyHidden>
) : (
wrappedLabel
))}
</Label>
{note && <HelpText small>{note}</HelpText>}
</Wrapper>
</React.Fragment>
</>
);
}
+4 -4
View File
@@ -1,10 +1,10 @@
// @flow
import styled from 'styled-components';
import styled from "styled-components";
const ClickablePadding = styled.div`
min-height: 50vh;
cursor: ${({ onClick }) => (onClick ? 'text' : 'default')};
${({ grow }) => grow && `flex-grow: 1;`};
min-height: 10em;
cursor: ${({ onClick }) => (onClick ? "text" : "default")};
${({ grow }) => grow && `flex-grow: 100;`};
`;
export default ClickablePadding;
+76
View File
@@ -0,0 +1,76 @@
// @flow
import { sortBy, keyBy } from "lodash";
import { observer, inject } from "mobx-react";
import * as React from "react";
import { MAX_AVATAR_DISPLAY } from "shared/constants";
import DocumentPresenceStore from "stores/DocumentPresenceStore";
import ViewsStore from "stores/ViewsStore";
import Document from "models/Document";
import { AvatarWithPresence } from "components/Avatar";
import Facepile from "components/Facepile";
type Props = {
views: ViewsStore,
presence: DocumentPresenceStore,
document: Document,
currentUserId: string,
};
@observer
class Collaborators extends React.Component<Props> {
componentDidMount() {
this.props.views.fetchPage({ documentId: this.props.document.id });
}
render() {
const { document, presence, views, currentUserId } = this.props;
let documentPresence = presence.get(document.id);
documentPresence = documentPresence
? Array.from(documentPresence.values())
: [];
const documentViews = views.inDocument(document.id);
const presentIds = documentPresence.map((p) => p.userId);
const editingIds = documentPresence
.filter((p) => p.isEditing)
.map((p) => p.userId);
// ensure currently present via websocket are always ordered first
const mostRecentViewers = sortBy(
documentViews.slice(0, MAX_AVATAR_DISPLAY),
(view) => {
return presentIds.includes(view.user.id);
}
);
const viewersKeyedByUserId = keyBy(mostRecentViewers, (v) => v.user.id);
const overflow = documentViews.length - mostRecentViewers.length;
return (
<Facepile
users={mostRecentViewers.map((v) => v.user)}
overflow={overflow}
renderAvatar={(user) => {
const isPresent = presentIds.includes(user.id);
const isEditing = editingIds.includes(user.id);
const { lastViewedAt } = viewersKeyedByUserId[user.id];
return (
<AvatarWithPresence
key={user.id}
user={user}
lastViewedAt={lastViewedAt}
isPresent={isPresent}
isEditing={isEditing}
isCurrentUser={currentUserId === user.id}
/>
);
}}
/>
);
}
}
export default inject("views", "presence")(Collaborators);
@@ -1,64 +0,0 @@
// @flow
import * as React from 'react';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
import styled from 'styled-components';
import Flex from 'shared/components/Flex';
import Avatar from 'components/Avatar';
import Tooltip from 'components/Tooltip';
import Document from 'models/Document';
type Props = { document: Document };
const Collaborators = ({ document }: Props) => {
const {
createdAt,
updatedAt,
createdBy,
updatedBy,
collaborators,
} = document;
let tooltip;
if (createdAt === updatedAt) {
tooltip = `${createdBy.name} published ${distanceInWordsToNow(
new Date(createdAt)
)} ago`;
} else {
tooltip = `${updatedBy.name} modified ${distanceInWordsToNow(
new Date(updatedAt)
)} ago`;
}
return (
<Avatars>
{collaborators.map(user => (
<Tooltip
tooltip={collaborators.length > 1 ? user.name : tooltip}
placement="bottom"
key={user.id}
>
<AvatarWrapper>
<Avatar src={user.avatarUrl} />
</AvatarWrapper>
</Tooltip>
))}
</Avatars>
);
};
const AvatarWrapper = styled.div`
width: 24px;
height: 24px;
margin-right: -10px;
&:first-child {
margin-right: 0;
}
`;
const Avatars = styled(Flex)`
align-items: center;
flex-direction: row-reverse;
`;
export default Collaborators;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import Collaborators from './Collaborators';
export default Collaborators;
+45
View File
@@ -0,0 +1,45 @@
// @flow
import { inject, observer } from "mobx-react";
import { PrivateCollectionIcon, CollectionIcon } from "outline-icons";
import { getLuminance } from "polished";
import * as React from "react";
import UiStore from "stores/UiStore";
import Collection from "models/Collection";
import { icons } from "components/IconPicker";
type Props = {
collection: Collection,
expanded?: boolean,
size?: number,
ui: UiStore,
};
function ResolvedCollectionIcon({ collection, expanded, size, ui }: Props) {
// If the chosen icon color is very dark then we invert it in dark mode
// otherwise it will be impossible to see against the dark background.
const color =
ui.resolvedTheme === "dark"
? getLuminance(collection.color) > 0.12
? collection.color
: "currentColor"
: collection.color;
if (collection.icon && collection.icon !== "collection") {
try {
const Component = icons[collection.icon].component;
return <Component color={color} size={size} />;
} catch (error) {
console.warn("Failed to render custom icon " + collection.icon);
}
}
if (collection.private) {
return (
<PrivateCollectionIcon color={color} expanded={expanded} size={size} />
);
}
return <CollectionIcon color={color} expanded={expanded} size={size} />;
}
export default inject("ui")(observer(ResolvedCollectionIcon));
-189
View File
@@ -1,189 +0,0 @@
// @flow
import * as React from 'react';
import { observable, computed, action } from 'mobx';
import { observer } from 'mobx-react';
import styled from 'styled-components';
import Flex from 'shared/components/Flex';
import { LabelText, Outline } from 'components/Input';
import { validateColorHex } from 'shared/utils/color';
const colors = [
'#4E5C6E',
'#19B7FF',
'#7F6BFF',
'#FC7419',
'#FC2D2D',
'#FFE100',
'#14CF9F',
'#EE84F0',
'#2F362F',
];
type Props = {
onSelect: (color: string) => void,
value?: string,
};
@observer
class ColorPicker extends React.Component<Props> {
@observable selectedColor: string = colors[0];
@observable customColorValue: string = '';
@observable customColorSelected: boolean;
componentWillMount() {
const { value } = this.props;
if (value && colors.includes(value)) {
this.selectedColor = value;
} else if (value) {
this.customColorSelected = true;
this.customColorValue = value.replace('#', '');
}
}
componentDidMount() {
this.fireCallback();
}
fireCallback = () => {
this.props.onSelect(
this.customColorSelected ? this.customColor : this.selectedColor
);
};
@computed
get customColor(): string {
return this.customColorValue &&
validateColorHex(`#${this.customColorValue}`)
? `#${this.customColorValue}`
: colors[0];
}
@action
setColor = (color: string) => {
this.selectedColor = color;
this.customColorSelected = false;
this.fireCallback();
};
@action
focusOnCustomColor = (event: SyntheticEvent<*>) => {
this.selectedColor = '';
this.customColorSelected = true;
this.fireCallback();
};
@action
setCustomColor = (event: SyntheticEvent<*>) => {
let target = event.target;
if (target instanceof HTMLInputElement) {
const color = target.value;
this.customColorValue = color.replace('#', '');
this.fireCallback();
}
};
render() {
return (
<Flex column>
<LabelText>Color</LabelText>
<StyledOutline justify="space-between">
<Flex>
{colors.map(color => (
<Swatch
key={color}
color={color}
active={
color === this.selectedColor && !this.customColorSelected
}
onClick={() => this.setColor(color)}
/>
))}
</Flex>
<Flex justify="flex-end">
<strong>Custom color:</strong>
<HexHash>#</HexHash>
<CustomColorInput
placeholder="FFFFFF"
onFocus={this.focusOnCustomColor}
onChange={this.setCustomColor}
value={this.customColorValue}
maxLength={6}
/>
<Swatch
color={this.customColor}
active={this.customColorSelected}
/>
</Flex>
</StyledOutline>
</Flex>
);
}
}
type SwatchProps = {
onClick?: () => void,
color?: string,
active?: boolean,
};
const Swatch = ({ onClick, ...props }: SwatchProps) => (
<SwatchOutset onClick={onClick} {...props}>
<SwatchInset {...props} />
</SwatchOutset>
);
const SwatchOutset = styled(Flex)`
width: 24px;
height: 24px;
margin-right: 5px;
border: 2px solid ${({ active, color }) => (active ? color : 'transparent')};
border-radius: 2px;
background: ${({ color }) => color};
${({ onClick }) => onClick && `cursor: pointer;`} &:last-child {
margin-right: 0;
}
`;
const SwatchInset = styled(Flex)`
width: 20px;
height: 20px;
border: 1px solid ${({ active, color }) => (active ? 'white' : 'transparent')};
border-radius: 2px;
background: ${({ color }) => color};
`;
const StyledOutline = styled(Outline)`
padding: 5px;
flex-wrap: wrap;
strong {
font-weight: 500;
}
`;
const HexHash = styled.div`
margin-left: 12px;
padding-bottom: 0;
font-weight: 500;
user-select: none;
`;
const CustomColorInput = styled.input`
border: 0;
flex: 1;
width: 65px;
margin-right: 12px;
padding-bottom: 0;
outline: none;
background: none;
font-family: ${props => props.theme.monospaceFontFamily};
font-weight: 500;
&::placeholder {
color: ${props => props.theme.slate};
font-family: ${props => props.theme.monospaceFontFamily};
font-weight: 500;
}
`;
export default ColorPicker;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import ColorPicker from './ColorPicker';
export default ColorPicker;
@@ -1,25 +1,25 @@
// @flow
import * as React from 'react';
import copy from 'copy-to-clipboard';
import copy from "copy-to-clipboard";
import * as React from "react";
type Props = {
text: string,
children?: React.Node,
onClick?: () => *,
onCopy: () => *,
onClick?: () => void,
onCopy: () => void,
};
class CopyToClipboard extends React.PureComponent<Props> {
onClick = (ev: SyntheticEvent<*>) => {
onClick = (ev: SyntheticEvent<>) => {
const { text, onCopy, children } = this.props;
const elem = React.Children.only(children);
copy(text, {
debug: !!__DEV__,
debug: process.env.NODE_ENV !== "production",
});
if (onCopy) onCopy();
if (elem && elem.props && typeof elem.props.onClick === 'function') {
if (elem && elem.props && typeof elem.props.onClick === "function") {
elem.props.onClick(ev);
}
};
-3
View File
@@ -1,3 +0,0 @@
// @flow
import CopyToClipboard from './CopyToClipboard';
export default CopyToClipboard;
+24
View File
@@ -0,0 +1,24 @@
// @flow
import * as React from "react";
type Props = {
delay?: number,
children: React.Node,
};
export default function DelayedMount({ delay = 250, children }: Props) {
const [isShowing, setShowing] = React.useState(false);
React.useEffect(() => {
const timeout = setTimeout(() => setShowing(true), delay);
return () => {
clearTimeout(timeout);
};
}, []);
if (!isShowing) {
return null;
}
return children;
}
-21
View File
@@ -1,21 +0,0 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import Flex from 'shared/components/Flex';
const Divider = () => {
return (
<Flex auto justify="center">
<Content />
</Flex>
);
};
const Content = styled.span`
display: flex;
width: 50%;
margin: 20px 0;
border-bottom: 1px solid #eee;
`;
export default Divider;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import Divider from './Divider';
export default Divider;
@@ -1,27 +1,26 @@
// @flow
import * as React from 'react';
import { withRouter } from 'react-router-dom';
import { observable, action } from 'mobx';
import { observer, inject } from 'mobx-react';
import styled from 'styled-components';
import Waypoint from 'react-waypoint';
import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
import { observable, action } from "mobx";
import { observer, inject } from "mobx-react";
import * as React from "react";
import { type RouterHistory, type Match } from "react-router-dom";
import { Waypoint } from "react-waypoint";
import styled from "styled-components";
import { DEFAULT_PAGINATION_LIMIT } from 'stores/DocumentsStore';
import Document from 'models/Document';
import RevisionsStore from 'stores/RevisionsStore';
import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore";
import DocumentsStore from "stores/DocumentsStore";
import RevisionsStore from "stores/RevisionsStore";
import Flex from 'shared/components/Flex';
import { ListPlaceholder } from 'components/LoadingPlaceholder';
import Revision from './components/Revision';
import { documentHistoryUrl } from 'utils/routeHelpers';
import Flex from "components/Flex";
import { ListPlaceholder } from "components/LoadingPlaceholder";
import Revision from "./components/Revision";
import { documentHistoryUrl } from "utils/routeHelpers";
type Props = {
match: Object,
document: Document,
match: Match,
documents: DocumentsStore,
revisions: RevisionsStore,
revision?: Object,
history: Object,
history: RouterHistory,
};
@observer
@@ -32,7 +31,6 @@ class DocumentHistory extends React.Component<Props> {
@observable allowLoadMore: boolean = true;
async componentDidMount() {
this.selectFirstRevision();
await this.loadMoreResults();
this.selectFirstRevision();
}
@@ -44,7 +42,7 @@ class DocumentHistory extends React.Component<Props> {
const results = await this.props.revisions.fetchPage({
limit,
offset: this.offset,
id: this.props.document.id,
documentId: this.props.match.params.documentSlug,
});
if (
@@ -61,10 +59,14 @@ class DocumentHistory extends React.Component<Props> {
};
selectFirstRevision = () => {
const revisions = this.revisions;
if (revisions.length && !this.props.revision) {
if (this.revisions.length) {
const document = this.props.documents.getByUrl(
this.props.match.params.documentSlug
);
if (!document) return;
this.props.history.replace(
documentHistoryUrl(this.props.document, this.revisions[0].id)
documentHistoryUrl(document, this.revisions[0].id)
);
}
};
@@ -77,44 +79,47 @@ class DocumentHistory extends React.Component<Props> {
};
get revisions() {
return this.props.revisions.getDocumentRevisions(this.props.document.id);
const document = this.props.documents.getByUrl(
this.props.match.params.documentSlug
);
if (!document) return [];
return this.props.revisions.getDocumentRevisions(document.id);
}
render() {
const showLoading = !this.isLoaded && this.isFetching;
const maxChanges = this.revisions.reduce((acc, change) => {
if (acc < change.diff.added + change.diff.removed) {
return change.diff.added + change.diff.removed;
}
return acc;
}, 0);
const document = this.props.documents.getByUrl(
this.props.match.params.documentSlug
);
const showLoading = (!this.isLoaded && this.isFetching) || !document;
return (
<Wrapper column>
{showLoading ? (
<Loading>
<ListPlaceholder count={5} />
</Loading>
) : (
<ArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0}
>
{this.revisions.map((revision, index) => (
<Revision
key={revision.id}
revision={revision}
document={this.props.document}
maxChanges={maxChanges}
showMenu={index !== 0}
/>
))}
</ArrowKeyNavigation>
)}
{this.allowLoadMore && (
<Waypoint key={this.offset} onEnter={this.loadMoreResults} />
)}
</Wrapper>
<Sidebar>
<Wrapper column>
{showLoading ? (
<Loading>
<ListPlaceholder count={5} />
</Loading>
) : (
<ArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0}
>
{this.revisions.map((revision, index) => (
<Revision
key={revision.id}
revision={revision}
document={document}
showMenu={index !== 0}
selected={this.props.match.params.revisionId === revision.id}
/>
))}
</ArrowKeyNavigation>
)}
{this.allowLoadMore && (
<Waypoint key={this.offset} onEnter={this.loadMoreResults} />
)}
</Wrapper>
</Sidebar>
);
}
}
@@ -127,12 +132,18 @@ const Wrapper = styled(Flex)`
position: fixed;
top: 0;
right: 0;
bottom: 0;
min-width: ${props => props.theme.sidebarWidth};
border-left: 1px solid ${props => props.theme.slateLight};
overflow: scroll;
z-index: 1;
min-width: ${(props) => props.theme.sidebarWidth};
height: 100%;
overflow-y: auto;
overscroll-behavior: none;
`;
export default withRouter(inject('revisions')(DocumentHistory));
const Sidebar = styled(Flex)`
background: ${(props) => props.theme.background};
min-width: ${(props) => props.theme.sidebarWidth};
border-left: 1px solid ${(props) => props.theme.divider};
z-index: 1;
`;
export default inject("documents", "revisions")(DocumentHistory);
@@ -1,58 +0,0 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import Flex from 'shared/components/Flex';
type Props = {
added: number,
removed: number,
max: number,
color?: string,
width: number,
};
export default function DiffSummary({
added,
removed,
max,
color,
width = 180,
}: Props) {
const summary = [];
if (added) summary.push(`+${added}`);
if (removed) summary.push(`-${removed}`);
const hasChanges = !!summary.length;
return (
<Flex align="center">
{hasChanges && (
<Diff>
<Bar color={color} style={{ width: `${added / max * width}px` }} />
<Bar color={color} style={{ width: `${removed / max * width}px` }} />
</Diff>
)}
<Summary>{hasChanges ? summary.join(', ') : 'No changes'}</Summary>
</Flex>
);
}
const Summary = styled.div`
display: inline-block;
font-size: 10px;
opacity: 0.5;
flex-grow: 100;
text-transform: uppercase;
`;
const Diff = styled(Flex)`
height: 6px;
margin-right: 2px;
`;
const Bar = styled.div`
display: inline-block;
background: ${props => props.color || props.theme.text};
height: 100%;
opacity: 0.3;
margin-right: 1px;
`;
@@ -1,21 +1,30 @@
// @flow
import * as React from 'react';
import { NavLink } from 'react-router-dom';
import styled, { withTheme } from 'styled-components';
import format from 'date-fns/format';
import { MoreIcon } from 'outline-icons';
import format from "date-fns/format";
import { MoreIcon } from "outline-icons";
import * as React from "react";
import { NavLink } from "react-router-dom";
import styled, { withTheme } from "styled-components";
import Flex from 'shared/components/Flex';
import Time from 'shared/components/Time';
import Avatar from 'components/Avatar';
import RevisionMenu from 'menus/RevisionMenu';
import DiffSummary from './DiffSummary';
import Document from "models/Document";
import Revision from "models/Revision";
import Avatar from "components/Avatar";
import Flex from "components/Flex";
import Time from "components/Time";
import RevisionMenu from "menus/RevisionMenu";
import { documentHistoryUrl } from 'utils/routeHelpers';
import { documentHistoryUrl } from "utils/routeHelpers";
class Revision extends React.Component<*> {
type Props = {
theme: Object,
showMenu: boolean,
selected: boolean,
document: Document,
revision: Revision,
};
class RevisionListItem extends React.Component<Props> {
render() {
const { revision, document, maxChanges, showMenu, theme } = this.props;
const { revision, document, showMenu, selected, theme } = this.props;
return (
<StyledNavLink
@@ -23,20 +32,21 @@ class Revision extends React.Component<*> {
activeStyle={{ background: theme.primary, color: theme.white }}
>
<Author>
<StyledAvatar src={revision.createdBy.avatarUrl} />{' '}
<StyledAvatar src={revision.createdBy.avatarUrl} />{" "}
{revision.createdBy.name}
</Author>
<Meta>
<Time dateTime={revision.createdAt}>
{format(revision.createdAt, 'MMMM Do, YYYY h:mm a')}
{format(revision.createdAt, "MMMM Do, YYYY h:mm a")}
</Time>
</Meta>
<DiffSummary {...revision.diff} max={maxChanges} />
{showMenu && (
<StyledRevisionMenu
document={document}
revision={revision}
label={<MoreIcon color={theme.white} />}
label={
<MoreIcon color={selected ? theme.white : theme.textTertiary} />
}
/>
)}
</StyledNavLink>
@@ -52,16 +62,15 @@ const StyledAvatar = styled(Avatar)`
const StyledRevisionMenu = styled(RevisionMenu)`
position: absolute;
right: 16px;
top: 16px;
top: 20px;
`;
const StyledNavLink = styled(NavLink)`
color: ${props => props.theme.text};
color: ${(props) => props.theme.text};
display: block;
padding: 16px;
padding: 8px 16px;
font-size: 15px;
position: relative;
height: 100px;
`;
const Author = styled(Flex)`
@@ -77,4 +86,4 @@ const Meta = styled.p`
padding: 0;
`;
export default withTheme(Revision);
export default withTheme(RevisionListItem);
+1 -1
View File
@@ -1,3 +1,3 @@
// @flow
import DocumentHistory from './DocumentHistory';
import DocumentHistory from "./DocumentHistory";
export default DocumentHistory;
+7 -16
View File
@@ -1,20 +1,15 @@
// @flow
import * as React from 'react';
import Document from 'models/Document';
import DocumentPreview from 'components/DocumentPreview';
import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
import * as React from "react";
import Document from "models/Document";
import DocumentPreview from "components/DocumentPreview";
type Props = {
documents: Document[],
showCollection?: boolean,
limit?: number,
};
export default function DocumentList({
limit,
showCollection,
documents,
}: Props) {
export default function DocumentList({ limit, documents, ...rest }: Props) {
const items = limit ? documents.splice(0, limit) : documents;
return (
@@ -22,12 +17,8 @@ export default function DocumentList({
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0}
>
{items.map(document => (
<DocumentPreview
key={document.id}
document={document}
showCollection={showCollection}
/>
{items.map((document) => (
<DocumentPreview key={document.id} document={document} {...rest} />
))}
</ArrowKeyNavigation>
);
+123
View File
@@ -0,0 +1,123 @@
// @flow
import { inject, observer } from "mobx-react";
import * as React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import AuthStore from "stores/AuthStore";
import CollectionsStore from "stores/CollectionsStore";
import Document from "models/Document";
import Breadcrumb from "components/Breadcrumb";
import Flex from "components/Flex";
import Time from "components/Time";
const Container = styled(Flex)`
color: ${(props) => props.theme.textTertiary};
font-size: 13px;
white-space: nowrap;
overflow: hidden;
`;
const Modified = styled.span`
color: ${(props) =>
props.highlight ? props.theme.text : props.theme.textTertiary};
font-weight: ${(props) => (props.highlight ? "600" : "400")};
`;
type Props = {
collections: CollectionsStore,
auth: AuthStore,
showCollection?: boolean,
showPublished?: boolean,
document: Document,
children: React.Node,
to?: string,
};
function DocumentMeta({
auth,
collections,
showPublished,
showCollection,
document,
children,
to,
...rest
}: Props) {
const {
modifiedSinceViewed,
updatedAt,
updatedBy,
createdAt,
publishedAt,
archivedAt,
deletedAt,
isDraft,
} = document;
// Prevent meta information from displaying if updatedBy is not available.
// Currently the situation where this is true is rendering share links.
if (!updatedBy) {
return null;
}
let content;
if (deletedAt) {
content = (
<span>
deleted <Time dateTime={deletedAt} /> ago
</span>
);
} else if (archivedAt) {
content = (
<span>
archived <Time dateTime={archivedAt} /> ago
</span>
);
} else if (createdAt === updatedAt) {
content = (
<span>
created <Time dateTime={updatedAt} /> ago
</span>
);
} else if (publishedAt && (publishedAt === updatedAt || showPublished)) {
content = (
<span>
published <Time dateTime={publishedAt} /> ago
</span>
);
} else if (isDraft) {
content = (
<span>
saved <Time dateTime={updatedAt} /> ago
</span>
);
} else {
content = (
<Modified highlight={modifiedSinceViewed}>
updated <Time dateTime={updatedAt} /> ago
</Modified>
);
}
const collection = collections.get(document.collectionId);
const updatedByMe = auth.user && auth.user.id === updatedBy.id;
return (
<Container align="center" {...rest}>
{updatedByMe ? "You" : updatedBy.name}&nbsp;
{to ? <Link to={to}>{content}</Link> : content}
{showCollection && collection && (
<span>
&nbsp;in&nbsp;
<strong>
<Breadcrumb document={document} onlyText />
</strong>
</span>
)}
{children}
</Container>
);
}
export default inject("collections", "auth")(observer(DocumentMeta));
+48
View File
@@ -0,0 +1,48 @@
// @flow
import { inject } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import ViewsStore from "stores/ViewsStore";
import Document from "models/Document";
import DocumentMeta from "components/DocumentMeta";
type Props = {|
views: ViewsStore,
document: Document,
isDraft: boolean,
to?: string,
|};
function DocumentMetaWithViews({ views, to, isDraft, document }: Props) {
const totalViews = views.countForDocument(document.id);
return (
<Meta document={document} to={to}>
{totalViews && !isDraft ? (
<>
&nbsp;&middot; Viewed{" "}
{totalViews === 1 ? "once" : `${totalViews} times`}
</>
) : null}
</Meta>
);
}
const Meta = styled(DocumentMeta)`
margin: -12px 0 2em 0;
font-size: 14px;
a {
color: inherit;
&:hover {
text-decoration: underline;
}
}
@media print {
display: none;
}
`;
export default inject("views")(DocumentMetaWithViews);
+151 -101
View File
@@ -1,28 +1,149 @@
// @flow
import * as React from 'react';
import { observer } from 'mobx-react';
import { Link } from 'react-router-dom';
import Document from 'models/Document';
import styled, { withTheme } from 'styled-components';
import Flex from 'shared/components/Flex';
import Highlight from 'components/Highlight';
import { StarredIcon } from 'outline-icons';
import PublishingInfo from './components/PublishingInfo';
import DocumentMenu from 'menus/DocumentMenu';
import { observer } from "mobx-react";
import { StarredIcon, PlusIcon } from "outline-icons";
import * as React from "react";
import { Link, withRouter, type RouterHistory } from "react-router-dom";
import styled, { withTheme } from "styled-components";
import Document from "models/Document";
import Badge from "components/Badge";
import Button from "components/Button";
import DocumentMeta from "components/DocumentMeta";
import Flex from "components/Flex";
import Highlight from "components/Highlight";
import Tooltip from "components/Tooltip";
import DocumentMenu from "menus/DocumentMenu";
import { newDocumentUrl } from "utils/routeHelpers";
type Props = {
history: RouterHistory,
document: Document,
highlight?: ?string,
context?: ?string,
showCollection?: boolean,
innerRef?: *,
showPublished?: boolean,
showPin?: boolean,
showDraft?: boolean,
showTemplate?: boolean,
};
const SEARCH_RESULT_REGEX = /<b\b[^>]*>(.*?)<\/b>/gi;
@observer
class DocumentPreview extends React.Component<Props> {
handleStar = (ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();
this.props.document.star();
};
handleUnstar = (ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();
this.props.document.unstar();
};
replaceResultMarks = (tag: string) => {
// don't use SEARCH_RESULT_REGEX here as it causes
// an infinite loop to trigger a regex inside it's own callback
return tag.replace(/<b\b[^>]*>(.*?)<\/b>/gi, "$1");
};
handleNewFromTemplate = (event) => {
event.preventDefault();
event.stopPropagation();
const { document } = this.props;
this.props.history.push(
newDocumentUrl(document.collectionId, {
templateId: document.id,
})
);
};
render() {
const {
document,
showCollection,
showPublished,
showPin,
showDraft = true,
showTemplate,
highlight,
context,
} = this.props;
const queryIsInTitle =
!!highlight &&
!!document.title.toLowerCase().includes(highlight.toLowerCase());
return (
<DocumentLink
to={{
pathname: document.url,
state: { title: document.titleWithDefault },
}}
>
<Heading>
<Title text={document.titleWithDefault} highlight={highlight} />
{!document.isDraft &&
!document.isArchived &&
!document.isTemplate && (
<Actions>
{document.isStarred ? (
<StyledStar onClick={this.handleUnstar} solid />
) : (
<StyledStar onClick={this.handleStar} />
)}
</Actions>
)}
{document.isDraft && showDraft && (
<Tooltip tooltip="Only visible to you" delay={500} placement="top">
<Badge>Draft</Badge>
</Tooltip>
)}
{document.isTemplate && showTemplate && (
<Badge primary>Template</Badge>
)}
<SecondaryActions>
{document.isTemplate &&
!document.isArchived &&
!document.isDeleted && (
<Button
onClick={this.handleNewFromTemplate}
icon={<PlusIcon />}
neutral
>
New doc
</Button>
)}
&nbsp;
<DocumentMenu document={document} showPin={showPin} />
</SecondaryActions>
</Heading>
{!queryIsInTitle && (
<ResultContext
text={context}
highlight={highlight ? SEARCH_RESULT_REGEX : undefined}
processResult={this.replaceResultMarks}
/>
)}
<DocumentMeta
document={document}
showCollection={showCollection}
showPublished={showPublished}
/>
</DocumentLink>
);
}
}
const StyledStar = withTheme(styled(({ solid, theme, ...props }) => (
<StarredIcon color={solid ? theme.black : theme.text} {...props} />
<StarredIcon color={theme.text} {...props} />
))`
flex-shrink: 0;
opacity: ${props => (props.solid ? '1 !important' : 0)};
opacity: ${(props) => (props.solid ? "1 !important" : 0)};
transition: all 100ms ease-in-out;
&:hover {
@@ -33,7 +154,8 @@ const StyledStar = withTheme(styled(({ solid, theme, ...props }) => (
}
`);
const StyledDocumentMenu = styled(DocumentMenu)`
const SecondaryActions = styled(Flex)`
align-items: center;
position: absolute;
right: 16px;
top: 50%;
@@ -42,27 +164,30 @@ const StyledDocumentMenu = styled(DocumentMenu)`
const DocumentLink = styled(Link)`
display: block;
margin: 0 -16px;
padding: 10px 16px;
margin: 10px -8px;
padding: 6px 8px;
border-radius: 8px;
border: 2px solid transparent;
max-height: 50vh;
min-width: 100%;
max-width: calc(100vw - 40px);
overflow: hidden;
position: relative;
${StyledDocumentMenu} {
${SecondaryActions} {
opacity: 0;
}
&:hover,
&:active,
&:focus {
background: ${props => props.theme.smokeLight};
border: 2px solid ${props => props.theme.smoke};
background: ${(props) => props.theme.listItemHoverBackground};
outline: none;
${StyledStar}, ${StyledDocumentMenu} {
${SecondaryActions} {
opacity: 1;
}
${StyledStar} {
opacity: 0.5;
&:hover {
@@ -70,10 +195,6 @@ const DocumentLink = styled(Link)`
}
}
}
&:focus {
border: 2px solid ${props => props.theme.slateDark};
}
`;
const Heading = styled.h3`
@@ -84,6 +205,9 @@ const Heading = styled.h3`
margin-bottom: 0.25em;
overflow: hidden;
white-space: nowrap;
color: ${(props) => props.theme.text};
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
`;
const Actions = styled(Flex)`
@@ -99,84 +223,10 @@ const Title = styled(Highlight)`
const ResultContext = styled(Highlight)`
display: block;
color: ${props => props.theme.slateDark};
color: ${(props) => props.theme.textTertiary};
font-size: 14px;
margin-top: -0.25em;
margin-bottom: 0.25em;
`;
const SEARCH_RESULT_REGEX = /<b\b[^>]*>(.*?)<\/b>/gi;
@observer
class DocumentPreview extends React.Component<Props> {
star = (ev: SyntheticEvent<*>) => {
ev.preventDefault();
ev.stopPropagation();
this.props.document.star();
};
unstar = (ev: SyntheticEvent<*>) => {
ev.preventDefault();
ev.stopPropagation();
this.props.document.unstar();
};
replaceResultMarks = (tag: string) => {
// don't use SEARCH_RESULT_REGEX here as it causes
// an infinite loop to trigger a regex inside it's own callback
return tag.replace(/<b\b[^>]*>(.*?)<\/b>/gi, '$1');
};
render() {
const {
document,
showCollection,
innerRef,
highlight,
context,
...rest
} = this.props;
const queryIsInTitle =
!!highlight &&
!!document.title.toLowerCase().match(highlight.toLowerCase());
return (
<DocumentLink
to={{
pathname: document.url,
state: { title: document.title },
}}
innerRef={innerRef}
{...rest}
>
<Heading>
<Title text={document.title} highlight={highlight} />
{!document.isDraft && (
<Actions>
{document.starred ? (
<StyledStar onClick={this.unstar} solid />
) : (
<StyledStar onClick={this.star} />
)}
</Actions>
)}
<StyledDocumentMenu document={document} />
</Heading>
{!queryIsInTitle && (
<ResultContext
text={context}
highlight={highlight ? SEARCH_RESULT_REGEX : undefined}
processResult={this.replaceResultMarks}
/>
)}
<PublishingInfo
document={document}
collection={showCollection ? document.collection : undefined}
/>
</DocumentLink>
);
}
}
export default DocumentPreview;
export default withRouter(DocumentPreview);
@@ -1,65 +0,0 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import Collection from 'models/Collection';
import Document from 'models/Document';
import Flex from 'shared/components/Flex';
import Time from 'shared/components/Time';
const Container = styled(Flex)`
color: ${props => props.theme.slate};
font-size: 13px;
`;
const Modified = styled.span`
color: ${props =>
props.highlight ? props.theme.slateDark : props.theme.slate};
font-weight: ${props => (props.highlight ? '600' : '400')};
`;
type Props = {
collection?: Collection,
document: Document,
views?: number,
};
function PublishingInfo({ collection, document }: Props) {
const {
modifiedSinceViewed,
updatedAt,
updatedBy,
publishedAt,
isDraft,
} = document;
const neverUpdated = publishedAt === updatedAt;
return (
<Container align="center">
{publishedAt && neverUpdated ? (
<span>
{updatedBy.name} published <Time dateTime={publishedAt} /> ago
</span>
) : (
<React.Fragment>
{updatedBy.name}
{isDraft ? (
<span>
&nbsp;saved <Time dateTime={updatedAt} /> ago
</span>
) : (
<Modified highlight={modifiedSinceViewed}>
&nbsp;modified <Time dateTime={updatedAt} /> ago
</Modified>
)}
</React.Fragment>
)}
{collection && (
<span>
&nbsp;in <strong>{isDraft ? 'Drafts' : collection.name}</strong>
</span>
)}
</Container>
);
}
export default PublishingInfo;
+1 -1
View File
@@ -1,3 +1,3 @@
// @flow
import DocumentPreview from './DocumentPreview';
import DocumentPreview from "./DocumentPreview";
export default DocumentPreview;
@@ -1,42 +0,0 @@
// @flow
import { observable, action } from 'mobx';
import invariant from 'invariant';
import { client } from 'utils/ApiClient';
import type { User } from 'types';
type View = {
user: User,
count: number,
};
class DocumentViewersStore {
documentId: string;
@observable viewers: Array<View>;
@observable isFetching: boolean;
@action
fetchViewers = async () => {
this.isFetching = true;
try {
const res = await client.post(
'/views.list',
{
id: this.documentId,
},
{ cache: true }
);
invariant(res && res.data, 'Data should be available');
this.viewers = res.data.users;
} catch (e) {
console.error('Something went wrong');
}
this.isFetching = false;
};
constructor(documentId: string) {
this.documentId = documentId;
}
}
export default DocumentViewersStore;
@@ -1,71 +0,0 @@
// @flow
import * as React from 'react';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import Popover from 'components/Popover';
import styled from 'styled-components';
import DocumentViewers from './components/DocumentViewers';
import DocumentViewersStore from './DocumentViewersStore';
import Flex from 'shared/components/Flex';
const Container = styled(Flex)`
font-size: 13px;
user-select: none;
a {
color: #ccc;
&:hover {
color: #aaa;
}
}
`;
type Props = {
documentId: string,
count: number,
};
@observer
class DocumentViews extends React.Component<Props> {
@observable opened: boolean = false;
anchor: ?HTMLElement;
store: DocumentViewersStore;
constructor(props: Props) {
super(props);
this.store = new DocumentViewersStore(props.documentId);
}
openPopover = () => {
this.opened = true;
};
closePopover = () => {
this.opened = false;
};
setRef = (ref: ?HTMLElement) => {
this.anchor = ref;
};
render() {
return (
<Container align="center">
<a ref={this.setRef} onClick={this.openPopover}>
Viewed {this.props.count} {this.props.count === 1 ? 'time' : 'times'}
</a>
{this.opened && (
<Popover anchor={this.anchor} onClose={this.closePopover}>
<DocumentViewers
onMount={this.store.fetchViewers}
viewers={this.store.viewers}
/>
</Popover>
)}
</Container>
);
}
}
export default DocumentViews;
@@ -1,52 +0,0 @@
// @flow
import * as React from 'react';
import Flex from 'shared/components/Flex';
import styled from 'styled-components';
import map from 'lodash/map';
import Avatar from 'components/Avatar';
import Scrollable from 'components/Scrollable';
type Props = {
viewers: Array<Object>,
onMount: Function,
};
const List = styled.ul`
list-style: none;
font-size: 13px;
margin: -4px 0;
padding: 0;
li {
padding: 4px 0;
}
`;
const UserName = styled.span`
padding-left: 8px;
`;
class DocumentViewers extends React.Component<Props> {
componentDidMount() {
this.props.onMount();
}
render() {
return (
<Scrollable>
<List>
{map(this.props.viewers, view => (
<li key={view.user.id}>
<Flex align="center">
<Avatar src={view.user.avatarUrl} />{' '}
<UserName>{view.user.name}</UserName>
</Flex>
</li>
))}
</List>
</Scrollable>
);
}
}
export default DocumentViewers;
@@ -1,3 +0,0 @@
// @flow
import DocumentViewers from './DocumentViewers';
export default DocumentViewers;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import DocumentViews from './DocumentViews';
export default DocumentViews;
@@ -1,35 +1,41 @@
// @flow
import * as React from 'react';
import { observable } from 'mobx';
import { observer, inject } from 'mobx-react';
import { injectGlobal } from 'styled-components';
import importFile from 'utils/importFile';
import invariant from 'invariant';
import _ from 'lodash';
import Dropzone from 'react-dropzone';
import DocumentsStore from 'stores/DocumentsStore';
import LoadingIndicator from 'components/LoadingIndicator';
import invariant from "invariant";
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import * as React from "react";
import Dropzone from "react-dropzone";
import { withRouter, type RouterHistory, type Match } from "react-router-dom";
import { createGlobalStyle } from "styled-components";
import DocumentsStore from "stores/DocumentsStore";
import LoadingIndicator from "components/LoadingIndicator";
import importFile from "utils/importFile";
const EMPTY_OBJECT = {};
let importingLock = false;
type Props = {
children?: React.Node,
children: React.Node,
collectionId: string,
documentId?: string,
activeClassName?: string,
rejectClassName?: string,
documents: DocumentsStore,
disabled: boolean,
history: Object,
location: Object,
match: Match,
history: RouterHistory,
staticContext: Object,
};
// eslint-disable-next-line
injectGlobal`
export const GlobalStyles = createGlobalStyle`
.activeDropZone {
background: ${props => props.theme.slateDark};
svg { fill: ${props => props.theme.white}; }
border-radius: 4px;
background: ${(props) => props.theme.slateDark};
svg { fill: ${(props) => props.theme.white}; }
}
.activeDropZone a {
color: ${props => props.theme.white} !important;
color: ${(props) => props.theme.white} !important;
}
`;
@@ -38,7 +44,10 @@ class DropToImport extends React.Component<Props> {
@observable isImporting: boolean = false;
onDropAccepted = async (files = []) => {
if (importingLock) return;
this.isImporting = true;
importingLock = true;
try {
let collectionId = this.props.collectionId;
@@ -47,8 +56,8 @@ class DropToImport extends React.Component<Props> {
if (documentId && !collectionId) {
const document = await this.props.documents.fetch(documentId);
invariant(document, 'Document not available');
collectionId = document.collection.id;
invariant(document, "Document not available");
collectionId = document.collectionId;
}
for (const file of files) {
@@ -63,23 +72,24 @@ class DropToImport extends React.Component<Props> {
this.props.history.push(doc.url);
}
}
} catch (err) {
// TODO: show error alert.
} finally {
this.isImporting = false;
importingLock = false;
}
};
render() {
const props = _.omit(
this.props,
'history',
'documentId',
'collectionId',
'documents',
'disabled',
'menuOpen'
);
const {
documentId,
collectionId,
documents,
disabled,
location,
match,
history,
staticContext,
...rest
} = this.props;
if (this.props.disabled) return this.props.children;
@@ -87,11 +97,11 @@ class DropToImport extends React.Component<Props> {
<Dropzone
accept="text/markdown, text/plain"
onDropAccepted={this.onDropAccepted}
style={{}}
style={EMPTY_OBJECT}
disableClick
disablePreview
multiple
{...props}
{...rest}
>
{this.isImporting && <LoadingIndicator />}
{this.props.children}
@@ -100,4 +110,4 @@ class DropToImport extends React.Component<Props> {
}
}
export default inject('documents')(DropToImport);
export default inject("documents")(withRouter(DropToImport));
-3
View File
@@ -1,3 +0,0 @@
// @flow
import DropToImport from './DropToImport';
export default DropToImport;
+227 -47
View File
@@ -1,71 +1,226 @@
// @flow
import * as React from 'react';
import invariant from 'invariant';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import styled from 'styled-components';
import { PortalWithState } from 'react-portal';
import Flex from 'shared/components/Flex';
import { fadeAndScaleIn } from 'shared/styles/animations';
import invariant from "invariant";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { MoreIcon } from "outline-icons";
import { rgba } from "polished";
import * as React from "react";
import { PortalWithState } from "react-portal";
import styled from "styled-components";
import { fadeAndScaleIn } from "shared/styles/animations";
import Flex from "components/Flex";
import NudeButton from "components/NudeButton";
let previousClosePortal;
let counter = 0;
type Children =
| React.Node
| ((options: { closePortal: () => void }) => React.Node);
type Props = {
label: React.Node,
label?: React.Node,
onOpen?: () => void,
onClose?: () => void,
children?: React.Node,
children?: Children,
className?: string,
hover?: boolean,
style?: Object,
position?: "left" | "right" | "center",
};
@observer
class DropdownMenu extends React.Component<Props> {
@observable top: number;
@observable right: number;
id: string = `menu${counter++}`;
closeTimeout: TimeoutID;
handleOpen = (openPortal: (SyntheticEvent<*>) => *) => {
return (ev: SyntheticMouseEvent<*>) => {
@observable top: ?number;
@observable bottom: ?number;
@observable right: ?number;
@observable left: ?number;
@observable position: "left" | "right" | "center";
@observable fixed: ?boolean;
@observable bodyRect: ClientRect;
@observable labelRect: ClientRect;
@observable dropdownRef: { current: null | HTMLElement } = React.createRef();
@observable menuRef: { current: null | HTMLElement } = React.createRef();
handleOpen = (
openPortal: (SyntheticEvent<>) => void,
closePortal: () => void
) => {
return (ev: SyntheticMouseEvent<HTMLElement>) => {
ev.preventDefault();
const currentTarget = ev.currentTarget;
invariant(document.body, 'why you not here');
invariant(document.body, "why you not here");
if (currentTarget instanceof HTMLDivElement) {
const bodyRect = document.body.getBoundingClientRect();
const targetRect = currentTarget.getBoundingClientRect();
this.top = targetRect.bottom - bodyRect.top;
this.right = bodyRect.width - targetRect.left - targetRect.width;
this.bodyRect = document.body.getBoundingClientRect();
this.labelRect = currentTarget.getBoundingClientRect();
this.top = this.labelRect.bottom - this.bodyRect.top;
this.bottom = undefined;
this.position = this.props.position || "left";
if (currentTarget.parentElement) {
const triggerParentStyle = getComputedStyle(
currentTarget.parentElement
);
if (triggerParentStyle.position === "static") {
this.fixed = true;
this.top = this.labelRect.bottom;
}
}
this.initPosition();
// attempt to keep only one flyout menu open at once
if (previousClosePortal && !this.props.hover) {
previousClosePortal();
}
previousClosePortal = closePortal;
openPortal(ev);
}
};
};
initPosition() {
if (this.position === "left") {
this.right =
this.bodyRect.width - this.labelRect.left - this.labelRect.width;
} else if (this.position === "center") {
this.left = this.labelRect.left + this.labelRect.width / 2;
} else {
this.left = this.labelRect.left;
}
}
onOpen = () => {
if (typeof this.props.onOpen === "function") {
this.props.onOpen();
}
this.fitOnTheScreen();
};
fitOnTheScreen() {
if (!this.dropdownRef || !this.dropdownRef.current) return;
const el = this.dropdownRef.current;
const sticksOutPastBottomEdge =
el.clientHeight + this.top > window.innerHeight;
if (sticksOutPastBottomEdge) {
this.top = undefined;
this.bottom = this.fixed ? 0 : -1 * window.pageYOffset;
} else {
this.bottom = undefined;
}
if (this.position === "left" || this.position === "right") {
const totalWidth =
Math.sign(this.position === "left" ? -1 : 1) * el.offsetLeft +
el.scrollWidth;
const isVisible = totalWidth < window.innerWidth;
if (!isVisible) {
if (this.position === "right") {
this.position = "left";
this.left = undefined;
} else if (this.position === "left") {
this.position = "right";
this.right = undefined;
}
}
}
this.initPosition();
this.forceUpdate();
}
closeAfterTimeout = (closePortal: () => void) => () => {
if (this.closeTimeout) {
clearTimeout(this.closeTimeout);
}
this.closeTimeout = setTimeout(closePortal, 500);
};
clearCloseTimeout = () => {
if (this.closeTimeout) {
clearTimeout(this.closeTimeout);
}
};
render() {
const { className, label, children } = this.props;
const { className, hover, label, children } = this.props;
return (
<div className={className}>
<PortalWithState
onOpen={this.props.onOpen}
onOpen={this.onOpen}
onClose={this.props.onClose}
closeOnOutsideClick
closeOnEsc
>
{({ closePortal, openPortal, portal }) => (
<React.Fragment>
<Label onClick={this.handleOpen(openPortal)}>{label}</Label>
{({ closePortal, openPortal, isOpen, portal }) => (
<>
<Label
onMouseMove={hover ? this.clearCloseTimeout : undefined}
onMouseOut={
hover ? this.closeAfterTimeout(closePortal) : undefined
}
onMouseEnter={
hover ? this.handleOpen(openPortal, closePortal) : undefined
}
onClick={
hover ? undefined : this.handleOpen(openPortal, closePortal)
}
>
{label || (
<NudeButton
id={`${this.id}button`}
aria-haspopup="true"
aria-expanded={isOpen ? "true" : "false"}
aria-controls={this.id}
>
<MoreIcon />
</NudeButton>
)}
</Label>
{portal(
<Menu
onClick={ev => {
ev.stopPropagation();
closePortal();
}}
style={this.props.style}
<Position
ref={this.dropdownRef}
position={this.position}
fixed={this.fixed}
top={this.top}
bottom={this.bottom}
left={this.left}
right={this.right}
>
{children}
</Menu>
<Menu
ref={this.menuRef}
onMouseMove={hover ? this.clearCloseTimeout : undefined}
onMouseOut={
hover ? this.closeAfterTimeout(closePortal) : undefined
}
onClick={
typeof children === "function"
? undefined
: (ev) => {
ev.stopPropagation();
closePortal();
}
}
style={this.props.style}
id={this.id}
aria-labelledby={`${this.id}button`}
role="menu"
>
{typeof children === "function"
? children({ closePortal })
: children}
</Menu>
</Position>
)}
</React.Fragment>
</>
)}
</PortalWithState>
</div>
@@ -74,33 +229,58 @@ class DropdownMenu extends React.Component<Props> {
}
const Label = styled(Flex).attrs({
justify: 'center',
align: 'center',
justify: "center",
align: "center",
})`
z-index: 1000;
z-index: ${(props) => props.theme.depths.menu};
cursor: pointer;
`;
const Position = styled.div`
position: ${({ fixed }) => (fixed ? "fixed" : "absolute")};
display: flex;
${({ left }) => (left !== undefined ? `left: ${left}px` : "")};
${({ right }) => (right !== undefined ? `right: ${right}px` : "")};
${({ top }) => (top !== undefined ? `top: ${top}px` : "")};
${({ bottom }) => (bottom !== undefined ? `bottom: ${bottom}px` : "")};
max-height: 75%;
z-index: ${(props) => props.theme.depths.menu};
transform: ${(props) =>
props.position === "center" ? "translateX(-50%)" : "initial"};
pointer-events: none;
`;
const Menu = styled.div`
animation: ${fadeAndScaleIn} 200ms ease;
transform-origin: 75% 0;
position: absolute;
right: ${({ right }) => right}px;
top: ${({ top }) => top}px;
z-index: 1000;
border: ${props => props.theme.slateLight};
background: ${props => props.theme.white};
transform-origin: ${(props) => (props.left !== undefined ? "25%" : "75%")} 0;
backdrop-filter: blur(10px);
background: ${(props) => rgba(props.theme.menuBackground, 0.8)};
border: ${(props) =>
props.theme.menuBorder ? `1px solid ${props.theme.menuBorder}` : "none"};
border-radius: 2px;
padding: 0.5em 0;
min-width: 160px;
min-width: 180px;
overflow: hidden;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 8px rgba(0, 0, 0, 0.08),
0 2px 4px rgba(0, 0, 0, 0.08);
overflow-y: auto;
box-shadow: ${(props) => props.theme.menuShadow};
pointer-events: all;
hr {
margin: 0.5em 12px;
}
@media print {
display: none;
}
`;
export const Header = styled.h3`
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
color: ${(props) => props.theme.sidebarText};
letter-spacing: 0.04em;
margin: 1em 12px 0.5em;
`;
export default DropdownMenu;
+46 -11
View File
@@ -1,16 +1,38 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import { CheckmarkIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
type Props = {
onClick?: (SyntheticEvent<*>) => *,
onClick?: (SyntheticEvent<>) => void | Promise<void>,
children?: React.Node,
selected?: boolean,
disabled?: boolean,
};
const DropdownMenuItem = ({ onClick, children, ...rest }: Props) => {
const DropdownMenuItem = ({
onClick,
children,
selected,
disabled,
...rest
}: Props) => {
return (
<MenuItem onClick={onClick} {...rest}>
<MenuItem
onClick={disabled ? undefined : onClick}
disabled={disabled}
role="menuitem"
tabIndex="-1"
{...rest}
>
{selected !== undefined && (
<>
<CheckmarkIcon
color={selected === false ? "transparent" : undefined}
/>
&nbsp;
</>
)}
{children}
</MenuItem>
);
@@ -20,33 +42,46 @@ const MenuItem = styled.a`
display: flex;
margin: 0;
padding: 6px 12px;
height: 32px;
width: 100%;
min-height: 32px;
color: ${props =>
props.disabled ? props.theme.slate : props.theme.slateDark};
color: ${(props) =>
props.disabled ? props.theme.textTertiary : props.theme.textSecondary};
justify-content: left;
align-items: center;
font-size: 15px;
cursor: default;
user-select: none;
svg {
svg:not(:last-child) {
margin-right: 8px;
}
${props =>
svg {
opacity: ${(props) => (props.disabled ? ".5" : 1)};
}
${(props) =>
props.disabled
? ''
? "pointer-events: none;"
: `
&:hover {
color: ${props.theme.white};
background: ${props.theme.primary};
box-shadow: none;
cursor: pointer;
svg {
fill: ${props.theme.white};
}
}
&:focus {
color: ${props.theme.white};
background: ${props.theme.primary};
outline: none;
}
`};
`;
+2 -2
View File
@@ -1,3 +1,3 @@
// @flow
export { default as DropdownMenu } from './DropdownMenu';
export { default as DropdownMenuItem } from './DropdownMenuItem';
export { default as DropdownMenu, Header } from "./DropdownMenu";
export { default as DropdownMenuItem } from "./DropdownMenuItem";
+129
View File
@@ -0,0 +1,129 @@
// @flow
import { lighten } from "polished";
import * as React from "react";
import { withRouter, type RouterHistory } from "react-router-dom";
import styled, { withTheme } from "styled-components";
import UiStore from "stores/UiStore";
import ErrorBoundary from "components/ErrorBoundary";
import Tooltip from "components/Tooltip";
import embeds from "../embeds";
import isInternalUrl from "utils/isInternalUrl";
import { uploadFile } from "utils/uploadFile";
const RichMarkdownEditor = React.lazy(() => import("rich-markdown-editor"));
const EMPTY_ARRAY = [];
type Props = {
id?: string,
defaultValue?: string,
readOnly?: boolean,
grow?: boolean,
disableEmbeds?: boolean,
ui?: UiStore,
};
type PropsWithRef = Props & {
forwardedRef: React.Ref<any>,
history: RouterHistory,
};
class Editor extends React.Component<PropsWithRef> {
onUploadImage = async (file: File) => {
const result = await uploadFile(file, { documentId: this.props.id });
return result.url;
};
onClickLink = (href: string) => {
// on page hash
if (href[0] === "#") {
window.location.href = href;
return;
}
if (isInternalUrl(href)) {
// relative
let navigateTo = href;
// probably absolute
if (href[0] !== "/") {
try {
const url = new URL(href);
navigateTo = url.pathname + url.hash;
} catch (err) {
navigateTo = href;
}
}
this.props.history.push(navigateTo);
} else {
window.open(href, "_blank");
}
};
onShowToast = (message: string) => {
if (this.props.ui) {
this.props.ui.showToast(message);
}
};
render() {
return (
<ErrorBoundary reloadOnChunkMissing>
<StyledEditor
ref={this.props.forwardedRef}
uploadImage={this.onUploadImage}
onClickLink={this.onClickLink}
onShowToast={this.onShowToast}
embeds={this.props.disableEmbeds ? EMPTY_ARRAY : embeds}
tooltip={EditorTooltip}
{...this.props}
/>
</ErrorBoundary>
);
}
}
const StyledEditor = styled(RichMarkdownEditor)`
flex-grow: ${(props) => (props.grow ? 1 : 0)};
justify-content: start;
> div {
transition: ${(props) => props.theme.backgroundTransition};
}
.notice-block.tip,
.notice-block.warning {
font-weight: 500;
}
p {
a {
color: ${(props) => props.theme.text};
border-bottom: 1px solid ${(props) => lighten(0.5, props.theme.text)};
text-decoration: none !important;
font-weight: 500;
&:hover {
border-bottom: 1px solid ${(props) => props.theme.text};
text-decoration: none;
}
}
}
`;
const EditorTooltip = ({ children, ...props }) => (
<Tooltip offset="0, 16" delay={150} {...props}>
<Span>{children}</Span>
</Tooltip>
);
const Span = styled.span`
outline: none;
`;
const EditorWithRouterAndTheme = withRouter(withTheme(Editor));
export default React.forwardRef<Props, typeof Editor>((props, ref) => (
<EditorWithRouterAndTheme {...props} forwardedRef={ref} />
));
+8
View File
@@ -0,0 +1,8 @@
// @flow
import styled from "styled-components";
const Empty = styled.p`
color: ${(props) => props.theme.textTertiary};
`;
export default Empty;
-20
View File
@@ -1,20 +0,0 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
type Props = {
children: string,
};
const Empty = (props: Props) => {
const { children, ...rest } = props;
return <Container {...rest}>{children}</Container>;
};
const Container = styled.div`
display: flex;
color: ${props => props.theme.slate};
text-align: center;
`;
export default Empty;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import Empty from './Empty';
export default Empty;
+35 -21
View File
@@ -1,16 +1,18 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import { observer } from 'mobx-react';
import { observable } from 'mobx';
import HelpText from 'components/HelpText';
import Button from 'components/Button';
import CenteredContent from 'components/CenteredContent';
import PageTitle from 'components/PageTitle';
import { githubIssuesUrl } from '../../shared/utils/routeHelpers';
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import HelpText from "components/HelpText";
import PageTitle from "components/PageTitle";
import { githubIssuesUrl } from "../../shared/utils/routeHelpers";
import env from "env";
type Props = {
children: React.Node,
reloadOnChunkMissing?: boolean,
};
@observer
@@ -20,15 +22,27 @@ class ErrorBoundary extends React.Component<Props> {
componentDidCatch(error: Error, info: Object) {
this.error = error;
console.error(error);
// Error handler is often blocked by the browser
if (window.Bugsnag) {
Bugsnag.notifyException(error, { react: info });
if (
this.props.reloadOnChunkMissing &&
error.message &&
error.message.match(/chunk/)
) {
// If the editor bundle fails to load then reload the entire window. This
// can happen if a deploy happens between the user loading the initial JS
// bundle and the async-loaded editor JS bundle as the hash will change.
window.location.reload(true);
return;
}
if (window.Sentry) {
window.Sentry.captureException(error);
}
}
handleReload = () => {
window.location.reload();
window.location.reload(true);
};
handleShowDetails = () => {
@@ -41,26 +55,26 @@ class ErrorBoundary extends React.Component<Props> {
render() {
if (this.error) {
const isReported = !!window.Bugsnag;
const isReported = !!window.Sentry && env.DEPLOYMENT === "hosted";
return (
<CenteredContent>
<PageTitle title="Something Unexpected Happened" />
<h1>Something Unexpected Happened</h1>
<HelpText>
Sorry, an unrecoverable error occurred{isReported &&
' our engineers have been notified'}. Please try reloading the
page, it may have been a temporary glitch.
Sorry, an unrecoverable error occurred
{isReported && " our engineers have been notified"}. Please try
reloading the page, it may have been a temporary glitch.
</HelpText>
{this.showDetails && <Pre>{this.error.toString()}</Pre>}
<p>
<Button onClick={this.handleReload}>Reload</Button>{' '}
<Button onClick={this.handleReload}>Reload</Button>{" "}
{this.showDetails ? (
<Button onClick={this.handleReportBug} light>
<Button onClick={this.handleReportBug} neutral>
Report a Bug
</Button>
) : (
<Button onClick={this.handleShowDetails} light>
<Button onClick={this.handleShowDetails} neutral>
Show Details
</Button>
)}
@@ -73,7 +87,7 @@ class ErrorBoundary extends React.Component<Props> {
}
const Pre = styled.pre`
background: ${props => props.theme.smoke};
background: ${(props) => props.theme.smoke};
padding: 16px;
border-radius: 4px;
font-size: 12px;
+76
View File
@@ -0,0 +1,76 @@
// @flow
import { observer, inject } from "mobx-react";
import * as React from "react";
import styled, { withTheme } from "styled-components";
import User from "models/User";
import Avatar from "components/Avatar";
import Flex from "components/Flex";
type Props = {
users: User[],
size?: number,
overflow: number,
renderAvatar: (user: User) => React.Node,
};
@observer
class Facepile extends React.Component<Props> {
render() {
const {
users,
overflow,
size = 32,
renderAvatar = renderDefaultAvatar,
...rest
} = this.props;
return (
<Avatars {...rest}>
{overflow > 0 && (
<More size={size}>
<span>+{overflow}</span>
</More>
)}
{users.map((user) => (
<AvatarWrapper key={user.id}>{renderAvatar(user)}</AvatarWrapper>
))}
</Avatars>
);
}
}
function renderDefaultAvatar(user: User) {
return <Avatar user={user} src={user.avatarUrl} size={32} />;
}
const AvatarWrapper = styled.div`
margin-right: -8px;
&:first-child {
margin-right: 0;
}
`;
const More = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
border-radius: 100%;
background: ${(props) => props.theme.slate};
color: ${(props) => props.theme.text};
border: 2px solid ${(props) => props.theme.background};
text-align: center;
font-size: 11px;
font-weight: 600;
`;
const Avatars = styled(Flex)`
align-items: center;
flex-direction: row-reverse;
cursor: pointer;
`;
export default inject("views", "presence")(withTheme(Facepile));
+9
View File
@@ -0,0 +1,9 @@
// @flow
import styled from "styled-components";
import { fadeIn } from "shared/styles/animations";
const Fade = styled.span`
animation: ${fadeIn} ${(props) => props.timing || "250ms"} ease-in-out;
`;
export default Fade;
-9
View File
@@ -1,9 +0,0 @@
// @flow
import styled from 'styled-components';
import { fadeIn } from 'shared/styles/animations';
const Fade = styled.span`
animation: ${fadeIn} ${props => props.timing || '250ms'} ease-in-out;
`;
export default Fade;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import Fade from './Fade';
export default Fade;
@@ -1,23 +1,24 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import * as React from "react";
import styled from "styled-components";
type JustifyValues =
| 'center'
| 'space-around'
| 'space-between'
| 'flex-start'
| 'flex-end';
| "center"
| "space-around"
| "space-between"
| "flex-start"
| "flex-end";
type AlignValues =
| 'stretch'
| 'center'
| 'baseline'
| 'flex-start'
| 'flex-end';
| "stretch"
| "center"
| "baseline"
| "flex-start"
| "flex-end";
type Props = {
column?: ?boolean,
shrink?: ?boolean,
align?: AlignValues,
justify?: JustifyValues,
auto?: ?boolean,
@@ -32,10 +33,11 @@ const Flex = (props: Props) => {
const Container = styled.div`
display: flex;
flex: ${({ auto }) => (auto ? '1 1 auto' : 'initial')};
flex-direction: ${({ column }) => (column ? 'column' : 'row')};
flex: ${({ auto }) => (auto ? "1 1 auto" : "initial")};
flex-direction: ${({ column }) => (column ? "column" : "row")};
align-items: ${({ align }) => align};
justify-content: ${({ justify }) => justify};
flex-shrink: ${({ shrink }) => (shrink ? 1 : "initial")};
min-height: 0;
min-width: 0;
`;
+24
View File
@@ -0,0 +1,24 @@
// @flow
import * as React from "react";
import styled from "styled-components";
import Empty from "components/Empty";
import Fade from "components/Fade";
import Flex from "components/Flex";
export default function FullscreenLoading() {
return (
<Fade timing={500}>
<Centered>
<Empty>Loading</Empty>
</Centered>
</Fade>
);
}
const Centered = styled(Flex)`
text-align: center;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
`;
@@ -1,5 +1,5 @@
// @flow
import * as React from 'react';
import * as React from "react";
type Props = {
size?: number,
@@ -7,7 +7,7 @@ type Props = {
className?: string,
};
function GithubLogo({ size = 34, fill = '#FFF', className }: Props) {
function GithubLogo({ size = 34, fill = "#FFF", className }: Props) {
return (
<svg
fill={fill}
@@ -1,5 +1,5 @@
// @flow
import * as React from 'react';
import * as React from "react";
type Props = {
size?: number,
@@ -7,7 +7,7 @@ type Props = {
className?: string,
};
function GoogleLogo({ size = 34, fill = '#FFF', className }: Props) {
function GoogleLogo({ size = 34, fill = "#FFF", className }: Props) {
return (
<svg
fill={fill}
+94
View File
@@ -0,0 +1,94 @@
// @flow
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import { MAX_AVATAR_DISPLAY } from "shared/constants";
import GroupMembershipsStore from "stores/GroupMembershipsStore";
import CollectionGroupMembership from "models/CollectionGroupMembership";
import Group from "models/Group";
import GroupMembers from "scenes/GroupMembers";
import Facepile from "components/Facepile";
import Flex from "components/Flex";
import ListItem from "components/List/Item";
import Modal from "components/Modal";
type Props = {
group: Group,
groupMemberships: GroupMembershipsStore,
membership?: CollectionGroupMembership,
showFacepile: boolean,
renderActions: ({ openMembersModal: () => void }) => React.Node,
};
@observer
class GroupListItem extends React.Component<Props> {
@observable membersModalOpen: boolean = false;
handleMembersModalOpen = () => {
this.membersModalOpen = true;
};
handleMembersModalClose = () => {
this.membersModalOpen = false;
};
render() {
const { group, groupMemberships, showFacepile, renderActions } = this.props;
const memberCount = group.memberCount;
const membershipsInGroup = groupMemberships.inGroup(group.id);
const users = membershipsInGroup
.slice(0, MAX_AVATAR_DISPLAY)
.map((gm) => gm.user);
const overflow = memberCount - users.length;
return (
<>
<ListItem
title={
<Title onClick={this.handleMembersModalOpen}>{group.name}</Title>
}
subtitle={
<>
{memberCount} member{memberCount === 1 ? "" : "s"}
</>
}
actions={
<Flex align="center">
{showFacepile && (
<Facepile
onClick={this.handleMembersModalOpen}
users={users}
overflow={overflow}
/>
)}
&nbsp;
{renderActions({
openMembersModal: this.handleMembersModalOpen,
})}
</Flex>
}
/>
<Modal
title="Group members"
onRequestClose={this.handleMembersModalClose}
isOpen={this.membersModalOpen}
>
<GroupMembers group={group} onSubmit={this.handleMembersModalClose} />
</Modal>
</>
);
}
}
const Title = styled.span`
&:hover {
text-decoration: underline;
cursor: pointer;
}
`;
export default inject("groupMemberships")(GroupListItem);
+3 -1
View File
@@ -1,8 +1,10 @@
// @flow
import styled from 'styled-components';
import styled from "styled-components";
const Heading = styled.h1`
display: flex;
align-items: center;
${(props) => (props.centered ? "text-align: center;" : "")}
svg {
margin-left: -6px;
+10
View File
@@ -0,0 +1,10 @@
// @flow
import styled from "styled-components";
const HelpText = styled.p`
margin-top: 0;
color: ${(props) => props.theme.textSecondary};
font-size: ${(props) => (props.small ? "13px" : "inherit")};
`;
export default HelpText;
-10
View File
@@ -1,10 +0,0 @@
// @flow
import styled from 'styled-components';
const HelpText = styled.p`
margin-top: 0;
color: ${props => props.theme.slateDark};
font-size: ${props => (props.small ? '13px' : 'auto')};
`;
export default HelpText;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import HelpText from './HelpText';
export default HelpText;
@@ -1,7 +1,7 @@
// @flow
import * as React from 'react';
import replace from 'string-replace-to-array';
import styled from 'styled-components';
import * as React from "react";
import replace from "string-replace-to-array";
import styled from "styled-components";
type Props = {
highlight: ?string | RegExp,
@@ -22,8 +22,8 @@ function Highlight({
regex = highlight;
} else {
regex = new RegExp(
(highlight || '').replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'),
caseSensitive ? 'g' : 'gi'
(highlight || "").replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"),
caseSensitive ? "g" : "gi"
);
}
return (
@@ -38,7 +38,9 @@ function Highlight({
}
const Mark = styled.mark`
background: ${props => props.theme.yellow};
background: ${(props) => props.theme.yellow};
border-radius: 2px;
padding: 0 4px;
`;
export default Highlight;
-3
View File
@@ -1,3 +0,0 @@
// @flow
import Highlight from './Highlight';
export default Highlight;
+237
View File
@@ -0,0 +1,237 @@
// @flow
import { inject } from "mobx-react";
import { transparentize } from "polished";
import * as React from "react";
import { Portal } from "react-portal";
import styled from "styled-components";
import { fadeAndSlideIn } from "shared/styles/animations";
import { parseDocumentSlugFromUrl } from "shared/utils/parseDocumentSlug";
import DocumentsStore from "stores/DocumentsStore";
import HoverPreviewDocument from "components/HoverPreviewDocument";
import isInternalUrl from "utils/isInternalUrl";
const DELAY_OPEN = 300;
const DELAY_CLOSE = 300;
type Props = {
node: HTMLAnchorElement,
event: MouseEvent,
documents: DocumentsStore,
onClose: () => void,
};
function HoverPreview({ node, documents, onClose, event }: Props) {
// previews only work for internal doc links for now
if (!isInternalUrl(node.href)) {
return null;
}
const slug = parseDocumentSlugFromUrl(node.href);
const [isVisible, setVisible] = React.useState(false);
const timerClose = React.useRef();
const timerOpen = React.useRef();
const cardRef = React.useRef<?HTMLDivElement>();
const startCloseTimer = () => {
stopOpenTimer();
timerClose.current = setTimeout(() => {
if (isVisible) setVisible(false);
onClose();
}, DELAY_CLOSE);
};
const stopCloseTimer = () => {
if (timerClose.current) {
clearTimeout(timerClose.current);
}
};
const startOpenTimer = () => {
timerOpen.current = setTimeout(() => setVisible(true), DELAY_OPEN);
};
const stopOpenTimer = () => {
if (timerOpen.current) {
clearTimeout(timerOpen.current);
}
};
React.useEffect(() => {
if (slug) {
documents.prefetchDocument(slug, {
prefetch: true,
});
}
startOpenTimer();
if (cardRef.current) {
cardRef.current.addEventListener("mouseenter", stopCloseTimer);
}
if (cardRef.current) {
cardRef.current.addEventListener("mouseleave", startCloseTimer);
}
node.addEventListener("mouseout", startCloseTimer);
node.addEventListener("mouseover", stopCloseTimer);
node.addEventListener("mouseover", startOpenTimer);
return () => {
node.removeEventListener("mouseout", startCloseTimer);
node.removeEventListener("mouseover", stopCloseTimer);
node.removeEventListener("mouseover", startOpenTimer);
if (cardRef.current) {
cardRef.current.removeEventListener("mouseenter", stopCloseTimer);
}
if (cardRef.current) {
cardRef.current.removeEventListener("mouseleave", startCloseTimer);
}
if (timerClose.current) {
clearTimeout(timerClose.current);
}
};
}, [node]);
const anchorBounds = node.getBoundingClientRect();
const cardBounds = cardRef.current
? cardRef.current.getBoundingClientRect()
: undefined;
const left = cardBounds
? Math.min(anchorBounds.left, window.innerWidth - 16 - 350)
: anchorBounds.left;
const leftOffset = anchorBounds.left - left;
return (
<Portal>
<Position
top={anchorBounds.bottom + window.scrollY}
left={left}
aria-hidden
>
<div ref={cardRef}>
<HoverPreviewDocument url={node.href}>
{(content) =>
isVisible ? (
<Animate>
<Card>
<Margin />
<CardContent>{content}</CardContent>
</Card>
<Pointer offset={leftOffset + anchorBounds.width / 2} />
</Animate>
) : null
}
</HoverPreviewDocument>
</div>
</Position>
</Portal>
);
}
const Animate = styled.div`
animation: ${fadeAndSlideIn} 150ms ease;
@media print {
display: none;
}
`;
// fills the gap between the card and pointer to avoid a dead zone
const Margin = styled.div`
position: absolute;
top: -11px;
left: 0;
right: 0;
height: 11px;
`;
const CardContent = styled.div`
overflow: hidden;
max-height: 350px;
user-select: none;
`;
// &:after — gradient mask for overflow text
const Card = styled.div`
backdrop-filter: blur(10px);
background: ${(props) => props.theme.background};
border: ${(props) =>
props.theme.menuBorder ? `1px solid ${props.theme.menuBorder}` : "none"};
border-radius: 4px;
box-shadow: 0 30px 90px -20px rgba(0, 0, 0, 0.3),
0 0 1px 1px rgba(0, 0, 0, 0.05);
padding: 16px;
width: 350px;
font-size: 0.9em;
position: relative;
.placeholder,
.heading-anchor {
display: none;
}
&:after {
content: "";
display: block;
position: absolute;
pointer-events: none;
background: linear-gradient(
90deg,
${(props) => transparentize(1, props.theme.background)} 0%,
${(props) => transparentize(1, props.theme.background)} 75%,
${(props) => props.theme.background} 90%
);
bottom: 0;
left: 0;
right: 0;
height: 1.7em;
border-bottom: 16px solid ${(props) => props.theme.background};
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
`;
const Position = styled.div`
margin-top: 10px;
position: ${({ fixed }) => (fixed ? "fixed" : "absolute")};
display: flex;
max-height: 75%;
${({ top }) => (top !== undefined ? `top: ${top}px` : "")};
${({ left }) => (left !== undefined ? `left: ${left}px` : "")};
`;
const Pointer = styled.div`
top: -22px;
left: ${(props) => props.offset}px;
width: 22px;
height: 22px;
position: absolute;
transform: translateX(-50%);
&:before,
&:after {
content: "";
display: inline-block;
position: absolute;
bottom: 0;
right: 0;
}
&:before {
border: 8px solid transparent;
border-bottom-color: ${(props) =>
props.theme.menuBorder || "rgba(0, 0, 0, 0.1)"};
right: -1px;
}
&:after {
border: 7px solid transparent;
border-bottom-color: ${(props) => props.theme.background};
}
`;
export default inject("documents")(HoverPreview);
+53
View File
@@ -0,0 +1,53 @@
// @flow
import { inject, observer } from "mobx-react";
import * as React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { parseDocumentSlugFromUrl } from "shared/utils/parseDocumentSlug";
import DocumentsStore from "stores/DocumentsStore";
import DocumentMetaWithViews from "components/DocumentMetaWithViews";
import Editor from "components/Editor";
type Props = {
url: string,
documents: DocumentsStore,
children: (React.Node) => React.Node,
};
function HoverPreviewDocument({ url, documents, children }: Props) {
const slug = parseDocumentSlugFromUrl(url);
documents.prefetchDocument(slug, {
prefetch: true,
});
const document = slug ? documents.getByUrl(slug) : undefined;
if (!document) return null;
return children(
<Content to={document.url}>
<Heading>{document.titleWithDefault}</Heading>
<DocumentMetaWithViews isDraft={document.isDraft} document={document} />
<React.Suspense fallback={<div />}>
<Editor
key={document.id}
defaultValue={document.getSummary()}
disableEmbeds
readOnly
/>
</React.Suspense>
</Content>
);
}
const Content = styled(Link)`
cursor: pointer;
`;
const Heading = styled.h2`
margin: 0 0 0.75em;
color: ${(props) => props.theme.text};
`;
export default inject("documents")(observer(HoverPreviewDocument));
+249
View File
@@ -0,0 +1,249 @@
// @flow
import { observable } from "mobx";
import { observer } from "mobx-react";
import {
CollectionIcon,
CoinsIcon,
AcademicCapIcon,
BeakerIcon,
BuildingBlocksIcon,
CloudIcon,
CodeIcon,
EditIcon,
EyeIcon,
LeafIcon,
LightBulbIcon,
MoonIcon,
NotepadIcon,
PadlockIcon,
PaletteIcon,
QuestionMarkIcon,
SunIcon,
VehicleIcon,
} from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import { DropdownMenu } from "components/DropdownMenu";
import Flex from "components/Flex";
import HelpText from "components/HelpText";
import { LabelText } from "components/Input";
import NudeButton from "components/NudeButton";
const TwitterPicker = React.lazy(() =>
import("react-color/lib/components/twitter/Twitter")
);
export const icons = {
collection: {
component: CollectionIcon,
keywords: "collection",
},
coins: {
component: CoinsIcon,
keywords: "coins money finance sales income revenue cash",
},
academicCap: {
component: AcademicCapIcon,
keywords: "learn teach lesson guide tutorial onboarding training",
},
beaker: {
component: BeakerIcon,
keywords: "lab research experiment test",
},
buildingBlocks: {
component: BuildingBlocksIcon,
keywords: "app blocks product prototype",
},
cloud: {
component: CloudIcon,
keywords: "cloud service aws infrastructure",
},
code: {
component: CodeIcon,
keywords: "developer api code development engineering programming",
},
eye: {
component: EyeIcon,
keywords: "eye view",
},
leaf: {
component: LeafIcon,
keywords: "leaf plant outdoors nature ecosystem climate",
},
lightbulb: {
component: LightBulbIcon,
keywords: "lightbulb idea",
},
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",
},
question: {
component: QuestionMarkIcon,
keywords: "question help support faq",
},
sun: {
component: SunIcon,
keywords: "day sun weather",
},
vehicle: {
component: VehicleIcon,
keywords: "truck car travel transport",
},
};
const colors = [
"#4E5C6E",
"#0366d6",
"#9E5CF7",
"#FF825C",
"#FF5C80",
"#FFBE0B",
"#42DED1",
"#00D084",
"#FF4DFA",
"#2F362F",
];
type Props = {
onOpen?: () => void,
onChange: (color: string, icon: string) => void,
icon: string,
color: string,
};
function preventEventBubble(event) {
event.stopPropagation();
}
@observer
class IconPicker extends React.Component<Props> {
@observable isOpen: boolean = false;
node: ?HTMLElement;
componentDidMount() {
window.addEventListener("click", this.handleClickOutside);
}
componentWillUnmount() {
window.removeEventListener("click", this.handleClickOutside);
}
handleClose = () => {
this.isOpen = false;
};
handleOpen = () => {
this.isOpen = true;
if (this.props.onOpen) {
this.props.onOpen();
}
};
handleClickOutside = (ev: SyntheticMouseEvent<>) => {
// $FlowFixMe
if (ev.target && this.node && this.node.contains(ev.target)) {
return;
}
this.handleClose();
};
render() {
const Component = icons[this.props.icon || "collection"].component;
return (
<Wrapper ref={(ref) => (this.node = ref)}>
<label>
<LabelText>Icon</LabelText>
</label>
<DropdownMenu
onOpen={this.handleOpen}
label={
<LabelButton>
<Component role="button" color={this.props.color} size={30} />
</LabelButton>
}
>
<Icons onClick={preventEventBubble}>
{Object.keys(icons).map((name) => {
const Component = icons[name].component;
return (
<IconButton
key={name}
onClick={() => this.props.onChange(this.props.color, name)}
style={{ width: 30, height: 30 }}
>
<Component color={this.props.color} size={30} />
</IconButton>
);
})}
</Icons>
<Flex onClick={preventEventBubble}>
<React.Suspense fallback={<Loading>Loading</Loading>}>
<ColorPicker
color={this.props.color}
onChange={(color) =>
this.props.onChange(color.hex, this.props.icon)
}
colors={colors}
triangle="hide"
/>
</React.Suspense>
</Flex>
</DropdownMenu>
</Wrapper>
);
}
}
const Icons = styled.div`
padding: 15px 9px 9px 15px;
width: 276px;
`;
const LabelButton = styled(NudeButton)`
border: 1px solid ${(props) => props.theme.inputBorder};
width: 32px;
height: 32px;
`;
const IconButton = styled(NudeButton)`
border-radius: 4px;
margin: 0px 6px 6px 0px;
width: 30px;
height: 30px;
`;
const Loading = styled(HelpText)`
padding: 16px;
`;
const ColorPicker = styled(TwitterPicker)`
box-shadow: none !important;
background: transparent !important;
`;
const Wrapper = styled("div")`
display: inline-block;
position: relative;
`;
export default IconPicker;
+165
View File
@@ -0,0 +1,165 @@
// @flow
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import Flex from "components/Flex";
import VisuallyHidden from "components/VisuallyHidden";
const RealTextarea = styled.textarea`
border: 0;
flex: 1;
padding: 8px 12px 8px ${(props) => (props.hasIcon ? "8px" : "12px")};
outline: none;
background: none;
color: ${(props) => props.theme.text};
&:disabled,
&::placeholder {
color: ${(props) => props.theme.placeholder};
}
`;
const RealInput = styled.input`
border: 0;
flex: 1;
padding: 8px 12px 8px ${(props) => (props.hasIcon ? "8px" : "12px")};
outline: none;
background: none;
color: ${(props) => props.theme.text};
height: 30px;
&:disabled,
&::placeholder {
color: ${(props) => props.theme.placeholder};
}
&::-webkit-search-cancel-button {
-webkit-appearance: searchfield-cancel-button;
}
`;
const Wrapper = styled.div`
flex: ${(props) => (props.flex ? "1" : "0")};
max-width: ${(props) => (props.short ? "350px" : "100%")};
min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : "0")};
max-height: ${({ maxHeight }) => (maxHeight ? `${maxHeight}px` : "initial")};
`;
const IconWrapper = styled.span`
position: relative;
left: 4px;
width: 24px;
height: 24px;
`;
export const Outline = styled(Flex)`
display: flex;
flex: 1;
margin: ${(props) =>
props.margin !== undefined ? props.margin : "0 0 16px"};
color: inherit;
border-width: 1px;
border-style: solid;
border-color: ${(props) =>
props.hasError
? "red"
: props.focused
? props.theme.inputBorderFocused
: props.theme.inputBorder};
border-radius: 4px;
font-weight: normal;
align-items: center;
overflow: hidden;
`;
export const LabelText = styled.div`
font-weight: 500;
padding-bottom: 4px;
display: inline-block;
`;
export type Props = {
type?: string,
value?: string,
label?: string,
className?: string,
labelHidden?: boolean,
flex?: boolean,
short?: boolean,
margin?: string | number,
icon?: React.Node,
onFocus?: (ev: SyntheticEvent<>) => void,
onBlur?: (ev: SyntheticEvent<>) => void,
};
@observer
class Input extends React.Component<Props> {
input: ?HTMLInputElement;
@observable focused: boolean = false;
handleBlur = (ev: SyntheticEvent<>) => {
this.focused = false;
if (this.props.onBlur) {
this.props.onBlur(ev);
}
};
handleFocus = (ev: SyntheticEvent<>) => {
this.focused = true;
if (this.props.onFocus) {
this.props.onFocus(ev);
}
};
focus() {
if (this.input) {
this.input.focus();
}
}
render() {
const {
type = "text",
icon,
label,
margin,
className,
short,
flex,
labelHidden,
onFocus,
onBlur,
...rest
} = this.props;
const InputComponent = type === "textarea" ? RealTextarea : RealInput;
const wrappedLabel = <LabelText>{label}</LabelText>;
return (
<Wrapper className={className} short={short} flex={flex}>
<label>
{label &&
(labelHidden ? (
<VisuallyHidden>{wrappedLabel}</VisuallyHidden>
) : (
wrappedLabel
))}
<Outline focused={this.focused} margin={margin}>
{icon && <IconWrapper>{icon}</IconWrapper>}
<InputComponent
ref={(ref) => (this.input = ref)}
onBlur={this.handleBlur}
onFocus={this.handleFocus}
type={type === "textarea" ? undefined : type}
hasIcon={!!icon}
{...rest}
/>
</Outline>
</label>
</Wrapper>
);
}
}
export default Input;
-84
View File
@@ -1,84 +0,0 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import Flex from 'shared/components/Flex';
const RealTextarea = styled.textarea`
border: 0;
flex: 1;
padding: 8px 12px;
outline: none;
background: none;
&:disabled,
&::placeholder {
color: ${props => props.theme.slate};
}
`;
const RealInput = styled.input`
border: 0;
flex: 1;
padding: 8px 12px;
outline: none;
background: none;
&:disabled,
&::placeholder {
color: ${props => props.theme.slate};
}
`;
const Wrapper = styled.div`
max-width: ${props => (props.short ? '350px' : '100%')};
`;
export const Outline = styled(Flex)`
display: flex;
flex: 1;
margin: 0 0 16px;
color: inherit;
border-width: 1px;
border-style: solid;
border-color: ${props => (props.hasError ? 'red' : props.theme.slateLight)};
border-radius: 4px;
font-weight: normal;
&:focus {
border-color: ${props => props.theme.slate};
}
`;
export const LabelText = styled.div`
font-weight: 500;
padding-bottom: 4px;
`;
export type Props = {
type?: string,
value?: string,
label?: string,
className?: string,
short?: boolean,
};
export default function Input({
type = 'text',
label,
className,
short,
...rest
}: Props) {
const InputComponent = type === 'textarea' ? RealTextarea : RealInput;
return (
<Wrapper className={className} short={short}>
<label>
{label && <LabelText>{label}</LabelText>}
<Outline>
<InputComponent {...rest} />
</Outline>
</label>
</Wrapper>
);
}
-4
View File
@@ -1,4 +0,0 @@
// @flow
import Input, { LabelText, Outline } from './Input';
export default Input;
export { LabelText, Outline };
+13
View File
@@ -0,0 +1,13 @@
// @flow
import styled from "styled-components";
import Input from "./Input";
const InputLarge = styled(Input)`
height: 40px;
input {
height: 40px;
}
`;
export default InputLarge;
+70
View File
@@ -0,0 +1,70 @@
// @flow
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import * as React from "react";
import styled, { withTheme } from "styled-components";
import UiStore from "stores/UiStore";
import Editor from "components/Editor";
import HelpText from "components/HelpText";
import { LabelText, Outline } from "components/Input";
type Props = {
label: string,
minHeight?: number,
maxHeight?: number,
readOnly?: boolean,
ui: UiStore,
};
@observer
class InputRich extends React.Component<Props> {
@observable editorComponent: React.ComponentType<any>;
@observable focused: boolean = false;
handleBlur = () => {
this.focused = false;
};
handleFocus = () => {
this.focused = true;
};
render() {
const { label, minHeight, maxHeight, ui, ...rest } = this.props;
return (
<>
<LabelText>{label}</LabelText>
<StyledOutline
maxHeight={maxHeight}
minHeight={minHeight}
focused={this.focused}
>
<React.Suspense fallback={<HelpText>Loading editor</HelpText>}>
<Editor
onBlur={this.handleBlur}
onFocus={this.handleFocus}
ui={ui}
grow
{...rest}
/>
</React.Suspense>
</StyledOutline>
</>
);
}
}
const StyledOutline = styled(Outline)`
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 inject("ui")(withTheme(InputRich));
+74
View File
@@ -0,0 +1,74 @@
// @flow
import { observable } from "mobx";
import { observer } from "mobx-react";
import { SearchIcon } from "outline-icons";
import * as React from "react";
import keydown from "react-keydown";
import { withRouter, type RouterHistory } from "react-router-dom";
import styled, { withTheme } from "styled-components";
import Input from "./Input";
import { searchUrl } from "utils/routeHelpers";
type Props = {
history: RouterHistory,
theme: Object,
placeholder?: string,
collectionId?: string,
};
@observer
class InputSearch extends React.Component<Props> {
input: ?Input;
@observable focused: boolean = false;
@keydown("meta+f")
focus(ev) {
ev.preventDefault();
if (this.input) {
this.input.focus();
}
}
handleSearchInput = (ev) => {
ev.preventDefault();
this.props.history.push(
searchUrl(ev.target.value, this.props.collectionId)
);
};
handleFocus = () => {
this.focused = true;
};
handleBlur = () => {
this.focused = false;
};
render() {
const { theme, placeholder = "Search…" } = this.props;
return (
<InputMaxWidth
ref={(ref) => (this.input = ref)}
type="search"
placeholder={placeholder}
onInput={this.handleSearchInput}
icon={
<SearchIcon
color={this.focused ? theme.inputBorderFocused : theme.inputBorder}
/>
}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
margin={0}
/>
);
}
}
const InputMaxWidth = styled(Input)`
max-width: 30vw;
`;
export default withTheme(withRouter(InputSearch));
+72
View File
@@ -0,0 +1,72 @@
// @flow
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import VisuallyHidden from "components/VisuallyHidden";
import { Outline, LabelText } from "./Input";
const Select = styled.select`
border: 0;
flex: 1;
padding: 8px 12px;
outline: none;
background: none;
color: ${(props) => props.theme.text};
&:disabled,
&::placeholder {
color: ${(props) => props.theme.placeholder};
}
`;
type Option = { label: string, value: string };
export type Props = {
value?: string,
label?: string,
className?: string,
labelHidden?: boolean,
options: Option[],
};
@observer
class InputSelect extends React.Component<Props> {
@observable focused: boolean = false;
handleBlur = () => {
this.focused = false;
};
handleFocus = () => {
this.focused = true;
};
render() {
const { label, className, labelHidden, options, ...rest } = this.props;
const wrappedLabel = <LabelText>{label}</LabelText>;
return (
<label>
{label &&
(labelHidden ? (
<VisuallyHidden>{wrappedLabel}</VisuallyHidden>
) : (
wrappedLabel
))}
<Outline focused={this.focused} className={className}>
<Select onBlur={this.handleBlur} onFocus={this.handleFocus} {...rest}>
{options.map((option) => (
<option value={option.value} key={option.value}>
{option.label}
</option>
))}
</Select>
</Outline>
</label>
);
}
}
export default InputSelect;

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