From 799c7e69368f7bf9096f7d7b7d7bbd6c519363be Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Wed, 3 Jan 2024 15:14:09 -0500 Subject: [PATCH] feat: v4.0.0 (#1016) Co-authored-by: Denys Konovalov Co-authored-by: Mathieu COSYNS <64072917+Mathieu-COSYNS@users.noreply.github.com> --- .github/workflows/build.yml | 8 +- .github/workflows/cypress.yml | 49 + .gitignore | 4 + .prettierignore | 4 + .prettierrc | 6 + cypress.config.ts | 19 + cypress/README.md | 72 + cypress/cypress.d.ts | 82 + ...itorial_workflow_spec_bitbucket_backend.js | 30 + ...orkflow_spec_git-gateway_github_backend.js | 31 + ...orkflow_spec_git-gateway_gitlab_backend.js | 31 + ...al_workflow_spec_github_backend_graphql.js | 37 + ...c_github_backend_graphql_open_authoring.js | 38 + ...orial_workflow_spec_github_backend_rest.js | 37 + ...spec_github_backend_rest_open_authoring.js | 38 + .../editorial_workflow_spec_gitlab_backend.js | 36 + ...itorial_workflow_spec_proxy_git_backend.js | 32 + cypress/e2e/_old/field_validations_spec.js | 54 + ...8n_editorial_workflow_spec_test_backend.js | 38 + ...n_simple_workflow_spec_proxy_fs_backend.js | 148 + .../_old/markdown_widget_backspace_spec.js | 76 + .../_old/markdown_widget_code_block_spec.js | 134 + .../e2e/_old/markdown_widget_enter_spec.js | 80 + .../e2e/_old/markdown_widget_hotkeys_spec.js | 109 + cypress/e2e/_old/markdown_widget_link_spec.js | 64 + cypress/e2e/_old/markdown_widget_list_spec.js | 734 ++ .../e2e/_old/markdown_widget_marks_spec.js | 31 + .../e2e/_old/markdown_widget_quote_spec.js | 370 + .../media_library_spec_bitbucket_backend.js | 27 + ...rary_spec_bitbucket_backend_large_media.js | 28 + ..._git-gateway_github_backend_large_media.js | 28 + ..._git-gateway_gitlab_backend_large_media.js | 28 + ...dia_library_spec_github_backend_graphql.js | 34 + .../media_library_spec_github_backend_rest.js | 34 + .../_old/media_library_spec_gitlab_backend.js | 27 + .../media_library_spec_proxy_git_backend.js | 29 + .../_old/media_library_spec_test_backend.js | 21 + cypress/e2e/_old/search_suggestion_spec.js | 71 + .../simple_workflow_spec_bitbucket_backend.ts | 30 + ...orkflow_spec_git-gateway_github_backend.js | 31 + ...orkflow_spec_git-gateway_gitlab_backend.js | 31 + ...le_workflow_spec_github_backend_graphql.js | 37 + ...imple_workflow_spec_github_backend_rest.js | 37 + .../simple_workflow_spec_gitlab_backend.js | 30 + .../simple_workflow_spec_proxy_fs_backend.js | 32 + .../simple_workflow_spec_proxy_git_backend.js | 35 + cypress/e2e/common/editorial_workflow.js | 92 + .../common/editorial_workflow_migrations.js | 50 + cypress/e2e/common/entries.ts | 121 + cypress/e2e/common/i18n.js | 55 + .../common/i18n_editorial_workflow_spec.js | 53 + cypress/e2e/common/media_library.js | 162 + cypress/e2e/common/open_authoring.js | 75 + cypress/e2e/common/simple_workflow.ts | 85 + cypress/e2e/common/spec_utils.ts | 47 + .../editorial_workflow_test_backend.spec.ts | 358 + .../e2e/simple_workflow_test_backend.spec.ts | 28 + cypress/e2e/view_filters.spec.ts | 60 + cypress/e2e/view_groups.spec.ts | 58 + cypress/fixtures/media/decap.png | Bin 0 -> 2683 bytes cypress/fixtures/media/netlify.png | Bin 0 -> 3470 bytes cypress/interface.ts | 55 + cypress/plugins/bitbucket.js | 310 + cypress/plugins/common.js | 80 + cypress/plugins/gitGateway.js | 284 + cypress/plugins/github.js | 426 + cypress/plugins/gitlab.js | 275 + cypress/plugins/index.ts | 178 + cypress/plugins/proxy.js | 105 + cypress/plugins/testBackend.ts | 13 + cypress/run.mjs | 41 + ...e image from global media library.snap.png | Bin 0 -> 43699 bytes ...a -- can publish entry with image.snap.png | Bin 0 -> 91445 bytes ...edia -- can save entry with image.snap.png | Bin 0 -> 93001 bytes ...ad image from entry media library.snap.png | Bin 0 -> 91523 bytes ...d image from global media library.snap.png | Bin 0 -> 69356 bytes ...try image in global media library.snap.png | Bin 0 -> 43240 bytes ...try image in global media library.snap.png | Bin 0 -> 69952 bytes ...ublished entry image in grid view.snap.png | Bin 0 -> 108897 bytes ...e image from global media library.snap.png | Bin 0 -> 44376 bytes ...I -- can publish entry with image.snap.png | Bin 0 -> 92522 bytes ... API -- can save entry with image.snap.png | Bin 0 -> 94427 bytes ...ad image from entry media library.snap.png | Bin 0 -> 92416 bytes ...d image from global media library.snap.png | Bin 0 -> 70454 bytes ...try image in global media library.snap.png | Bin 0 -> 44560 bytes ...try image in global media library.snap.png | Bin 0 -> 71088 bytes ...ublished entry image in grid view.snap.png | Bin 0 -> 109821 bytes ...e image from global media library.snap.png | Bin 0 -> 44977 bytes ...I -- can publish entry with image.snap.png | Bin 0 -> 92556 bytes ... API -- can save entry with image.snap.png | Bin 0 -> 94441 bytes ...ad image from entry media library.snap.png | Bin 0 -> 92331 bytes ...d image from global media library.snap.png | Bin 0 -> 70454 bytes ...try image in global media library.snap.png | Bin 0 -> 44573 bytes ...try image in global media library.snap.png | Bin 0 -> 70878 bytes ...ublished entry image in grid view.snap.png | Bin 0 -> 109884 bytes ...e image from global media library.snap.png | Bin 0 -> 60106 bytes ...y -- can publish entry with image.snap.png | Bin 0 -> 95513 bytes ...rary -- can save entry with image.snap.png | Bin 0 -> 95725 bytes ...ad image from entry media library.snap.png | Bin 0 -> 78834 bytes ...d image from global media library.snap.png | Bin 0 -> 77908 bytes ...try image in global media library.snap.png | Bin 0 -> 56580 bytes ...try image in global media library.snap.png | Bin 0 -> 68438 bytes ...ublished entry image in grid view.snap.png | Bin 0 -> 123045 bytes cypress/support/commands.ts | 322 + cypress/support/e2e.ts | 27 + cypress/utils/README.md | 5 + cypress/utils/config.ts | 42 + cypress/utils/constants.ts | 39 + cypress/utils/regexp.ts | 3 + cypress/utils/steps.ts | 572 ++ lerna.json | 3 +- nx.json | 20 +- package.json | 33 +- packages/app/.eslintrc.js | 6 +- packages/app/babel.config.js | 6 - packages/app/package.json | 75 +- packages/app/tsconfig.json | 2 +- packages/app/webpack.config.js | 1 - packages/core/.eslintrc.js | 6 +- packages/core/babel.config.js | 6 - .../dev-test/backends/bitbucket/config.yml | 1193 ++- .../dev-test/backends/git-gateway/config.yml | 1195 ++- .../core/dev-test/backends/github/config.yml | 1197 ++- .../core/dev-test/backends/gitlab/config.yml | 1209 ++- .../core/dev-test/backends/gitlab/index.js | 53 +- .../_posts/2022-11-01-something/index.md | 1 - .../proxy/_posts/2022-11-01-test/index.md | 1 - .../proxy/_posts/2022-11-02-test/index.md | 1 - .../core/dev-test/backends/proxy/config.yml | 1194 ++- .../core/dev-test/backends/test/config.yml | 1598 ++++ .../core/dev-test/backends/test/index.html | 13 + packages/core/dev-test/config.yml | 131 +- packages/core/dev-test/data.js | 308 + packages/core/dev-test/index.html | 310 +- packages/core/dev-test/index.js | 192 +- packages/core/jest.config.js | 1 - packages/core/package.json | 184 +- packages/core/src/__mocks__/backend.ts | 6 +- packages/core/src/__mocks__/yaml.ts | 1 + packages/core/src/__tests__/backend.spec.ts | 1110 +++ packages/core/src/__tests__/testConfig.ts | 78 +- .../core/src/actions/__tests__/config.spec.ts | 1310 +++ packages/core/src/actions/auth.ts | 7 + packages/core/src/actions/config.ts | 427 +- .../core/src/actions/editorialWorkflow.ts | 657 ++ packages/core/src/actions/entries.ts | 181 +- packages/core/src/actions/globalUI.ts | 12 +- packages/core/src/actions/media.ts | 13 +- packages/core/src/actions/mediaLibrary.ts | 14 +- packages/core/src/actions/search.ts | 2 + packages/core/src/backend.ts | 589 +- packages/core/src/backends/bitbucket/API.ts | 377 +- .../backends/bitbucket/AuthenticationPage.tsx | 12 +- .../backends/bitbucket/__tests__/api.spec.ts | 43 + .../src/backends/bitbucket/implementation.ts | 129 +- .../git-gateway/AuthenticationPage.tsx | 11 +- .../src/backends/git-gateway/GitHubAPI.ts | 14 +- .../__tests__/AuthenticationPage.spec.tsx | 63 + .../git-gateway/__tests__/GitHubAPI.spec.ts | 110 + .../backends/git-gateway/implementation.tsx | 117 +- packages/core/src/backends/gitea/API.ts | 2 +- .../src/backends/gitea/AuthenticationPage.tsx | 12 +- .../gitea/__tests__/implementation.spec.ts | 4 +- .../src/backends/gitea/implementation.tsx | 45 +- packages/core/src/backends/github/API.ts | 728 +- .../backends/github/AuthenticationPage.css | 26 + .../backends/github/AuthenticationPage.tsx | 98 +- .../src/backends/github/__tests__/API.spec.ts | 61 +- .../github/__tests__/implementation.spec.ts | 44 +- .../src/backends/github/implementation.tsx | 252 +- packages/core/src/backends/github/types.ts | 1345 +++ packages/core/src/backends/gitlab/API.ts | 340 +- .../backends/gitlab/AuthenticationPage.tsx | 16 +- .../src/backends/gitlab/__tests__/API.spec.ts | 107 +- .../backends/gitlab/__tests__/gitlab.spec.ts | 633 ++ .../src/backends/gitlab/implementation.ts | 155 +- .../src/backends/proxy/AuthenticationPage.tsx | 9 +- .../core/src/backends/proxy/implementation.ts | 109 +- .../src/backends/test/AuthenticationPage.tsx | 8 +- .../test/__tests__/implementation.spec.ts | 272 + .../core/src/backends/test/implementation.ts | 200 +- packages/core/src/bootstrap.tsx | 16 +- packages/core/src/components/App.tsx | 93 +- .../core/src/components/ErrorBoundary.css | 9 +- .../core/src/components/ErrorBoundary.tsx | 14 +- packages/core/src/components/MainView.css | 6 +- packages/core/src/components/MainView.tsx | 11 +- packages/core/src/components/NotFoundPage.tsx | 12 +- .../src/components/collections/Collection.css | 4 +- .../collections/CollectionControls.tsx | 7 +- .../collections/CollectionHeader.tsx | 25 +- .../components/collections/CollectionPage.tsx | 6 +- .../collections/CollectionRoute.tsx | 13 +- .../collections/CollectionSearch.css | 52 +- .../collections/CollectionSearch.tsx | 19 +- .../components/collections/CollectionView.tsx | 211 +- .../components/collections/FilterControl.css | 24 +- .../components/collections/FilterControl.tsx | 27 +- .../components/collections/GroupControl.css | 23 +- .../components/collections/GroupControl.tsx | 19 +- .../collections/NestedCollection.css | 8 +- .../collections/NestedCollection.tsx | 16 +- .../components/collections/SortControl.css | 23 +- .../components/collections/SortControl.tsx | 22 +- .../collections/entries/Entries.classes.ts | 2 +- .../collections/entries/Entries.css | 32 +- .../collections/entries/Entries.tsx | 20 +- .../collections/entries/EntriesCollection.tsx | 35 +- .../collections/entries/EntriesSearch.tsx | 16 +- .../collections/entries/EntryCard.css | 55 +- .../collections/entries/EntryCard.tsx | 113 +- .../collections/entries/EntryListing.tsx | 79 +- .../entries/EntryListingCardGrid.tsx | 26 +- .../collections/entries/EntryListingGrid.tsx | 9 +- .../collections/entries/EntryListingTable.tsx | 36 +- .../collections/entries/EntryRow.tsx | 33 +- .../MobileCollectionControls.classes.ts | 1 - .../mobile/MobileCollectionControls.css | 11 +- .../mobile/MobileCollectionControls.tsx | 8 +- .../mobile/MobileCollectionControlsDrawer.tsx | 5 +- .../src/components/common/alert/Alert.css | 6 +- .../src/components/common/alert/Alert.tsx | 10 +- .../common/autocomplete/Autocomplete.css | 51 +- .../common/autocomplete/Autocomplete.tsx | 38 +- .../src/components/common/button/Button.tsx | 2 +- .../components/common/button/IconButton.css | 10 + .../components/common/button/IconButton.tsx | 36 +- .../common/button/useButtonClassNames.tsx | 3 + .../components/common/card/Card.classes.ts | 9 +- .../core/src/components/common/card/Card.css | 60 +- .../core/src/components/common/card/Card.tsx | 4 +- .../components/common/card/CardActionArea.tsx | 32 +- .../components/common/card/CardContent.tsx | 6 +- .../src/components/common/card/CardHeader.tsx | 4 +- .../src/components/common/card/CardMedia.tsx | 6 +- .../components/common/checkbox/Checkbox.css | 59 +- .../components/common/checkbox/Checkbox.tsx | 20 +- .../src/components/common/confirm/Confirm.css | 12 +- .../src/components/common/confirm/Confirm.tsx | 12 +- .../components/common/field/ErrorMessage.css | 3 +- .../components/common/field/ErrorMessage.tsx | 2 +- .../src/components/common/field/Field.css | 50 +- .../src/components/common/field/Field.tsx | 12 +- .../core/src/components/common/field/Hint.css | 21 - .../core/src/components/common/field/Hint.tsx | 5 - .../src/components/common/field/Label.css | 24 +- .../src/components/common/field/Label.tsx | 5 - .../src/components/common/image/Image.css | 6 +- .../src/components/common/image/Image.tsx | 6 +- .../core/src/components/common/link/Link.tsx | 7 +- .../core/src/components/common/menu/Menu.css | 19 +- .../core/src/components/common/menu/Menu.tsx | 7 +- .../src/components/common/menu/MenuGroup.css | 5 +- .../src/components/common/menu/MenuGroup.tsx | 4 +- .../components/common/menu/MenuItemButton.css | 76 +- .../components/common/menu/MenuItemButton.tsx | 14 +- .../components/common/menu/MenuItemLink.css | 16 +- .../components/common/menu/MenuItemLink.tsx | 4 +- .../src/components/common/modal/Modal.css | 9 +- .../core/src/components/common/pill/Pill.css | 57 +- .../core/src/components/common/pill/Pill.tsx | 23 +- .../common/progress/CircularProgress.css | 8 +- .../src/components/common/progress/Loader.css | 6 +- .../src/components/common/progress/Loader.tsx | 4 +- .../src/components/common/select/Option.css | 13 +- .../src/components/common/select/Select.css | 27 +- .../src/components/common/select/Select.tsx | 11 +- .../src/components/common/switch/Switch.css | 51 +- .../src/components/common/table/Table.css | 45 +- .../src/components/common/table/Table.tsx | 4 +- .../src/components/common/table/TableCell.tsx | 4 +- .../common/table/TableHeaderCell.tsx | 4 +- .../src/components/common/table/TableRow.tsx | 4 +- .../components/common/text-field/TextArea.css | 12 +- .../common/text-field/TextField.css | 36 +- .../common/view-style/ViewStyleControl.css | 12 - .../common/view-style/ViewStyleControl.tsx | 26 +- .../components/common/widget/PreviewHOC.tsx | 5 +- .../common/widget/useWidgetsFor.tsx | 114 +- .../components/common/widget/widgetFor.css | 3 +- .../components/common/widget/widgetFor.tsx | 40 +- .../src/components/entry-editor/Editor.tsx | 468 +- .../entry-editor/EditorInterface.css | 141 +- .../entry-editor/EditorInterface.tsx | 314 +- .../components/entry-editor/EditorRoute.tsx | 11 +- .../components/entry-editor/EditorToolbar.css | 17 +- .../components/entry-editor/EditorToolbar.tsx | 390 +- .../EditorWorkflowToolbarButtons.css | 8 + .../EditorWorkflowToolbarButtons.tsx | 151 + .../editor-control-pane/EditorControl.tsx | 46 +- .../editor-control-pane/EditorControlPane.css | 5 +- .../editor-control-pane/EditorControlPane.tsx | 30 +- .../editor-control-pane/LocaleDropdown.css | 11 +- .../editor-control-pane/LocaleDropdown.tsx | 10 +- .../editor-preview-pane/EditorPreview.tsx | 5 +- .../EditorPreviewContent.tsx | 2 +- .../editor-preview-pane/EditorPreviewPane.css | 16 +- .../editor-preview-pane/EditorPreviewPane.tsx | 69 +- .../PreviewFrameContent.css | 3 +- .../PreviewFrameContent.tsx | 42 +- .../widgets/Unknown/UnknownControl.css | 6 +- .../widgets/Unknown/UnknownControl.tsx | 11 +- .../widgets/Unknown/UnknownPreview.tsx | 11 +- .../src/components/login/GoBackButton.tsx | 7 +- packages/core/src/components/login/Login.css | 18 +- packages/core/src/components/login/Login.tsx | 39 +- .../media-library/MediaLibraryModal.css | 22 +- .../media-library/MediaLibraryModal.tsx | 16 +- .../components/media-library/MediaPage.tsx | 9 +- .../common/CopyToClipBoardButton.tsx | 14 +- .../common/CurrentMediaDetails.tsx | 4 +- .../media-library/common/EmptyMessage.tsx | 4 +- .../media-library/common/FileUploadButton.tsx | 13 +- .../common/FolderCreationDialog.css | 6 +- .../common/FolderCreationDialog.tsx | 25 +- .../common/InlineEditTextField.css | 39 +- .../common/MediaLibrary.classes.ts | 1 - .../media-library/common/MediaLibrary.css | 86 +- .../media-library/common/MediaLibrary.tsx | 39 +- .../media-library/common/MediaLibraryCard.css | 69 +- .../media-library/common/MediaLibraryCard.tsx | 26 +- .../common/MediaLibraryCardGrid.tsx | 14 +- .../common/MediaLibrarySearch.tsx | 6 +- .../components/navbar/BottomNavigation.css | 4 +- .../components/navbar/BottomNavigation.tsx | 37 +- .../src/components/navbar/Breadcrumbs.css | 30 +- .../src/components/navbar/Breadcrumbs.tsx | 13 +- .../core/src/components/navbar/NavLink.css | 23 +- .../core/src/components/navbar/NavLink.tsx | 27 +- .../core/src/components/navbar/Navbar.css | 31 +- .../core/src/components/navbar/Navbar.tsx | 39 +- .../components/navbar/NavigationDrawer.tsx | 6 +- .../src/components/navbar/QuickCreate.tsx | 16 +- .../components/navbar/SettingsDropdown.css | 6 - .../components/navbar/SettingsDropdown.tsx | 120 +- .../src/components/navbar/Sidebar.classes.ts | 2 +- .../core/src/components/navbar/Sidebar.css | 8 +- .../core/src/components/navbar/Sidebar.tsx | 2 +- .../src/components/navbar/SidebarContent.tsx | 88 +- .../src/components/snackbar/SnackbarAlert.css | 49 +- .../src/components/snackbar/SnackbarAlert.tsx | 103 +- .../src/components/snackbar/Snackbars.tsx | 24 +- .../src/components/theme/ThemeManager.tsx | 118 + .../components/theme/components/ThemeCard.css | 222 + .../components/theme/components/ThemeCard.tsx | 134 + .../theme/components/ThemeSelectorDialog.css | 27 + .../theme/components/ThemeSelectorDialog.tsx | 64 + .../src/components/theme/defaultThemes.ts | 113 + .../src/components/theme/hooks/useTheme.ts | 19 + .../src/components/theme/hooks/useThemes.ts | 45 + .../src/components/theme/util/createTheme.ts | 92 + .../workflow/ActiveWorkflowCard.tsx | 46 + .../src/components/workflow/Dashboard.css | 73 + .../src/components/workflow/Dashboard.tsx | 174 + .../src/components/workflow/WorkflowCard.css | 28 + .../src/components/workflow/WorkflowCard.tsx | 191 + .../components/workflow/WorkflowColumn.css | 50 + .../components/workflow/WorkflowColumn.tsx | 69 + .../workflow/WorkflowStatusPill.tsx | 38 + .../hooks/useWorkflowBoardSections.ts | 61 + .../hooks/useWorkflowEntriesByCollection.ts | 18 + .../components/workflow/util/workflow.util.ts | 6 + packages/core/src/constants.ts | 28 + packages/core/src/constants/configSchema.tsx | 149 +- packages/core/src/constants/enums.ts | 9 + .../core/src/constants/fieldInference.tsx | 6 +- packages/core/src/constants/publishModes.ts | 30 + packages/core/src/constants/views.ts | 2 +- packages/core/src/formats/FileFormatter.ts | 12 +- packages/core/src/formats/JsonFormatter.ts | 2 + packages/core/src/formats/TomlFormatter.ts | 2 + packages/core/src/formats/YamlFormatter.ts | 43 +- .../formats/__tests__/YamlFormatter.spec.ts | 140 +- packages/core/src/formats/formats.ts | 4 +- packages/core/src/formats/frontmatter.ts | 18 +- packages/core/src/index.ts | 3 + packages/core/src/interface.ts | 370 +- .../core/src/lib/__tests__/registry.spec.ts | 1401 +++ packages/core/src/lib/auth/implicit-oauth.ts | 13 +- packages/core/src/lib/auth/netlify-auth.ts | 2 +- packages/core/src/lib/auth/pkce-oauth.ts | 13 +- packages/core/src/lib/formatters.ts | 85 +- .../hooks/__tests__/useMediaFiles.spec.tsx | 12 +- packages/core/src/lib/hooks/index.ts | 1 + packages/core/src/lib/hooks/useBreadcrumbs.ts | 24 +- .../core/src/lib/hooks/useCurrentBackend.ts | 19 + packages/core/src/lib/hooks/useDefaultPath.ts | 13 + packages/core/src/lib/hooks/useEntries.ts | 25 +- .../core/src/lib/hooks/useEntryCallback.ts | 14 +- packages/core/src/lib/hooks/useFilters.ts | 6 +- .../core/src/lib/hooks/useFolderSupport.ts | 13 +- packages/core/src/lib/hooks/useGroups.ts | 11 +- .../core/src/lib/hooks/useHasChildErrors.ts | 2 +- .../core/src/lib/hooks/useIsMediaAsset.ts | 24 +- packages/core/src/lib/hooks/useMediaAsset.ts | 42 +- packages/core/src/lib/hooks/useMediaFiles.ts | 12 +- packages/core/src/lib/hooks/useMediaInsert.ts | 9 +- .../core/src/lib/hooks/useMediaPersist.ts | 2 +- packages/core/src/lib/hooks/useNewEntryUrl.ts | 4 +- .../core/src/lib/hooks/usePublishedEntries.ts | 7 +- .../core/src/lib/hooks/useRefWithCallback.ts | 18 + .../src/lib/hooks/useUnpublishedEntries.ts | 16 + packages/core/src/lib/i18n.ts | 266 +- packages/core/src/lib/registry.ts | 570 +- packages/core/src/lib/util/API.ts | 41 +- packages/core/src/lib/util/APIUtils.ts | 34 + packages/core/src/lib/util/Cursor.ts | 15 +- .../src/lib/util/EditorialWorkflowError.ts | 12 + .../src/lib/util/__tests__/field.util.spec.ts | 273 +- .../lib/util/__tests__/filter.util.spec.ts | 24 +- .../src/lib/util/__tests__/media.util.spec.ts | 98 +- .../lib/util/__tests__/nested.util.spec.ts | 22 +- packages/core/src/lib/util/backendUtil.ts | 1 + packages/core/src/lib/util/collection.util.ts | 212 +- packages/core/src/lib/util/entry.util.ts | 85 +- .../core/src/lib/util/events/DataEvent.ts | 2 +- packages/core/src/lib/util/field.util.ts | 16 +- packages/core/src/lib/util/filter.util.ts | 60 +- packages/core/src/lib/util/implementation.ts | 82 +- packages/core/src/lib/util/index.ts | 5 +- packages/core/src/lib/util/media.util.ts | 40 +- packages/core/src/lib/util/nested.util.ts | 54 +- packages/core/src/lib/util/search.util.ts | 2 +- packages/core/src/lib/util/sort.util.ts | 4 +- packages/core/src/lib/util/theming.util.ts | 11 +- packages/core/src/lib/util/unsentRequest.ts | 11 +- packages/core/src/lib/util/validation.util.ts | 2 +- .../core/src/lib/widgets/stringTemplate.ts | 72 +- packages/core/src/live/Data.tsx | 2 +- packages/core/src/live/useData.tsx | 2 +- packages/core/src/locales/bg/index.ts | 120 +- packages/core/src/locales/ca/index.ts | 115 +- packages/core/src/locales/cs/index.ts | 115 +- packages/core/src/locales/da/index.ts | 157 +- packages/core/src/locales/de/index.ts | 149 +- packages/core/src/locales/en/index.ts | 119 +- packages/core/src/locales/es/index.ts | 115 +- packages/core/src/locales/fa/index.ts | 302 + packages/core/src/locales/fr/index.ts | 117 +- packages/core/src/locales/gr/index.ts | 116 +- packages/core/src/locales/he/index.ts | 116 +- packages/core/src/locales/hr/index.ts | 115 +- packages/core/src/locales/hu/index.ts | 115 +- packages/core/src/locales/it/index.ts | 115 +- packages/core/src/locales/ja/index.ts | 115 +- packages/core/src/locales/ko/index.ts | 188 +- packages/core/src/locales/lt/index.ts | 115 +- packages/core/src/locales/nb_no/index.ts | 113 +- packages/core/src/locales/nl/index.ts | 115 +- packages/core/src/locales/nn_no/index.ts | 113 +- packages/core/src/locales/pl/index.ts | 118 +- packages/core/src/locales/pt/index.ts | 117 +- packages/core/src/locales/ro/index.ts | 119 +- packages/core/src/locales/ru/index.ts | 142 +- packages/core/src/locales/sl/index.ts | 117 +- packages/core/src/locales/sv/index.ts | 115 +- packages/core/src/locales/th/index.ts | 114 +- packages/core/src/locales/tr/index.ts | 118 +- packages/core/src/locales/ua/index.ts | 299 + packages/core/src/locales/uk/index.ts | 114 +- packages/core/src/locales/vi/index.ts | 114 +- packages/core/src/locales/zh_Hans/index.ts | 116 +- packages/core/src/locales/zh_Hant/index.ts | 112 +- .../src/reducers/__tests__/entryDraft.spec.ts | 4 +- packages/core/src/reducers/collections.ts | 22 +- packages/core/src/reducers/config.ts | 10 +- .../core/src/reducers/editorialWorkflow.ts | 216 + packages/core/src/reducers/entries.ts | 60 +- packages/core/src/reducers/entryDraft.ts | 76 +- packages/core/src/reducers/globalUI.ts | 32 +- packages/core/src/reducers/index.ts | 2 + packages/core/src/reducers/mediaLibrary.ts | 8 +- .../src/reducers/selectors/collections.ts | 20 +- .../core/src/reducers/selectors/config.ts | 53 +- .../reducers/selectors/editorialWorkflow.ts | 40 + .../core/src/reducers/selectors/entries.ts | 193 +- .../core/src/reducers/selectors/entryDraft.ts | 31 +- .../core/src/reducers/selectors/globalUI.ts | 4 + .../src/reducers/selectors/mediaLibrary.ts | 50 +- .../core/src/reducers/selectors/medias.ts | 10 +- .../core/src/reducers/selectors/scroll.ts | 6 + .../core/src/styles/datetime/calendar.css | 143 - packages/core/src/styles/datetime/clock.css | 62 - .../core/src/styles/datetime/datetime.css | 130 - packages/core/src/styles/main.css | 524 +- packages/core/src/types/global.d.ts | 3 + packages/core/src/types/svg.d.ts | 2 +- packages/core/src/valueObjects/createEntry.ts | 9 +- .../src/widgets/boolean/BooleanControl.css | 23 + .../src/widgets/boolean/BooleanControl.tsx | 29 +- .../boolean/__tests__/BooleanControl.spec.ts | 2 +- packages/core/src/widgets/boolean/index.ts | 2 +- packages/core/src/widgets/boolean/schema.ts | 2 + .../core/src/widgets/code/CodeControl.css | 33 +- .../core/src/widgets/code/CodeControl.tsx | 17 +- .../core/src/widgets/code/CodePreview.tsx | 2 +- .../core/src/widgets/code/SettingsButton.css | 5 - .../core/src/widgets/code/SettingsButton.tsx | 15 +- .../core/src/widgets/code/SettingsPane.css | 13 +- .../core/src/widgets/code/SettingsPane.tsx | 7 +- .../core/src/widgets/code/data/languages.ts | 2 +- packages/core/src/widgets/code/index.ts | 2 +- .../widgets/code/scripts/process-languages.ts | 4 +- .../src/widgets/colorstring/ColorControl.css | 12 +- .../src/widgets/colorstring/ColorControl.tsx | 11 +- .../src/widgets/colorstring/ColorPreview.tsx | 2 +- .../__tests__/ColorControl.spec.ts | 4 +- .../core/src/widgets/colorstring/index.ts | 2 +- .../core/src/widgets/colorstring/validator.ts | 2 +- .../src/widgets/datetime/DateTimeControl.css | 44 +- .../src/widgets/datetime/DateTimeControl.tsx | 256 +- .../src/widgets/datetime/DateTimePreview.tsx | 2 +- .../__tests__/DateTimeControl.spec.ts | 238 +- .../__tests__/getDefaultValue.spec.ts | 10 +- .../widgets/datetime/components/NowButton.css | 5 +- .../widgets/datetime/components/NowButton.tsx | 10 +- .../core/src/widgets/datetime/constants.ts | 4 +- .../src/widgets/datetime/datetime.util.ts | 89 + .../src/widgets/datetime/getDefaultValue.ts | 2 +- packages/core/src/widgets/datetime/index.ts | 2 +- .../src/widgets/file/FileImageControl.css | 9 +- .../core/src/widgets/file/FilePreview.tsx | 6 +- .../widgets/file/__test__/FileControl.spec.ts | 33 +- .../widgets/file/components/SortableImage.css | 49 +- .../widgets/file/components/SortableImage.tsx | 21 +- .../widgets/file/components/SortableLink.css | 18 +- .../widgets/file/components/SortableLink.tsx | 18 +- packages/core/src/widgets/file/index.ts | 2 +- .../core/src/widgets/file/withFileControl.tsx | 12 +- .../core/src/widgets/image/ImagePreview.tsx | 6 +- packages/core/src/widgets/image/index.ts | 2 +- .../src/widgets/keyvalue/KeyValueControl.css | 5 - .../src/widgets/keyvalue/KeyValueControl.tsx | 12 +- .../src/widgets/keyvalue/KeyValuePreview.tsx | 2 +- .../__tests__/KeyValueControl.spec.ts | 4 +- .../keyvalue/__tests__/converters.spec.ts | 2 +- .../keyvalue/__tests__/validator.spec.ts | 2 +- .../core/src/widgets/keyvalue/converters.ts | 13 +- packages/core/src/widgets/keyvalue/index.ts | 2 +- .../core/src/widgets/keyvalue/validator.ts | 2 +- .../src/widgets/list/DelimitedListControl.tsx | 2 +- .../core/src/widgets/list/ListControl.css | 49 +- .../core/src/widgets/list/ListControl.tsx | 5 +- .../core/src/widgets/list/ListPreview.tsx | 6 +- .../list/__tests__/ListControl.spec.tsx | 4 +- .../list/components/ListFieldWrapper.tsx | 8 +- .../src/widgets/list/components/ListItem.tsx | 21 +- .../list/components/ListItemWrapper.css | 63 +- .../list/components/ListItemWrapper.tsx | 32 +- packages/core/src/widgets/list/index.ts | 2 +- .../core/src/widgets/list/typedListHelpers.ts | 2 +- packages/core/src/widgets/map/MapPreview.tsx | 2 +- packages/core/src/widgets/map/index.ts | 2 +- .../core/src/widgets/map/withMapControl.tsx | 2 +- .../src/widgets/markdown/MarkdownPreview.tsx | 2 +- packages/core/src/widgets/markdown/index.ts | 2 +- .../mdx/withShortcodeMdxComponent.tsx | 2 +- .../widgets/markdown/plate/PlateEditor.tsx | 13 +- .../balloon-toolbar/BalloonToolbar.css | 10 +- .../balloon-toolbar/BalloonToolbar.tsx | 8 +- .../__tests__/BalloonToolbar.spec.tsx | 20 +- .../buttons/BlockquoteToolbarButton.tsx | 8 +- .../components/buttons/BoldToolbarButton.tsx | 6 +- .../buttons/CodeBlockToolbarButtons.tsx | 8 +- .../components/buttons/CodeToolbarButton.tsx | 6 +- .../buttons/DecreaseIndentToolbarButton.tsx | 6 +- .../buttons/DeleteColumnToolbarButton.tsx | 6 +- .../buttons/DeleteRowToolbarButton.tsx | 6 +- .../buttons/DeleteTableToolbarButton.tsx | 6 +- .../components/buttons/FontTypeSelect.css | 36 +- .../components/buttons/FontTypeSelect.tsx | 19 +- .../buttons/IncreaseIndentToolbarButton.tsx | 6 +- .../buttons/InsertColumnToolbarButton.tsx | 6 +- .../buttons/InsertImageToolbarButton.tsx | 12 +- .../buttons/InsertLinkToolbarButton.tsx | 12 +- .../buttons/InsertRowToolbarButton.tsx | 6 +- .../buttons/InsertTableToolbarButton.tsx | 8 +- .../buttons/ItalicToolbarButton.tsx | 6 +- .../buttons/OrderedListToolbarButton.tsx | 6 +- .../buttons/ShortcodeToolbarButton.tsx | 2 + .../buttons/StrikethroughToolbarButton.tsx | 6 +- .../buttons/UnorderedListToolbarButton.tsx | 6 +- .../buttons/common/ToolbarButton.css | 6 +- .../buttons/common/ToolbarButton.tsx | 5 +- .../components/color-picker/ColorButton.css | 11 +- .../components/color-picker/ColorButton.tsx | 1 + .../plate/components/common/MediaPopover.css | 16 +- .../plate/components/common/MediaPopover.tsx | 12 +- .../nodes/blockquote/BlockquoteElement.css | 3 +- .../nodes/code-block/CodeBlockElement.css | 8 +- .../nodes/code-block/CodeBlockElement.tsx | 4 +- .../nodes/code-block/CodeBlockFrame.tsx | 12 +- .../plate/components/nodes/code/Code.css | 7 +- .../nodes/image/withImageElement.tsx | 6 +- .../components/nodes/link/LinkElement.css | 5 +- .../components/nodes/link/withLinkElement.tsx | 4 +- .../nodes/shortcode/withShortcodeElement.tsx | 13 +- .../plate/components/nodes/table/Table.css | 32 +- .../plate/components/toolbar/Toolbar.css | 7 +- .../plate/components/toolbar/Toolbar.tsx | 4 +- .../plate/hooks/useToolbarButtons.tsx | 16 +- .../slate/deserializeMarkdown.ts | 2 +- .../slate/processShortcodeConfig.ts | 2 +- .../serialization/slate/toSlatePlugin.ts | 4 +- .../tests-util/serializationTests.util.tsx | 2 +- .../widgets/markdown/withMarkdownControl.tsx | 6 +- .../core/src/widgets/number/NumberControl.css | 35 + .../core/src/widgets/number/NumberControl.tsx | 22 +- .../core/src/widgets/number/NumberPreview.tsx | 2 +- .../number/__tests__/NumberControl.spec.ts | 6 +- .../number/__tests__/validator.spec.ts | 2 +- packages/core/src/widgets/number/index.ts | 2 +- packages/core/src/widgets/number/schema.ts | 2 + packages/core/src/widgets/number/validator.ts | 2 +- .../core/src/widgets/object/ObjectControl.css | 65 +- .../core/src/widgets/object/ObjectControl.tsx | 12 +- .../src/widgets/object/ObjectFieldWrapper.tsx | 7 +- .../core/src/widgets/object/ObjectPreview.tsx | 2 +- .../object/__tests__/ObjectControl.spec.tsx | 4 +- packages/core/src/widgets/object/index.ts | 2 +- .../src/widgets/relation/RelationControl.tsx | 195 +- .../src/widgets/relation/RelationPreview.tsx | 2 +- .../src/widgets/relation/RelationSummary.tsx | 141 + .../__tests__/RelationControl.spec.ts | 46 +- .../relation/__tests__/validator.spec.ts | 2 +- packages/core/src/widgets/relation/index.ts | 4 +- packages/core/src/widgets/relation/types.ts | 7 + packages/core/src/widgets/relation/util.ts | 136 + .../core/src/widgets/relation/validator.ts | 2 +- .../core/src/widgets/select/SelectControl.tsx | 17 +- .../core/src/widgets/select/SelectPreview.tsx | 4 +- .../select/__tests__/SelectControl.spec.ts | 8 +- .../select/__tests__/validator.spec.ts | 2 +- packages/core/src/widgets/select/index.ts | 2 +- packages/core/src/widgets/select/validator.ts | 2 +- .../core/src/widgets/string/StringControl.css | 35 + .../core/src/widgets/string/StringControl.tsx | 22 +- .../core/src/widgets/string/StringPreview.tsx | 4 +- .../string/__tests__/StringControl.spec.ts | 4 +- packages/core/src/widgets/string/index.ts | 4 +- packages/core/src/widgets/string/schema.ts | 2 + .../core/src/widgets/text/TextControl.tsx | 4 +- .../core/src/widgets/text/TextPreview.tsx | 4 +- .../text/__tests__/TextControl.spec.ts | 4 +- packages/core/src/widgets/text/index.ts | 4 +- .../core/src/widgets/uuid/UUIDControl.css | 5 - .../core/src/widgets/uuid/UUIDControl.tsx | 12 +- .../core/src/widgets/uuid/UUIDPreview.tsx | 2 +- .../uuid/__tests__/UUIDControl.spec.ts | 4 +- packages/core/src/widgets/uuid/index.ts | 2 +- packages/core/test/data/collections.mock.ts | 82 +- packages/core/test/data/config.mock.ts | 13 +- packages/core/test/data/entry.mock.ts | 38 +- packages/core/test/data/fields.mock.ts | 9 +- packages/core/test/data/widgets.mock.ts | 12 +- .../core/test/harnesses/widget.harness.tsx | 37 +- packages/core/test/mockFetch.ts | 160 +- packages/core/test/setupEnv.js | 17 + packages/core/tsconfig.base.json | 2 +- packages/core/webpack.config.js | 1 - packages/demo/.prettierignore | 4 + packages/demo/.prettierrc | 6 + packages/demo/index.html | 317 - packages/demo/package.json | 42 +- .../public/editor-friendly-user-interface.svg | 1 + packages/demo/public/editorial/config.yml | 1551 ++++ .../intuitive-workflow-for-content-teams.svg | 1 + packages/demo/public/{ => simple}/config.yml | 86 +- packages/demo/src/cms.js | 170 + packages/demo/src/cms.jsx | 300 - packages/demo/src/data.js | 302 + packages/demo/src/editorial/index.html | 14 + packages/demo/src/index.html | 201 + packages/demo/src/simple/index.html | 14 + packages/demo/vite.config.ts | 45 +- packages/docs/.eslintrc.js | 1 - packages/docs/content/community.json | 2 +- .../docs/add-to-your-site-bundling.mdx | 47 +- .../content/docs/add-to-your-site-cdn.mdx | 55 +- .../docs/content/docs/add-to-your-site.mdx | 2 +- .../docs/content/docs/additional-links.mdx | 2 +- .../docs/content/docs/backends-overview.mdx | 19 +- packages/docs/content/docs/beta-features.mdx | 187 +- packages/docs/content/docs/cms-events.mdx | 288 + .../docs/content/docs/collection-overview.mdx | 197 +- .../docs/content/docs/collection-types.mdx | 6 +- .../content/docs/configuration-options.mdx | 55 +- .../docs/content/docs/contributor-guide.mdx | 6 +- packages/docs/content/docs/custom-icons.mdx | 2 +- .../docs/content/docs/custom-previews.mdx | 15 +- packages/docs/content/docs/custom-theme.mdx | 471 + packages/docs/content/docs/custom-widgets.mdx | 4 +- .../content/docs/customization-overview.mdx | 2 +- .../content/docs/decap-migration-guide.mdx | 190 +- .../docs/content/docs/editorial-workflow.mdx | 35 + packages/docs/content/docs/gitea-backend.mdx | 5 +- packages/docs/content/docs/i18n-support.mdx | 5 +- packages/docs/content/docs/local-backend.mdx | 2 +- .../docs/content/docs/migration-guide-v3.mdx | 89 - .../docs/content/docs/migration-guide-v4.mdx | 263 + packages/docs/content/docs/open-authoring.mdx | 107 + .../content/docs/start-with-a-template.mdx | 6 +- packages/docs/content/docs/test-backend.mdx | 2 +- .../docs/content/docs/updating-your-cms.mdx | 12 +- packages/docs/content/docs/widget-boolean.mdx | 8 +- .../docs/content/docs/widget-datetime.mdx | 6 +- packages/docs/content/docs/widget-list.mdx | 2 +- .../docs/content/docs/widget-markdown.mdx | 2 +- packages/docs/content/docs/widget-number.mdx | 2 + packages/docs/content/docs/widget-object.mdx | 14 +- .../docs/content/docs/widget-relation.mdx | 2 +- packages/docs/content/docs/widget-string.mdx | 2 + packages/docs/content/docs/widgets.mdx | 2 +- .../docs/content/docs/writing-style-guide.mdx | 6 +- packages/docs/content/homepage.json | 10 +- packages/docs/content/menu.json | 4 + packages/docs/content/releases.json | 91 + packages/docs/next.config.js | 9 +- packages/docs/package.json | 53 +- .../img/editor-friendly-user-interface.svg | 2 +- .../intuitive-workflow-for-content-teams.svg | 1 + .../table_of_contents/DocsTableOfContents.tsx | 16 +- .../docs/src/components/layout/Header.tsx | 42 +- packages/docs/src/interface.ts | 3 +- packages/docs/src/lib/docs.ts | 27 +- packages/docs/src/pages/index.tsx | 9 +- packages/docs/src/pages/releases.tsx | 94 +- packages/tools/package.json | 24 +- packages/tools/src/localeSync.ts | 16 +- tailwind.base.config.js | 1 + tsconfig.json | 63 + yarn.lock | 7652 ++++++++++------- 732 files changed, 48477 insertions(+), 10886 deletions(-) create mode 100644 .github/workflows/cypress.yml create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 cypress.config.ts create mode 100644 cypress/README.md create mode 100644 cypress/cypress.d.ts create mode 100644 cypress/e2e/_old/editorial_workflow_spec_bitbucket_backend.js create mode 100644 cypress/e2e/_old/editorial_workflow_spec_git-gateway_github_backend.js create mode 100644 cypress/e2e/_old/editorial_workflow_spec_git-gateway_gitlab_backend.js create mode 100644 cypress/e2e/_old/editorial_workflow_spec_github_backend_graphql.js create mode 100644 cypress/e2e/_old/editorial_workflow_spec_github_backend_graphql_open_authoring.js create mode 100644 cypress/e2e/_old/editorial_workflow_spec_github_backend_rest.js create mode 100644 cypress/e2e/_old/editorial_workflow_spec_github_backend_rest_open_authoring.js create mode 100644 cypress/e2e/_old/editorial_workflow_spec_gitlab_backend.js create mode 100644 cypress/e2e/_old/editorial_workflow_spec_proxy_git_backend.js create mode 100644 cypress/e2e/_old/field_validations_spec.js create mode 100644 cypress/e2e/_old/i18n_editorial_workflow_spec_test_backend.js create mode 100644 cypress/e2e/_old/i18n_simple_workflow_spec_proxy_fs_backend.js create mode 100644 cypress/e2e/_old/markdown_widget_backspace_spec.js create mode 100644 cypress/e2e/_old/markdown_widget_code_block_spec.js create mode 100644 cypress/e2e/_old/markdown_widget_enter_spec.js create mode 100644 cypress/e2e/_old/markdown_widget_hotkeys_spec.js create mode 100644 cypress/e2e/_old/markdown_widget_link_spec.js create mode 100644 cypress/e2e/_old/markdown_widget_list_spec.js create mode 100644 cypress/e2e/_old/markdown_widget_marks_spec.js create mode 100644 cypress/e2e/_old/markdown_widget_quote_spec.js create mode 100644 cypress/e2e/_old/media_library_spec_bitbucket_backend.js create mode 100644 cypress/e2e/_old/media_library_spec_bitbucket_backend_large_media.js create mode 100644 cypress/e2e/_old/media_library_spec_git-gateway_github_backend_large_media.js create mode 100644 cypress/e2e/_old/media_library_spec_git-gateway_gitlab_backend_large_media.js create mode 100644 cypress/e2e/_old/media_library_spec_github_backend_graphql.js create mode 100644 cypress/e2e/_old/media_library_spec_github_backend_rest.js create mode 100644 cypress/e2e/_old/media_library_spec_gitlab_backend.js create mode 100644 cypress/e2e/_old/media_library_spec_proxy_git_backend.js create mode 100644 cypress/e2e/_old/media_library_spec_test_backend.js create mode 100644 cypress/e2e/_old/search_suggestion_spec.js create mode 100644 cypress/e2e/_old/simple_workflow_spec_bitbucket_backend.ts create mode 100644 cypress/e2e/_old/simple_workflow_spec_git-gateway_github_backend.js create mode 100644 cypress/e2e/_old/simple_workflow_spec_git-gateway_gitlab_backend.js create mode 100644 cypress/e2e/_old/simple_workflow_spec_github_backend_graphql.js create mode 100644 cypress/e2e/_old/simple_workflow_spec_github_backend_rest.js create mode 100644 cypress/e2e/_old/simple_workflow_spec_gitlab_backend.js create mode 100644 cypress/e2e/_old/simple_workflow_spec_proxy_fs_backend.js create mode 100644 cypress/e2e/_old/simple_workflow_spec_proxy_git_backend.js create mode 100644 cypress/e2e/common/editorial_workflow.js create mode 100644 cypress/e2e/common/editorial_workflow_migrations.js create mode 100644 cypress/e2e/common/entries.ts create mode 100644 cypress/e2e/common/i18n.js create mode 100644 cypress/e2e/common/i18n_editorial_workflow_spec.js create mode 100644 cypress/e2e/common/media_library.js create mode 100644 cypress/e2e/common/open_authoring.js create mode 100644 cypress/e2e/common/simple_workflow.ts create mode 100644 cypress/e2e/common/spec_utils.ts create mode 100644 cypress/e2e/editorial_workflow_test_backend.spec.ts create mode 100644 cypress/e2e/simple_workflow_test_backend.spec.ts create mode 100644 cypress/e2e/view_filters.spec.ts create mode 100644 cypress/e2e/view_groups.spec.ts create mode 100644 cypress/fixtures/media/decap.png create mode 100644 cypress/fixtures/media/netlify.png create mode 100644 cypress/interface.ts create mode 100644 cypress/plugins/bitbucket.js create mode 100644 cypress/plugins/common.js create mode 100644 cypress/plugins/gitGateway.js create mode 100644 cypress/plugins/github.js create mode 100644 cypress/plugins/gitlab.js create mode 100644 cypress/plugins/index.ts create mode 100644 cypress/plugins/proxy.js create mode 100644 cypress/plugins/testBackend.ts create mode 100644 cypress/run.mjs create mode 100644 cypress/snapshots/media_library_spec_git-gateway_backend_large_media.js/Git Gateway Backend Media Library - Large Media -- can delete image from global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_git-gateway_backend_large_media.js/Git Gateway Backend Media Library - Large Media -- can publish entry with image.snap.png create mode 100644 cypress/snapshots/media_library_spec_git-gateway_backend_large_media.js/Git Gateway Backend Media Library - Large Media -- can save entry with image.snap.png create mode 100644 cypress/snapshots/media_library_spec_git-gateway_backend_large_media.js/Git Gateway Backend Media Library - Large Media -- can upload image from entry media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_git-gateway_backend_large_media.js/Git Gateway Backend Media Library - Large Media -- can upload image from global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_git-gateway_backend_large_media.js/Git Gateway Backend Media Library - Large Media -- should not show draft entry image in global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_git-gateway_backend_large_media.js/Git Gateway Backend Media Library - Large Media -- should show published entry image in global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_git-gateway_backend_large_media.js/Git Gateway Backend Media Library - Large Media -- should show published entry image in grid view.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_graphql.js/GitHub Backend Media Library - GraphQL API -- can delete image from global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_graphql.js/GitHub Backend Media Library - GraphQL API -- can publish entry with image.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_graphql.js/GitHub Backend Media Library - GraphQL API -- can save entry with image.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_graphql.js/GitHub Backend Media Library - GraphQL API -- can upload image from entry media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_graphql.js/GitHub Backend Media Library - GraphQL API -- can upload image from global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_graphql.js/GitHub Backend Media Library - GraphQL API -- should not show draft entry image in global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_graphql.js/GitHub Backend Media Library - GraphQL API -- should show published entry image in global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_graphql.js/GitHub Backend Media Library - GraphQL API -- should show published entry image in grid view.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_rest.js/GitHub Backend Media Library - REST API -- can delete image from global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_rest.js/GitHub Backend Media Library - REST API -- can publish entry with image.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_rest.js/GitHub Backend Media Library - REST API -- can save entry with image.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_rest.js/GitHub Backend Media Library - REST API -- can upload image from entry media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_rest.js/GitHub Backend Media Library - REST API -- can upload image from global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_rest.js/GitHub Backend Media Library - REST API -- should not show draft entry image in global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_rest.js/GitHub Backend Media Library - REST API -- should show published entry image in global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_github_backend_rest.js/GitHub Backend Media Library - REST API -- should show published entry image in grid view.snap.png create mode 100644 cypress/snapshots/media_library_spec_test_backend.js/Test Backend Media Library -- can delete image from global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_test_backend.js/Test Backend Media Library -- can publish entry with image.snap.png create mode 100644 cypress/snapshots/media_library_spec_test_backend.js/Test Backend Media Library -- can save entry with image.snap.png create mode 100644 cypress/snapshots/media_library_spec_test_backend.js/Test Backend Media Library -- can upload image from entry media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_test_backend.js/Test Backend Media Library -- can upload image from global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_test_backend.js/Test Backend Media Library -- should not show draft entry image in global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_test_backend.js/Test Backend Media Library -- should show published entry image in global media library.snap.png create mode 100644 cypress/snapshots/media_library_spec_test_backend.js/Test Backend Media Library -- should show published entry image in grid view.snap.png create mode 100644 cypress/support/commands.ts create mode 100644 cypress/support/e2e.ts create mode 100644 cypress/utils/README.md create mode 100644 cypress/utils/config.ts create mode 100644 cypress/utils/constants.ts create mode 100644 cypress/utils/regexp.ts create mode 100644 cypress/utils/steps.ts create mode 100644 packages/core/dev-test/backends/test/config.yml create mode 100644 packages/core/dev-test/backends/test/index.html create mode 100644 packages/core/dev-test/data.js create mode 100644 packages/core/src/__tests__/backend.spec.ts create mode 100644 packages/core/src/actions/__tests__/config.spec.ts create mode 100644 packages/core/src/actions/editorialWorkflow.ts create mode 100644 packages/core/src/backends/bitbucket/__tests__/api.spec.ts create mode 100644 packages/core/src/backends/git-gateway/__tests__/AuthenticationPage.spec.tsx create mode 100644 packages/core/src/backends/git-gateway/__tests__/GitHubAPI.spec.ts create mode 100644 packages/core/src/backends/github/AuthenticationPage.css create mode 100644 packages/core/src/backends/gitlab/__tests__/gitlab.spec.ts create mode 100644 packages/core/src/backends/test/__tests__/implementation.spec.ts create mode 100644 packages/core/src/components/entry-editor/EditorWorkflowToolbarButtons.css create mode 100644 packages/core/src/components/entry-editor/EditorWorkflowToolbarButtons.tsx create mode 100644 packages/core/src/components/theme/ThemeManager.tsx create mode 100644 packages/core/src/components/theme/components/ThemeCard.css create mode 100644 packages/core/src/components/theme/components/ThemeCard.tsx create mode 100644 packages/core/src/components/theme/components/ThemeSelectorDialog.css create mode 100644 packages/core/src/components/theme/components/ThemeSelectorDialog.tsx create mode 100644 packages/core/src/components/theme/defaultThemes.ts create mode 100644 packages/core/src/components/theme/hooks/useTheme.ts create mode 100644 packages/core/src/components/theme/hooks/useThemes.ts create mode 100644 packages/core/src/components/theme/util/createTheme.ts create mode 100644 packages/core/src/components/workflow/ActiveWorkflowCard.tsx create mode 100644 packages/core/src/components/workflow/Dashboard.css create mode 100644 packages/core/src/components/workflow/Dashboard.tsx create mode 100644 packages/core/src/components/workflow/WorkflowCard.css create mode 100644 packages/core/src/components/workflow/WorkflowCard.tsx create mode 100644 packages/core/src/components/workflow/WorkflowColumn.css create mode 100644 packages/core/src/components/workflow/WorkflowColumn.tsx create mode 100644 packages/core/src/components/workflow/WorkflowStatusPill.tsx create mode 100644 packages/core/src/components/workflow/hooks/useWorkflowBoardSections.ts create mode 100644 packages/core/src/components/workflow/hooks/useWorkflowEntriesByCollection.ts create mode 100644 packages/core/src/components/workflow/util/workflow.util.ts create mode 100644 packages/core/src/constants/enums.ts create mode 100644 packages/core/src/constants/publishModes.ts create mode 100644 packages/core/src/lib/__tests__/registry.spec.ts create mode 100644 packages/core/src/lib/hooks/useCurrentBackend.ts create mode 100644 packages/core/src/lib/hooks/useDefaultPath.ts create mode 100644 packages/core/src/lib/hooks/useRefWithCallback.ts create mode 100644 packages/core/src/lib/hooks/useUnpublishedEntries.ts create mode 100644 packages/core/src/lib/util/EditorialWorkflowError.ts create mode 100644 packages/core/src/locales/fa/index.ts create mode 100644 packages/core/src/locales/ua/index.ts create mode 100644 packages/core/src/reducers/editorialWorkflow.ts create mode 100644 packages/core/src/reducers/selectors/editorialWorkflow.ts create mode 100644 packages/core/src/reducers/selectors/scroll.ts delete mode 100644 packages/core/src/styles/datetime/calendar.css delete mode 100644 packages/core/src/styles/datetime/clock.css delete mode 100644 packages/core/src/styles/datetime/datetime.css create mode 100644 packages/core/src/widgets/boolean/BooleanControl.css create mode 100644 packages/core/src/widgets/datetime/datetime.util.ts create mode 100644 packages/core/src/widgets/number/NumberControl.css create mode 100644 packages/core/src/widgets/relation/RelationSummary.tsx create mode 100644 packages/core/src/widgets/relation/types.ts create mode 100644 packages/core/src/widgets/relation/util.ts create mode 100644 packages/core/src/widgets/string/StringControl.css create mode 100644 packages/demo/.prettierignore create mode 100644 packages/demo/.prettierrc delete mode 100644 packages/demo/index.html create mode 100644 packages/demo/public/editor-friendly-user-interface.svg create mode 100644 packages/demo/public/editorial/config.yml create mode 100644 packages/demo/public/intuitive-workflow-for-content-teams.svg rename packages/demo/public/{ => simple}/config.yml (96%) create mode 100644 packages/demo/src/cms.js delete mode 100644 packages/demo/src/cms.jsx create mode 100644 packages/demo/src/data.js create mode 100644 packages/demo/src/editorial/index.html create mode 100644 packages/demo/src/index.html create mode 100644 packages/demo/src/simple/index.html create mode 100644 packages/docs/content/docs/cms-events.mdx create mode 100644 packages/docs/content/docs/custom-theme.mdx create mode 100644 packages/docs/content/docs/editorial-workflow.mdx delete mode 100644 packages/docs/content/docs/migration-guide-v3.mdx create mode 100644 packages/docs/content/docs/migration-guide-v4.mdx create mode 100644 packages/docs/content/docs/open-authoring.mdx create mode 100644 packages/docs/public/img/intuitive-workflow-for-content-teams.svg create mode 100644 tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4e4006cc..0dfc0929 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: packages/app/yarn.lock packages/core/yarn.lock packages/docs/yarn.lock - node-version: 16 + node-version: 18 - name: Install run: | @@ -50,7 +50,7 @@ jobs: packages/app/yarn.lock packages/core/yarn.lock packages/docs/yarn.lock - node-version: 16 + node-version: 18 - name: Install run: | @@ -75,7 +75,7 @@ jobs: packages/app/yarn.lock packages/core/yarn.lock packages/docs/yarn.lock - node-version: 16 + node-version: 18 - name: Install run: | @@ -105,7 +105,7 @@ jobs: packages/app/yarn.lock packages/core/yarn.lock packages/docs/yarn.lock - node-version: 16 + node-version: 18 - name: Install run: | diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml new file mode 100644 index 00000000..9c313848 --- /dev/null +++ b/.github/workflows/cypress.yml @@ -0,0 +1,49 @@ +name: Cypress Tests + +on: + workflow_dispatch: + push: + branches: ['main', 'next'] + pull_request: + branches: ['main', 'next'] + +jobs: + cypress-run: + runs-on: ubuntu-latest + + strategy: + fail-fast: false # https://github.com/cypress-io/github-action/issues/48 + matrix: + containers: [1, 2] # Uses 2 parallel instances + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + cache: yarn + cache-dependency-path: | + yarn.lock + packages/app/yarn.lock + packages/core/yarn.lock + packages/docs/yarn.lock + node-version: 18 + + - name: Install + run: | + yarn install --frozen-lockfile + + - name: Run Cypress Tests + uses: cypress-io/github-action@v6 + with: + start: yarn dev + wait-on: 'http://localhost:8080' + record: true # Records to Cypress Cloud + parallel: true # Runs test in parallel using settings above + env: + # For recording and parallelization to work you must set your CYPRESS_RECORD_KEY + # in GitHub repo → Settings → Secrets → Actions + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + # Creating a token https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token + GITHUB_TOKEN: ${{ secrets.CYPRESS_GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 7c4e60e7..d9379e20 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ /node_modules *.log .vscode +cypress/screenshots +cypress/downloads +.env +.temp diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..863642f5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +dist/ +bin/ +public/ +.cache/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..30e01f34 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "arrowParens": "avoid", + "trailingComma": "all", + "singleQuote": true, + "printWidth": 100 +} diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 00000000..e1066657 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "cypress"; +import setupNodeEvents from "./cypress/plugins"; + +export default defineConfig({ + projectId: "wvw3x3", + retries: { + runMode: 2, + openMode: 0, + }, + chromeWebSecurity: false, + e2e: { + video: false, + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents, + baseUrl: "http://localhost:8080", + specPattern: "cypress/e2e/*.spec.ts", + }, +}); diff --git a/cypress/README.md b/cypress/README.md new file mode 100644 index 00000000..a1516ffc --- /dev/null +++ b/cypress/README.md @@ -0,0 +1,72 @@ +# Cypress Tests Guide + +## Introduction + +[Cypress](https://www.cypress.io/) is a JavaScript End to End Testing Framework that runs in the browser. + +Cypress tests run with a [local version](../packages/core/dev-test) of the CMS. + +During the setup of a spec file, the relevant `index.html` and `config.yml` are copied from `packages/core/dev-test/backends/` to `dev-test`. + +Tests for the `test` backend use mock data generated in `dev-test/backends/test/index.html`. + +Tests for the other backends use previously [recorded data](fixtures) and stub `fetch` [calls](support/commands.js#L52). See more about recording tests data [here](#recording-tests-data). + +## Run Tests Locally + +```bash +yarn test:e2e # builds the demo site and runs Cypress in headless mode with mock data +``` + +## Debug Tests + +```bash +yarn develop # starts a local dev server with the demo site +yarn test:e2e:exec # runs Cypress in non-headless mode with mock data +``` + +## Recording Tests Data + +When recording tests, access to the relevant backend API is required, thus one must set up a `.env` file in the root project directory in the following format: + +```bash +GITHUB_REPO_OWNER=owner +GITHUB_REPO_NAME=repo +GITHUB_REPO_TOKEN=tokenWithWritePermissions +GITHUB_OPEN_AUTHORING_OWNER=forkOwner +GITHUB_OPEN_AUTHORING_TOKEN=tokenWithWritePermissions + +GITLAB_REPO_OWNER=owner +GITLAB_REPO_NAME=repo +GITLAB_REPO_TOKEN=tokenWithWritePermissions + +BITBUCKET_REPO_OWNER=owner +BITBUCKET_REPO_NAME=repo +BITBUCKET_OUATH_CONSUMER_KEY=ouathConsumerKey +BITBUCKET_OUATH_CONSUMER_SECRET=ouathConsumerSecret + +NETLIFY_API_TOKEN=netlifyApiToken +NETLIFY_INSTALLATION_ID=netlifyGitHubInstallationId +``` + +> The structure of the relevant repo should match the settings in [`config.yml`](../packages/core/dev-test/backends//config.yml#L1) + +To start a recording run the following commands: + +```bash +yarn develop # starts a local dev server with the demo site +yarn mock:server:start # starts the recording proxy +yarn test:e2e:record-fixtures:dev # runs Cypress in non-headless and pass data through the recording proxy +yarn mock:server:stop # stops the recording proxy +``` + +> During the recorded process a clone of the relevant repo will be created under `.temp` and reset between tests. + +Recordings are sanitized from any possible sensitive data and [transformed](plugins/common.js#L34) into an easier to process format. + +To avoid recording all the tests over and over again, a recommended process is to: + +1. Mark the specific test as `only` by changing `it("some test...` to `it.only("some test...` for the relevant test. +2. Run the test in recording mode. +3. Exit Cypress and stop the proxy. +4. Run the test normally (with mock data) to verify the recording works. diff --git a/cypress/cypress.d.ts b/cypress/cypress.d.ts new file mode 100644 index 00000000..4a495f9e --- /dev/null +++ b/cypress/cypress.d.ts @@ -0,0 +1,82 @@ +/// + +import type { + SetupBackendProps, + SetupBackendTestProps, + SeedRepoProps, + TeardownBackendTestProps, + TeardownBackendProps, +} from './interface'; +import type { Config as CMSConfig, DeepPartial } from '@staticcms/core/interface'; + +interface KeyProps { + shift?: boolean; + times?: number; +} + +declare global { + namespace Cypress { + interface Chainable { + task(event: 'setupBackend', props: SetupBackendProps): Chainable; + task(event: 'setupBackendTest', props: SetupBackendTestProps): Chainable>; + task(event: 'seedRepo', props: SeedRepoProps): Chainable>; + task(event: 'teardownBackendTest', props: TeardownBackendTestProps): Chainable>; + task(event: 'teardownBackend', props: TeardownBackendProps): Chainable>; + task(event: 'updateConfig', props: DeepPartial): Chainable>; + + login(): Chainable; + loginAndNewPost(): Chainable; + + dragTo(selector: string, options?: { delay?: number }): Chainable; + + getMarkdownEditor(): Chainable; + confirmMarkdownEditorContent(expected: string): Chainable; + clearMarkdownEditorContent(): Chainable; + confirmRawEditorContent(expected: string): Chainable; + + enter(props?: KeyProps): Chainable; + backspace(props?: KeyProps): Chainable; + selectAll(props?: KeyProps): Chainable; + up(props?: KeyProps): Chainable; + down(props?: KeyProps): Chainable; + left(props?: KeyProps): Chainable; + right(props?: KeyProps): Chainable; + tabkey(props?: KeyProps): Chainable; + + selection( + fn: (this: Cypress.ObjectLike, currentSubject: JQuery) => Chainable, + ): Chainable; + setSelection( + query: + | string + | { + anchorQuery: string; + anchorOffset?: number; + focusQuery: string; + focusOffset?: number; + }, + endQuery: string, + ): Chainable; + + setCursor(query: string, atStart?: boolean): Chainable; + setCursorBefore(query: string): Chainable; + setCursorAfter(query: string): Chainable; + + print(message: string): Chainable; + + insertCodeBlock(): Chainable; + insertEditorComponent(title: string): Chainable; + + clickToolbarButton(title: string, opts: { times: number }): Chainable; + clickHeadingOneButton(opts: { times: number }): Chainable; + clickHeadingTwoButton(opts: { times: number }): Chainable; + clickOrderedListButton(opts: { times: number }): Chainable; + clickUnorderedListButton(opts: { times: number }): Chainable; + clickCodeButton(opts: { times: number }): Chainable; + clickItalicButton(opts: { times: number }): Chainable; + clickQuoteButton(opts: { times: number }): Chainable; + clickLinkButton(opts: { times: number }): Chainable; + clickModeToggle(): Chainable; + } + } +} diff --git a/cypress/e2e/_old/editorial_workflow_spec_bitbucket_backend.js b/cypress/e2e/_old/editorial_workflow_spec_bitbucket_backend.js new file mode 100644 index 00000000..e706c53a --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_bitbucket_backend.js @@ -0,0 +1,30 @@ +import fixture from '../common/editorial_workflow'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'bitbucket'; + +describe('BitBucket Backend Editorial Workflow', () => { + let taskResult = { data: {} }; + + before(() => { + specUtils.before(taskResult, { publish_mode: 'editorial_workflow' }, backend); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + }); +}); diff --git a/cypress/e2e/_old/editorial_workflow_spec_git-gateway_github_backend.js b/cypress/e2e/_old/editorial_workflow_spec_git-gateway_github_backend.js new file mode 100644 index 00000000..a4c7d500 --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_git-gateway_github_backend.js @@ -0,0 +1,31 @@ +import fixture from '../common/editorial_workflow'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'git-gateway'; +const provider = 'github'; + +describe('Git Gateway (GitHub) Backend Editorial Workflow', () => { + let taskResult = { data: {} }; + + before(() => { + specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + }); +}); diff --git a/cypress/e2e/_old/editorial_workflow_spec_git-gateway_gitlab_backend.js b/cypress/e2e/_old/editorial_workflow_spec_git-gateway_gitlab_backend.js new file mode 100644 index 00000000..baa36591 --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_git-gateway_gitlab_backend.js @@ -0,0 +1,31 @@ +import fixture from '../common/editorial_workflow'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'git-gateway'; +const provider = 'gitlab'; + +describe('Git Gateway (GitLab) Backend Editorial Workflow', () => { + let taskResult = { data: {} }; + + before(() => { + specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + }); +}); diff --git a/cypress/e2e/_old/editorial_workflow_spec_github_backend_graphql.js b/cypress/e2e/_old/editorial_workflow_spec_github_backend_graphql.js new file mode 100644 index 00000000..15745c79 --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_github_backend_graphql.js @@ -0,0 +1,37 @@ +import fixture from '../common/editorial_workflow'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'github'; + +describe('Github Backend Editorial Workflow - GraphQL API', () => { + const taskResult = { data: {} }; + + before(() => { + specUtils.before( + taskResult, + { + backend: { use_graphql: true, open_authoring: false }, + publish_mode: 'editorial_workflow', + }, + backend, + ); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + }); +}); diff --git a/cypress/e2e/_old/editorial_workflow_spec_github_backend_graphql_open_authoring.js b/cypress/e2e/_old/editorial_workflow_spec_github_backend_graphql_open_authoring.js new file mode 100644 index 00000000..40b4354c --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_github_backend_graphql_open_authoring.js @@ -0,0 +1,38 @@ +import fixture from '../common/open_authoring'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'github'; + +describe('Github Backend Editorial Workflow - GraphQL API - Open Authoring', () => { + const taskResult = { data: {} }; + + before(() => { + specUtils.before( + taskResult, + { + backend: { use_graphql: true, open_authoring: true }, + publish_mode: 'editorial_workflow', + }, + backend, + ); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + getForkUser: () => taskResult.data.forkUser, + }); +}); diff --git a/cypress/e2e/_old/editorial_workflow_spec_github_backend_rest.js b/cypress/e2e/_old/editorial_workflow_spec_github_backend_rest.js new file mode 100644 index 00000000..46d9bfe0 --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_github_backend_rest.js @@ -0,0 +1,37 @@ +import fixture from '../common/editorial_workflow'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'github'; + +describe('Github Backend Editorial Workflow - REST API', () => { + let taskResult = { data: {} }; + + before(() => { + specUtils.before( + taskResult, + { + backend: { use_graphql: false, open_authoring: false }, + publish_mode: 'editorial_workflow', + }, + backend, + ); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + }); +}); diff --git a/cypress/e2e/_old/editorial_workflow_spec_github_backend_rest_open_authoring.js b/cypress/e2e/_old/editorial_workflow_spec_github_backend_rest_open_authoring.js new file mode 100644 index 00000000..3feeb5bf --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_github_backend_rest_open_authoring.js @@ -0,0 +1,38 @@ +import fixture from '../common/open_authoring'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'github'; + +describe('Github Backend Editorial Workflow - REST API - Open Authoring', () => { + let taskResult = { data: {} }; + + before(() => { + specUtils.before( + taskResult, + { + backend: { use_graphql: false, open_authoring: true }, + publish_mode: 'editorial_workflow', + }, + backend, + ); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + getForkUser: () => taskResult.data.forkUser, + }); +}); diff --git a/cypress/e2e/_old/editorial_workflow_spec_gitlab_backend.js b/cypress/e2e/_old/editorial_workflow_spec_gitlab_backend.js new file mode 100644 index 00000000..836064a6 --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_gitlab_backend.js @@ -0,0 +1,36 @@ +import fixture from '../common/editorial_workflow'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'gitlab'; + +describe('GitLab Backend Editorial Workflow', () => { + let taskResult = { data: {} }; + + before(() => { + specUtils.before(taskResult, { publish_mode: 'editorial_workflow' }, backend); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + if ( + Cypress.mocha.getRunner().suite.ctx.currentTest.title === + 'can change status on and publish multiple entries' + ) { + Cypress.mocha.getRunner().suite.ctx.currentTest.skip(); + } + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + }); +}); diff --git a/cypress/e2e/_old/editorial_workflow_spec_proxy_git_backend.js b/cypress/e2e/_old/editorial_workflow_spec_proxy_git_backend.js new file mode 100644 index 00000000..64796b1a --- /dev/null +++ b/cypress/e2e/_old/editorial_workflow_spec_proxy_git_backend.js @@ -0,0 +1,32 @@ +import fixture from '../common/editorial_workflow'; +import * as specUtils from '../common/spec_utils'; +import { entry1, entry2, entry3 } from '../common/entries'; + +const backend = 'proxy'; +const mode = 'git'; + +describe.skip(`Proxy Backend Editorial Workflow - '${mode}' mode`, () => { + let taskResult = { data: {} }; + + before(() => { + specUtils.before(taskResult, { publish_mode: 'editorial_workflow', mode }, backend); + Cypress.config('defaultCommandTimeout', 5 * 1000); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + fixture({ + entries: [entry1, entry2, entry3], + getUser: () => taskResult.data.user, + }); +}); diff --git a/cypress/e2e/_old/field_validations_spec.js b/cypress/e2e/_old/field_validations_spec.js new file mode 100644 index 00000000..98201220 --- /dev/null +++ b/cypress/e2e/_old/field_validations_spec.js @@ -0,0 +1,54 @@ +import { + login, + validateObjectFieldsAndExit, + validateNestedObjectFieldsAndExit, + validateListFieldsAndExit, + validateNestedListFieldsAndExit, +} from '../../utils/steps'; +import { setting1, setting2 } from '../../utils/constants'; + +describe('Test Backend Editorial Workflow', () => { + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + }); + + beforeEach(() => { + cy.task('setupBackend', { backend: 'test' }); + }); + + it('can validate object fields', () => { + login({ editorialWorkflow: true }); + + cy.contains('a', 'Posts').click(); + + validateObjectFieldsAndExit(setting1); + }); + + it('can validate fields nested in an object field', () => { + login({ editorialWorkflow: true }); + + cy.contains('a', 'Posts').click(); + + validateNestedObjectFieldsAndExit(setting1); + }); + + it('can validate list fields', () => { + login({ editorialWorkflow: true }); + + cy.contains('a', 'Posts').click(); + + validateListFieldsAndExit(setting2); + }); + + it('can validate deeply nested list fields', () => { + login({ editorialWorkflow: true }); + + cy.contains('a', 'Posts').click(); + + validateNestedListFieldsAndExit(setting2); + }); +}); diff --git a/cypress/e2e/_old/i18n_editorial_workflow_spec_test_backend.js b/cypress/e2e/_old/i18n_editorial_workflow_spec_test_backend.js new file mode 100644 index 00000000..9e138cf7 --- /dev/null +++ b/cypress/e2e/_old/i18n_editorial_workflow_spec_test_backend.js @@ -0,0 +1,38 @@ +import fixture from '../common/i18n_editorial_workflow_spec'; + +const backend = 'test'; + +describe(`I18N Test Backend Editorial Workflow`, () => { + const taskResult = { data: {} }; + + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { + backend, + options: { + publish_mode: 'editorial_workflow', + i18n: { + locales: ['en', 'de', 'fr'], + }, + collections: [ + { + folder: 'content/i18n', + i18n: true, + fields: [{ i18n: true }, {}, { i18n: 'duplicate' }], + }, + ], + }, + }); + }); + + after(() => { + cy.task('teardownBackend', { backend }); + }); + + const entry = { + Title: 'first title', + Body: 'first body', + }; + + fixture({ entry, getUser: () => taskResult.data.user }); +}); diff --git a/cypress/e2e/_old/i18n_simple_workflow_spec_proxy_fs_backend.js b/cypress/e2e/_old/i18n_simple_workflow_spec_proxy_fs_backend.js new file mode 100644 index 00000000..da717b8f --- /dev/null +++ b/cypress/e2e/_old/i18n_simple_workflow_spec_proxy_fs_backend.js @@ -0,0 +1,148 @@ +import * as specUtils from '../common/spec_utils'; +import { login } from '../../utils/steps'; +import { createEntryTranslateAndPublish } from '../common/i18n'; + +const backend = 'proxy'; +const mode = 'fs'; + +const expectedEnContent = `--- +template: post +title: first title +date: 1970-01-01T00:00:00.000Z +description: first description +category: first category +tags: + - tag1 +--- +`; + +const expectedDeContent = `--- +title: de +date: 1970-01-01T00:00:00.000Z +--- +`; + +const expectedFrContent = `--- +title: fr +date: 1970-01-01T00:00:00.000Z +--- +`; + +const contentSingleFile = `--- +en: + template: post + date: 1970-01-01T00:00:00.000Z + title: first title + body: first body + description: first description + category: first category + tags: + - tag1 +de: + date: 1970-01-01T00:00:00.000Z + title: de +fr: + date: 1970-01-01T00:00:00.000Z + title: fr +--- +`; + +describe(`I18N Proxy Backend Simple Workflow - '${mode}' mode`, () => { + const taskResult = { data: {} }; + + const entry = { + Title: 'first title', + Body: 'first body', + Description: 'first description', + Category: 'first category', + Tags: 'tag1', + }; + + before(() => { + specUtils.before( + taskResult, + { + mode, + publish_mode: 'simple', + i18n: { + locales: ['en', 'de', 'fr'], + }, + collections: [{ i18n: true, fields: [{}, { i18n: true }, {}, { i18n: 'duplicate' }] }], + }, + backend, + ); + Cypress.config('taskTimeout', 15 * 1000); + Cypress.config('defaultCommandTimeout', 5 * 1000); + }); + + after(() => { + specUtils.after(taskResult, backend); + }); + + beforeEach(() => { + specUtils.beforeEach(taskResult, backend); + }); + + afterEach(() => { + specUtils.afterEach(taskResult, backend); + }); + + it('can create entry with translation in locale_folders mode', () => { + cy.task('updateConfig', { i18n: { structure: 'multiple_folders' } }); + + login({ user: taskResult.data.user }); + + createEntryTranslateAndPublish(entry); + + cy.readFile(`${taskResult.data.tempDir}/content/posts/en/1970-01-01-first-title.md`).should( + 'contain', + expectedEnContent, + ); + + cy.readFile(`${taskResult.data.tempDir}/content/posts/de/1970-01-01-first-title.md`).should( + 'eq', + expectedDeContent, + ); + + cy.readFile(`${taskResult.data.tempDir}/content/posts/fr/1970-01-01-first-title.md`).should( + 'eq', + expectedFrContent, + ); + }); + + it('can create entry with translation in single_file mode', () => { + cy.task('updateConfig', { i18n: { structure: 'multiple_files' } }); + + login({ user: taskResult.data.user }); + + createEntryTranslateAndPublish(entry); + + cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.en.md`).should( + 'contain', + expectedEnContent, + ); + + cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.de.md`).should( + 'eq', + expectedDeContent, + ); + + cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.fr.md`).should( + 'eq', + expectedFrContent, + ); + }); + + it('can create entry with translation in locale_file_extensions mode', () => { + cy.task('updateConfig', { i18n: { structure: 'single_file' } }); + + login({ user: taskResult.data.user }); + + createEntryTranslateAndPublish(entry); + + cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.md`).should( + 'eq', + contentSingleFile, + ); + }); +}); diff --git a/cypress/e2e/_old/markdown_widget_backspace_spec.js b/cypress/e2e/_old/markdown_widget_backspace_spec.js new file mode 100644 index 00000000..b15a04bf --- /dev/null +++ b/cypress/e2e/_old/markdown_widget_backspace_spec.js @@ -0,0 +1,76 @@ +describe('Markdown widget', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + // describe('pressing backspace', () => { + it('sets non-default block to default when empty', () => { + cy.focused().clickHeadingOneButton().backspace().confirmMarkdownEditorContent(` +

+ `); + }); + it('moves to previous block when no character left to delete', () => { + cy.focused().type('foo').enter().clickHeadingOneButton().type('a').backspace({ times: 2 }) + .confirmMarkdownEditorContent(` +

foo

+ `); + }); + it('does nothing at start of first block in document when non-empty and non-default', () => { + cy.focused().clickHeadingOneButton().type('foo').setCursorBefore('foo').backspace({ times: 4 }) + .confirmMarkdownEditorContent(` +

foo

+ `); + }); + it('deletes individual characters in middle of non-empty non-default block in document', () => { + cy.focused().clickHeadingOneButton().type('foo').setCursorAfter('fo').backspace({ times: 3 }) + .confirmMarkdownEditorContent(` +

o

+ `); + }); + it('at beginning of non-first block, moves default block content to previous block', () => { + cy + .focused() + .clickHeadingOneButton() + .type('foo') + .enter() + .type('bar') + .setCursorBefore('bar') + .backspace().confirmMarkdownEditorContent(` +

foobar

+ `); + }); + it('at beginning of non-first block, moves non-default block content to previous block', () => { + cy + .focused() + .type('foo') + .enter() + .clickHeadingOneButton() + .type('bar') + .enter() + .clickHeadingTwoButton() + .type('baz') + .setCursorBefore('baz') + .backspace() + .confirmMarkdownEditorContent( + ` +

foo

+

barbaz

+ `, + ) + .setCursorBefore('bar') + .backspace().confirmMarkdownEditorContent(` +

foobarbaz

+ `); + // }); + }); +}); diff --git a/cypress/e2e/_old/markdown_widget_code_block_spec.js b/cypress/e2e/_old/markdown_widget_code_block_spec.js new file mode 100644 index 00000000..5fee16e7 --- /dev/null +++ b/cypress/e2e/_old/markdown_widget_code_block_spec.js @@ -0,0 +1,134 @@ +import { oneLineTrim, stripIndent } from 'common-tags'; + +describe('Markdown widget code block', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + describe('code block', () => { + it('outputs code', () => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy + .insertCodeBlock() + .type('foo') + .enter() + .type('bar') + .confirmMarkdownEditorContent( + ` + ${codeBlock(` + foo + bar + `)} + `, + ) + .wait(500) + .clickModeToggle().confirmMarkdownEditorContent(` + ${codeBlockRaw(` + foo + bar + `)} + `); + }); + }); +}); + +function codeBlockRaw(content) { + return ['```', ...stripIndent(content).split('\n'), '```'] + .map( + line => oneLineTrim` +
+ + + ${line} + + +
+ `, + ) + .join(''); +} + +function codeBlock(content) { + const lines = stripIndent(content) + .split('\n') + .map( + (line, idx) => ` +
+
+
${idx + 1}
+
+
${line}
+
+ `, + ) + .join(''); + + return oneLineTrim` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
xxxxxxxxxx
+
+
+
+
+
 
+
+
+ ${lines} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +  + + +
+
+
+
+ `; +} diff --git a/cypress/e2e/_old/markdown_widget_enter_spec.js b/cypress/e2e/_old/markdown_widget_enter_spec.js new file mode 100644 index 00000000..4ca26ce7 --- /dev/null +++ b/cypress/e2e/_old/markdown_widget_enter_spec.js @@ -0,0 +1,80 @@ +describe('Markdown widget breaks', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + describe('pressing enter', () => { + it('creates new default block from empty block', () => { + cy.focused().enter().confirmMarkdownEditorContent(` +

+

+ `); + }); + it('creates new default block when selection collapsed at end of block', () => { + cy.focused().type('foo').enter().confirmMarkdownEditorContent(` +

foo

+

+ `); + }); + it('creates new default block when selection collapsed at end of non-default block', () => { + cy.clickHeadingOneButton().type('foo').enter().confirmMarkdownEditorContent(` +

foo

+

+ `); + }); + it('creates new default block when selection collapsed in empty non-default block', () => { + cy.clickHeadingOneButton().enter().confirmMarkdownEditorContent(` +

+

+ `); + }); + it('splits block into two same-type blocks when collapsed selection at block start', () => { + cy.clickHeadingOneButton().type('foo').setCursorBefore('foo').enter() + .confirmMarkdownEditorContent(` +

+

foo

+ `); + }); + it('splits block into two same-type blocks when collapsed in middle of selection at block start', () => { + cy.clickHeadingOneButton().type('foo').setCursorBefore('oo').enter() + .confirmMarkdownEditorContent(` +

f

+

oo

+ `); + }); + it('deletes selected content and splits to same-type block when selection is expanded', () => { + cy.clickHeadingOneButton().type('foo bar').setSelection('o b').enter() + .confirmMarkdownEditorContent(` +

fo

+

ar

+ `); + }); + }); + + describe('pressing shift+enter', () => { + it('creates line break', () => { + cy.focused().enter({ shift: true }).confirmMarkdownEditorContent(` +

+ +

+ `); + }); + it('creates consecutive line break', () => { + cy.focused().enter({ shift: true, times: 4 }).confirmMarkdownEditorContent(` +

+ +

+ `); + }); + }); +}); diff --git a/cypress/e2e/_old/markdown_widget_hotkeys_spec.js b/cypress/e2e/_old/markdown_widget_hotkeys_spec.js new file mode 100644 index 00000000..35288775 --- /dev/null +++ b/cypress/e2e/_old/markdown_widget_hotkeys_spec.js @@ -0,0 +1,109 @@ +import { HOT_KEY_MAP } from '../../utils/constants'; +const headingNumberToWord = ['', 'one', 'two', 'three', 'four', 'five', 'six']; +const isMac = Cypress.platform === 'darwin'; +const modifierKey = isMac ? '{meta}' : '{ctrl}'; +// eslint-disable-next-line func-style +const replaceMod = str => str.replace(/mod\+/g, modifierKey).replace(/shift\+/g, '{shift}'); + +describe('Markdown widget hotkeys', () => { + describe('hot keys', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + cy.focused().type('foo').setSelection('foo').as('selection'); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + describe('bold', () => { + it('pressing mod+b bolds the text', () => { + cy.get('@selection') + .type(replaceMod(HOT_KEY_MAP['bold'])) + .confirmMarkdownEditorContent( + ` +

+ foo +

+ `, + ) + .type(replaceMod(HOT_KEY_MAP['bold'])); + }); + }); + + describe('italic', () => { + it('pressing mod+i italicizes the text', () => { + cy.get('@selection') + .type(replaceMod(HOT_KEY_MAP['italic'])) + .confirmMarkdownEditorContent( + ` +

+ foo +

+ `, + ) + .type(replaceMod(HOT_KEY_MAP['italic'])); + }); + }); + + describe('strikethrough', () => { + it('pressing mod+shift+s displays a strike through the text', () => { + cy.get('@selection') + .type(replaceMod(HOT_KEY_MAP['strikethrough'])) + .confirmMarkdownEditorContent( + ` +

+ foo +

+ `, + ) + .type(replaceMod(HOT_KEY_MAP['strikethrough'])); + }); + }); + + describe('code', () => { + it('pressing mod+shift+c displays a code block around the text', () => { + cy.get('@selection') + .type(replaceMod(HOT_KEY_MAP['code'])) + .confirmMarkdownEditorContent( + ` +

+ foo +

+ `, + ) + .type(replaceMod(HOT_KEY_MAP['code'])); + }); + }); + + describe('link', () => { + before(() => {}); + it('pressing mod+k transforms the text to a link', () => { + cy.window().then(win => { + cy.get('@selection').type(replaceMod(HOT_KEY_MAP['link'])); + cy.stub(win, 'prompt').returns('https://google.com'); + cy.confirmMarkdownEditorContent('

foo

').type( + replaceMod(HOT_KEY_MAP['link']), + ); + }); + }); + }); + + describe('headings', () => { + for (let i = 1; i <= 6; i++) { + it(`pressing mod+${i} transforms the text to a heading`, () => { + cy.get('@selection') + .type(replaceMod(HOT_KEY_MAP[`heading-${headingNumberToWord[i]}`])) + .confirmMarkdownEditorContent(`foo`) + .type(replaceMod(HOT_KEY_MAP[`heading-${headingNumberToWord[i]}`])); + }); + } + }); + }); +}); diff --git a/cypress/e2e/_old/markdown_widget_link_spec.js b/cypress/e2e/_old/markdown_widget_link_spec.js new file mode 100644 index 00000000..79ee57f3 --- /dev/null +++ b/cypress/e2e/_old/markdown_widget_link_spec.js @@ -0,0 +1,64 @@ +describe('Markdown widget link', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + describe('link', () => { + it('can add a new valid link', () => { + const link = 'https://www.staticcms.org/'; + cy.window().then(win => { + cy.stub(win, 'prompt').returns(link); + }); + cy.focused().clickLinkButton(); + + cy.confirmMarkdownEditorContent(`

${link}

`); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(300); + cy.clickModeToggle(); + + cy.confirmRawEditorContent(`<${link}>`); + }); + + it('can add a new invalid link', () => { + const link = 'www.staticcms.org'; + cy.window().then(win => { + cy.stub(win, 'prompt').returns(link); + }); + cy.focused().clickLinkButton(); + + cy.confirmMarkdownEditorContent(`

${link}

`); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(300); + cy.clickModeToggle(); + + cy.confirmRawEditorContent(`[${link}](${link})`); + }); + + it('can select existing text as link', () => { + const link = 'https://www.staticcms.org'; + cy.window().then(win => { + cy.stub(win, 'prompt').returns(link); + }); + + const text = 'Static CMS'; + cy.focused().getMarkdownEditor().type(text).setSelection(text).clickLinkButton(); + + cy.confirmMarkdownEditorContent(`

${text}

`); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(300); + cy.clickModeToggle(); + + cy.confirmRawEditorContent(`[${text}](${link})`); + }); + }); +}); diff --git a/cypress/e2e/_old/markdown_widget_list_spec.js b/cypress/e2e/_old/markdown_widget_list_spec.js new file mode 100644 index 00000000..f54e352c --- /dev/null +++ b/cypress/e2e/_old/markdown_widget_list_spec.js @@ -0,0 +1,734 @@ +describe('Markdown widget', () => { + describe('list', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + // describe('toolbar buttons', () => { + it('creates and focuses empty list', () => { + cy.clickUnorderedListButton().confirmMarkdownEditorContent(` +
    +
  • +

    +
  • +
+ `); + }); + + it('removes list', () => { + cy.clickUnorderedListButton().clickUnorderedListButton().confirmMarkdownEditorContent(` +

+ `); + }); + + it('converts a list item to a paragraph block which is a sibling of the parent list', () => { + cy.clickUnorderedListButton().type('foo').enter().clickUnorderedListButton() + .confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
  • +
+

+ `); + }); + + it('converts empty nested list item to empty paragraph block in parent list item', () => { + cy + .clickUnorderedListButton() + .type('foo') + .enter() + .tabkey() + .type('bar') + .enter() + .tabkey() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
      +
    • +

      bar

      +
        +
      • +

        +
      • +
      +
    • +
    +
  • +
+ `, + ) + .clickUnorderedListButton() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
      +
    • +

      bar

      +

      +
    • +
    +
  • +
+ `, + ) + .backspace({ times: 4 }) + .clickUnorderedListButton().confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +

    +
  • +
+ `); + }); + + it('moves nested list item content to parent list item when in first block', () => { + cy + .clickUnorderedListButton() + .type('foo') + .enter() + .tabkey() + .type('bar') + .enter() + .tabkey() + .type('baz') + .clickUnorderedListButton() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
      +
    • +

      bar

      +

      baz

      +
    • +
    +
  • +
+ `, + ) + .up() + .clickUnorderedListButton() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +

    bar

    +

    baz

    +
  • +
+ `, + ) + .up() + .clickUnorderedListButton().confirmMarkdownEditorContent(` +

foo

+

bar

+

baz

+ `); + }); + + it('affects only the current block with collapsed selection', () => { + cy + .focused() + .type('foo') + .enter() + .type('bar') + .enter() + .type('baz') + .up() + .clickUnorderedListButton().confirmMarkdownEditorContent(` +

foo

+
    +
  • +

    bar

    +
  • +
+

baz

+ `); + }); + + it('wrap each bottom-most block in a selection with a list item block', () => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy + .focused() + .type('foo') + .enter() + .type('bar') + .enter() + .type('baz') + .setSelection('foo', 'baz') + .wait(500) + .clickUnorderedListButton().confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
  • +
  • +

    baz

    +
  • +
+ `); + }); + + it('unwraps list item block from each selected list item and unwraps all of them from the outer list block', () => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy + .clickUnorderedListButton() + .type('foo') + .enter() + .type('bar') + .enter() + .type('baz') + .setSelection('foo', 'baz') + .wait(500) + .clickUnorderedListButton().confirmMarkdownEditorContent(` +

foo

+

bar

+

baz

+ `); + }); + + it('combines adjacent same-typed lists, not differently typed lists', () => { + cy.focused() + .type('foo') + .enter() + .type('bar') + .enter() + .type('baz') + .up() + .clickUnorderedListButton() + .up() + .clickUnorderedListButton() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
  • +
+

baz

+ `, + ) + .down({ times: 2 }) + .focused() + .clickUnorderedListButton() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
  • +
  • +

    baz

    +
  • +
+ `, + ) + .up() + .enter() + .type('qux') + .tabkey() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
      +
    • +

      qux

      +
    • +
    +
  • +
  • +

    baz

    +
  • +
+ `, + ) + .up() + .enter() + .type('quux') + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
      +
    • +

      quux

      +
    • +
    • +

      qux

      +
    • +
    +
  • +
  • +

    baz

    +
  • +
+ `, + ) + .clickOrderedListButton() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
      +
    1. +

      quux

      +
    2. +
    +
      +
    • +

      qux

      +
    • +
    +
  • +
  • +

    baz

    +
  • +
+ `, + ) + .setSelection({ + anchorQuery: 'ul > li > ol p', + anchorOffset: 1, + focusQuery: 'ul > li > ul:last-child p', + focusOffset: 2, + }); + }); + + // while this works on dev environemnt, it will always fail in cypress - has something to do with text selection + // it('affects only selected list items', () => { + // cy + // .clickUnorderedListButton() + // .type('foo') + // .enter() + // .type('bar') + // .enter() + // .type('baz') + // .setSelection('bar') + // .clickUnorderedListButton() + // .confirmMarkdownEditorContent( + // ` + //
    + //
  • + //

    foo

    + //
  • + //
+ //

bar

+ //
    + //
  • + //

    baz

    + //
  • + //
+ // `, + // ) + // .clickUnorderedListButton() + // .setSelection('bar', 'baz') + // .clickUnorderedListButton() + // .confirmMarkdownEditorContent( + // ` + //
    + //
  • + //

    foo

    + //
  • + //
+ //

bar

+ //

baz

+ // `, + // ) + // .clickUnorderedListButton() + // .confirmMarkdownEditorContent( + // ` + //
    + //
  • + //

    foo

    + //
  • + //
  • + //

    bar

    + //
  • + //
  • + //

    baz

    + //
  • + //
+ // `, + // ) + // .setSelection('baz') + // .clickUnorderedListButton() + // .confirmMarkdownEditorContent( + // ` + //
    + //
  • + //

    foo

    + //
  • + //
  • + //

    bar

    + //
  • + //
+ //

baz

+ // `, + // ) + // .clickUnorderedListButton() + // .tabkey() + // .setCursorAfter('baz') + // .enter() + // .tabkey() + // .type('qux') + // .confirmMarkdownEditorContent( + // ` + //
    + //
  • + //

    foo

    + //
  • + //
  • + //

    bar

    + //
      + //
    • + //

      baz

      + //
        + //
      • + //

        qux

        + //
      • + //
      + //
    • + //
    + //
  • + //
+ // `, + // ) + // .setSelection('baz') + // .clickOrderedListButton() + // .confirmMarkdownEditorContent( + // ` + //
    + //
  • + //

    foo

    + //
  • + //
  • + //

    bar

    + //
      + //
    1. + //

      baz

      + //
        + //
      • + //

        qux

        + //
      • + //
      + //
    2. + //
    + //
  • + //
+ // `, + // ) + // .setCursorAfter('qux') + // .enter({ times: 2 }) + // .clickUnorderedListButton() + // .confirmMarkdownEditorContent(` + //
    + //
  • + //

    foo

    + //
  • + //
  • + //

    bar

    + //
      + //
    1. + //

      baz

      + //
        + //
      • + //

        qux

        + //
      • + //
      + //
    2. + //
    + //
      + //
    • + //

      + //
    • + //
    + //
  • + //
+ // `); + // }); + // }); + // }); + + // describe('on Enter', () => { + it('removes the list item and list if empty', () => { + cy.clickUnorderedListButton().enter().confirmMarkdownEditorContent(` +

+ `); + }); + + it('creates a new list item in a non-empty list', () => { + cy + .clickUnorderedListButton() + .type('foo') + .enter() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
  • +
  • +

    +
  • +
+ `, + ) + .type('bar') + .enter().confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
  • +
  • +

    +
  • +
+ `); + }); + + it('creates a new default block below a list when hitting Enter twice on an empty list item of the list', () => { + cy.clickUnorderedListButton().type('foo').enter({ times: 2 }).confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
  • +
+

+ `); + }); + // }); + + // describe('on Backspace', () => { + it('removes the list item and list if empty', () => { + cy.clickUnorderedListButton().backspace().confirmMarkdownEditorContent(` +

+ `); + }); + + it('removes the list item if list not empty', () => { + cy.clickUnorderedListButton().type('foo').enter().backspace().confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +

    +
  • +
+ `); + }); + + it('does not remove list item if empty with non-default block', () => { + cy.clickUnorderedListButton().clickHeadingOneButton().backspace() + .confirmMarkdownEditorContent(` +
    +
  • +

    +
  • +
+ `); + }); + // }); + + // describe('on Tab', () => { + it('does nothing in top level list', () => { + cy + .clickUnorderedListButton() + .tabkey() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    +
  • +
+ `, + ) + .type('foo') + .tabkey().confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
  • +
+ `); + }); + + it('indents nested list items', () => { + cy + .clickUnorderedListButton() + .type('foo') + .enter() + .type('bar') + .tabkey() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
      +
    • +

      bar

      +
    • +
    +
  • +
+ `, + ) + .enter() + .tabkey().confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
      +
    • +

      bar

      +
        +
      • +

        +
      • +
      +
    • +
    +
  • +
+ `); + }); + + it('only nests up to one level down from the parent list', () => { + cy.clickUnorderedListButton().type('foo').enter().tabkey().confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
      +
    • +

      +
    • +
    +
  • +
+ `); + }); + + it('unindents nested list items with shift', () => { + cy.clickUnorderedListButton().type('foo').enter().tabkey().tabkey({ shift: true }) + .confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
  • +
  • +

    +
  • +
+ `); + }); + + it('indents and unindents from one level below parent back to document root', () => { + cy + .clickUnorderedListButton() + .type('foo') + .enter() + .tabkey() + .type('bar') + .enter() + .tabkey() + .type('baz') + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
      +
    • +

      bar

      +
        +
      • +

        baz

        +
      • +
      +
    • +
    +
  • +
+ `, + ) + .tabkey({ shift: true }) + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
      +
    • +

      bar

      +
    • +
    • +

      baz

      +
    • +
    +
  • +
+ `, + ) + .tabkey({ shift: true }).confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
      +
    • +

      bar

      +
    • +
    +
  • +
  • +

    baz

    +
  • +
+ `); + }); + // }); + }); +}); diff --git a/cypress/e2e/_old/markdown_widget_marks_spec.js b/cypress/e2e/_old/markdown_widget_marks_spec.js new file mode 100644 index 00000000..b6ad15ab --- /dev/null +++ b/cypress/e2e/_old/markdown_widget_marks_spec.js @@ -0,0 +1,31 @@ +describe('Markdown widget', () => { + describe('code mark', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + describe('toolbar button', () => { + it('can combine code mark with other marks', () => { + cy.clickItalicButton().type('foo').setSelection('oo').clickCodeButton() + .confirmMarkdownEditorContent(` +

+ f + + oo + +

+ `); + }); + }); + }); +}); diff --git a/cypress/e2e/_old/markdown_widget_quote_spec.js b/cypress/e2e/_old/markdown_widget_quote_spec.js new file mode 100644 index 00000000..1b138e44 --- /dev/null +++ b/cypress/e2e/_old/markdown_widget_quote_spec.js @@ -0,0 +1,370 @@ +describe('Markdown widget', () => { + describe('quote block', () => { + before(() => { + Cypress.config('defaultCommandTimeout', 4000); + cy.task('setupBackend', { backend: 'test' }); + }); + + beforeEach(() => { + cy.loginAndNewPost(); + cy.clearMarkdownEditorContent(); + }); + + after(() => { + cy.task('teardownBackend', { backend: 'test' }); + }); + + // describe('toggle quote', () => { + it('toggles empty quote block on and off in empty editor', () => { + cy + .clickQuoteButton() + .confirmMarkdownEditorContent( + ` +
+

+
+ `, + ) + .clickQuoteButton().confirmMarkdownEditorContent(` +

+ `); + }); + it('toggles empty quote block on and off for current block', () => { + cy + .focused() + .type('foo') + .clickQuoteButton() + .confirmMarkdownEditorContent( + ` +
+

foo

+
+ `, + ) + .clickQuoteButton().confirmMarkdownEditorContent(` +

foo

+ `); + }); + it('toggles entire quote block without expanded selection', () => { + cy.clickQuoteButton().type('foo').enter().type('bar').clickQuoteButton() + .confirmMarkdownEditorContent(` +

foo

+

bar

+ `); + }); + it('toggles entire quote block with complex content', () => { + cy + .clickQuoteButton() + .clickUnorderedListButton() + .clickHeadingOneButton() + .type('foo') + .enter({ times: 2 }) // First Enter creates new list item. Second Enter turns that list item into a default block. + .clickQuoteButton() // Unwrap the quote block. + .confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
  • +
+

+ `); + }); + it('toggles empty quote block on and off for selected blocks', () => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy + .focused() + .type('foo') + .enter() + .type('bar') + .setSelection('foo', 'bar') + .wait(500) + .clickQuoteButton() + .confirmMarkdownEditorContent( + ` +
+

foo

+

bar

+
+ `, + ) + .clickQuoteButton() + .confirmMarkdownEditorContent( + ` +

foo

+

bar

+ `, + ) + .clickQuoteButton().confirmMarkdownEditorContent(` +
+

foo

+

bar

+
+ `); + }); + it('toggles empty quote block on and off for partially selected blocks', () => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy + .focused() + .type('foo') + .enter() + .type('bar') + .setSelection('oo', 'ba') + .wait(500) + .clickQuoteButton() + .confirmMarkdownEditorContent( + ` +
+

foo

+

bar

+
+ `, + ) + .clickQuoteButton() + .confirmMarkdownEditorContent( + ` +

foo

+

bar

+ `, + ) + .clickQuoteButton().confirmMarkdownEditorContent(` +
+

foo

+

bar

+
+ `); + }); + it('toggles quote block on and off for multiple selected list items', () => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy + .focused() + .clickUnorderedListButton() + .type('foo') + .enter() + .type('bar') + .setSelection('foo', 'bar') + .wait(500) + .clickQuoteButton() + .confirmMarkdownEditorContent( + ` +
+
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
  • +
+
+ `, + ) + .clickQuoteButton() + .confirmMarkdownEditorContent( + ` +
    +
  • +

    foo

    +
  • +
  • +

    bar

    +
  • +
+ `, + ) + .setCursorAfter('bar') + .wait(500) + .enter() + .type('baz') + .setSelection('bar', 'baz') + .wait(500) + .clickQuoteButton().confirmMarkdownEditorContent(` +
    +
  • +

    foo

    +
  • +
+
+
    +
  • +

    bar

    +
  • +
  • +

    baz

    +
  • +
+
+ `); + }); + it('creates new quote block if parent is not a quote, can deeply nest', () => { + cy.clickQuoteButton() + .clickUnorderedListButton() + .clickQuoteButton() + .clickUnorderedListButton() + .clickQuoteButton() + .clickUnorderedListButton() + .clickQuoteButton() + .type('foo') + // Content should contains 4
tags and 3