From af942a27e41b47e257b6cd46c01a13cd381fed04 Mon Sep 17 00:00:00 2001 From: Thibaud Colas <thibaudcolas@gmail.com> Date: Fri, 4 Feb 2022 11:57:55 +0000 Subject: [PATCH] Reformat codebase with Prettier (#7912) - Automated reformatting - Manually change code where Prettier reformatting causes issues - Revert "Disable Prettier formatting in CI for now" --- .circleci/config.yml | 63 +- .eslintrc.js | 254 ++- .github/workflows/codeql-analysis.yml | 20 +- .github/workflows/test.yml | 55 +- .readthedocs.yml | 4 +- client/.storybook/main.js | 18 +- client/.storybook/preview.js | 4 +- client/scss/components/_breadcrumb.scss | 153 +- client/scss/components/_bulk_actions.scss | 195 +- client/scss/components/_button-select.scss | 26 +- client/scss/components/_button.scss | 1107 ++++++------ client/scss/components/_chooser.scss | 180 +- .../scss/components/_comments-controls.scss | 198 +- .../_comments-notification-dropdown.scss | 110 +- client/scss/components/_dropdown.legacy.scss | 587 +++--- client/scss/components/_dropdown.scss | 128 +- client/scss/components/_footer.scss | 290 +-- client/scss/components/_forms.scss | 694 +++---- client/scss/components/_grid.legacy.scss | 138 +- client/scss/components/_header.scss | 432 ++--- client/scss/components/_help-block.scss | 110 +- .../scss/components/_human-readable-date.scss | 30 +- client/scss/components/_icons.scss | 183 +- client/scss/components/_indicator.scss | 55 +- client/scss/components/_link.legacy.scss | 10 +- client/scss/components/_listing.scss | 1324 +++++++------- client/scss/components/_loading-mask.scss | 64 +- client/scss/components/_logo.scss | 190 +- client/scss/components/_main-nav.scss | 906 +++++----- .../scss/components/_media-placeholder.scss | 26 +- .../scss/components/_messages.capability.scss | 22 +- client/scss/components/_messages.scss | 134 +- client/scss/components/_messages.status.scss | 12 +- client/scss/components/_modals.scss | 178 +- .../scss/components/_privacy-indicator.scss | 32 +- client/scss/components/_progressbar.scss | 42 +- client/scss/components/_skiplink.scss | 24 +- client/scss/components/_status-tag.scss | 66 +- client/scss/components/_switch.scss | 173 +- client/scss/components/_tabs.scss | 250 +-- client/scss/components/_tag.scss | 82 +- client/scss/components/_tooltips.scss | 120 +- client/scss/components/_workflow-tasks.scss | 68 +- client/scss/components/browser-message.scss | 6 +- client/scss/core.scss | 8 - client/scss/elements/_elements.scss | 44 +- client/scss/elements/_forms.scss | 351 ++-- client/scss/elements/_root.scss | 38 +- client/scss/elements/_typography.scss | 84 +- client/scss/objects/_avatar.scss | 68 +- client/scss/objects/_objects.scss | 19 +- client/scss/overrides/_pages.homepage.scss | 2 +- .../scss/overrides/_pages.page-explorer.scss | 2 +- .../scss/overrides/_utilities.dropdowns.scss | 69 +- client/scss/overrides/_utilities.focus.scss | 4 +- client/scss/overrides/_utilities.hidden.scss | 38 +- client/scss/overrides/_utilities.legacy.scss | 41 +- .../overrides/_utilities.text.legacy.scss | 2 +- client/scss/overrides/_utilities.text.scss | 6 +- .../overrides/_utilities.visuallyhidden.scss | 4 +- .../overrides/_vendor.datetimepicker.scss | 535 +++--- client/scss/overrides/_vendor.tagit.scss | 47 +- client/scss/settings/_variables.icons.scss | 172 +- client/scss/settings/_variables.scss | 41 +- client/scss/sidebar.scss | 7 - client/scss/tools/_functions.breakpoints.scss | 22 +- client/scss/tools/_mixins.breakpoints.scss | 29 +- client/scss/tools/_mixins.general.scss | 132 +- client/scss/tools/_mixins.grid.scss | 68 +- client/scss/tools/_various.colors.scss | 96 +- client/src/api/admin.test.js | 15 +- client/src/api/admin.ts | 37 +- client/src/api/client.js | 25 +- client/src/components/Button/Button.test.js | 8 +- client/src/components/Button/Button.tsx | 8 +- .../CommentApp/__fixtures__/state.tsx | 11 +- .../components/CommentApp/actions/comments.ts | 20 +- .../components/CommentApp/actions/settings.ts | 2 +- .../components/Comment/index.stories.tsx | 7 +- .../CommentApp/components/Comment/index.tsx | 185 +- .../CommentApp/components/Comment/style.scss | 274 +-- .../components/CommentHeader/index.tsx | 117 +- .../components/CommentHeader/style.scss | 233 +-- .../components/CommentReply/index.stories.tsx | 3 +- .../components/CommentReply/index.tsx | 116 +- .../components/CommentReply/style.scss | 198 +- .../CommentApp/components/TextArea/index.tsx | 98 +- client/src/components/CommentApp/main.scss | 196 +- client/src/components/CommentApp/main.tsx | 145 +- .../components/CommentApp/selectors/index.ts | 33 +- .../CommentApp/selectors/selectors.test.ts | 117 +- .../CommentApp/state/comments.test.ts | 24 +- .../components/CommentApp/state/comments.ts | 271 +-- .../components/CommentApp/state/settings.ts | 21 +- .../src/components/CommentApp/utils/layout.ts | 37 +- .../components/CommentApp/utils/storybook.tsx | 42 +- .../CommentableEditor/CommentableEditor.scss | 46 +- .../CommentableEditor.test.tsx | 30 +- .../CommentableEditor/CommentableEditor.tsx | 629 ++++--- client/src/components/Draftail/DraftUtils.js | 20 +- .../components/Draftail/DraftUtils.test.js | 9 +- client/src/components/Draftail/Draftail.scss | 120 +- .../EditorFallback/EditorFallback.scss | 18 +- .../EditorFallback/EditorFallback.test.js | 16 +- .../components/Draftail/Tooltip/Tooltip.js | 32 +- .../components/Draftail/Tooltip/Tooltip.scss | 130 +- .../Draftail/Tooltip/Tooltip.test.js | 12 +- .../components/Draftail/blocks/EmbedBlock.js | 13 +- .../Draftail/blocks/EmbedBlock.scss | 4 +- .../Draftail/blocks/EmbedBlock.test.js | 8 +- .../components/Draftail/blocks/ImageBlock.js | 13 +- .../Draftail/blocks/ImageBlock.scss | 40 +- .../Draftail/blocks/ImageBlock.test.js | 12 +- .../components/Draftail/blocks/MediaBlock.js | 10 +- .../Draftail/blocks/MediaBlock.scss | 72 +- .../Draftail/blocks/MediaBlock.test.js | 21 +- .../Draftail/decorators/Document.js | 11 +- .../Draftail/decorators/Document.test.js | 64 +- .../components/Draftail/decorators/Link.js | 15 +- .../Draftail/decorators/Link.test.js | 37 +- .../Draftail/decorators/TooltipEntity.js | 22 +- .../Draftail/decorators/TooltipEntity.scss | 26 +- .../Draftail/decorators/TooltipEntity.test.js | 116 +- client/src/components/Draftail/index.js | 61 +- client/src/components/Draftail/index.test.js | 34 +- .../Draftail/sources/ModalWorkflowSource.js | 62 +- .../sources/ModalWorkflowSource.test.js | 337 ++-- client/src/components/Explorer/Explorer.scss | 322 ++-- .../src/components/Explorer/Explorer.test.js | 14 +- client/src/components/Explorer/Explorer.tsx | 30 +- .../Explorer/ExplorerHeader.test.js | 19 +- .../components/Explorer/ExplorerHeader.tsx | 48 +- .../src/components/Explorer/ExplorerItem.scss | 126 +- .../components/Explorer/ExplorerItem.test.js | 2 +- .../src/components/Explorer/ExplorerItem.tsx | 39 +- .../components/Explorer/ExplorerPanel.test.js | 135 +- .../src/components/Explorer/ExplorerPanel.tsx | 38 +- .../Explorer/ExplorerToggle.test.js | 16 +- .../components/Explorer/ExplorerToggle.tsx | 18 +- client/src/components/Explorer/PageCount.tsx | 15 +- client/src/components/Explorer/actions.ts | 102 +- client/src/components/Explorer/index.test.js | 3 +- client/src/components/Explorer/index.tsx | 48 +- .../Explorer/reducers/explorer.test.js | 15 +- .../components/Explorer/reducers/explorer.ts | 45 +- .../src/components/Explorer/reducers/index.ts | 4 +- .../Explorer/reducers/nodes.test.js | 21 +- .../src/components/Explorer/reducers/nodes.ts | 177 +- client/src/components/Hallo/_halloeditor.scss | 140 +- client/src/components/Hallo/_halloembed.scss | 28 +- .../src/components/Hallo/_hallotoolbar.scss | 40 +- .../src/components/Hallo/_richtext-image.scss | 26 +- client/src/components/Icon/Icon.tsx | 12 +- .../LoadingSpinner/LoadingSpinner.js | 3 +- .../LoadingSpinner/LoadingSpinner.scss | 9 +- .../src/components/PageExplorer/PageCount.tsx | 10 +- .../components/PageExplorer/PageExplorer.scss | 269 ++- .../PageExplorer/PageExplorer.test.js | 14 +- .../components/PageExplorer/PageExplorer.tsx | 32 +- .../PageExplorer/PageExplorerHeader.test.js | 19 +- .../PageExplorer/PageExplorerHeader.tsx | 50 +- .../PageExplorer/PageExplorerItem.scss | 126 +- .../PageExplorer/PageExplorerItem.test.js | 2 +- .../PageExplorer/PageExplorerItem.tsx | 41 +- .../PageExplorer/PageExplorerPanel.test.js | 111 +- .../PageExplorer/PageExplorerPanel.tsx | 17 +- client/src/components/PageExplorer/actions.ts | 102 +- client/src/components/PageExplorer/index.tsx | 24 +- .../PageExplorer/reducers/explorer.test.js | 15 +- .../PageExplorer/reducers/explorer.ts | 44 +- .../components/PageExplorer/reducers/index.ts | 9 +- .../PageExplorer/reducers/nodes.test.js | 21 +- .../components/PageExplorer/reducers/nodes.ts | 177 +- client/src/components/Portal/Portal.js | 3 +- client/src/components/Portal/Portal.test.js | 8 +- .../PublicationStatus/PublicationStatus.scss | 8 +- .../PublicationStatus.test.js | 40 +- client/src/components/Sidebar/Sidebar.scss | 191 +- .../components/Sidebar/Sidebar.stories.tsx | 91 +- client/src/components/Sidebar/Sidebar.tsx | 49 +- .../src/components/Sidebar/SidebarPanel.scss | 90 +- .../src/components/Sidebar/SidebarPanel.tsx | 20 +- client/src/components/Sidebar/index.tsx | 6 +- .../components/Sidebar/menu/LinkMenuItem.tsx | 35 +- .../src/components/Sidebar/menu/MenuItem.scss | 151 +- .../src/components/Sidebar/menu/MenuItem.tsx | 1 - .../Sidebar/menu/PageExplorerMenuItem.tsx | 50 +- .../components/Sidebar/menu/SubMenuItem.scss | 176 +- .../components/Sidebar/menu/SubMenuItem.tsx | 53 +- .../components/Sidebar/modules/MainMenu.scss | 310 ++-- .../components/Sidebar/modules/MainMenu.tsx | 66 +- .../components/Sidebar/modules/Search.scss | 126 +- .../src/components/Sidebar/modules/Search.tsx | 50 +- .../Sidebar/modules/WagtailBranding.scss | 242 +-- .../Sidebar/modules/WagtailBranding.tsx | 75 +- .../components/StreamField/StreamField.scss | 6 +- .../StreamField/blocks/BaseSequenceBlock.js | 148 +- .../StreamField/blocks/FieldBlock.js | 47 +- .../StreamField/blocks/FieldBlock.test.js | 51 +- .../StreamField/blocks/ListBlock.js | 52 +- .../StreamField/blocks/ListBlock.test.js | 121 +- .../StreamField/blocks/StaticBlock.test.js | 41 +- .../StreamField/blocks/StreamBlock.js | 102 +- .../StreamField/blocks/StreamBlock.test.js | 380 ++-- .../StreamField/blocks/StructBlock.js | 48 +- .../StreamField/blocks/StructBlock.test.js | 91 +- .../StreamField/scss/_variables.scss | 5 +- .../scss/components/c-sf-add-button.scss | 82 +- .../scss/components/c-sf-add-panel.scss | 53 +- .../scss/components/c-sf-block.scss | 300 ++-- .../scss/components/c-sf-button.scss | 65 +- .../scss/components/c-sf-container.scss | 168 +- .../src/components/Transition/Transition.scss | 50 +- .../components/Transition/Transition.test.js | 4 +- .../components/UpgradeNotification/index.js | 53 +- client/src/custom.d.ts | 48 +- client/src/entrypoints/admin/bulk-actions.js | 113 +- client/src/entrypoints/admin/collapsible.js | 7 +- client/src/entrypoints/admin/comments.js | 148 +- client/src/entrypoints/admin/core.js | 160 +- .../entrypoints/admin/date-time-chooser.js | 144 +- client/src/entrypoints/admin/draftail.test.js | 9 +- .../entrypoints/admin/expanding-formset.js | 4 +- .../admin/expanding-formset.test.js | 96 +- .../src/entrypoints/admin/filtered-select.js | 7 +- .../src/entrypoints/admin/hallo-bootstrap.js | 61 +- .../admin/hallo-plugins/hallo-hr.js | 6 +- .../hallo-plugins/hallo-requireparagraphs.js | 11 +- .../admin/hallo-plugins/hallo-wagtaillink.js | 26 +- .../entrypoints/admin/lock-unlock-action.js | 48 +- .../src/entrypoints/admin/modal-workflow.js | 12 +- .../entrypoints/admin/modal-workflow.test.js | 10 +- .../entrypoints/admin/page-chooser-modal.js | 4 +- client/src/entrypoints/admin/page-chooser.js | 6 +- client/src/entrypoints/admin/page-editor.js | 149 +- .../src/entrypoints/admin/privacy-switch.js | 29 +- .../entrypoints/admin/sidebar-legacy.test.js | 3 +- client/src/entrypoints/admin/sidebar.js | 25 +- .../entrypoints/admin/task-chooser-modal.js | 24 +- client/src/entrypoints/admin/task-chooser.js | 4 +- .../src/entrypoints/admin/telepath/blocks.js | 37 +- .../src/entrypoints/admin/telepath/widgets.js | 399 +++-- .../admin/telepath/widgets.test.js | 331 ++-- client/src/entrypoints/admin/userbar.js | 90 +- .../src/entrypoints/admin/workflow-action.js | 175 +- .../entrypoints/contrib/table_block/table.js | 43 +- .../contrib/table_block/table.test.js | 61 +- .../typed_table_block/typed_table_block.js | 117 +- .../typed_table_block.test.js | 72 +- .../documents/document-chooser-telepath.js | 5 +- .../entrypoints/documents/document-chooser.js | 4 +- .../src/entrypoints/images/image-chooser.js | 6 +- .../snippets/snippet-chooser-telepath.js | 5 +- .../entrypoints/snippets/snippet-chooser.js | 1 - client/src/includes/initIE11Warning.js | 6 +- client/src/includes/initSubmenus.js | 12 +- client/src/utils/actions.ts | 2 +- client/src/utils/cleanForSlug.js | 10 +- client/src/utils/cleanForSlug.test.js | 19 +- client/src/utils/focus.js | 2 +- client/src/utils/focus.test.js | 12 +- client/src/utils/performance.js | 4 +- client/src/utils/version.js | 5 +- client/tests/integration/.eslintrc.js | 4 +- .../tests/integration/PuppeteerEnvironment.js | 3 +- client/tests/integration/editbird.test.js | 6 +- client/tests/integration/editor.test.js | 4 +- client/tests/integration/groups.test.js | 2 +- client/tests/integration/jest.config.js | 5 +- client/tests/integration/listing.test.js | 7 +- client/tests/integration/users.test.js | 2 +- client/tests/mock-fetch.js | 32 +- client/tests/stubs.js | 15 +- client/webpack.config.js | 373 +++- docs/_static/css/custom.css | 86 +- docs/_static/css/docsearch.overrides.css | 12 +- docs/_static/js/banner.js | 29 +- tsconfig.json | 27 +- .../static_src/wagtailadmin/css/normalize.css | 200 +-- .../wagtailadmin/fonts/wagtail-icomoon.json | 102 +- .../wagtailadmin/scss/layouts/404.scss | 97 +- .../wagtailadmin/scss/layouts/account.scss | 58 +- .../scss/layouts/compare-revisions.scss | 98 +- .../wagtailadmin/scss/layouts/home.scss | 266 +-- .../wagtailadmin/scss/layouts/login.scss | 260 +-- .../scss/layouts/page-editor.scss | 1162 ++++++------ .../wagtailadmin/scss/layouts/report.scss | 114 +- .../scss/layouts/workflow-edit.scss | 34 +- .../scss/layouts/workflow-progress.scss | 96 +- .../static_src/wagtailadmin/scss/userbar.scss | 505 +++--- .../wagtailmodeladmin/js/prepopulate.js | 92 +- .../scss/breadcrumbs_page.scss | 8 +- .../scss/choose_parent_page.scss | 4 +- .../wagtailmodeladmin/scss/index.scss | 300 ++-- .../redirects/tests/files/example.json | 24 +- .../wagtailsettings/js/site-switcher.js | 20 +- .../wagtailstyleguide/scss/styleguide.scss | 328 ++-- .../scss/typed_table_block.scss | 150 +- .../static_src/wagtaildocs/js/add-multiple.js | 318 ++-- .../wagtaildocs/js/document-chooser-modal.js | 247 +-- .../js/hallo-plugins/hallo-wagtaildoclink.js | 103 +- .../wagtaildocs/scss/add-multiple.scss | 144 +- .../wagtailembeds/js/embed-chooser-modal.js | 38 +- .../js/hallo-plugins/hallo-wagtailembeds.js | 102 +- .../wagtailimages/js/add-multiple.js | 370 ++-- .../wagtailimages/js/focal-point-chooser.js | 158 +- .../js/hallo-plugins/hallo-wagtailimage.js | 96 +- .../wagtailimages/js/image-chooser-modal.js | 345 ++-- .../wagtailimages/js/image-url-generator.js | 144 +- .../wagtailimages/scss/add-multiple.scss | 262 +-- .../scss/focal-point-chooser.scss | 28 +- .../wagtailsearch/js/query-chooser-modal.js | 140 +- .../wagtailsearch/queries/chooser_field.js | 26 +- .../js/snippet-chooser-modal.js | 112 +- .../js/snippet-multiple-select.js | 188 +- wagtail/tests/customuser/fixtures/test.json | 181 +- .../fixtures/test_explorable_pages.json | 160 +- .../customuser/fixtures/test_specific.json | 31 +- wagtail/tests/demosite/fixtures/demosite.json | 1596 ++++++++--------- wagtail/tests/emailuser/fixtures/test.json | 221 ++- .../fixtures/test_explorable_pages.json | 194 +- .../emailuser/fixtures/test_specific.json | 29 +- .../fixtures/modeladmintest_test.json | 290 +-- wagtail/tests/testapp/fixtures/test.json | 1241 +++++++------ .../fixtures/test_explorable_pages.json | 522 +++--- .../tests/testapp/fixtures/test_specific.json | 324 ++-- .../static_src/wagtailusers/js/group-form.js | 24 +- .../wagtailusers/scss/groups_edit.scss | 45 +- 328 files changed, 18951 insertions(+), 17086 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b37e488d24..5954f27f7e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,9 +12,9 @@ jobs: key: pipenv-v1-{{ checksum "setup.py" }} # Only install if .venv wasn’t cached. - run: | - if [[ ! -e ".venv" ]]; then - pipenv install -e .[testing] - fi + if [[ ! -e ".venv" ]]; then + pipenv install -e .[testing] + fi - save_cache: key: pipenv-v1-{{ checksum "setup.py" }} paths: @@ -39,9 +39,9 @@ jobs: key: frontend-v1-{{ checksum "package-lock.json" }} # Only install if node_modules wasn’t cached. - run: | - if [[ ! -e "node_modules" ]]; then - npm install --no-save --no-optional --no-audit --no-fund --progress=false - fi + if [[ ! -e "node_modules" ]]; then + npm install --no-save --no-optional --no-audit --no-fund --progress=false + fi - save_cache: paths: - node_modules @@ -54,8 +54,7 @@ jobs: - wagtail - run: npm run lint:js - run: npm run lint:css - # TODO Remove || true and enforce a successful exit code. - - run: npm run lint:format || true + - run: npm run lint:format - run: npm run test:unit:coverage -- --runInBand - run: bash <(curl -s https://codecov.io/bash) -F frontend @@ -73,9 +72,9 @@ jobs: key: pipenv-v1-{{ checksum "setup.py" }} # Only install if .venv wasn’t cached. - run: | - if [[ ! -e ".venv" ]]; then - pipenv install -e .[testing] - fi + if [[ ! -e ".venv" ]]; then + pipenv install -e .[testing] + fi - save_cache: key: pipenv-v1-{{ checksum "setup.py" }} paths: @@ -84,9 +83,9 @@ jobs: key: ui_tests-npm_integration-v1-{{ checksum "client/tests/integration/package-lock.json" }} # Only install if node_modules wasn’t cached. - run: | - if [[ ! -e "client/tests/integration/node_modules" ]]; then - npm --prefix ./client/tests/integration ci - fi + if [[ ! -e "client/tests/integration/node_modules" ]]; then + npm --prefix ./client/tests/integration ci + fi - save_cache: key: ui_tests-npm_integration-v1-{{ checksum "client/tests/integration/package-lock.json" }} paths: @@ -123,22 +122,22 @@ jobs: - run: python scripts/nightly/upload.py workflows: - version: 2 - test: - jobs: - - backend - - frontend - - ui_tests: - requires: - - frontend + version: 2 + test: + jobs: + - backend + - frontend + - ui_tests: + requires: + - frontend - nightly: - jobs: - - nightly-build - triggers: - - schedule: - cron: "0 0 * * *" - filters: - branches: - only: - - main + nightly: + jobs: + - nightly-build + triggers: + - schedule: + cron: '0 0 * * *' + filters: + branches: + only: + - main diff --git a/.eslintrc.js b/.eslintrc.js index 6dc5a277f3..1ff41cdbc4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,83 +1,81 @@ // Rules which have been enforced in configuration upgrades and flag issues in existing code. // We need to consider whether to disable those rules permanently, or fix the issues. const legacyCode = { - "class-methods-use-this": "off", - "constructor-super": "off", - "default-param-last": "off", - "import/extensions": "off", - "import/first": "off", - "import/newline-after-import": "off", - "import/no-extraneous-dependencies": "off", - "import/no-unresolved": "off", - "import/no-useless-path-segments": "off", - "import/order": "off", - "import/prefer-default-export": "off", - "jsx-a11y/alt-text": "off", - "jsx-a11y/anchor-is-valid": "off", - "jsx-a11y/click-events-have-key-events": "off", - "jsx-a11y/interactive-supports-focus": "off", - "jsx-a11y/no-noninteractive-element-interactions": "off", - "jsx-a11y/role-supports-aria-props": "off", - "lines-between-class-members": "off", - "max-classes-per-file": "off", - "no-await-in-loop": "off", - "no-continue": "off", - "no-else-return": "off", - "no-extra-boolean-cast": "off", - "no-import-assign": "off", - "no-lonely-if": "off", - "no-plusplus": "off", - "no-prototype-builtins": "off", - "no-restricted-syntax": "off", - "no-this-before-super": "off", - "operator-assignment": "off", - "prefer-destructuring": "off", - "prefer-object-spread": "off", - "prefer-promise-reject-errors": "off", - "react-hooks/exhaustive-deps": "off", - "react-hooks/rules-of-hooks": "off", - "react/button-has-type": "off", - "react/destructuring-assignment": "off", - "react/forbid-prop-types": "off", - "react/function-component-definition": "off", - "react/jsx-curly-brace-presence": "off", - "react/jsx-filename-extension": "off", - "react/jsx-no-useless-fragment": "off", - "react/jsx-props-no-spreading": "off", - "react/no-danger": "off", - "react/no-deprecated": "off", - "react/require-default-props": "off", -} + 'class-methods-use-this': 'off', + 'constructor-super': 'off', + 'default-param-last': 'off', + 'import/extensions': 'off', + 'import/first': 'off', + 'import/newline-after-import': 'off', + 'import/no-extraneous-dependencies': 'off', + 'import/no-unresolved': 'off', + 'import/no-useless-path-segments': 'off', + 'import/order': 'off', + 'import/prefer-default-export': 'off', + 'jsx-a11y/alt-text': 'off', + 'jsx-a11y/anchor-is-valid': 'off', + 'jsx-a11y/click-events-have-key-events': 'off', + 'jsx-a11y/interactive-supports-focus': 'off', + 'jsx-a11y/no-noninteractive-element-interactions': 'off', + 'jsx-a11y/role-supports-aria-props': 'off', + 'lines-between-class-members': 'off', + 'max-classes-per-file': 'off', + 'no-await-in-loop': 'off', + 'no-continue': 'off', + 'no-else-return': 'off', + 'no-extra-boolean-cast': 'off', + 'no-import-assign': 'off', + 'no-lonely-if': 'off', + 'no-plusplus': 'off', + 'no-prototype-builtins': 'off', + 'no-restricted-syntax': 'off', + 'no-this-before-super': 'off', + 'operator-assignment': 'off', + 'prefer-destructuring': 'off', + 'prefer-object-spread': 'off', + 'prefer-promise-reject-errors': 'off', + 'react-hooks/exhaustive-deps': 'off', + 'react-hooks/rules-of-hooks': 'off', + 'react/button-has-type': 'off', + 'react/destructuring-assignment': 'off', + 'react/forbid-prop-types': 'off', + 'react/function-component-definition': 'off', + 'react/jsx-curly-brace-presence': 'off', + 'react/jsx-filename-extension': 'off', + 'react/jsx-no-useless-fragment': 'off', + 'react/jsx-props-no-spreading': 'off', + 'react/no-danger': 'off', + 'react/no-deprecated': 'off', + 'react/require-default-props': 'off', +}; module.exports = { - "extends": [ - "@wagtail/eslint-config-wagtail", - "plugin:@typescript-eslint/recommended" + extends: [ + '@wagtail/eslint-config-wagtail', + 'plugin:@typescript-eslint/recommended', ], - "parser": "@typescript-eslint/parser", - "plugins": [ - "@typescript-eslint" - ], - "env": { - "jest": true, - "browser": true, + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + env: { + jest: true, + browser: true, }, - "rules": { - "no-underscore-dangle": ["error", { "allow": ["__REDUX_DEVTOOLS_EXTENSION__"] }], - // note you must disable the base rule as it can report incorrect errors - "no-use-before-define": "off", - "@typescript-eslint/no-use-before-define": ["error"], - - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/explicit-member-accessibility": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-explicit-any": "off", - 'react/jsx-filename-extension': [ - "error", - { extensions: ['.js', '.tsx'] }, + rules: { + 'no-underscore-dangle': [ + 'error', + { allow: ['__REDUX_DEVTOOLS_EXTENSION__'] }, ], + // note you must disable the base rule as it can report incorrect errors + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': ['error'], + + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'react/jsx-filename-extension': ['error', { extensions: ['.js', '.tsx'] }], 'import/extensions': [ - "error", + 'error', 'always', { ignorePackages: true, @@ -91,66 +89,66 @@ module.exports = { ], ...legacyCode, }, - "overrides": [ + overrides: [ { // Rules we don’t want to enforce for test and tooling code. - "files": ["*.test.ts", "*.test.tsx", "*.test.js", "webpack.config.js"], - "rules": { - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-var-requires": "off" - } - }, - { - "files": ["docs/_static/**"], - "globals": { "$": "readonly" } - }, - { - "files": ["wagtail/**/**"], - "globals": { - "$": "readonly", - "addMessage": "readonly", - "buildExpandingFormset": "readonly", - "cancelSpinner": "readonly", - "escapeHtml": "readonly", - "insertRichTextDeleteControl": "readonly", - "jQuery": "readonly", - "jsonData": "readonly", - "ModalWorkflow": "readonly", - "DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS": "writable", - "EMBED_CHOOSER_MODAL_ONLOAD_HANDLERS": "writable", - "IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS": "writable", - "QUERY_CHOOSER_MODAL_ONLOAD_HANDLERS": "writable", - "SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS": "writable" + files: ['*.test.ts', '*.test.tsx', '*.test.js', 'webpack.config.js'], + rules: { + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-var-requires': 'off', }, - "rules": { - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-use-before-define": "off", - "camelcase": [ - "error", + }, + { + files: ['docs/_static/**'], + globals: { $: 'readonly' }, + }, + { + files: ['wagtail/**/**'], + globals: { + $: 'readonly', + addMessage: 'readonly', + buildExpandingFormset: 'readonly', + cancelSpinner: 'readonly', + escapeHtml: 'readonly', + insertRichTextDeleteControl: 'readonly', + jQuery: 'readonly', + jsonData: 'readonly', + ModalWorkflow: 'readonly', + DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS: 'writable', + EMBED_CHOOSER_MODAL_ONLOAD_HANDLERS: 'writable', + IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS: 'writable', + QUERY_CHOOSER_MODAL_ONLOAD_HANDLERS: 'writable', + SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS: 'writable', + }, + rules: { + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-use-before-define': 'off', + 'camelcase': [ + 'error', { - "allow": [ - "__unused_webpack_module", - "__webpack_modules__", - "__webpack_require__" + allow: [ + '__unused_webpack_module', + '__webpack_modules__', + '__webpack_require__', ], - "properties": "never" - } + properties: 'never', + }, ], - "consistent-return": "off", - "func-names": "off", - "id-length": "off", - "indent": "off", - "key-spacing": "off", - "new-cap": "off", - "newline-per-chained-call": "off", - "no-param-reassign": "off", - "no-underscore-dangle": "off", - "object-shorthand": "off", - "prefer-arrow-callback": "off", - "quote-props": "off", - "space-before-function-paren": "off", - "vars-on-top": "off" - } - } - ] -} + 'consistent-return': 'off', + 'func-names': 'off', + 'id-length': 'off', + 'indent': 'off', + 'key-spacing': 'off', + 'new-cap': 'off', + 'newline-per-chained-call': 'off', + 'no-param-reassign': 'off', + 'no-underscore-dangle': 'off', + 'object-shorthand': 'off', + 'prefer-arrow-callback': 'off', + 'quote-props': 'off', + 'space-before-function-paren': 'off', + 'vars-on-top': 'off', + }, + }, + ], +}; diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6d57af5599..d891c9e07c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,4 +1,4 @@ -name: "CodeQL" +name: 'CodeQL' on: schedule: @@ -13,16 +13,16 @@ jobs: fail-fast: false matrix: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - language: [ 'javascript', 'python' ] + language: ['javascript', 'python'] steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dcae2c5065..bf08bafce9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,11 +3,10 @@ name: Wagtail CI on: push: paths-ignore: - - 'docs/**' + - 'docs/**' pull_request: paths-ignore: - - 'docs/**' - + - 'docs/**' # Our test suite should cover: # - all supported databases against current Python and Django @@ -36,8 +35,8 @@ jobs: strategy: matrix: include: - - python: "3.9" - django: "Django>=4.0,<4.1" + - python: '3.9' + django: 'Django>=4.0,<4.1' steps: - uses: actions/checkout@v2 @@ -62,22 +61,22 @@ jobs: strategy: matrix: include: - - python: "3.7" - django: "Django>=3.2,<3.3" + - python: '3.7' + django: 'Django>=3.2,<3.3' experimental: false - - python: "3.10" - django: "Django>=4.0,<4.1" + - python: '3.10' + django: 'Django>=4.0,<4.1' notz: notz experimental: false - - python: "3.10" - django: "Django>=4.0,<4.1" + - python: '3.10' + django: 'Django>=4.0,<4.1' experimental: false emailuser: emailuser - - python: "3.10" - django: "git+https://github.com/django/django.git@stable/4.0.x#egg=Django" + - python: '3.10' + django: 'git+https://github.com/django/django.git@stable/4.0.x#egg=Django' experimental: true - - python: "3.10" - django: "git+https://github.com/django/django.git@main#egg=Django" + - python: '3.10' + django: 'git+https://github.com/django/django.git@main#egg=Django' experimental: true services: @@ -116,11 +115,11 @@ jobs: strategy: matrix: include: - - python: "3.8" - django: "Django>=3.2,<3.3" + - python: '3.8' + django: 'Django>=3.2,<3.3' experimental: false - - python: "3.9" - django: "Django>=4.0,<4.1" + - python: '3.9' + django: 'Django>=4.0,<4.1' experimental: false services: @@ -150,7 +149,7 @@ jobs: ./runtests.py env: DATABASE_ENGINE: django.db.backends.mysql - DATABASE_HOST: "127.0.0.1" + DATABASE_HOST: '127.0.0.1' DATABASE_USER: root # https://github.com/elastic/elastic-github-actions doesn't work for Elasticsearch 5, @@ -160,8 +159,8 @@ jobs: strategy: matrix: include: - - python: "3.7" - django: "Django>=3.2,<3.3" + - python: '3.7' + django: 'Django>=3.2,<3.3' steps: - name: Configure sysctl limits run: | @@ -200,8 +199,8 @@ jobs: strategy: matrix: include: - - python: "3.9" - django: "Django>=4.0,<4.1" + - python: '3.9' + django: 'Django>=4.0,<4.1' emailuser: emailuser steps: - name: Configure sysctl limits @@ -244,8 +243,8 @@ jobs: strategy: matrix: include: - - python: "3.7" - django: "Django>=3.2,<3.3" + - python: '3.7' + django: 'Django>=3.2,<3.3' services: postgres: @@ -293,8 +292,8 @@ jobs: strategy: matrix: include: - - python: "3.8" - django: "Django>=4.0,<4.1" + - python: '3.8' + django: 'Django>=4.0,<4.1' experimental: false services: diff --git a/.readthedocs.yml b/.readthedocs.yml index 6e892b94b9..369c645dcb 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,4 +1,4 @@ python: - version: 3.7 - pip_install: true + version: 3.7 + pip_install: true requirements_file: null diff --git a/client/.storybook/main.js b/client/.storybook/main.js index 3e81d1f93b..4a6c4be52e 100644 --- a/client/.storybook/main.js +++ b/client/.storybook/main.js @@ -1,9 +1,7 @@ module.exports = { - "stories": [ - "../src/**/*.stories.@(js|jsx|ts|tsx)" - ], - "core": { - "builder": "webpack5" + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + core: { + builder: 'webpack5', }, webpackFinal: (config) => { config.resolve.fallback.crypto = false; @@ -12,17 +10,17 @@ module.exports = { { test: /\.(scss|css)$/, use: [ - "style-loader", - "css-loader", + 'style-loader', + 'css-loader', { - loader: "postcss-loader", + loader: 'postcss-loader', options: { postcssOptions: { - plugins: ["autoprefixer", "cssnano"], + plugins: ['autoprefixer', 'cssnano'], }, }, }, - "sass-loader", + 'sass-loader', ], }, ]; diff --git a/client/.storybook/preview.js b/client/.storybook/preview.js index c9310b152a..ab2594549b 100644 --- a/client/.storybook/preview.js +++ b/client/.storybook/preview.js @@ -4,11 +4,11 @@ import '../../wagtail/admin/static_src/wagtailadmin/scss/core.scss'; import '../../wagtail/admin/static_src/wagtailadmin/scss/sidebar.scss'; export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, + actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, -} +}; diff --git a/client/scss/components/_breadcrumb.scss b/client/scss/components/_breadcrumb.scss index 50d801dbf9..fda939ff1b 100644 --- a/client/scss/components/_breadcrumb.scss +++ b/client/scss/components/_breadcrumb.scss @@ -1,90 +1,89 @@ .breadcrumb { - @include unlist(); - @include clearfix(); - padding-top: 1.4em; - font-size: 0.85em; - line-height: 1.5em; - margin-left: 2em; - background: $color-teal; + @include unlist(); + @include clearfix(); + padding-top: 1.4em; + font-size: 0.85em; + line-height: 1.5em; + margin-left: 2em; + background: $color-teal; - li, - .breadcrumb-item { - display: block; - float: left; - position: relative; - text-decoration: none; - white-space: nowrap; - padding: 4px; + li, + .breadcrumb-item { + display: block; + float: left; + position: relative; + text-decoration: none; + white-space: nowrap; + padding: 4px; - &:hover { - background: $color-teal-dark; - } - - &.breadcrumb-dropdown { - padding: 0; - font-size: initial; - } - - .t-default .u-btn-current { - color: inherit; - background: rgba(0, 91, 94, 0.6); - font-size: 1.15em; - border: 0; - line-height: 1.6; - } + &:hover { + background: $color-teal-dark; } - li > a, - .breadcrumb-link { - color: $color-white; - display: block; - padding: calc(0.5em - 4px) 1em; - white-space: nowrap; - line-height: 1.6em; - - &:hover { - background: $color-teal-dark; - color: $color-white; - - .arrow_right_icon { - color: $color-teal; - } - } - - .title { - display: inline-block; - max-width: 11.8em; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - vertical-align: bottom; - } + &.breadcrumb-dropdown { + padding: 0; + font-size: initial; } + .t-default .u-btn-current { + color: inherit; + background: rgba(0, 91, 94, 0.6); + font-size: 1.15em; + border: 0; + line-height: 1.6; + } + } + + li > a, + .breadcrumb-link { + color: $color-white; + display: block; + padding: calc(0.5em - 4px) 1em; + white-space: nowrap; + line-height: 1.6em; + + &:hover { + background: $color-teal-dark; + color: $color-white; + + .arrow_right_icon { + color: $color-teal; + } + } + + .title { + display: inline-block; + max-width: 11.8em; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + vertical-align: bottom; + } + } + + .home_icon { + @include svg-icon(1em); + transform: scale(1.5) translate(0, 0.1em); + } + + .arrow_right_icon { + @include svg-icon(1em); + color: $color-teal-dark; + transform: scale(1.75) translate(0.3em, 0.1em); + } + + @include media-breakpoint-up(sm) { + padding-top: 0; + background: $color-teal-darker; + margin-left: -($desktop-nice-padding); + margin-right: -($desktop-nice-padding); + .home_icon { - @include svg-icon(1em); - transform: scale(1.5) translate(0, 0.1em); + margin-left: 1.25em; } .arrow_right_icon { - @include svg-icon(1em); - color: $color-teal-dark; - transform: scale(1.75) translate(0.3em, 0.1em); - } - - @include media-breakpoint-up(sm) { - padding-top: 0; - background: $color-teal-darker; - margin-left: -($desktop-nice-padding); - margin-right: -($desktop-nice-padding); - - .home_icon { - margin-left: 1.25em; - } - - .arrow_right_icon { - color: $color-teal; - } + color: $color-teal; } + } } - diff --git a/client/scss/components/_bulk_actions.scss b/client/scss/components/_bulk_actions.scss index ec49c37a05..5ffc1d272e 100644 --- a/client/scss/components/_bulk_actions.scss +++ b/client/scss/components/_bulk_actions.scss @@ -1,109 +1,108 @@ .bulk-actions-filter-checkbox { - .table-headers & { - > div { - display: flex; - align-items: center; - } - - .c-dropdown__button { - padding-left: 0.3rem; - } - - .bulk-actions-choices, - .bulk-actions-choices > ul { - display: flex; - align-items: center; - } - - .bulk-actions-choices li { - margin: 0 0.5em; - } - - .bulk-actions-choices span { - text-transform: none; - } + .table-headers & { + > div { + display: flex; + align-items: center; } -} + .c-dropdown__button { + padding-left: 0.3rem; + } + + .bulk-actions-choices, + .bulk-actions-choices > ul { + display: flex; + align-items: center; + } + + .bulk-actions-choices li { + margin: 0 0.5em; + } + + .bulk-actions-choices span { + text-transform: none; + } + } +} .bulk-actions-choices { - &.footer { - @include transition(transform 0.1s ease 0.1s); + &.footer { + @include transition(transform 0.1s ease 0.1s); - &.hidden { - transform: translateY(200px); - visibility: hidden; - } - - .button { - font-weight: 600; - } - - .bulk-actions-more { - .button { - border: 0; - } - - .button:not(:hover) { - color: $color-teal; - } - - .c-dropdown__button { - text-transform: uppercase; - } - - .c-dropdown__menu { - bottom: 50px; - flex-direction: column; - } - } - - .bulk-actions-more.is-open { - .c-dropdown__menu.u-toggle { - display: flex; - } - } + &.hidden { + transform: translateY(200px); + visibility: hidden; } - .footer__container { + .button { + font-weight: 600; + } + + .bulk-actions-more { + .button { + border: 0; + } + + .button:not(:hover) { + color: $color-teal; + } + + .c-dropdown__button { + text-transform: uppercase; + } + + .c-dropdown__menu { + bottom: 50px; + flex-direction: column; + } + } + + .bulk-actions-more.is-open { + .c-dropdown__menu.u-toggle { display: flex; - justify-content: space-around; - width: 100%; - align-items: center; - padding: 1.25em; - border-radius: 4px 4px 0 0; - margin-left: 30px; - - input[type='checkbox'] { - margin-right: 1.25em; - } - - .bulk-actions-buttons { - border-left: 1px solid $color-grey-2; - padding-left: 1.5em; - - .bulk-action-btn { - max-width: 160px; - overflow-x: hidden; - text-overflow: ellipsis; - } - } - - .num-objects { - text-transform: none; - margin: 0 5px; - } - - .num-objects-in-listing { - color: $color-teal-light; - background-color: transparent; - border: 0; - font-family: $font-sans; - padding: 0; - } - - .button:not(:hover) { - background-color: $color-white; - } + } } + } + + .footer__container { + display: flex; + justify-content: space-around; + width: 100%; + align-items: center; + padding: 1.25em; + border-radius: 4px 4px 0 0; + margin-left: 30px; + + input[type='checkbox'] { + margin-right: 1.25em; + } + + .bulk-actions-buttons { + border-left: 1px solid $color-grey-2; + padding-left: 1.5em; + + .bulk-action-btn { + max-width: 160px; + overflow-x: hidden; + text-overflow: ellipsis; + } + } + + .num-objects { + text-transform: none; + margin: 0 5px; + } + + .num-objects-in-listing { + color: $color-teal-light; + background-color: transparent; + border: 0; + font-family: $font-sans; + padding: 0; + } + + .button:not(:hover) { + background-color: $color-white; + } + } } diff --git a/client/scss/components/_button-select.scss b/client/scss/components/_button-select.scss index 68819b115d..2a02cbb19f 100644 --- a/client/scss/components/_button-select.scss +++ b/client/scss/components/_button-select.scss @@ -1,21 +1,21 @@ .button-select { - &__option { - display: block; - width: 100%; - margin-bottom: 10px; + &__option { + display: block; + width: 100%; + margin-bottom: 10px; - background-color: #fff; - border-color: $color-teal; - color: #262626; + background-color: #fff; + border-color: $color-teal; + color: #262626; - &--selected { - background-color: $color-teal; - color: #fff; - } + &--selected { + background-color: $color-teal; + color: #fff; } + } } .button-select .button-select__option { - /* override default margin from horizontally-aligned buttons */ - margin-left: 0; + /* override default margin from horizontally-aligned buttons */ + margin-left: 0; } diff --git a/client/scss/components/_button.scss b/client/scss/components/_button.scss index 621d4b45d3..a53bc5002f 100644 --- a/client/scss/components/_button.scss +++ b/client/scss/components/_button.scss @@ -1,4 +1,4 @@ -@use "sass:color"; +@use 'sass:color'; // Core button style // Note that these styles include methods to render buttons the same x-browser, described here: // http: //cbjdigital.com/blog/2010/08/bulletproof_css_input_button_heights @@ -6,18 +6,196 @@ // input[type=reset], // input[type=button], .button { - border-radius: 3px; - font-family: $font-sans; + border-radius: 3px; + font-family: $font-sans; + width: auto; + height: 2.4em; + padding: 0 1em; + font-size: 0.9em; + font-weight: normal; + vertical-align: middle; + display: inline-block; + background-color: $color-button; + border: 1px solid $color-button; + color: $color-white; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + position: relative; + overflow: hidden; + box-sizing: border-box; + -webkit-font-smoothing: auto; + // stylelint-disable-next-line property-no-vendor-prefix + -moz-appearance: none; + + transition: background-color 0.1s ease; + + &:hover { + color: $color-teal; + } + + &.yes { + background-color: $color-button-yes; + border: 1px solid $color-button-yes; + + &.button-secondary { + border: 1px solid $color-button-yes; + color: $color-button-yes; + background-color: transparent; + } + + &:hover { + color: $color-white; + border-color: transparent; + background-color: $color-button-yes-hover; + } + + &.button-nobg:hover { + color: $color-button-yes; + background-color: transparent; + } + } + + &.warning { + background-color: $color-button-warning; + border: 1px solid $color-button-warning; + + &.button-secondary { + border: 1px solid $color-button-warning; + color: $color-button-warning; + background-color: transparent; + } + + &:hover { + color: $color-white; + border-color: transparent; + background-color: $color-button-warning-hover; + } + + &.button-nobg:hover { + color: $color-button-warning; + background-color: transparent; + } + } + + &.no, + &.serious { + background-color: $color-button-no; + border: 1px solid $color-button-no; + + &.button-secondary { + border: 1px solid $color-button-no; + color: $color-button-no; + background-color: transparent; + } + + &:hover { + color: $color-white; + border-color: transparent; + background-color: $color-button-no-hover; + } + + &.button-nobg:hover { + color: $color-button-no; + background-color: transparent; + } + } + + &.button-nobg { + border: 0; + background-color: transparent; + } + + &.bicolor { + border: 1px solid transparent; + padding-left: 3.5em; + + &:before { + // iconfont + font-size: 1rem; + position: absolute; + left: 0; + top: 0; + width: 2em; + line-height: 1.85em; + height: 100%; + text-align: center; + background-color: rgba(0, 0, 0, 0.2); + display: block; + border-top-left-radius: inherit; + border-bottom-left-radius: inherit; + } + + .icon-wrapper { + background-color: rgba(0, 0, 0, 0.2); + display: block; + position: absolute; + left: 0; + top: 0; + width: 3em; + line-height: 1.85em; + height: 100%; + text-align: center; + border-top-left-radius: inherit; + border-bottom-left-radius: inherit; + } + + &.button--icon { + &:before { + display: none; // TODO: remove once the icon font styles are gone + } + + .icon { + @include svg-icon(1rem); + padding: 0.75em; + } + } + + &.button--icon-flipped { + .icon { + transform: scaleX(-1); + } + } + + &.button-secondary { + border: 1px solid rgba(0, 0, 0, 0.2); + } + } + + &.button-small.bicolor { + padding-left: 3.5em; + + .icon-wrapper { + width: 2em; + } + + &.button--icon .icon { + @include svg-icon(0.9rem); + padding: 0.25em; + } + } + + // + input[type=submit], + // + input[type=reset], + // + input[type=button], + + .button { + // + button { + margin-left: 1em; + } + + // A completely unstyled button + &.unbutton { + border-radius: 0; width: auto; - height: 2.4em; - padding: 0 1em; - font-size: 0.9em; + height: auto; + padding: 0; + font-size: inherit; font-weight: normal; vertical-align: middle; - display: inline-block; - background-color: $color-button; - border: 1px solid $color-button; - color: $color-white; + display: inline; + background-color: transparent; + border: 0; + color: inherit; text-decoration: none; text-transform: uppercase; white-space: nowrap; @@ -28,620 +206,439 @@ // stylelint-disable-next-line property-no-vendor-prefix -moz-appearance: none; - transition: background-color 0.1s ease; + &:hover, + &:focus, + &:active { + background-color: transparent; + } + } - &:hover { - color: $color-teal; + // stylelint-disable-next-line no-duplicate-selectors + &:hover { + background-color: $color-button-hover; + color: $color-white; + border-color: transparent; + + &.hover-no { + background-color: $color-button-no; + } + } + + &.button-longrunning { + span { + // iconfont + @include transition(all 0.3s ease); + transform: scale(0.9); + display: inline-block; + height: 0.9em; + position: relative; + opacity: 0; + width: 0; + visibility: hidden; + text-align: center; + padding-right: 0; } - - &.yes { - background-color: $color-button-yes; - border: 1px solid $color-button-yes; - - &.button-secondary { - border: 1px solid $color-button-yes; - color: $color-button-yes; - background-color: transparent; - } - - &:hover { - color: $color-white; - border-color: transparent; - background-color: $color-button-yes-hover; - } - - &.button-nobg:hover { - color: $color-button-yes; - background-color: transparent; - } + em { + font-style: normal; } - &.warning { - background-color: $color-button-warning; - border: 1px solid $color-button-warning; - - &.button-secondary { - border: 1px solid $color-button-warning; - color: $color-button-warning; - background-color: transparent; - } - - &:hover { - color: $color-white; - border-color: transparent; - background-color: $color-button-warning-hover; - } - - &.button-nobg:hover { - color: $color-button-warning; - background-color: transparent; - } + &.button-longrunning-active span { + // iconfont + transform: scale(1); + visibility: visible; + width: 1em; + height: 1em; + opacity: 0.8; + padding-right: 0.5em; } - - &.no, - &.serious { - background-color: $color-button-no; - border: 1px solid $color-button-no; - - &.button-secondary { - border: 1px solid $color-button-no; - color: $color-button-no; - background-color: transparent; - } - - &:hover { - color: $color-white; - border-color: transparent; - background-color: $color-button-no-hover; - } - - &.button-nobg:hover { - color: $color-button-no; - background-color: transparent; - } + span.icon-spinner:after { + // iconfont + text-align: center; + position: absolute; + left: 0; + margin: 0; + line-height: 1em; + display: inline-block; + font-size: 1em; } - &.button-nobg { - border: 0; - background-color: transparent; + svg.icon-spinner { + @include transition(all 0.3s ease); + display: none; } - &.bicolor { - border: 1px solid transparent; - padding-left: 3.5em; + &.button-longrunning-active svg.icon-spinner { + @include svg-icon(); - &:before { // iconfont - font-size: 1rem; - position: absolute; - left: 0; - top: 0; - width: 2em; - line-height: 1.85em; - height: 100%; - text-align: center; - background-color: rgba(0, 0, 0, 0.2); - display: block; - border-top-left-radius: inherit; - border-bottom-left-radius: inherit; - } - - .icon-wrapper { - background-color: rgba(0, 0, 0, 0.2); - display: block; - position: absolute; - left: 0; - top: 0; - width: 3em; - line-height: 1.85em; - height: 100%; - text-align: center; - border-top-left-radius: inherit; - border-bottom-left-radius: inherit; - } - - &.button--icon { - &:before { - display: none; // TODO: remove once the icon font styles are gone - } - - .icon { - @include svg-icon(1rem); - padding: 0.75em; - } - } - - &.button--icon-flipped { - .icon { - transform: scaleX(-1); - } - } - - &.button-secondary { - border: 1px solid rgba(0, 0, 0, 0.2); - } + transform: scale(1); + display: inline-block; + opacity: 0.8; + padding: 0; + margin-right: 0.5em; } - &.button-small.bicolor { - padding-left: 3.5em; - - .icon-wrapper { - width: 2em; - } - - &.button--icon .icon { - @include svg-icon(0.9rem); - padding: 0.25em; - } + &.button-longrunning-active .button-longrunning__icon { + display: none; } + } - - // + input[type=submit], - // + input[type=reset], - // + input[type=button], - + .button { - // + button { - margin-left: 1em; - } - - // A completely unstyled button - &.unbutton { - border-radius: 0; - width: auto; - height: auto; - padding: 0; - font-size: inherit; - font-weight: normal; - vertical-align: middle; - display: inline; - background-color: transparent; - border: 0; - color: inherit; - text-decoration: none; - text-transform: uppercase; - white-space: nowrap; - position: relative; - overflow: hidden; - box-sizing: border-box; - -webkit-font-smoothing: auto; - // stylelint-disable-next-line property-no-vendor-prefix - -moz-appearance: none; - - &:hover, - &:focus, - &:active { - background-color: transparent; - } - } - - // stylelint-disable-next-line no-duplicate-selectors - &:hover { - background-color: $color-button-hover; - color: $color-white; - border-color: transparent; - - &.hover-no { - background-color: $color-button-no; - } - } - - &.button-longrunning { - span { // iconfont - @include transition(all 0.3s ease); - transform: scale(0.9); - display: inline-block; - height: 0.9em; - position: relative; - opacity: 0; - width: 0; - visibility: hidden; - text-align: center; - padding-right: 0; - } - - em { - font-style: normal; - } - - &.button-longrunning-active span { // iconfont - transform: scale(1); - visibility: visible; - width: 1em; - height: 1em; - opacity: 0.8; - padding-right: 0.5em; - } - - span.icon-spinner:after { // iconfont - text-align: center; - position: absolute; - left: 0; - margin: 0; - line-height: 1em; - display: inline-block; - font-size: 1em; - } - - svg.icon-spinner { - @include transition(all 0.3s ease); - display: none; - } - - &.button-longrunning-active svg.icon-spinner { - @include svg-icon(); - - transform: scale(1); - display: inline-block; - opacity: 0.8; - padding: 0; - margin-right: 0.5em; - } - - &.button-longrunning-active .button-longrunning__icon { - display: none; - } - } - - &:disabled, - &[disabled], - &.disabled { - background-color: $color-grey-3; - border-color: $color-grey-3; - color: $color-grey-2; - cursor: default; - } - - &.button-secondary:disabled, - &.button-secondary[disabled], - &.button-secondary.disabled { - background-color: $color-white; - border-color: $color-grey-3; - color: $color-grey-3; - } - - &.button-nostroke { - border: 0; - } - - &.button-strokeonhover { - border: 1px solid transparent; - - &:hover { - border-color: $color-grey-2; - } - - &.button-nobg:hover { - background-color: transparent; - } - } - - &.text-notransform { - text-transform: initial; - } - - &.button--icon { - .icon { - @include svg-icon(1.5em); - } - } - - @include media-breakpoint-up(sm) { - font-size: 0.95em; - padding: 0 1.4em; - height: 3em; - - &.bicolor { - padding-left: 3.7em; - - &:before { - width: 2em; - line-height: 2.2em; - font-size: 1.1rem; - } - } - - &.button-small.bicolor { - // line-height: 2.2em; - padding-left: 3em; - - &:before { - width: 1.8em; - line-height: 1.65em; - } - } - } -} - - -.button-small { - padding: 0 0.8em; - height: 2em; - font-size: 0.95em; -} - -.button-secondary { - color: $color-button; - background-color: transparent; -} - -// Buttons which are only an icon -.button.icon.text-replace { // iconfont - font-size: 0; // unavoidable duplication of setting in icons.scss - width: 1.8rem; - height: 1.8rem; - - &:before { - line-height: 1.7em; - } - - @include media-breakpoint-up(sm) { - width: 2.2rem; - height: 2.2rem; - - &:before { - line-height: 2.1em; - } - - - &.button-small { - height: 1.8rem; - width: 1.8rem; - - // stylelint-disable-next-line max-nesting-depth - &:before { - line-height: 1.7em; - } - } - } -} - -.button--icon.text-replace { - font-size: 0; - text-align: center; - - .icon { - font-size: initial; - @include svg-icon(1rem, middle); - padding: 0.5em; - } - - &.button-small { - line-height: 1.7rem; - height: 1.8rem; - width: 1.8rem; - - .icon { - padding: 0.25em; - } - } - - @include media-breakpoint-up(sm) { - width: 2.2rem; - height: 2.2rem; - } -} - -button.button.bicolor .icon-wrapper { - line-height: 1.65em; // work around differences in a and button elements -} - -.button-neutral { + &:disabled, + &[disabled], + &.disabled { + background-color: $color-grey-3; + border-color: $color-grey-3; color: $color-grey-2; + cursor: default; + } + + &.button-secondary:disabled, + &.button-secondary[disabled], + &.button-secondary.disabled { + background-color: $color-white; + border-color: $color-grey-3; + color: $color-grey-3; + } + + &.button-nostroke { + border: 0; + } + + &.button-strokeonhover { + border: 1px solid transparent; &:hover { - color: $color-teal; - } -} - -.yes { - background-color: $color-button-yes; - border: 1px solid $color-button-yes; - - &.button-secondary { - border: 1px solid $color-button-yes; - color: $color-button-yes; - background-color: transparent; - } - - &:hover { - color: $color-white; - border-color: transparent; - background-color: $color-button-yes-hover; + border-color: $color-grey-2; } &.button-nobg:hover { - color: $color-button-yes; - background-color: transparent; + background-color: transparent; } + } + + &.text-notransform { + text-transform: initial; + } + + &.button--icon { + .icon { + @include svg-icon(1.5em); + } + } + + @include media-breakpoint-up(sm) { + font-size: 0.95em; + padding: 0 1.4em; + height: 3em; + + &.bicolor { + padding-left: 3.7em; + + &:before { + width: 2em; + line-height: 2.2em; + font-size: 1.1rem; + } + } + + &.button-small.bicolor { + // line-height: 2.2em; + padding-left: 3em; + + &:before { + width: 1.8em; + line-height: 1.65em; + } + } + } +} + +.button-small { + padding: 0 0.8em; + height: 2em; + font-size: 0.95em; +} + +.button-secondary { + color: $color-button; + background-color: transparent; +} + +// Buttons which are only an icon +.button.icon.text-replace { + // iconfont + font-size: 0; // unavoidable duplication of setting in icons.scss + width: 1.8rem; + height: 1.8rem; + + &:before { + line-height: 1.7em; + } + + @include media-breakpoint-up(sm) { + width: 2.2rem; + height: 2.2rem; + + &:before { + line-height: 2.1em; + } + + &.button-small { + height: 1.8rem; + width: 1.8rem; + + // stylelint-disable-next-line max-nesting-depth + &:before { + line-height: 1.7em; + } + } + } +} + +.button--icon.text-replace { + font-size: 0; + text-align: center; + + .icon { + font-size: initial; + @include svg-icon(1rem, middle); + padding: 0.5em; + } + + &.button-small { + line-height: 1.7rem; + height: 1.8rem; + width: 1.8rem; + + .icon { + padding: 0.25em; + } + } + + @include media-breakpoint-up(sm) { + width: 2.2rem; + height: 2.2rem; + } +} + +button.button.bicolor .icon-wrapper { + line-height: 1.65em; // work around differences in a and button elements +} + +.button-neutral { + color: $color-grey-2; + + &:hover { + color: $color-teal; + } +} + +.yes { + background-color: $color-button-yes; + border: 1px solid $color-button-yes; + + &.button-secondary { + border: 1px solid $color-button-yes; + color: $color-button-yes; + background-color: transparent; + } + + &:hover { + color: $color-white; + border-color: transparent; + background-color: $color-button-yes-hover; + } + + &.button-nobg:hover { + color: $color-button-yes; + background-color: transparent; + } } .no, .serious { - background-color: $color-button-no; + background-color: $color-button-no; + border: 1px solid $color-button-no; + + &.button-secondary { border: 1px solid $color-button-no; + color: $color-button-no; + background-color: transparent; + } - &.button-secondary { - border: 1px solid $color-button-no; - color: $color-button-no; - background-color: transparent; - } + &:hover { + color: $color-white; + border-color: transparent; + background-color: $color-button-no-hover; + } - &:hover { - color: $color-white; - border-color: transparent; - background-color: $color-button-no-hover; - } - - &.button-nobg:hover { - color: $color-button-no; - background-color: transparent; - } + &.button-nobg:hover { + color: $color-button-no; + background-color: transparent; + } } .button-nobg { - border: 0; - background-color: transparent; + border: 0; + background-color: transparent; } .bicolor { - border: 0; - padding-left: 3.5em; + border: 0; + padding-left: 3.5em; - &:before { - font-size: 1rem; - position: absolute; - left: 0; - top: 0; - width: 2em; - line-height: 1.85em; - height: 100%; - text-align: center; - background-color: rgba(0, 0, 0, 0.2); - display: block; - } + &:before { + font-size: 1rem; + position: absolute; + left: 0; + top: 0; + width: 2em; + line-height: 1.85em; + height: 100%; + text-align: center; + background-color: rgba(0, 0, 0, 0.2); + display: block; + } } .button-small.bicolor { - padding-left: 3.5em; + padding-left: 3.5em; - &:before { - width: 2em; - font-size: 0.8rem; - line-height: 1.65em; - } + &:before { + width: 2em; + font-size: 0.8rem; + line-height: 1.65em; + } } - a.button { - line-height: 2.4em; - height: auto; + line-height: 2.4em; + height: auto; - &.button-small { - line-height: 1.85em; - } + &.button-small { + line-height: 1.85em; + } - @include media-breakpoint-up(sm) { - line-height: 2.9em; - } + @include media-breakpoint-up(sm) { + line-height: 2.9em; + } } // Special styles to counteract Firefox's completely unwarranted assumptions about button styles -input[type=submit], -input[type=reset], -input[type=button], +input[type='submit'], +input[type='reset'], +input[type='button'], button { - padding: 0 1em; + padding: 0 1em; - @include media-breakpoint-up(sm) { - &.button-small { - height: 2em; - } + @include media-breakpoint-up(sm) { + &.button-small { + height: 2em; } + } } .button-group { - @include clearfix; + @include clearfix; - input[type=submit], - input[type=reset], - input[type=button], + input[type='submit'], + input[type='reset'], + input[type='button'], + .button, + button { + border-radius: 0; + float: left; + margin-right: 1px; + margin-left: 0; + + &:only-child { + border-radius: 3px; + } + + &:first-child { + border-radius: 3px 0 0 3px; + } + + &:last-child { + border-radius: 0 3px 3px 0; + margin-right: 0; + } + } + + &.button-group-square { + &, + input[type='submit'], + input[type='reset'], + input[type='button'], .button, button { - border-radius: 0; - float: left; - margin-right: 1px; - margin-left: 0; - - &:only-child { - border-radius: 3px; - } - - &:first-child { - border-radius: 3px 0 0 3px; - } - - &:last-child { - border-radius: 0 3px 3px 0; - margin-right: 0; - } - } - - &.button-group-square { - &, - input[type=submit], - input[type=reset], - input[type=button], - .button, - button { - border-radius: 0; - } + border-radius: 0; } + } } - .multiple { - padding: 0; - max-width: 1024px - 50px; + padding: 0; + max-width: 1024px - 50px; + overflow: hidden; + + > li { + @include row(); + border-radius: 2px; + position: relative; overflow: hidden; + background-color: $color-white; + padding: 1em 10em 1em 1.5em; // 10em padding leaves room for controls + margin-bottom: 1em; + border: 1px solid color.adjust($color-grey-4, $lightness: 3%); // really trying to avoid creating more greys, but this one is better than grey 4 or 5 + } - > li { - @include row(); - border-radius: 2px; - position: relative; - overflow: hidden; - background-color: $color-white; - padding: 1em 10em 1em 1.5em; // 10em padding leaves room for controls - margin-bottom: 1em; - border: 1px solid color.adjust($color-grey-4, $lightness: 3%); // really trying to avoid creating more greys, but this one is better than grey 4 or 5 + &.moving { + position: relative; + } + + li.moving { + position: absolute; + width: 100%; + } + + fieldset { + padding-top: 0; + padding-bottom: 0; + } + + // Object controls + .controls { + position: absolute; + z-index: 1; + right: 1em; + top: 1em; + color: $color-white; + + li { + float: left; + margin-right: 1px; + + &:last-child { + margin-right: 0; + } } - &.moving { - position: relative; + .disabled { + display: none; + visibility: hidden; } - - li.moving { - position: absolute; - width: 100%; - } - - fieldset { - padding-top: 0; - padding-bottom: 0; - } - - // Object controls - .controls { - position: absolute; - z-index: 1; - right: 1em; - top: 1em; - color: $color-white; - - li { - float: left; - margin-right: 1px; - - &:last-child { - margin-right: 0; - } - } - - .disabled { - display: none; - visibility: hidden; - } - } - + } } // wrapper around add button for multiple objects .add { - font-weight: 700; - cursor: pointer; - margin-top: 0; - margin-bottom: 0; - padding-top: 1em; - padding-bottom: 2em; - clear: both; + font-weight: 700; + cursor: pointer; + margin-top: 0; + margin-bottom: 0; + padding-top: 1em; + padding-bottom: 2em; + clear: both; } diff --git a/client/scss/components/_chooser.scss b/client/scss/components/_chooser.scss index 6059144d80..0a1ed15698 100644 --- a/client/scss/components/_chooser.scss +++ b/client/scss/components/_chooser.scss @@ -9,84 +9,88 @@ overridden here? hmm. */ .chooser { - // We show the 'chosen' state... - @include clearfix(); + // We show the 'chosen' state... + @include clearfix(); - input[type=text] { - float: left; - width: 50%; - margin-right: 1em; + input[type='text'] { + float: left; + width: 50%; + margin-right: 1em; + } + + .chosen { + display: block; + } + + .unchosen, + .chosen { + position: relative; + + .icon { + color: $color-grey-3; + @include svg-icon(2.5em); + vertical-align: middle; + margin-right: 0.625rem; } - .chosen { - display: block; + // TODO: [icon-font] remove when the Wagtail icon font is removed + &:before { + vertical-align: middle; + font-family: $font-wagtail-icons; + content: ''; + // position: relative + display: inline-block; + // float: left; + color: $color-grey-3; + line-height: 1em; + font-size: 2.5em; + margin-right: 0.3em; } + } - .unchosen, + .unchosen { + display: none; + } + + .actions { + @include clearfix; + overflow: hidden; + + li { + float: left; + margin: 0.3em; + } + } + + // ...unless the .page-chooser has the 'blank' class set + &.blank { .chosen { - position: relative; - - .icon { - color: $color-grey-3; - @include svg-icon(2.5em); - vertical-align: middle; - margin-right: 0.625rem; - } - - // TODO: [icon-font] remove when the Wagtail icon font is removed - &:before { - vertical-align: middle; - font-family: $font-wagtail-icons; - content: ''; - // position: relative - display: inline-block; - // float: left; - color: $color-grey-3; - line-height: 1em; - font-size: 2.5em; - margin-right: 0.3em; - } + display: none; } .unchosen { - display: none; - } - - .actions { - @include clearfix; - overflow: hidden; - - li { - float: left; - margin: 0.3em; - } - } - - // ...unless the .page-chooser has the 'blank' class set - &.blank { - .chosen { display: none; } - - .unchosen { display: block; } + display: block; } + } } // standard way of doing a chooser where the chosen object's title is overlaid .page-chooser, .snippet-chooser, .document-chooser { - .chosen { - .title { - color: $color-grey-1; - // display: block; - padding-left: 1em; - display: inline-block; - } - - .actions { - clear: both; - padding-top: 0.6em; - } + .chosen { + .title { + color: $color-grey-1; + // display: block; + padding-left: 1em; + display: inline-block; } + + .actions { + clear: both; + padding-top: 0.6em; + } + } } // TODO: [icon-font] remove when the Wagtail icon font is removed @@ -94,37 +98,37 @@ overridden here? hmm. .snippet-chooser, .document-chooser, .image-chooser { - .unchosen, - .chosen { - &:before { - display: none; - } + .unchosen, + .chosen { + &:before { + display: none; } + } } .image-chooser { - .chosen { - padding-left: $thumbnail-width; + .chosen { + padding-left: $thumbnail-width; - &:before { - display: inline-block; - } - - .preview-image { - float: left; - margin-left: -($thumbnail-width); - margin-right: 1em; - max-width: $thumbnail-width; - - // Resize standard Wagtail thumbnail size (165x165) to 130 for space-saving purposes. - // We could request a 130x130 rendition, but that's just unnecessary and burdens installations - // where images are store off-site with higher rendering times. - img { - max-width: $thumbnail-width; - max-height: $thumbnail-width; - height: auto; - width: auto; - } - } + &:before { + display: inline-block; } + + .preview-image { + float: left; + margin-left: -($thumbnail-width); + margin-right: 1em; + max-width: $thumbnail-width; + + // Resize standard Wagtail thumbnail size (165x165) to 130 for space-saving purposes. + // We could request a 130x130 rendition, but that's just unnecessary and burdens installations + // where images are store off-site with higher rendering times. + img { + max-width: $thumbnail-width; + max-height: $thumbnail-width; + height: auto; + width: auto; + } + } + } } diff --git a/client/scss/components/_comments-controls.scss b/client/scss/components/_comments-controls.scss index 02ba6263dc..ca1b5981c2 100644 --- a/client/scss/components/_comments-controls.scss +++ b/client/scss/components/_comments-controls.scss @@ -1,113 +1,113 @@ .comments-controls { - position: relative; - display: flex; - justify-content: flex-end; - padding-right: 2px; - height: 42px; - box-sizing: border-box; + position: relative; + display: flex; + justify-content: flex-end; + padding-right: 2px; + height: 42px; + box-sizing: border-box; - @include media-breakpoint-up(sm) { - padding-right: 30px; - height: 43px; - } + @include media-breakpoint-up(sm) { + padding-right: 30px; + height: 43px; + } } .comments-toggle { - $root: &; - float: none; + $root: &; + float: none; + position: relative; + cursor: pointer; + display: flex; + width: auto; + align-items: center; + + // Wagtail adds some top padding to labels on mobile + padding: 0; + + &--active, + &:hover { + #{$root}__label { + opacity: 1; + } + + #{$root}__icon { + color: $color-white; + } + } + + &__icon { + position: absolute; + left: -12px; + bottom: 3px; + width: 52px; + height: 52px; + color: $color-teal-dark; + transition: color 100ms cubic-bezier(0.4, 0, 0.2, 1); + } + + &__count { + position: absolute; + top: -1px; + right: -8px; + width: 19px; + height: 19px; + box-sizing: border-box; + border-radius: 50%; + background-color: $color-salmon; + border: 1px solid $color-teal; + color: $color-white; + font-size: 9px; + font-weight: 700; + text-align: center; + line-height: 17px; + + &:empty { + display: none; + } + } + + &__icon-wrapper { + width: 28px; + height: 52px; position: relative; - cursor: pointer; - display: flex; - width: auto; - align-items: center; + } - // Wagtail adds some top padding to labels on mobile - padding: 0; + &__label { + font-size: 12px; + text-transform: uppercase; + color: $color-white; + font-weight: 400; + margin-right: 10px; + opacity: 0; + pointer-events: none; + transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1); + } - &--active, - &:hover { - #{$root}__label { - opacity: 1; - } + [type='checkbox'] { + position: absolute; + opacity: 0; + pointer-events: none; + top: -20px; + } - #{$root}__icon { - color: $color-white; - } - } - - &__icon { - position: absolute; - left: -12px; - bottom: 3px; - width: 52px; - height: 52px; - color: $color-teal-dark; - transition: color 100ms cubic-bezier(0.4, 0, 0.2, 1); - } - - &__count { - position: absolute; - top: -1px; - right: -8px; - width: 19px; - height: 19px; - box-sizing: border-box; - border-radius: 50%; - background-color: $color-salmon; - border: 1px solid $color-teal; - color: $color-white; - font-size: 9px; - font-weight: 700; - text-align: center; - line-height: 17px; - - &:empty { - display: none; - } - } - - &__icon-wrapper { - width: 28px; - height: 52px; - position: relative; - } - - &__label { - font-size: 12px; - text-transform: uppercase; - color: $color-white; - font-weight: 400; - margin-right: 10px; - opacity: 0; - pointer-events: none; - transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1); - } - - [type=checkbox] { - position: absolute; - opacity: 0; - pointer-events: none; - top: -20px; - } - - [type=checkbox]:checked + &__icon { - opacity: 1; - } + [type='checkbox']:checked + &__icon { + opacity: 1; + } } .comment-notifications-toggle { - label { - padding: 0; - margin: 0; - padding-top: 5px; - font-weight: normal; - font-size: 13px; - color: $color-white; - display: flex; - justify-content: space-between; - } + label { + padding: 0; + margin: 0; + padding-top: 5px; + font-weight: normal; + font-size: 13px; + color: $color-white; + display: flex; + justify-content: space-between; + } - .switch__toggle { - margin-left: 15px; - } + .switch__toggle { + margin-left: 15px; + } } diff --git a/client/scss/components/_comments-notification-dropdown.scss b/client/scss/components/_comments-notification-dropdown.scss index 2a69f70bf7..c4b8b34dff 100644 --- a/client/scss/components/_comments-notification-dropdown.scss +++ b/client/scss/components/_comments-notification-dropdown.scss @@ -1,66 +1,66 @@ .comment-notifications-toggle-button { - $root: &; - padding: 0 17px; - margin: 0; - display: flex; - align-items: center; - border: 0; - background-color: transparent; + $root: &; + padding: 0 17px; + margin: 0; + display: flex; + align-items: center; + border: 0; + background-color: transparent; - &--active, - &:hover { - #{$root}__icon { - color: $color-white; - } + &--active, + &:hover { + #{$root}__icon { + color: $color-white; } + } - &--icon-toggle { - #{$root}__icon { - transform: rotate(180deg) translate3d(3px, 10px, 0); - } + &--icon-toggle { + #{$root}__icon { + transform: rotate(180deg) translate3d(3px, 10px, 0); } + } - &__icon { - width: 15px; - height: 18px; - color: $color-teal-dark; - transition: color 100ms cubic-bezier(0.4, 0, 0.2, 1); - } + &__icon { + width: 15px; + height: 18px; + color: $color-teal-dark; + transition: color 100ms cubic-bezier(0.4, 0, 0.2, 1); + } } .comment-notifications-dropdown { + position: absolute; + display: none; + bottom: -92px; + z-index: 51; + background-color: $color-text-base; + padding: 20px; + border-radius: 6px; + min-width: 260px; + box-sizing: border-box; + border: 1px solid $color-text-base; + + &__title { + font-size: 12px; + text-transform: uppercase; + font-weight: 700; + color: $color-white; + } + + &--active { + display: block; + } + + &::before { + content: ''; position: absolute; - display: none; - bottom: -92px; - z-index: 51; - background-color: $color-text-base; - padding: 20px; - border-radius: 6px; - min-width: 260px; - box-sizing: border-box; - border: 1px solid $color-text-base; - - &__title { - font-size: 12px; - text-transform: uppercase; - font-weight: 700; - color: $color-white; - } - - &--active { - display: block; - } - - &::before { - content: ''; - position: absolute; - top: -8px; - width: 0; - height: 0; - z-index: 2; - right: 18px; - border-style: solid; - border-width: 0 8px 8px 8px; - border-color: transparent transparent $color-text-base transparent; - } + top: -8px; + width: 0; + height: 0; + z-index: 2; + right: 18px; + border-style: solid; + border-width: 0 8px 8px 8px; + border-color: transparent transparent $color-text-base transparent; + } } diff --git a/client/scss/components/_dropdown.legacy.scss b/client/scss/components/_dropdown.legacy.scss index 59c0cb0deb..6fd8eda1d9 100644 --- a/client/scss/components/_dropdown.legacy.scss +++ b/client/scss/components/_dropdown.legacy.scss @@ -1,342 +1,343 @@ .dropdown { - @include clearfix(); - position: relative; + @include clearfix(); + position: relative; - input[type=submit], - input[type=reset], - input[type=button], - button, - .button { - padding: 0 5em 0 1em; - display: block; - width: 100%; - height: 3em; - line-height: 3em; - text-align: left; - float: left; + input[type='submit'], + input[type='reset'], + input[type='button'], + button, + .button { + padding: 0 5em 0 1em; + display: block; + width: 100%; + height: 3em; + line-height: 3em; + text-align: left; + float: left; + } + + .action-secondary { + opacity: 0.8; + } + + input[type='submit'], + input[type='reset'], + input[type='button'], + button { + line-height: inherit; + } + + ul { + @include unlist(); + background-color: $color-teal; + position: absolute; + overflow: hidden; + top: 100%; + left: -2000px; + z-index: 500; + opacity: 0; + + li { + float: none; + border-color: rgba(255, 255, 255, 0.2); + border-style: solid; + border-width: 1px 0 0; + overflow: hidden; + + a:focus, + button:focus { + border: 3px solid $color-focus-outline; + } } - .action-secondary { - opacity: 0.8; + // Media for Windows High Contrast + @media (forced-colors: $media-forced-colours) { + li { + border-width: 1px; + } + + li:hover { + border-color: Highlight; + } + + li a, + li button { + forced-color-adjust: none; + background-color: $color-black; + border-color: $color-white; + color: $color-white; + } + + li a:focus, + li button:focus { + background-color: $color-black; + forced-color-adjust: none; + border: 4px solid #0ff; + color: $color-white; + } } - input[type=submit], - input[type=reset], - input[type=button], + a { + box-sizing: border-box; + white-space: nowrap; + position: relative; + text-decoration: none; + text-transform: uppercase; + display: block; + color: $color-white; + padding: 1em; + font-weight: normal; + + &:hover { + background-color: $color-teal-darker; + } + + &.icon { + padding-right: 5em; + + // stylelint-disable-next-line max-nesting-depth + &:before, + &:after { + right: 1em; + } + } + + &.shortcut { + padding-right: 7em; + } + } + + a, + input[type='submit'], + input[type='reset'], + input[type='button'], + .button, button { - line-height: inherit; + border-radius: 0; + font-size: 0.95em; + -webkit-font-smoothing: auto; } - ul { - @include unlist(); - background-color: $color-teal; - position: absolute; - overflow: hidden; - top: 100%; - left: -2000px; - z-index: 500; - opacity: 0; - - li { - float: none; - border-color: rgba(255, 255, 255, 0.2); - border-style: solid; - border-width: 1px 0 0; - overflow: hidden; - - a:focus, - button:focus { - border: 3px solid $color-focus-outline; - } - } - - // Media for Windows High Contrast - @media (forced-colors: $media-forced-colours) { - li { - border-width: 1px; - } - - li:hover { - border-color: Highlight; - } - - li a, - li button { - forced-color-adjust: none; - background-color: $color-black; - border-color: $color-white; - color: $color-white; - } - - li a:focus, - li button:focus { - background-color: $color-black; - forced-color-adjust: none; - border: 4px solid #0ff; - color: $color-white; - } - } - - a { - box-sizing: border-box; - white-space: nowrap; - position: relative; - text-decoration: none; - text-transform: uppercase; - display: block; - color: $color-white; - padding: 1em; - font-weight: normal; - - &:hover { - background-color: $color-teal-darker; - } - - &.icon { - padding-right: 5em; - - // stylelint-disable-next-line max-nesting-depth - &:before, - &:after { - right: 1em; - } - } - - &.shortcut { - padding-right: 7em; - } - } - - a, - input[type=submit], - input[type=reset], - input[type=button], - .button, - button { - border-radius: 0; - font-size: 0.95em; - -webkit-font-smoothing: auto; - } - - label { - padding: 1.3em; - } - - .kbd { - position: absolute; - right: 1em; - font-weight: 600; - font-size: 0.8em; - color: rgba(0, 0, 0, 0.3); - } - + label { + padding: 1.3em; } - &.open ul { - box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.2); - opacity: 1; - left: 0; - display: block; + .kbd { + position: absolute; + right: 1em; + font-weight: 600; + font-size: 0.8em; + color: rgba(0, 0, 0, 0.3); + } + } + + &.open ul { + box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.2); + opacity: 1; + left: 0; + display: block; + } + + &.match-width ul { + width: 100%; + min-width: 110px; + + li { + white-space: nowrap; + } + } + + &.dropup ul { + box-shadow: 0 -3px 3px 0 rgba(0, 0, 0, 0.2); + top: auto; + bottom: 100%; + + li { + border-width: 0 0 1px; + } + } + + .dropdown-toggle { + color: $color-white; + text-transform: uppercase; + background-color: $color-teal; + line-height: 2.8em; + cursor: pointer; + height: 100%; + border-left: 1px solid rgba(255, 255, 255, 0.2); + position: absolute; + right: 0; + padding: 0 0.5em; + text-align: center; + + &:before, + &:after { + margin: 0; } - &.match-width ul { - width: 100%; - min-width: 110px; - - li { - white-space: nowrap; - } + &:before { + width: 1em; + font-size: 1.2rem; } - &.dropup ul { - box-shadow: 0 -3px 3px 0 rgba(0, 0, 0, 0.2); - top: auto; - bottom: 100%; + &:hover { + background-color: $color-teal-darker; + } - li { - border-width: 0 0 1px; - } + svg.icon { + // TODO: remove svg qualifier once the icon font styles are gone + @include svg-icon(1.3em); + } + } + + .bicolor + .dropdown-toggle { + background-color: $color-teal-darker; + + &:hover { + background-color: $color-teal-dark; + } + } + + &.open .dropdown-toggle { + background-color: $color-teal-darker; + } + + .bicolor:hover { + background-color: $color-teal-dark; + } + + // Styles for dropdowns which are also buttons e.g page editor + &.dropdown-button { + // Media for Windows High Contrast + @media (forced-colors: $media-forced-colours) { + button { + border-color: ActiveText; + } + + button:hover { + border-color: Highlight; + } + + a.button.bicolor.button:hover { + border-color: Highlight; + } } .dropdown-toggle { - color: $color-white; - text-transform: uppercase; - background-color: $color-teal; - line-height: 2.8em; - cursor: pointer; - height: 100%; - border-left: 1px solid rgba(255, 255, 255, 0.2); - position: absolute; - right: 0; - padding: 0 0.5em; - text-align: center; - - &:before, - &:after { - margin: 0; - } - - &:before { - width: 1em; - font-size: 1.2rem; - } - - &:hover { - background-color: $color-teal-darker; - } - - svg.icon { // TODO: remove svg qualifier once the icon font styles are gone - @include svg-icon(1.3em); - } + border-radius: 0 3px 3px 0; + // Media for Windows High Contrast + @media (forced-colors: $media-forced-colours) { + background: transparent; + box-sizing: border-box; + border: 1px solid ActiveText; + } } - .bicolor + .dropdown-toggle { - background-color: $color-teal-darker; - - &:hover { - background-color: $color-teal-dark; - } + .dropdown-toggle:hover { + // Media for Windows High Contrast + @media (forced-colors: $media-forced-colours) { + background-color: transparent; + border: 1px solid Highlight; + } } - &.open .dropdown-toggle { - background-color: $color-teal-darker; + &.open { + > input[type='button'], + > input[type='submit'], + > button, + > .button { + border-radius: 3px 3px 0 0; + } + + .dropdown-toggle { + border-radius: 0 3px 0 0; + } + } + } + + &.dropdown-button--white { + ul { + background-color: $color-grey-3; } - .bicolor:hover { - background-color: $color-teal-dark; + li a, + li .button { + background-color: $color-white; + color: $color-button; + border: 0; + + &:hover { + background-color: $color-grey-4; + } + + &.no { + color: $color-button-no; + } + + &.warning { + color: $color-button-warning; + } } + } - // Styles for dropdowns which are also buttons e.g page editor - &.dropdown-button { - // Media for Windows High Contrast - @media (forced-colors: $media-forced-colours) { - button { - border-color: ActiveText; - } + &.dropup.dropdown-button { + &.open { + > input[type='button'], + > input[type='submit'], + > button, + > .button { + border-radius: 0 0 3px 3px; + } - button:hover { - border-color: Highlight; - } - - a.button.bicolor.button:hover { - border-color: Highlight; - } - } - - .dropdown-toggle { - border-radius: 0 3px 3px 0; - // Media for Windows High Contrast - @media (forced-colors: $media-forced-colours) { - background: transparent; - box-sizing: border-box; - border: 1px solid ActiveText; - } - } - - .dropdown-toggle:hover { - // Media for Windows High Contrast - @media (forced-colors: $media-forced-colours) { - background-color: transparent; - border: 1px solid Highlight; - } - } - - &.open { - > input[type=button], - > input[type=submit], - > button, - > .button { - border-radius: 3px 3px 0 0; - } - - .dropdown-toggle { - border-radius: 0 3px 0 0; - } - } - } - - &.dropdown-button--white { - ul { - background-color: $color-grey-3; - } - - li a, - li .button { - background-color: $color-white; - color: $color-button; - border: 0; - - &:hover { - background-color: $color-grey-4; - } - - &.no { - color: $color-button-no; - } - - &.warning { - color: $color-button-warning; - } - } - } - - &.dropup.dropdown-button { - &.open { - > input[type=button], - > input[type=submit], - > button, - > .button { - border-radius: 0 0 3px 3px; - } - - .dropdown-toggle { - border-radius: 0 0 3px; - } - } + .dropdown-toggle { + border-radius: 0 0 3px; + } } + } } .dropdown.white { - ul { - background-color: $color-white; + ul { + background-color: $color-white; - li { - border-top: 1px solid rgba(0, 0, 0, 0.1); - } - - a { - color: $color-grey-2; - - &:hover { - background-color: $color-grey-3; - } - } + li { + border-top: 1px solid rgba(0, 0, 0, 0.1); } + + a { + color: $color-grey-2; + + &:hover { + background-color: $color-grey-3; + } + } + } } .dropdown.warning { - ul { - background-color: $color-button-warning; - } + ul { + background-color: $color-button-warning; + } - .dropdown-toggle { - background-color: $color-button-warning; + .dropdown-toggle { + background-color: $color-button-warning; - &:hover { - background-color: $color-button-warning-hover; - } + &:hover { + background-color: $color-button-warning-hover; } + } } // Transitions // stylelint-disable-next-line no-duplicate-selectors .dropdown ul { - @include transition(opacity 0.2s linear); + @include transition(opacity 0.2s linear); } .dropdown-button { - .button svg.icon { // TODO: leave only class when iconfont styles are removed - @include svg-icon(); - margin-right: 0.5em; - } + .button svg.icon { + // TODO: leave only class when iconfont styles are removed + @include svg-icon(); + margin-right: 0.5em; + } } diff --git a/client/scss/components/_dropdown.scss b/client/scss/components/_dropdown.scss index 615c681bb3..2742787b1c 100644 --- a/client/scss/components/_dropdown.scss +++ b/client/scss/components/_dropdown.scss @@ -5,105 +5,105 @@ // .c-dropdown { // } .c-dropdown__button { - display: inline-block; - box-sizing: border-box; - padding-left: 0.5rem; - padding-right: 0.25rem; - // Make this the same as the other buttons - line-height: 1.85; - border: solid 1px transparent; - border-radius: 2px; - font-size: 0.95em; - cursor: pointer; - -webkit-font-smoothing: subpixel-antialiased; - user-select: none; + display: inline-block; + box-sizing: border-box; + padding-left: 0.5rem; + padding-right: 0.25rem; + // Make this the same as the other buttons + line-height: 1.85; + border: solid 1px transparent; + border-radius: 2px; + font-size: 0.95em; + cursor: pointer; + -webkit-font-smoothing: subpixel-antialiased; + user-select: none; } .c-dropdown--large .c-dropdown__button { - line-height: 2.9em; - padding-left: 0.5rem; - padding-right: 0.5rem; + line-height: 2.9em; + padding-left: 0.5rem; + padding-right: 0.5rem; - .icon-site { - padding-right: 0.2rem; - } + .icon-site { + padding-right: 0.2rem; + } } .c-dropdown__icon { - padding-left: 0.4rem; - padding-right: 0.4rem; + padding-left: 0.4rem; + padding-right: 0.4rem; } .c-dropdown__toggle { - display: inline-block; + display: inline-block; } .c-dropdown__togle--icon { - &:before { - display: none; // TODO: remove when iconfont styles are removed - } + &:before { + display: none; // TODO: remove when iconfont styles are removed + } - .icon { - @include svg-icon(1em, middle); - } + .icon { + @include svg-icon(1em, middle); + } - .icon-arrow-up { - display: none; - } + .icon-arrow-up { + display: none; + } } .is-open .c-dropdown__togle--icon { - .icon-arrow-up { - display: inline-block; - } + .icon-arrow-up { + display: inline-block; + } - .icon-arrow-down { - display: none; - } + .icon-arrow-down { + display: none; + } } .c-dropdown__menu.c-dropdown__menu { - margin-top: 0.75rem; - padding: 0.75rem 1rem; - min-width: 8rem; - text-transform: none; - position: absolute; - z-index: 1000; - animation: dropdownIn 0.1s ease-out backwards; - list-style: none; - // Override any right alignment that might've been set by a parent element - // (such as the snippets header) - text-align: left; + margin-top: 0.75rem; + padding: 0.75rem 1rem; + min-width: 8rem; + text-transform: none; + position: absolute; + z-index: 1000; + animation: dropdownIn 0.1s ease-out backwards; + list-style: none; + // Override any right alignment that might've been set by a parent element + // (such as the snippets header) + text-align: left; } .c-dropdown__item { - margin-bottom: 0.375rem; - font-size: 0.8rem; + margin-bottom: 0.375rem; + font-size: 0.8rem; - &:hover { - .c-dropdown__indicator { - opacity: 0.6; - } + &:hover { + .c-dropdown__indicator { + opacity: 0.6; } + } } .c-dropdown__item:last-child { - margin-bottom: 0; + margin-bottom: 0; } .c-dropdown__divider { - border-color: #555; - border-style: dotted; - margin-top: 12px; - margin-bottom: 12px; + border-color: #555; + border-style: dotted; + margin-top: 12px; + margin-bottom: 12px; } @keyframes dropdownIn { - 0% { - opacity: 0; - } + 0% { + opacity: 0; + } - 100% { - opacity: 1; - } + 100% { + opacity: 1; + } } diff --git a/client/scss/components/_footer.scss b/client/scss/components/_footer.scss index d1ad6a91f1..9783c09b00 100644 --- a/client/scss/components/_footer.scss +++ b/client/scss/components/_footer.scss @@ -1,167 +1,167 @@ -@use "sass:math"; +@use 'sass:math'; .footer { - $border-curvature: 3px; - @include transition(bottom 0.5s ease 1s); - @include row(); + $border-curvature: 3px; + @include transition(bottom 0.5s ease 1s); + @include row(); - ul { - @include unlist(); + ul { + @include unlist(); + } + + li { + float: left; + + .dropdown li, // dropdown li + &:last-child { + margin-right: 0; + } + } + + &__container { + border-radius: $border-curvature $border-curvature 0 0; + background: $color-grey-1; + color: $color-white; + margin-top: 0; + margin-right: 0; + transition: transform 1s; + + &:first-child { + margin-top: 0; + box-shadow: 0 0 2px rgba(255, 255, 255, 0.5); + } + + &.footer__container--hidden { + transform: translateY(100%); } li { - float: left; + margin-right: 1em; + } + } - .dropdown li, // dropdown li - &:last-child { - margin-right: 0; - } + &__save-warning { + font-size: 0.95em; + display: flex; + align-items: center; + + .icon { + font-size: 1.2em; + margin-right: 0.5em; + } + + p { + margin: -0.2em 0 0 0; + } + } + + &__emphasise-span-tags span { + color: $color-orange; + } + + .actions { + width: 250px; + + &--primary { + width: 350px; + } + + .dropdown { + input[type='submit'], + input[type='reset'], + input[type='button'], + button, + .button { + padding-right: 2.6em; + } + } + } + + .preview .dropdown { + width: 250px; + } + + .meta { + float: right; + text-align: right; + padding: 7px math.div($grid-gutter-width, 2); + font-size: 0.85em; + + p { + margin: 0; + margin-right: $grid-gutter-width; + white-space: nowrap; + } + + a { + color: inherit; + + &:hover { + color: $color-link; + } + } + } + + @include media-breakpoint-down(xs) { + .actions, + .preview, + &__container, + .preview .dropdown { + width: 100%; + } + + margin-top: $mobile-nice-padding; + + .meta { + p { + white-space: normal; + width: 100%; + } + + .avatar { + left: auto; + } } &__container { - border-radius: $border-curvature $border-curvature 0 0; - background: $color-grey-1; - color: $color-white; - margin-top: 0; - margin-right: 0; - transition: transform 1s; + &:not(:first-child) { + border-radius: 0; + } - &:first-child { - margin-top: 0; - box-shadow: 0 0 2px rgba(255, 255, 255, 0.5); - } - - &.footer__container--hidden { - transform: translateY(100%); - } - - li { - margin-right: 1em; - } + &--hidden { + display: none; + } } &__save-warning { - font-size: 0.95em; - display: flex; - align-items: center; + display: flex; + flex-direction: row; + justify-content: center; + } + } - .icon { - font-size: 1.2em; - margin-right: 0.5em; - } + @include media-breakpoint-up(sm) { + margin-left: calc(#{$desktop-nice-padding} - 0.75em); + margin-right: $desktop-nice-padding; + width: auto; + position: fixed; + bottom: 0; - p { - margin: -0.2em 0 0 0; - } + > ul { + display: flex; } - &__emphasise-span-tags span { - color: $color-orange; + &__container { + padding: 0.75em; + margin-right: 0; + + &:not(:first-child) { + margin-left: -$border-curvature; + } } - .actions { - width: 250px; - - &--primary { - width: 350px; - } - - .dropdown { - input[type=submit], - input[type=reset], - input[type=button], - button, - .button { - padding-right: 2.6em; - } - } - } - - .preview .dropdown { - width: 250px; - } - - .meta { - float: right; - text-align: right; - padding: 7px math.div($grid-gutter-width, 2); - font-size: 0.85em; - - p { - margin: 0; - margin-right: $grid-gutter-width; - white-space: nowrap; - } - - a { - color: inherit; - - &:hover { - color: $color-link; - } - } - } - - @include media-breakpoint-down(xs) { - .actions, - .preview, - &__container, - .preview .dropdown { - width: 100%; - } - - margin-top: $mobile-nice-padding; - - .meta { - p { - white-space: normal; - width: 100%; - } - - .avatar { - left: auto; - } - } - - &__container { - &:not(:first-child) { - border-radius: 0; - } - - &--hidden { - display: none; - } - } - - &__save-warning { - display: flex; - flex-direction: row; - justify-content: center; - } - } - - @include media-breakpoint-up(sm) { - margin-left: calc(#{$desktop-nice-padding} - 0.75em); - margin-right: $desktop-nice-padding; - width: auto; - position: fixed; - bottom: 0; - - > ul { - display: flex; - } - - &__container { - padding: 0.75em; - margin-right: 0; - - &:not(:first-child) { - margin-left: -$border-curvature; - } - } - - &__save-warning { - margin-right: 50px; - } + &__save-warning { + margin-right: 50px; } + } } diff --git a/client/scss/components/_forms.scss b/client/scss/components/_forms.scss index a73592ab7d..7c918c5cd9 100644 --- a/client/scss/components/_forms.scss +++ b/client/scss/components/_forms.scss @@ -1,5 +1,5 @@ -@use "sass:map"; -@use "sass:math"; +@use 'sass:map'; +@use 'sass:math'; // stylelint-disable scss/comment-no-empty // These are the generic stylings for forms of any type. // If you're styling something specific to the page editing interface, @@ -26,12 +26,12 @@ // } // } .plain-checkbox-label { - // cancel heavy / floated label styles, for labels that should appear inline against checkboxes + // cancel heavy / floated label styles, for labels that should appear inline against checkboxes - float: none; - color: inherit; - font-weight: inherit; - font-size: inherit; + float: none; + color: inherit; + font-weight: inherit; + font-size: inherit; } // TODO: mixin, @@ -76,7 +76,7 @@ // Reset the arrow on `<select>`s in IE10+. select::-ms-expand { - display: none; + display: none; } // select boxes @@ -84,67 +84,67 @@ select::-ms-expand { .choice_field .input, .model_choice_field .input, .typed_choice_field .input { - position: relative; + position: relative; - // Add select arrow back on browsers where native ui has been removed - select ~ span:after { - border-radius: 0 6px 6px 0; - z-index: 0; - position: absolute; - right: 0; - top: 1px; - bottom: 0; - width: 1.5em; - font-family: $font-wagtail-icons; - content: map.get($icons, 'arrow-down'); - border: 1px solid $color-input-border; - border-width: 0 0 0 1px; - text-align: center; - line-height: 1.4em; - font-size: 3em; - pointer-events: none; - color: $color-grey-3; - margin: 0 1px 1px 0; + // Add select arrow back on browsers where native ui has been removed + select ~ span:after { + border-radius: 0 6px 6px 0; + z-index: 0; + position: absolute; + right: 0; + top: 1px; + bottom: 0; + width: 1.5em; + font-family: $font-wagtail-icons; + content: map.get($icons, 'arrow-down'); + border: 1px solid $color-input-border; + border-width: 0 0 0 1px; + text-align: center; + line-height: 1.4em; + font-size: 3em; + pointer-events: none; + color: $color-grey-3; + margin: 0 1px 1px 0; - .ie & { - display: none; - } + .ie & { + display: none; } + } - // Override default select padding so the chevron will overlap with long option text - select { - padding-right: 5em; - } + // Override default select padding so the chevron will overlap with long option text + select { + padding-right: 5em; + } } // Other text .help, .error-message { - border: 1px solid transparent; // ensure visible separation in Windows High Contrast mode - font-size: 0.85em; - font-weight: normal; - margin: 0.5em 0 0; + border: 1px solid transparent; // ensure visible separation in Windows High Contrast mode + font-size: 0.85em; + font-weight: normal; + margin: 0.5em 0 0; } .error-message { - font-size: 1em; - font-weight: bold; - color: $color-text-error; + font-size: 1em; + font-weight: bold; + color: $color-text-error; - @media (forced-colors: $media-forced-colours) { - forced-color-adjust: none; - color: $color-text-error-forced-color; - } + @media (forced-colors: $media-forced-colours) { + forced-color-adjust: none; + color: $color-text-error-forced-color; + } - &::before { - font-family: $font-wagtail-icons; - vertical-align: -10%; - content: map.get($icons, 'cross'); - } + &::before { + font-family: $font-wagtail-icons; + vertical-align: -10%; + content: map.get($icons, 'cross'); + } } .help { - color: $color-grey-2; + color: $color-grey-2; } fieldset:hover > .help, @@ -152,26 +152,26 @@ fieldset:hover > .help, .field:focus + .help, .field:hover + .help, li.focused > .help { - opacity: 1; + opacity: 1; } .required .field > label:after, label.required:after { - content: '*'; - color: $color-red; - font-weight: bold; - display: inline-block; - margin-left: 0.5em; - line-height: 1em; - font-size: 13px; + content: '*'; + color: $color-red; + font-weight: bold; + display: inline-block; + margin-left: 0.5em; + line-height: 1em; + font-size: 13px; } .error input, .error textarea, .error select, .error .tagit { - border-color: $color-red; - background-color: $color-input-error-bg; + border-color: $color-red; + background-color: $color-input-error-bg; } // Layouts for particular kinds of of fields @@ -179,7 +179,7 @@ label.required:after { // permanently show checkbox/radio help as they have no focus state .boolean_field .help, .radio .help { - opacity: 1; + opacity: 1; } // This is expected to go on the parent of the input/select/textarea @@ -189,86 +189,86 @@ label.required:after { .time_field, .date_time_field, .url_field { + .input { + position: relative; + + &:before, + &:after { + font-family: $font-wagtail-icons; + position: absolute; + top: 0.5em; + line-height: 100%; + font-size: 2em; + color: $color-grey-3; + } + + &:before { + left: 0.3em; + } + + &:after { + right: 0.5em; + } + } + + input:not([type='radio']), + input:not([type='checkbox']), + input:not([type='submit']), + input:not([type='button']) { + padding-left: 2.5em; + } + + // smaller fields required slight repositioning of icons + &.field-small { .input { - position: relative; - - &:before, - &:after { - font-family: $font-wagtail-icons; - position: absolute; - top: 0.5em; - line-height: 100%; - font-size: 2em; - color: $color-grey-3; - } - - &:before { - left: 0.3em; - } - - &:after { - right: 0.5em; - } - } - - input:not([type=radio]), - input:not([type=checkbox]), - input:not([type=submit]), - input:not([type=button]) { - padding-left: 2.5em; - } - - // smaller fields required slight repositioning of icons - &.field-small { - .input { - &:before, - &:after { - font-size: 1.3rem; // REMs are necessary here because IE doesn't treat generated content correctly - top: 0.3em; - } - - &:before { - left: 0.5em; - } - - &:after { - right: 0.5em; - } - } - } - - // special case for search spinners - &.icon-spinner:after { - color: $color-teal; - opacity: 0.8; - text-align: center; + &:before, + &:after { + font-size: 1.3rem; // REMs are necessary here because IE doesn't treat generated content correctly top: 0.3em; + } + + &:before { + left: 0.5em; + } + + &:after { + right: 0.5em; + } } + } + + // special case for search spinners + &.icon-spinner:after { + color: $color-teal; + opacity: 0.8; + text-align: center; + top: 0.3em; + } } .date_field, .date_time_field { - .input:before { - content: map.get($icons, 'date'); - } + .input:before { + content: map.get($icons, 'date'); + } } .time_field { - .input:before { - content: map.get($icons, 'time'); - } + .input:before { + content: map.get($icons, 'time'); + } } .url_field { - .input:before { - content: map.get($icons, 'link'); - } + .input:before { + content: map.get($icons, 'link'); + } } .daterange_field { - input:last-of-type { - margin-top: 1.2em; // Mirrors the label 1.2em top padding. - } + input:last-of-type { + margin-top: 1.2em; // Mirrors the label 1.2em top padding. + } } // This is specifically for list of radios/checkboxes @@ -276,301 +276,305 @@ label.required:after { .checkbox_select_multiple .input li, .multiple_choice_field .input li, .choice_field .input li { - label { - display: block; - width: auto; - float: none; - padding-top: 0; // Negates padding added to label for the group of fields as a whole - padding-bottom: 0.8em; - } + label { + display: block; + width: auto; + float: none; + padding-top: 0; // Negates padding added to label for the group of fields as a whole + padding-bottom: 0.8em; + } } .fields > li, .field-col { - @include clearfix(); - padding-top: 0.5em; - padding-bottom: 1.2em; + @include clearfix(); + padding-top: 0.5em; + padding-bottom: 1.2em; } .field-row { - @include clearfix(); + @include clearfix(); - // negative margin the bottom so it doesn't add too much space - margin-bottom: -1.2em; + // negative margin the bottom so it doesn't add too much space + margin-bottom: -1.2em; } .input { - clear: both; + clear: both; } // field sizing and alignment .field-small { - input, - textarea, - select, - .halloeditor, - .tagit { - border-radius: 3px; - padding: 0.4em 1em; - } + input, + textarea, + select, + .halloeditor, + .tagit { + border-radius: 3px; + padding: 0.4em 1em; + } } .field { - &.col1, - &.col2, - &.col3, - &.col4, - &.col5, - &.col6, - &.col7, - &.col8, - &.col9, - &.col10, - &.col11, - &.col12 { clear: both;} + &.col1, + &.col2, + &.col3, + &.col4, + &.col5, + &.col6, + &.col7, + &.col8, + &.col9, + &.col10, + &.col11, + &.col12 { + clear: both; + } } li.inline .field { - &.col1, - &.col2, - &.col3, - &.col4, - &.col5, - &.col6, - &.col7, - &.col8, - &.col9, - &.col10, - &.col11, - &.col12 { clear: none;} + &.col1, + &.col2, + &.col3, + &.col4, + &.col5, + &.col6, + &.col7, + &.col8, + &.col9, + &.col10, + &.col11, + &.col12 { + clear: none; + } } // solve gutter issues of inline fields ul.inline li:first-child, li.inline:first-child { - margin-left: math.div(-$grid-gutter-width, 2); + margin-left: math.div(-$grid-gutter-width, 2); } // search-bars .search-bar { - .required .field > label:after { - display: none; - } + .required .field > label:after { + display: none; + } - .button-filter { - height: 2.71em; - border-color: transparent; - } + .button-filter { + height: 2.71em; + border-color: transparent; + } } // file drop zones .drop-zone { - border-radius: 5px; - border: 2px dashed $color-grey-4; - padding: $mobile-nice-padding; - background-color: $color-grey-5; - margin-bottom: 1em; - text-align: center; + border-radius: 5px; + border: 2px dashed $color-grey-4; + padding: $mobile-nice-padding; + background-color: $color-grey-5; + margin-bottom: 1em; + text-align: center; - .drop-zone-help { - border: 0; - } + .drop-zone-help { + border: 0; + } - &.hovered { - border-color: $color-teal; - background-color: $color-input-focus; - } + &.hovered { + border-color: $color-teal; + background-color: $color-input-focus; + } } // Transitions // stylelint-disable-next-line no-duplicate-selectors .help { - @include transition(opacity 0.2s ease); + @include transition(opacity 0.2s ease); } .label-uppercase { - .field > label { - text-transform: uppercase; - } + .field > label { + text-transform: uppercase; + } } @include media-breakpoint-up(sm) { - .help { - opacity: 1; - } + .help { + opacity: 1; + } - .fields { - max-width: 800px; - } + .fields { + max-width: 800px; + } - .field { - @include row(); - } + .field { + @include row(); + } - .field-content { - @include column(10, 0); - } + .field-content { + @include column(10, 0); + } - .field-col { - float: left; + .field-col { + float: left; + padding-left: 0; + + // anything less than 4 columns or greater than 6 is impractical + &.col4 { + label { + @include column(2, 0, 4); + } + + .field-content { + @include column(2, $padding, 4); padding-left: 0; - - // anything less than 4 columns or greater than 6 is impractical - &.col4 { - label { - @include column(2, 0, 4); - } - - .field-content { - @include column(2, $padding, 4); - padding-left: 0; - } - } - - &.col6 { - label { - @include column(2, 0, 6); - } - - .field-content { - @include column(4, $padding, 6); - padding-left: 0; - } - } + } } - .label-above { - .field > label, - .field > .field-content { - display: block; - padding: 0 0 0.8em; - float: none; - width: auto; - } + &.col6 { + label { + @include column(2, 0, 6); + } + + .field-content { + @include column(4, $padding, 6); + padding-left: 0; + } } + } + + .label-above { + .field > label, + .field > .field-content { + display: block; + padding: 0 0 0.8em; + float: none; + width: auto; + } + } } .field-comment-control { - display: none; + display: none; } .tab-content--comments-enabled { - .field { - position: relative; + .field { + position: relative; + } + + .field-content { + padding-right: 45px; + + @include media-breakpoint-up(sm) { + padding-right: 60px; + } + } + + .widget-draftail_rich_text_area .field-content { + padding-right: 0; + } + + .field-comment-control { + position: absolute; + display: block; + top: 0; + right: 0; + height: 100%; + line-height: 100%; + + &--object { + right: 20px; + + @include media-breakpoint-up(lg) { + right: 350px; + } } - .field-content { - padding-right: 45px; + button { + @include transition(opacity 0.2s ease); + border: 0; + background: none; + width: 30px; + height: 30px; + padding: 0; + border-radius: 3px; + position: absolute; + top: 50%; + right: 0; - @include media-breakpoint-up(sm) { - padding-right: 60px; - } - } + @include media-breakpoint-up(sm) { + right: 10px; + } - .widget-draftail_rich_text_area .field-content { - padding-right: 0; - } - - .field-comment-control { - position: absolute; - display: block; - top: 0; + // Hide by default, reveal on hover of parent + @include media-breakpoint-up(md) { + opacity: 0; + pointer-events: none; + transform: translateY(-50%); right: 0; - height: 100%; - line-height: 100%; + } - &--object { - right: 20px; + .icon-reversed { + display: none; + } - @include media-breakpoint-up(lg) { - right: 350px; - } + &:hover { + cursor: pointer; + + // stylelint-disable max-nesting-depth + .icon-default { + display: none; } - button { - @include transition(opacity 0.2s ease); - border: 0; - background: none; - width: 30px; - height: 30px; - padding: 0; - border-radius: 3px; - position: absolute; - top: 50%; - right: 0; - - @include media-breakpoint-up(sm) { - right: 10px; - } - - // Hide by default, reveal on hover of parent - @include media-breakpoint-up(md) { - opacity: 0; - pointer-events: none; - transform: translateY(-50%); - right: 0; - } - - .icon-reversed { - display: none; - } - - &:hover { - cursor: pointer; - - // stylelint-disable max-nesting-depth - .icon-default { - display: none; - } - - .icon-reversed { - display: block; - } - } - - &:focus { - opacity: 1; - pointer-events: initial; - } - - > svg { - width: 30px; - height: 30px; - color: $color-teal; - - @media (forced-colors: $media-forced-colours) { - color: ButtonText; - border: 1px solid; - } - } + .icon-reversed { + display: block; } - } + } - .field-row .field-comment-control { - top: 0; - } + &:focus { + opacity: 1; + pointer-events: initial; + } - .field:not(.block_field) { - &:hover { - .field-comment-control button { - opacity: 1; - pointer-events: initial; - } + > svg { + width: 30px; + height: 30px; + color: $color-teal; + + @media (forced-colors: $media-forced-colours) { + color: ButtonText; + border: 1px solid; } + } } + } - .object { - &:hover { - .field-comment-control--object button { - opacity: 1; - pointer-events: initial; - } - } - } + .field-row .field-comment-control { + top: 0; + } - .object.model_choice_field { - .object-help { - right: 153px; - } + .field:not(.block_field) { + &:hover { + .field-comment-control button { + opacity: 1; + pointer-events: initial; + } } + } + + .object { + &:hover { + .field-comment-control--object button { + opacity: 1; + pointer-events: initial; + } + } + } + + .object.model_choice_field { + .object-help { + right: 153px; + } + } } diff --git a/client/scss/components/_grid.legacy.scss b/client/scss/components/_grid.legacy.scss index 10eead7b84..d179c994fa 100644 --- a/client/scss/components/_grid.legacy.scss +++ b/client/scss/components/_grid.legacy.scss @@ -1,101 +1,101 @@ .wrapper { - @include clearfix(); - height: 100vh; - transition: transform 0.2s ease; + @include clearfix(); + height: 100vh; + transition: transform 0.2s ease; } .content-wrapper { - box-sizing: border-box; - width: 100%; - height: 100%; // this has no effect on desktop, but on mobile it helps aesthetics of menu popout action - float: left; - position: relative; - background-color: $color-grey-4; - border-bottom: 1px solid $color-grey-3; + box-sizing: border-box; + width: 100%; + height: 100%; // this has no effect on desktop, but on mobile it helps aesthetics of menu popout action + float: left; + position: relative; + background-color: $color-grey-4; + border-bottom: 1px solid $color-grey-3; } .content { - @include row(); - background: $color-white; - border-top: 0 solid $color-grey-5; // this top border provides space for the floating logo to toggle the menu - min-height: 100%; - position: relative; // yuk. necessary for positions for jquery ui widgets + @include row(); + background: $color-white; + border-top: 0 solid $color-grey-5; // this top border provides space for the floating logo to toggle the menu + min-height: 100%; + position: relative; // yuk. necessary for positions for jquery ui widgets - @include media-breakpoint-up(sm) { - padding-bottom: 4em; - } + @include media-breakpoint-up(sm) { + padding-bottom: 4em; + } } @include media-breakpoint-up(sm) { - .content-wrapper { - border-bottom-right-radius: 5px; - } + .content-wrapper { + border-bottom-right-radius: 5px; + } - .content { - border-top: 0; - background-color: none; - padding-top: 0; - } + .content { + border-top: 0; + background-color: none; + padding-top: 0; + } } .row { - @include clearfix(); + @include clearfix(); } @include media-breakpoint-up(sm) { - .col1 { - @include column(1); - } + .col1 { + @include column(1); + } - .col2 { - @include column(2); - } + .col2 { + @include column(2); + } - .col3 { - @include column(3); - } + .col3 { + @include column(3); + } - .col4 { - @include column(4); - } + .col4 { + @include column(4); + } - .col5 { - @include column(5); - } + .col5 { + @include column(5); + } - .col6 { - @include column(6); - } + .col6 { + @include column(6); + } - .col7 { - @include column(7); - } + .col7 { + @include column(7); + } - .col8 { - @include column(8); - } + .col8 { + @include column(8); + } - .col9 { - @include column(9); - } + .col9 { + @include column(9); + } - .col10 { - @include column(10); - } + .col10 { + @include column(10); + } - .col11 { - @include column(11); - } + .col11 { + @include column(11); + } - .col12 { - @include column(12); - } + .col12 { + @include column(12); + } - .row { - @include row(); - } + .row { + @include row(); + } - .row-flush { - @include row-flush(); - } + .row-flush { + @include row-flush(); + } } diff --git a/client/scss/components/_header.scss b/client/scss/components/_header.scss index 5977ab5ed9..9e686b081e 100644 --- a/client/scss/components/_header.scss +++ b/client/scss/components/_header.scss @@ -1,236 +1,238 @@ -@use "sass:math"; +@use 'sass:math'; header { - padding-top: 1em; - padding-bottom: 1em; - background-color: $color-header-bg; - margin-bottom: 2em; + padding-top: 1em; + padding-bottom: 1em; + background-color: $color-header-bg; + margin-bottom: 2em; + color: $color-white; + + a { color: $color-white; + } + + h1, + h2 { + margin: 0; + color: $color-white; + } + + h1 { + padding: 0.2em 0; + + &.icon:before { + width: 1em; + display: none; + margin-right: 0.4em; + font-size: 1.5em; + } + } + + .col { + float: left; + margin-right: 2em; + } + + .left { + float: left; + + .hasform &:first-child { + padding-bottom: 0.5em; + float: none; + } + } + + .right { + text-align: right; + float: right; + } + + // For case where content below header should merge with it + &.merged { + margin-bottom: 0; + } + + &.tab-merged { + padding-left: $desktop-nice-padding; + padding-right: $desktop-nice-padding; + + .right:last-child { + padding-right: 0; + } + + @include media-breakpoint-down(xs) { + .breadcrumb { + padding-left: calc(#{$desktop-nice-padding} - 8px); + } + } + @include media-breakpoint-up(sm) { + .breadcrumb { + margin-left: -$desktop-nice-padding; + margin-right: -$desktop-nice-padding; + padding-left: math.div($desktop-nice-padding, 2); + } + } + } + + &.header-with-breadcrumb { + padding-top: 0; + + .breadcrumb { + margin-bottom: 1rem; + padding-left: math.div( + $desktop-nice-padding, + 2 + ); // rather than padding-left: revert; + } + } + + &.tab-merged, + &.no-border { + border: 0; + + @include media-breakpoint-down(xs) { + // To all hamburger menu to be visible + padding-left: 1.6em; + padding-right: 1.6em; + padding-top: 11px; + + .nice-padding { + margin-left: -$desktop-nice-padding; + } + } + } + + &.merged.no-border { + padding-bottom: 0; + } + + &.no-v-padding { + padding-top: 0; + padding-bottom: 0; + } + + .button { + background-color: $color-teal-darker; + + &:hover { + background-color: $color-teal-dark; + } + } + + label { + @include visuallyhidden(); + } + + input[type='text'], + select { + border-width: 0; + + &:focus { + background-color: $color-white; + } + } + + .error-message { + color: inherit; + } + + .fields { + margin-top: -0.5em; + + li { + padding-bottom: 0; + } + + .field { + padding: 0; + } + } + + .field-content { + width: auto; + padding: 0; + } + + .last-updated { + ul { + padding: 0; + } + + li { + display: inline; + margin-right: 2em; + } + + .avatar.small { + margin-left: 0; + } a { - color: $color-white; - } - - h1, - h2 { - margin: 0; - color: $color-white; - } - - h1 { - padding: 0.2em 0; - - &.icon:before { - width: 1em; - display: none; - margin-right: 0.4em; - font-size: 1.5em; - } - } - - .col { - float: left; - margin-right: 2em; - } - - .left { - float: left; - - .hasform &:first-child { - padding-bottom: 0.5em; - float: none; - } - } - - .right { - text-align: right; - float: right; - } - - // For case where content below header should merge with it - &.merged { - margin-bottom: 0; - } - - &.tab-merged { - padding-left: $desktop-nice-padding; - padding-right: $desktop-nice-padding; - - .right:last-child { - padding-right: 0; - } - - @include media-breakpoint-down(xs) { - .breadcrumb { - padding-left: calc(#{$desktop-nice-padding} - 8px); - } - } - @include media-breakpoint-up(sm) { - .breadcrumb { - margin-left: -$desktop-nice-padding; - margin-right: -$desktop-nice-padding; - padding-left: math.div($desktop-nice-padding, 2); - } - } - } - - &.header-with-breadcrumb { - padding-top: 0; - - .breadcrumb { - margin-bottom: 1rem; - padding-left: math.div($desktop-nice-padding, 2); // rather than padding-left: revert; - } - } - - &.tab-merged, - &.no-border { - border: 0; - - @include media-breakpoint-down(xs) { - // To all hamburger menu to be visible - padding-left: 1.6em; - padding-right: 1.6em; - padding-top: 11px; - - .nice-padding { - margin-left: -$desktop-nice-padding; - } - } - - } - - &.merged.no-border { - padding-bottom: 0; - } - - &.no-v-padding { - padding-top: 0; - padding-bottom: 0; - } - - .button { - background-color: $color-teal-darker; - - &:hover { - background-color: $color-teal-dark; - } - } - - label { - @include visuallyhidden(); - } - - input[type=text], - select { - border-width: 0; - - &:focus { - background-color: $color-white; - } - } - - .error-message { - color: inherit; - } - - .fields { - margin-top: -0.5em; - - li { - padding-bottom: 0; - } - - .field { - padding: 0; - } - } - - .field-content { - width: auto; - padding: 0; - } - - .last-updated { - ul { - padding: 0; - } - - li { - display: inline; - margin-right: 2em; - } - - .avatar.small { - margin-left: 0; - } - - a { - font-weight: bold; - } + font-weight: bold; } + } } // Media for Windows High Contrast @media (forced-colors: $media-forced-colours) { - header .field-content { - border: 0.1em solid $system-color-link-text; - } + header .field-content { + border: 0.1em solid $system-color-link-text; + } } @include media-breakpoint-up(sm) { - header { - padding-top: 1.5em; - padding-bottom: 1.5em; + header { + padding-top: 1.5em; + padding-bottom: 1.5em; - .left { - float: left; - margin-right: 0; + .left { + float: left; + margin-right: 0; - &:first-child { - padding-bottom: 0; - float: left; - } - } - - .second { - clear: none; - - .right, - .left { - float: right; - } - } - - h1.icon:before { - display: inline-block; - } - - .col3 { - @include column(3); - } - - .col3.actionbutton { - width: auto; - } - - .col6 { - @include column(6); - } - - .col9 { - @include column(9); - } + &:first-child { + padding-bottom: 0; + float: left; + } } + + .second { + clear: none; + + .right, + .left { + float: right; + } + } + + h1.icon:before { + display: inline-block; + } + + .col3 { + @include column(3); + } + + .col3.actionbutton { + width: auto; + } + + .col6 { + @include column(6); + } + + .col9 { + @include column(9); + } + } } .header-title { - @include media-breakpoint-down(xs) { - padding-left: $mobile-nav-indent; - } + @include media-breakpoint-down(xs) { + padding-left: $mobile-nav-indent; + } - &-icon { - @include svg-icon(); - margin-right: 0.5em; - } + &-icon { + @include svg-icon(); + margin-right: 0.5em; + } } diff --git a/client/scss/components/_help-block.scss b/client/scss/components/_help-block.scss index 7ec972efd2..a85e65c2e8 100644 --- a/client/scss/components/_help-block.scss +++ b/client/scss/components/_help-block.scss @@ -1,88 +1,88 @@ -@use "sass:color"; -@use "sass:map"; +@use 'sass:color'; +@use 'sass:map'; // Help text formatters .help-block { - padding: 1em; - margin: 1em 0; - clear: both; - color: $color-text-base; + padding: 1em; + margin: 1em 0; + clear: both; + color: $color-text-base; - p { - margin-top: 0; + p { + margin-top: 0; - &:last-child { - margin-bottom: 0; - } + &:last-child { + margin-bottom: 0; } + } - a { - color: $color-link; - } + a { + color: $color-link; + } } .help-info, .help-warning, .help-critical { - border-radius: 3px; - padding-left: 3.5em; - position: relative; + border-radius: 3px; + padding-left: 3.5em; + position: relative; - &:before { - font-family: $font-wagtail-icons; - position: absolute; - left: 1em; - top: 0.7em; - content: map.get($icons, 'help'); - font-size: 1.4em; - } + &:before { + font-family: $font-wagtail-icons; + position: absolute; + left: 1em; + top: 0.7em; + content: map.get($icons, 'help'); + font-size: 1.4em; + } } .help-info { - background-color: color.adjust($color-blue, $lightness: 30%); + background-color: color.adjust($color-blue, $lightness: 30%); - &:before { - color: $color-blue; - } + &:before { + color: $color-blue; + } } .help-warning { - background-color: color.adjust($color-orange, $lightness: 30%); + background-color: color.adjust($color-orange, $lightness: 30%); - &:before { - color: $color-orange; - content: map.get($icons, 'warning'); - } + &:before { + color: $color-orange; + content: map.get($icons, 'warning'); + } } .help-critical { - background-color: color.adjust($color-red, $lightness: 40%); + background-color: color.adjust($color-red, $lightness: 40%); - &:before { - color: $color-red; - content: map.get($icons, 'warning'); - } + &:before { + color: $color-red; + content: map.get($icons, 'warning'); + } } // Media for Windows High Contrast @media (forced-colors: $media-forced-colours) { - .help-block { - forced-color-adjust: none; - border: 1px solid $system-color-link-text; // ensure visible separation in Windows High Contrast mode - background-color: transparent; - color: $color-white; + .help-block { + forced-color-adjust: none; + border: 1px solid $system-color-link-text; // ensure visible separation in Windows High Contrast mode + background-color: transparent; + color: $color-white; - &:before { - color: currentColor; - } + &:before { + color: currentColor; } + } - .help-warning { - color: $color-text-warning-forced-color; - border-color: $color-text-warning-forced-color; - } + .help-warning { + color: $color-text-warning-forced-color; + border-color: $color-text-warning-forced-color; + } - .help-critical { - color: $color-text-error-forced-color; - border-color: $color-text-error-forced-color; - } + .help-critical { + color: $color-text-error-forced-color; + border-color: $color-text-error-forced-color; + } } diff --git a/client/scss/components/_human-readable-date.scss b/client/scss/components/_human-readable-date.scss index c4e93cb821..50d64cd437 100644 --- a/client/scss/components/_human-readable-date.scss +++ b/client/scss/components/_human-readable-date.scss @@ -1,21 +1,21 @@ // Displays 'timesince' formatted date with full date on hover .human-readable-date { - overflow: hidden; - display: block; - position: relative; + overflow: hidden; + display: block; + position: relative; + + &:before { + position: absolute; + display: none; + content: attr(title); + } + + &:hover { + visibility: hidden; &:before { - position: absolute; - display: none; - content: attr(title); - } - - &:hover { - visibility: hidden; - - &:before { - visibility: visible; - display: block; - } + visibility: visible; + display: block; } + } } diff --git a/client/scss/components/_icons.scss b/client/scss/components/_icons.scss index c8dad1af3b..c574d5e5c2 100644 --- a/client/scss/components/_icons.scss +++ b/client/scss/components/_icons.scss @@ -1,25 +1,25 @@ -@use "sass:string"; +@use 'sass:string'; @font-face { - font-family: 'wagtail'; - src: url('../fonts/wagtail.woff') format('woff'); - font-weight: normal; - font-style: normal; + font-family: 'wagtail'; + src: url('../fonts/wagtail.woff') format('woff'); + font-weight: normal; + font-style: normal; } // Set SVG icons to use the current text color in the location they appear as // their default fill color. Can be overridden for a specific icon by either // the color or fill properties. .icon { - fill: currentColor; + fill: currentColor; } .icon.teal { - color: $color-teal; + color: $color-teal; } .icon.white { - color: #fff; + color: #fff; } .icon:before, @@ -27,14 +27,14 @@ .hallotoolbar [class^='icon-'], .hallotoolbar [class*=' icon-']:before, .hallotoolbar [class^='icon-']:before { - @include icon(); // from _mixins.scss + @include icon(); // from _mixins.scss } // stylelint-disable-next-line no-duplicate-selectors .icon:after, .hallotoolbar [class^='icon-']:after, .hallotoolbar [class^='icon-']:after { - text-align: right; + text-align: right; } // stylelint-disable-next-line no-duplicate-selectors @@ -42,150 +42,149 @@ .hallotoolbar [class*=' icon-']:before, .hallotoolbar [class*=' icon-']:before, .hallotoolbar [class^='icon-']:before { - vertical-align: -10%; - margin-right: 0; + vertical-align: -10%; + margin-right: 0; } - // ============================================================================= // Icon factory methods // ============================================================================= @each $icon, $content in $icons { - .icon-#{$icon}:before { - content: string.quote(#{$content}); - } + .icon-#{$icon}:before { + content: string.quote(#{$content}); + } } @each $icon, $content in $icons-after { - .icon-#{$icon}:after { - content: string.quote(#{$content}); - } + .icon-#{$icon}:after { + content: string.quote(#{$content}); + } } - // ============================================================================= // Custom config for various icons // ============================================================================= .icon-download { - // Credit: Icon made by Freepik from Flaticon.com + // Credit: Icon made by Freepik from Flaticon.com } .icon-view:before, -.icon-no-view:before { // icon-font - vertical-align: -3.5px; - font-size: 1.1rem; +.icon-no-view:before { + // icon-font + vertical-align: -3.5px; + font-size: 1.1rem; } .icon-spinner:after, -.icon-spinner:before { // iconfont - width: 1em; - animation: spin-wag 0.5s infinite linear; - display: inline-block; +.icon-spinner:before { + // iconfont + width: 1em; + animation: spin-wag 0.5s infinite linear; + display: inline-block; } -svg.icon-spinner { // TODO: leave only class when iconfont styles are removed - animation: spin-wag 0.5s infinite linear; +svg.icon-spinner { + // TODO: leave only class when iconfont styles are removed + animation: spin-wag 0.5s infinite linear; } .icon-horizontalrule:before { - font-family: $font-sans; + font-family: $font-sans; } - .icon-larger:before { - font-size: 1.5em; + font-size: 1.5em; } -.icon.text-replace { // iconfont - font-size: 0; - line-height: 0; - overflow: hidden; +.icon.text-replace { + // iconfont + font-size: 0; + line-height: 0; + overflow: hidden; - &:before { - margin-right: 0; - font-size: 1rem; - display: inline-block; - width: 100%; - line-height: 1.2em; - text-align: center; - vertical-align: middle; - } + &:before { + margin-right: 0; + font-size: 1rem; + display: inline-block; + width: 100%; + line-height: 1.2em; + text-align: center; + vertical-align: middle; + } } .text-replace { - font-size: 0; - line-height: 0; - overflow: hidden; + font-size: 0; + line-height: 0; + overflow: hidden; - .icon { - @include svg-icon(1rem, middle); - } + .icon { + @include svg-icon(1rem, middle); + } } @keyframes spin-wag { - 0% { - transform: rotate(0deg); - } + 0% { + transform: rotate(0deg); + } - 100% { - transform: rotate(360deg); - } + 100% { + transform: rotate(360deg); + } } - - .icon-spinner:after { - display: inline-block; - line-height: 1; + display: inline-block; + line-height: 1; } // CSS-only circled question mark. // <span class="icon-help-inverse" aria-hidden="true"></span> .icon-help-inverse { - $size: 15px; + $size: 15px; - &:before { - display: inline-block; - width: $size; - height: $size; - line-height: $size; - font-size: 1.1em; - text-align: center; - border-radius: 100%; - color: $color-grey-2; - border: 1px solid currentColor; - } + &:before { + display: inline-block; + width: $size; + height: $size; + line-height: $size; + font-size: 1.1em; + text-align: center; + border-radius: 100%; + color: $color-grey-2; + border: 1px solid currentColor; + } } // stylelint-disable-next-line no-duplicate-selectors .icon { - &.initial { - @include svg-icon(1em); - vertical-align: initial; - } + &.initial { + @include svg-icon(1em); + vertical-align: initial; + } - &.default { - @include svg-icon(1.5em); - } + &.default { + @include svg-icon(1.5em); + } - &--flipped { - transform: scaleX(-1); - } + &--flipped { + transform: scaleX(-1); + } } .icon.locale-error { - vertical-align: text-top; - margin-right: 0.5em; - width: 1.5em; - height: 1.5em; - color: $color-red; + vertical-align: text-top; + margin-right: 0.5em; + width: 1.5em; + height: 1.5em; + color: $color-red; } // Media for Windows High Contrast mode @media (forced-colors: $media-forced-colours) { - .icon { - fill: $system-color-link-text; - } + .icon { + fill: $system-color-link-text; + } } diff --git a/client/scss/components/_indicator.scss b/client/scss/components/_indicator.scss index d1b4ecfa1d..f34886755f 100644 --- a/client/scss/components/_indicator.scss +++ b/client/scss/components/_indicator.scss @@ -1,9 +1,8 @@ -@use "sass:math"; +@use 'sass:math'; // ============================================================================= // Indicator light // ============================================================================= - // ============================================================================= // Indicator light // ============================================================================= @@ -11,47 +10,47 @@ $c-indicator-size: 0.625em; $c-indicator-margin: 0.25rem; .c-indicator { - display: inline-block; - border-radius: 50rem; - width: $c-indicator-size; - height: $c-indicator-size; - margin-top: -0.125rem; - margin-right: $c-indicator-margin; - font-size: 1rem; - vertical-align: middle; + display: inline-block; + border-radius: 50rem; + width: $c-indicator-size; + height: $c-indicator-size; + margin-top: -0.125rem; + margin-right: $c-indicator-margin; + font-size: 1rem; + vertical-align: middle; } // ============================================================================= // States // ============================================================================= .is-absent .c-indicator { - background: $color-state-absent; + background: $color-state-absent; } .is-live .c-indicator { - background: $color-state-live; + background: $color-state-live; } .is-draft .c-indicator { - background: $color-state-draft; + background: $color-state-draft; } // This is hipster. But it works. .is-live\+draft .c-indicator { - background: $color-state-draft; - position: relative; + background: $color-state-draft; + position: relative; - &:before { - content: ''; - width: math.div($c-indicator-size, 2); - height: $c-indicator-size; - position: absolute; - top: 0; - left: 0; - border-bottom-left-radius: 50rem; - border-top-left-radius: 50rem; - background: $color-state-live; - transform: rotate(45deg); - transform-origin: 100% 50%; - } + &:before { + content: ''; + width: math.div($c-indicator-size, 2); + height: $c-indicator-size; + position: absolute; + top: 0; + left: 0; + border-bottom-left-radius: 50rem; + border-top-left-radius: 50rem; + background: $color-state-live; + transform: rotate(45deg); + transform-origin: 100% 50%; + } } diff --git a/client/scss/components/_link.legacy.scss b/client/scss/components/_link.legacy.scss index 04f5543e6d..01229d4a0c 100644 --- a/client/scss/components/_link.legacy.scss +++ b/client/scss/components/_link.legacy.scss @@ -1,12 +1,12 @@ // makes a link look like regular text .nolink { - color: $color-text-base; + color: $color-text-base; - &:hover { - color: $color-teal; - } + &:hover { + color: $color-teal; + } } a.underlined { - border-bottom: 1px solid currentColor; + border-bottom: 1px solid currentColor; } diff --git a/client/scss/components/_listing.scss b/client/scss/components/_listing.scss index 0882c10ce0..c7aa5c64c7 100644 --- a/client/scss/components/_listing.scss +++ b/client/scss/components/_listing.scss @@ -1,792 +1,784 @@ // General listings, like for pages, images or snippets ul.listing { - @include unlist(); + @include unlist(); } .listing { - margin-bottom: 2em; - color: $color-text-base; - font-size: 0.95em; + margin-bottom: 2em; + color: $color-text-base; + font-size: 0.95em; - ul { - list-style-type: none; - padding-left: 0; - // @include unlist(); + ul { + list-style-type: none; + padding-left: 0; + // @include unlist(); + } + + > li { + padding: 1em 0; + border-bottom: 1px dashed $color-input-border; + } + + h3 { + margin: 0; + font-size: 1em; + } + + td, + th { + padding: 1.2em 1em; + + &.no-padding { + padding: 0; + } + } + + &.small td, + th { + padding: 0.6em 1em; + } + + thead { + font-size: 1.1em; + color: $color-text-base; + border-bottom: 1px solid $color-grey-4; + + th { + font-size: 0.9em; + text-align: left; + font-weight: normal; + white-space: nowrap; + text-transform: uppercase; + } + + th.children { + border: 0; + } + + th a { + text-decoration: none; + color: inherit; + position: relative; + + &.icon:after { + opacity: 0.5; + right: 0; + } + } + } + + &.full-width td:first-child, + &.full-width th:first-child { + padding-left: 20px; + } + + &.full-width { + margin-bottom: -3em; // this negates the padding added to the bottom of .content + } + + &.full-width th { + background-color: $color-thead-bg; + } + + .table-headers { + border-bottom: 1px solid $color-grey-4; + } + + tbody { + border-bottom: 1px dashed $color-input-border; + + tr { + border-top: 1px dashed $color-input-border; + + &:first-child { + border-top: 1px dashed $color-input-border; + } + + &:hover { + background-color: #fcfcfc; + } + } + + tr.selected { + background-color: #c8eae9; + + &:hover { + background-color: #b5e3e2; + } + } + } + + &.full-width tbody { + border: 0; + } + + &.chooser { + tbody .title a { + @include transition(none); + display: block; + } + + tbody tr:hover { + background-color: $color-teal; + color: $color-white; + + .title a, + .title a:hover { + color: $color-white; + } + + .parent a { + color: $color-white; + } + + .status-tag { + border-color: $color-white; + } + } + + tbody tr.disabled td { + opacity: 0.25; + } + + tbody tr.disabled td.children { + opacity: 1; + } + + tbody tr.disabled:hover { + background-color: inherit; + color: inherit; + + .title { + cursor: not-allowed; + } + + .status-tag { + border-color: inherit; + } + } + } + + &.small tbody tr { + font-size: 1em; + } + + &.full-width .divider td { + padding-left: 20px; + } + + // specific columns + .bulk { + padding-right: 0; + + label { + font-size: 1em; + display: block; + width: 100%; + position: relative; + } + + label span { + @include visuallyhidden(); + } + + input { + margin-top: 3px; + } + } + + .title { + word-break: break-word; + + .title-wrapper, + h2 { + text-transform: none; + margin: 0; + font-size: 1.15em; + font-weight: 600; + color: $color-text-base; + line-height: 1.5em; + + a { + color: inherit; + text-decoration: none; + + // stylelint-disable max-nesting-depth + &:hover { + color: $color-link; + } + } + } + } + + .actions { + @include clearfix(); + margin-top: 0.8em; + text-transform: uppercase; + margin-bottom: -0.5em; + font-size: 0.8rem; + + a { + text-decoration: none; } > li { - padding: 1em 0; - border-bottom: 1px dashed $color-input-border; + float: left; + padding: 0 0.5em 0 0; + margin: 0 0 0.5em; + + // line-height: 1em; + } + } + + &--inline-actions .actions { + display: inline-block; + margin-top: 0; + vertical-align: inherit; + } + + .button-secondary { + border-color: $color-grey-3; + background: $color-white; + + &.no:hover { + border-color: $color-button-no-hover; + background-color: $color-button-no-hover; + color: $color-white; } - h3 { - margin: 0; - font-size: 1em; + &:hover { + border-color: $color-teal; + background-color: $color-teal; + } + } + + // stylelint-disable-next-line no-duplicate-selectors + .button-secondary { + background-color: $color-white; + } + + .moderate-actions form { + float: left; + margin: 0 1em 1em 0; + } + + .children, + .no-children { + padding: 0; + + &:hover { + background-color: $color-grey-5; } - td, - th { - padding: 1.2em 1em; + a { + display: block; + padding: 2em 0; + } + } - &.no-padding { - padding: 0; - } + .children a { + color: $color-teal; + display: block; + + &:before { + font-size: 3rem; + } + } + + .no-children a { + color: $color-grey-3; + display: block; + + &:before { + font-size: 1.5rem; } - &.small td, - th { - padding: 0.6em 1em; + &:hover, + &:focus { + color: $color-teal; } - thead { - font-size: 1.1em; - color: $color-text-base; - border-bottom: 1px solid $color-grey-4; + &:focus { + opacity: 1; //opacity is already changed on hover on the parent tr + } + } - th { - font-size: 0.9em; - text-align: left; - font-weight: normal; - white-space: nowrap; - text-transform: uppercase; - } + &.small .children a:before { + font-size: 30px; + } - th.children { - border: 0; - } + th.ord { + text-align: center; - th a { - text-decoration: none; - color: inherit; - position: relative; + .icon { + width: 15px; + height: 18px; + vertical-align: middle; - &.icon:after { - opacity: 0.5; - right: 0; - } - } + &::before { + margin-right: 2px; + } } - &.full-width td:first-child, - &.full-width th:first-child { - padding-left: 20px; + &--active a, + a:hover { + color: $color-teal; } - &.full-width { - margin-bottom: -3em; // this negates the padding added to the bottom of .content + &--active .icon { + opacity: 1; + } + } + + .handle { + cursor: move; + width: 20px; + + &:before { + font-size: 20px; + color: $color-grey-3; + width: 1em; } - &.full-width th { - background-color: $color-thead-bg; - } - - .table-headers { - border-bottom: 1px solid $color-grey-4; - } - - tbody { - border-bottom: 1px dashed $color-input-border; - - - tr { - border-top: 1px dashed $color-input-border; - - &:first-child { - border-top: 1px dashed $color-input-border; - } - - &:hover { - background-color: #fcfcfc; - } - } - - tr.selected { - background-color: #c8eae9; - - &:hover { - background-color: #b5e3e2; - } - } - } - - &.full-width tbody { - border: 0; - } - - &.chooser { - tbody .title a { - @include transition(none); - display: block; - } - - tbody tr:hover { - background-color: $color-teal; - color: $color-white; - - .title a, - .title a:hover { - color: $color-white; - } - - .parent a { - color: $color-white; - } - - .status-tag { - border-color: $color-white; - } - } - - tbody tr.disabled td { - opacity: 0.25; - } - - tbody tr.disabled td.children { - opacity: 1; - } - - tbody tr.disabled:hover { - background-color: inherit; - color: inherit; - - .title { - cursor: not-allowed; - } - - .status-tag { - border-color: inherit; - } - } - } - - &.small tbody tr { - font-size: 1em; - } - - &.full-width .divider td { - padding-left: 20px; - } - - // specific columns - .bulk { - padding-right: 0; - - label { - font-size: 1em; - display: block; - width: 100%; - position: relative; - } - - label span { - @include visuallyhidden(); - } - - input { - margin-top: 3px; - } + &:hover:before { + color: $color-text-base; + } + } + + .ui-sortable-helper { + border: 1px dashed $color-input-border; + border-width: 1px 0; + + td { + display: none; } + .ord, .title { - word-break: break-word; + display: table-cell; + } + } - .title-wrapper, - h2 { - text-transform: none; - margin: 0; - font-size: 1.15em; - font-weight: 600; - color: $color-text-base; - line-height: 1.5em; + .dropzone { + height: 80px; + background-color: $color-grey-1; - a { - color: inherit; - text-decoration: none; + &:hover { + background-color: $color-grey-1; + } - // stylelint-disable max-nesting-depth - &:hover { - color: $color-link; - } - } - } + td { + padding: 0; + } + } + + table .no-results-message { + padding-left: 20px; + } + + .unpublished .title-wrapper { + opacity: 0.7; + } + + .index { + background-color: $color-grey-4; + + .title .title-wrapper { + font-size: 1.2em; + opacity: 1; + + a { + @include transition(opacity 0.2s ease); + } + + a:hover { + opacity: 0.7; + } } .actions { - @include clearfix(); - margin-top: 0.8em; - text-transform: uppercase; - margin-bottom: -0.5em; - font-size: 0.8rem; - - a { - text-decoration: none; - } - - > li { - float: left; - padding: 0 0.5em 0 0; - margin: 0 0 0.5em; - - // line-height: 1em; - } + margin-top: 1em; } - &--inline-actions .actions { - display: inline-block; - margin-top: 0; - vertical-align: inherit; + .button { + background-color: $color-white; + color: $color-teal; + border-color: rgba(0, 0, 0, 0.35); + + &:hover { + color: $color-white; + background: $color-teal-darker; + border-color: $color-teal-darker; + } + + &:active { + color: $color-white; + background: #333; + border-color: #333; + } + + &.bicolor { + background: $color-teal-darker; + + &:active { + color: $color-white; + background: #484848; + border-color: #333; + } + } } + } - .button-secondary { - border-color: $color-grey-3; - background: $color-white; + .indicator { + margin-right: 0; + font-size: 1em; + opacity: 0.7; + } - &.no:hover { - border-color: $color-button-no-hover; - background-color: $color-button-no-hover; - color: $color-white; - } - - &:hover { - border-color: $color-teal; - background-color: $color-teal; - } - } - - // stylelint-disable-next-line no-duplicate-selectors - .button-secondary { - background-color: $color-white; - } - - .moderate-actions form { - float: left; - margin: 0 1em 1em 0; - } - - .children, - .no-children { - padding: 0; - - &:hover { - background-color: $color-grey-5; - } - - a { - display: block; - padding: 2em 0; - } - } - - .children a { - color: $color-teal; - display: block; - - &:before { - font-size: 3rem; - } - } - - .no-children a { - color: $color-grey-3; - display: block; - - &:before { - font-size: 1.5rem; - } - - &:hover, - &:focus { - color: $color-teal; - } - - &:focus { - opacity: 1; //opacity is already changed on hover on the parent tr - } - } - - &.small .children a:before { - font-size: 30px; - } - - th.ord { - text-align: center; - - .icon { - width: 15px; - height: 18px; - vertical-align: middle; - - &::before { - margin-right: 2px; - } - } - - &--active a, - a:hover { - color: $color-teal; - } - - &--active .icon { - opacity: 1; - } - } - - .handle { - cursor: move; - width: 20px; - - &:before { - font-size: 20px; - color: $color-grey-3; - width: 1em; - } - - &:hover:before { - color: $color-text-base; - } - } - - .ui-sortable-helper { - border: 1px dashed $color-input-border; - border-width: 1px 0; - - td { - display: none; - - } - - .ord, - .title { - display: table-cell; - } - } - - .dropzone { - height: 80px; - background-color: $color-grey-1; - - &:hover { - background-color: $color-grey-1; - } - - td { - padding: 0; - } - } - - table .no-results-message { - padding-left: 20px; - } - - .unpublished .title-wrapper { - opacity: 0.7; - } - - .index { - background-color: $color-grey-4; - - .title .title-wrapper { - font-size: 1.2em; - opacity: 1; - - a { - @include transition(opacity 0.2s ease); - } - - a:hover { - opacity: 0.7; - } - } - - .actions { - margin-top: 1em; - } - - .button { - background-color: $color-white; - color: $color-teal; - border-color: rgba(0, 0, 0, 0.35); - - &:hover { - color: $color-white; - background: $color-teal-darker; - border-color: $color-teal-darker; - } - - &:active { - color: $color-white; - background: #333; - border-color: #333; - } - - &.bicolor { - background: $color-teal-darker; - - &:active { - color: $color-white; - background: #484848; - border-color: #333; - } - } - } - } - - .indicator { - margin-right: 0; - font-size: 1em; - opacity: 0.7; - } - - &.images img { - @include transition(border-color 0.2s ease); - border: 3px solid $color-white; - } + &.images img { + @include transition(border-color 0.2s ease); + border: 3px solid $color-white; + } } .image-choice { - // Force the link to be displayed as a block, so its focus outline has the right shape. - display: block; - color: inherit; - overflow-wrap: break-word; - word-wrap: break-word; + // Force the link to be displayed as a block, so its focus outline has the right shape. + display: block; + color: inherit; + overflow-wrap: break-word; + word-wrap: break-word; } // stylelint-disable-next-line no-duplicate-selectors ul.listing { - border-top: 1px dashed $color-input-border; - margin-bottom: 2em; + border-top: 1px dashed $color-input-border; + margin-bottom: 2em; } table.listing { - width: 100%; + width: 100%; } // explorer specific tweaks .page-explorer .listing { - position: relative; + position: relative; - .index { + .index { + color: $color-white; + background-color: $color-header-bg; + + td { + padding-top: 1.5em; + padding-bottom: 1.5em; + } + + .privacy-indicator { + font-size: 1em; + opacity: 1; + position: absolute; + right: 10%; + top: 2em; + } + + .locked-indicator { + font-size: 0.8em; + } + + .title { + h2 { color: $color-white; - background-color: $color-header-bg; + font-size: 1.8em; + font-weight: 500; - td { - padding-top: 1.5em; - padding-bottom: 1.5em; - } - - .privacy-indicator { - font-size: 1em; - opacity: 1; - position: absolute; - right: 10%; - top: 2em; - } - - .locked-indicator { - font-size: 0.8em; - } - - .title { - h2 { - color: $color-white; - font-size: 1.8em; - font-weight: 500; - - a:hover { - color: $color-white; - } - } - } - - .button { - background-color: transparent; - color: $color-white; - border-color: rgba(0, 0, 0, 0.35); - - &:hover { - color: $color-white; - background: $color-teal-darker; - } - - &.bicolor { - background: $color-teal-darker; - } + a:hover { + color: $color-white; } + } } - .table-headers { - height: 35px; + .button { + background-color: transparent; + color: $color-white; + border-color: rgba(0, 0, 0, 0.35); - .title { - padding-left: 0; - } - } + &:hover { + color: $color-white; + background: $color-teal-darker; + } - tbody .title { - padding-left: 0; + &.bicolor { + background: $color-teal-darker; + } } + } + + .table-headers { + height: 35px; + + .title { + padding-left: 0; + } + } + + tbody .title { + padding-left: 0; + } } .pagination { - text-align: center; + text-align: center; - p { - margin: 0; - } + p { + margin: 0; + } - ul { - @include unlist(); - margin-top: -1.7em; - } + ul { + @include unlist(); + margin-top: -1.7em; + } - li { - line-height: 1em; - } + li { + line-height: 1em; + } - .prev { - float: left; - } + .prev { + float: left; + } - .next { - float: right; - } + .next { + float: right; + } } .listing.full-width + .pagination { - margin-top: 3em; - border-top: 1px dashed #d9d9d9; - padding: 2em 50px 0; + margin-top: 3em; + border-top: 1px dashed #d9d9d9; + padding: 2em 50px 0; } - // listing filters .listing-filter { - @include clearfix(); - background-color: $color-grey-5; - border-width: 1px 0; - margin: 3em 0; + @include clearfix(); + background-color: $color-grey-5; + border-width: 1px 0; + margin: 3em 0; } .filter-title { - float: left; - text-transform: uppercase; - font-size: 0.95em; - padding: 1em; - margin: 0 1em 0 0; - background-color: $color-grey-4; + float: left; + text-transform: uppercase; + font-size: 0.95em; + padding: 1em; + margin: 0 1em 0 0; + background-color: $color-grey-4; } .filter-options { - @include unlist(); - @include clearfix(); - overflow: hidden; + @include unlist(); + @include clearfix(); + overflow: hidden; - li { - padding: 0.8em; - float: left; - } + li { + padding: 0.8em; + float: left; + } - &__icon { - width: 1em; - height: 1em; - margin-right: 0.2em; - vertical-align: middle; - position: relative; - top: -1px; - } + &__icon { + width: 1em; + height: 1em; + margin-right: 0.2em; + vertical-align: middle; + position: relative; + top: -1px; + } } - @include media-breakpoint-up(sm) { - .listing { - &.horiz { - display: flex; - flex-wrap: wrap; - } + .listing { + &.horiz { + display: flex; + flex-wrap: wrap; + } - &.images { - border: 1px solid $color-grey-4; - border-width: 0 0 0 1px; + &.images { + border: 1px solid $color-grey-4; + border-width: 0 0 0 1px; - > li { - padding: 1.5em; - width: 200px; - height: auto; - text-align: center; - margin-top: -1px; - border: 1px solid $color-grey-4; - border-width: 1px 1px 1px 0; - - .bulk-action-checkbox { - float: left; - margin: -0.5em 0.5em 0.5em -0.75em; - } - - .bulk-action-checkbox + .image-choice { - clear: both; - margin-top: 1em; - } - - .image { - text-align: center; - height: 180px; - - &:before { - content: ''; - display: inline-block; - height: 100%; - vertical-align: middle; - margin-right: -0.25em; - } - - img { - display: inline-block; - vertical-align: middle; - } - } - - &:hover { - background-color: #fdfdfd; - - img { - border-color: $color-teal; - } - } - } - } - - .actions { - visibility: hidden; - } - - .index .actions { - visibility: visible; - } - - - - td:hover .actions, - td:focus-within .actions { - visibility: visible; - } + > li { + padding: 1.5em; + width: 200px; + height: auto; + text-align: center; + margin-top: -1px; + border: 1px solid $color-grey-4; + border-width: 1px 1px 1px 0; .bulk-action-checkbox { - opacity: 0; - - &.show, - &:checked { - opacity: 1; - } + float: left; + margin: -0.5em 0.5em 0.5em -0.75em; } - .no-children { - border-color: transparent; - - a { - opacity: 0; - } + .bulk-action-checkbox + .image-choice { + clear: both; + margin-top: 1em; } - tr:hover, - tr:focus-within { - .no-children a, - .bulk-action-checkbox { - opacity: 1; - } + .image { + text-align: center; + height: 180px; + + &:before { + content: ''; + display: inline-block; + height: 100%; + vertical-align: middle; + margin-right: -0.25em; + } + + img { + display: inline-block; + vertical-align: middle; + } } - // used on the image listing - li:hover, - li:focus-within { - .bulk-action-checkbox { - opacity: 1; - } - } + &:hover { + background-color: #fdfdfd; - tr:hover .children { - background-color: $color-teal; - - a:before { - color: $color-white; - } - } - - td.children:hover { - background-color: $color-teal-darker; - } - - table .no-results-message { - padding-left: 50px; - } - - &.full-width td:first-child, - &.full-width th:first-child { - padding-left: 25px; - } - - &.full-width .divider td { - padding-left: 50px; + img { + border-color: $color-teal; + } } + } } -} + .actions { + visibility: hidden; + } + + .index .actions { + visibility: visible; + } + + td:hover .actions, + td:focus-within .actions { + visibility: visible; + } + + .bulk-action-checkbox { + opacity: 0; + + &.show, + &:checked { + opacity: 1; + } + } + + .no-children { + border-color: transparent; + + a { + opacity: 0; + } + } + + tr:hover, + tr:focus-within { + .no-children a, + .bulk-action-checkbox { + opacity: 1; + } + } + + // used on the image listing + li:hover, + li:focus-within { + .bulk-action-checkbox { + opacity: 1; + } + } + + tr:hover .children { + background-color: $color-teal; + + a:before { + color: $color-white; + } + } + + td.children:hover { + background-color: $color-teal-darker; + } + + table .no-results-message { + padding-left: 50px; + } + + &.full-width td:first-child, + &.full-width th:first-child { + padding-left: 25px; + } + + &.full-width .divider td { + padding-left: 50px; + } + } +} // State .listing__item--active { - > .actions { - visibility: visible; - } + > .actions { + visibility: visible; + } } // stylelint-disable no-duplicate-selectors // Transitions .listing { - thead .dropdown ul { - @include transition(none); - } + thead .dropdown ul { + @include transition(none); + } - .children, - .no-children { - @include transition(background-color 0.2s ease); - } + .children, + .no-children { + @include transition(background-color 0.2s ease); + } - .children a, - .no-children a { - @include transition(all 0.2s ease); - } + .children a, + .no-children a { + @include transition(all 0.2s ease); + } } // Ordering td.ord { - .handle { - // Align with the row's title text, and the column's label. - margin-top: -28px; - margin-left: 13px; - } + .handle { + // Align with the row's title text, and the column's label. + margin-top: -28px; + margin-left: 13px; + } } table.listing { - th.ordered { - color: $color-teal; - - &.ascending { - &:before { - content: '\25B2'; // up arrow - display: inline-block; - height: 100%; - vertical-align: middle; - } - } - - &.descending { - &:before { - content: '\25BC'; // down arrow - display: inline-block; - height: 100%; - vertical-align: middle; - } - } + th.ordered { + color: $color-teal; + &.ascending { + &:before { + content: '\25B2'; // up arrow + display: inline-block; + height: 100%; + vertical-align: middle; + } } + + &.descending { + &:before { + content: '\25BC'; // down arrow + display: inline-block; + height: 100%; + vertical-align: middle; + } + } + } } diff --git a/client/scss/components/_loading-mask.scss b/client/scss/components/_loading-mask.scss index 4a3b2f691d..011b1fc499 100644 --- a/client/scss/components/_loading-mask.scss +++ b/client/scss/components/_loading-mask.scss @@ -1,37 +1,37 @@ -@use "sass:map"; +@use 'sass:map'; // Loading mask: overlays a certain area with a loading spinner and a faded out cover to prevent interaction .loading-mask { - &.loading { - position: relative; + &.loading { + position: relative; - &:before, - &:after { - position: absolute; - display: block; - } - - &:before { - content: ''; - top: -5px; - left: -5px; - bottom: -5px; - right: -5px; - z-index: 1; - background-color: rgba(255, 255, 255, 0.5); - } - - &:after { - font-size: 30px; - width: 30px; - line-height: 30px; - left: 50%; - top: 50%; - margin: -15px 0 0 -15px; - font-family: $font-wagtail-icons; - animation: spin-wag 0.5s infinite linear; - content: map.get($icons, 'spinner'); - z-index: 2; - color: $color-teal; - } + &:before, + &:after { + position: absolute; + display: block; } + + &:before { + content: ''; + top: -5px; + left: -5px; + bottom: -5px; + right: -5px; + z-index: 1; + background-color: rgba(255, 255, 255, 0.5); + } + + &:after { + font-size: 30px; + width: 30px; + line-height: 30px; + left: 50%; + top: 50%; + margin: -15px 0 0 -15px; + font-family: $font-wagtail-icons; + animation: spin-wag 0.5s infinite linear; + content: map.get($icons, 'spinner'); + z-index: 2; + color: $color-teal; + } + } } diff --git a/client/scss/components/_logo.scss b/client/scss/components/_logo.scss index e1515a97ed..361e11438e 100644 --- a/client/scss/components/_logo.scss +++ b/client/scss/components/_logo.scss @@ -1,129 +1,129 @@ @keyframes tail-wag { - from { - transform: rotate(-3deg); - } + from { + transform: rotate(-3deg); + } - to { - transform: rotate(7deg); - } + to { + transform: rotate(7deg); + } } .logo { - display: flex; - align-items: center; - padding: 0.6em 1.2em; - color: #aaa; - -webkit-font-smoothing: auto; - position: relative; + display: flex; + align-items: center; + padding: 0.6em 1.2em; + color: #aaa; + -webkit-font-smoothing: auto; + position: relative; - &:hover { - color: $color-white; - } + &:hover { + color: $color-white; + } - @include media-breakpoint-up(sm) { - display: block; - margin: 2em auto; - text-align: center; - } + @include media-breakpoint-up(sm) { + display: block; + margin: 2em auto; + text-align: center; + } } .wagtail-logo-container__mobile { - margin-right: 10px; - background-color: #555; - border-radius: 50%; - padding: 5px 7.5px; + margin-right: 10px; + background-color: #555; + border-radius: 50%; + padding: 5px 7.5px; - .wagtail-logo { - width: 20px; - float: left; - border: 0; - } + .wagtail-logo { + width: 20px; + float: left; + border: 0; + } } .wagtail-logo-container__desktop { - position: relative; - width: 100px; + position: relative; + width: 100px; + height: 100px; + background-color: #555; + border-radius: 50%; + margin: 0 auto; + transition: all 0.25s cubic-bezier(0.28, 0.15, 0, 2.1); + + .page404__bg & { + background-color: transparent; + } + + .wagtail-logo-container-inner { + width: 52px; height: 100px; - background-color: #555; - border-radius: 50%; - margin: 0 auto; - transition: all 0.25s cubic-bezier(0.28, 0.15, 0, 2.1); + margin: auto; + position: relative; .page404__bg & { - background-color: transparent; + width: auto; + height: auto; + position: static; + } + } + + .wagtail-logo { + display: block; + left: 0; + top: 0; + width: 100%; + height: 100%; + position: absolute; + transition: inherit; + + &.wagtail-logo__eye--open { + // stylelint-disable-next-line declaration-no-important + display: inline !important; // doesn't work without `!important`, likely a specificity issue } - .wagtail-logo-container-inner { - width: 52px; - height: 100px; - margin: auto; - position: relative; + &.wagtail-logo__eye--closed { + // stylelint-disable-next-line declaration-no-important + display: none !important; + } + } - .page404__bg & { - width: auto; - height: auto; - position: static; + // Wagtail 'serious' animation (nod): + &.logo-serious { + &:hover { + transform: rotate(4deg); + } + } + + // Wagtail 'playful' animation (tail-wag, triggered by JS in base.html): + &.logo-playful { + &:hover { + transform: rotate(8deg); + transition: transform 1.2s ease; + + .wagtail-logo { + // stylelint-disable max-nesting-depth + &.wagtail-logo__tail { + animation: tail-wag 0.09s alternate; + animation-iteration-count: infinite; } - } - - .wagtail-logo { - display: block; - left: 0; - top: 0; - width: 100%; - height: 100%; - position: absolute; - transition: inherit; &.wagtail-logo__eye--open { - // stylelint-disable-next-line declaration-no-important - display: inline !important; // doesn't work without `!important`, likely a specificity issue + // stylelint-disable-next-line declaration-no-important + display: none !important; } &.wagtail-logo__eye--closed { - // stylelint-disable-next-line declaration-no-important - display: none !important; - } - } - - // Wagtail 'serious' animation (nod): - &.logo-serious { - &:hover { - transform: rotate(4deg); - } - } - - // Wagtail 'playful' animation (tail-wag, triggered by JS in base.html): - &.logo-playful { - &:hover { - transform: rotate(8deg); - transition: transform 1.2s ease; - - .wagtail-logo { - // stylelint-disable max-nesting-depth - &.wagtail-logo__tail { - animation: tail-wag 0.09s alternate; - animation-iteration-count: infinite; - } - - &.wagtail-logo__eye--open { - // stylelint-disable-next-line declaration-no-important - display: none !important; - } - - &.wagtail-logo__eye--closed { - // stylelint-disable-next-line declaration-no-important - display: inline !important; - } - } + // stylelint-disable-next-line declaration-no-important + display: inline !important; } + } } + } } // Media for Windows High Contrast mode @media (forced-colors: $media-forced-colours) { - .wagtail-logo-container__desktop { - background-color: $system-color-link-text; - } + .wagtail-logo-container__desktop { + background-color: $system-color-link-text; + } } diff --git a/client/scss/components/_main-nav.scss b/client/scss/components/_main-nav.scss index 387b98ea2e..b07ebec1c2 100644 --- a/client/scss/components/_main-nav.scss +++ b/client/scss/components/_main-nav.scss @@ -1,518 +1,516 @@ -@use "sass:map"; +@use 'sass:map'; .nav-wrapper { - position: relative; - margin-left: -$menu-width; - width: $menu-width; - float: left; - display: flex; - flex-direction: column; - height: 100%; + position: relative; + margin-left: -$menu-width; + width: $menu-width; + float: left; + display: flex; + flex-direction: column; + height: 100%; + background: $nav-grey-1; + + .inner { background: $nav-grey-1; + border-right: 1px solid transparent; // ensure visible separation in Windows High Contrast mode - .inner { - background: $nav-grey-1; - border-right: 1px solid transparent; // ensure visible separation in Windows High Contrast mode - - @include media-breakpoint-up(sm) { - // On medium, make it possible for the nav links to scroll. - display: flex; - flex-flow: column nowrap; - } + @include media-breakpoint-up(sm) { + // On medium, make it possible for the nav links to scroll. + display: flex; + flex-flow: column nowrap; } + } } .nav-toggle.icon { - position: absolute; - padding-left: $mobile-nice-padding; - cursor: pointer; - border: 1px solid transparent; // ensure visible separation in Windows High Contrast mode - background-color: transparent; + position: absolute; + padding-left: $mobile-nice-padding; + cursor: pointer; + border: 1px solid transparent; // ensure visible separation in Windows High Contrast mode + background-color: transparent; - &:before { - position: relative; - top: 3px; - font-size: 40px; - color: $color-white; - line-height: 40px; - content: '\2261'; - } + &:before { + position: relative; + top: 3px; + font-size: 40px; + color: $color-white; + line-height: 40px; + content: '\2261'; + } } .nav-main { - ul, - li { - margin: 0; - padding: 0; - list-style-type: none; + ul, + li { + margin: 0; + padding: 0; + list-style-type: none; + } + + li { + @include transition(border-color 0.2s ease); + position: relative; + } + + a { + @include transition(border-color 0.2s ease); + -webkit-font-smoothing: auto; + text-decoration: none; + display: block; + color: $color-menu-text; + padding: 0.8em 1.7em; + font-size: 1em; + font-weight: normal; + // Note, font-weights lower than normal, + // and font-size smaller than 1em (80% ~= 12.8px), + // makes the strokes thinner than 1px on non-retina screens + // making the text semi-transparent + &:hover, + &:focus { + background-color: $nav-item-hover-bg; + color: $color-white; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); + } + } + + .menu-item a { + position: relative; + white-space: nowrap; + border-left: 3px solid transparent; + + &:before { + font-size: 1rem; + vertical-align: -15%; + margin-right: 0.5em; } - li { - @include transition(border-color 0.2s ease); - position: relative; + // only really used for spinners and settings menu + &:after { + font-size: 1.5em; + margin: 0; + position: absolute; + right: 0.5em; + top: 0.5em; + margin-top: 0; } + } + .menu-active { + background: $nav-item-active-bg; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); + + > a { + border-left-color: $color-salmon; + color: $color-white; + } + } + + .nav-footer-submenu { a { - @include transition(border-color 0.2s ease); - -webkit-font-smoothing: auto; - text-decoration: none; - display: block; - color: $color-menu-text; - padding: 0.8em 1.7em; - font-size: 1em; - font-weight: normal; - // Note, font-weights lower than normal, - // and font-size smaller than 1em (80% ~= 12.8px), - // makes the strokes thinner than 1px on non-retina screens - // making the text semi-transparent - &:hover, - &:focus { - background-color: $nav-item-hover-bg; - color: $color-white; - text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - } + border-left: 3px solid transparent; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &:before { + font-size: 1rem; + margin-right: 0.5em; + vertical-align: -10%; + } } + } - .menu-item a { - position: relative; - white-space: nowrap; - border-left: 3px solid transparent; + .account { + display: none; + } - &:before { - font-size: 1rem; - vertical-align: -15%; - margin-right: 0.5em; - } - - // only really used for spinners and settings menu - &:after { - font-size: 1.5em; - margin: 0; - position: absolute; - right: 0.5em; - top: 0.5em; - margin-top: 0; - } - - } - - .menu-active { - background: $nav-item-active-bg; - text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - - > a { - border-left-color: $color-salmon; - color: $color-white; - } - } - - .nav-footer-submenu { - a { - border-left: 3px solid transparent; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - &:before { - font-size: 1rem; - margin-right: 0.5em; - vertical-align: -10%; - } - } - } - - .account { - display: none; - } - - *:focus { - @include show-focus-outline-inside; - } + *:focus { + @include show-focus-outline-inside; + } } .icon--menuitem { - width: 1.25em; - height: 1.25em; - margin-right: 0.5em; - vertical-align: text-top; + width: 1.25em; + height: 1.25em; + margin-right: 0.5em; + vertical-align: text-top; } .icon--submenu-trigger { - // The menus are collapsible on desktop only. - display: none; - @include media-breakpoint-up(sm) { - display: block; - width: 1.5em; - height: 1.5em; - position: absolute; - top: 0.8125em; - right: 0.5em; - @include transition(transform 0.3s ease); + // The menus are collapsible on desktop only. + display: none; + @include media-breakpoint-up(sm) { + display: block; + width: 1.5em; + height: 1.5em; + position: absolute; + top: 0.8125em; + right: 0.5em; + @include transition(transform 0.3s ease); - .menu-item.submenu-active & { - transform-origin: 50% 50%; - transform: rotate(180deg); - } + .menu-item.submenu-active & { + transform-origin: 50% 50%; + transform: rotate(180deg); } + } } .icon--submenu-header { - display: block; - width: 4rem; - height: 4rem; - margin: 0 auto 0.8em; - opacity: 0.15; + display: block; + width: 4rem; + height: 4rem; + margin: 0 auto 0.8em; + opacity: 0.15; } .nav-submenu { - background: $nav-submenu-bg; + background: $nav-submenu-bg; - h2 { - display: none; + h2 { + display: none; + } + + .menu-item a { + white-space: normal; + padding: 0.9em 1.7em 0.9em 4.5em; + + &:before { + margin-left: -1.5em; } - .menu-item a { - white-space: normal; - padding: 0.9em 1.7em 0.9em 4.5em; - - &:before { - margin-left: -1.5em; - } - - .icon--menuitem { - margin-left: -1.75em; - } - - &:hover { - background-color: rgba(100, 100, 100, 0.2); - } + .icon--menuitem { + margin-left: -1.75em; } - li { - border: 0; + &:hover { + background-color: rgba(100, 100, 100, 0.2); } + } - &__footer { - margin: 0; - padding: 0.9em 1.7em; - text-align: center; - color: $color-menu-text; - } + li { + border: 0; + } + + &__footer { + margin: 0; + padding: 0.9em 1.7em; + text-align: center; + color: $color-menu-text; + } } .nav-search { - position: relative; - padding: 0 1em 1em; - margin: 0; - width: 100%; - box-sizing: border-box; + position: relative; + padding: 0 1em 1em; + margin: 0; + width: 100%; + box-sizing: border-box; - label { - @include visuallyhidden(); + label { + @include visuallyhidden(); + } + + input, + button { + border-radius: 0; + font-size: 1em; + border: 0; + } + + input { + cursor: pointer; + border: 1px solid $nav-search-border; + background-color: $nav-search-bg; + color: $nav-search-color; + padding: 0.8em 2.5em 0.8em 1em; + font-weight: 600; + + &:hover { + background-color: $nav-search-hover-bg; } - input, - button { - border-radius: 0; - font-size: 1em; - border: 0; + &:active, + &:focus { + background-color: $nav-search-focus-bg; + color: $nav-search-focus-color; } - input { - cursor: pointer; - border: 1px solid $nav-search-border; - background-color: $nav-search-bg; - color: $nav-search-color; - padding: 0.8em 2.5em 0.8em 1em; - font-weight: 600; + &::placeholder { + color: $color-menu-text; + } + } - &:hover { - background-color: $nav-search-hover-bg; - } + button { + background-color: transparent; + position: absolute; + top: 0; + right: 1em; + bottom: 0; + padding: 0; + width: 3em; - &:active, - &:focus { - background-color: $nav-search-focus-bg; - color: $nav-search-focus-color; - } - - &::placeholder { - color: $color-menu-text; - } + &:hover { + background-color: $nav-item-hover-bg; } - button { - background-color: transparent; - position: absolute; - top: 0; - right: 1em; - bottom: 0; - padding: 0; - width: 3em; - - &:hover { - background-color: $nav-item-hover-bg; - } - - &:active { - background-color: $nav-item-active-bg; - } - - &:before { - font-family: $font-wagtail-icons; - font-weight: 200; - text-transform: none; - content: map.get($icons, 'search'); - display: block; - height: 100%; - line-height: 3.3em; - padding: 0 1em; - } + &:active { + background-color: $nav-item-active-bg; } + + &:before { + font-family: $font-wagtail-icons; + font-weight: 200; + text-transform: none; + content: map.get($icons, 'search'); + display: block; + height: 100%; + line-height: 3.3em; + padding: 0 1em; + } + } } // Navigation open condition body.nav-open { - .wrapper { - transform: translate3d($menu-width, 0, 0); - } + .wrapper { + transform: translate3d($menu-width, 0, 0); + } - .content-wrapper { - position: fixed; - } + .content-wrapper { + position: fixed; + } - footer { - bottom: 1px; - } + footer { + bottom: 1px; + } } // Explorer open condition, widens navigation area body.explorer-open { - .wrapper { - transform: translate3d($menu-width-max, 0, 0); - } + .wrapper { + transform: translate3d($menu-width-max, 0, 0); + } - .nav-wrapper { - margin-left: -$menu-width-max; - width: $menu-width-max; - } + .nav-wrapper { + margin-left: -$menu-width-max; + width: $menu-width-max; + } - .nav-main { - display: none; - } + .nav-main { + display: none; + } } @include media-breakpoint-up(sm) { - .wrapper, - body.nav-open .wrapper { - transform: none; - padding-left: $menu-width; + .wrapper, + body.nav-open .wrapper { + transform: none; + padding-left: $menu-width; - @include transition(padding-left $menu-transition-duration ease); + @include transition(padding-left $menu-transition-duration ease); + } + + body.sidebar-collapsed .wrapper { + padding-left: $menu-width-slim; + } + + .nav-wrapper { + // height and position necessary to force it to 100% height of screen (with some JS help) + position: absolute; + left: 0; + height: 100%; + margin-left: 0; + + .inner { + height: 100%; + position: fixed; + width: $menu-width; + z-index: $nav-wrapper-inner-z-index; + } + } + + .nav-toggle.unbutton { + display: none; + } + + .nav-main { + overflow: auto; + margin-bottom: $nav-footer-closed-height; + @include transition(margin-bottom 0.2s ease); + + .nav-footer { + position: fixed; + width: $menu-width; + bottom: 0; + background-color: $nav-footer-submenu-bg; } - body.sidebar-collapsed .wrapper { - padding-left: $menu-width-slim; + .nav-footer-submenu { + @include transition(max-height 0.2s ease); + max-height: 0; } - .nav-wrapper { - // height and position necessary to force it to 100% height of screen (with some JS help) - position: absolute; - left: 0; - height: 100%; - margin-left: 0; + &--open-footer { + margin-bottom: $nav-footer-open-height; - .inner { - height: 100%; - position: fixed; - width: $menu-width; - z-index: $nav-wrapper-inner-z-index; - } + .nav-footer-submenu { + max-height: $nav-footer-submenu-height; + } } - .nav-toggle.unbutton { - display: none; + .account { + @include clearfix; + background: $nav-footer-account-bg; + color: $color-menu-text; + text-transform: uppercase; + display: block; + cursor: pointer; + + &:hover { + background-color: rgba(100, 100, 100, 0.15); + color: $color-white; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); + } + + .avatar { + float: left; + margin-right: 0.9em; + + &:before { + color: inherit; + border-color: inherit; + } + } + + em { + box-sizing: border-box; + padding-right: 1.8em; + margin-top: 1.2em; + font-style: normal; + font-weight: 700; + width: 110px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + float: left; + + &:after { + font-size: 1.5em; + position: absolute; + right: 0.25em; + } + } + } + } + + .nav-submenu { + transform: translate3d(0, 0, 0); + position: fixed; + height: 100vh; + width: 0; + padding: 0; + top: 0; + left: $menu-width; + overflow: hidden; + display: flex; + flex-direction: column; + + h2, + &__list { + width: $menu-width; } - .nav-main { - overflow: auto; - margin-bottom: $nav-footer-closed-height; - @include transition(margin-bottom 0.2s ease); + h2 { + display: block; + padding: 0.2em 0; + font-size: 1.2em; + font-weight: 500; + text-transform: none; + text-align: center; + color: $color-menu-text; - .nav-footer { - position: fixed; - width: $menu-width; - bottom: 0; - background-color: $nav-footer-submenu-bg; - } + &:before { + font-size: 4em; + display: block; + text-align: center; + margin: 0 0 0.2em; + width: 100%; + opacity: 0.15; + } + } - .nav-footer-submenu { - @include transition(max-height 0.2s ease); - max-height: 0; - } + &__list { + overflow: auto; + flex-grow: 1; + } - &--open-footer { - margin-bottom: $nav-footer-open-height; + &__footer { + line-height: $nav-footer-closed-height; + padding: 0; + } + } - .nav-footer-submenu { - max-height: $nav-footer-submenu-height; - } - } + li.submenu-active { + background: $nav-submenu-bg; - .account { - @include clearfix; - background: $nav-footer-account-bg; - color: $color-menu-text; - text-transform: uppercase; - display: block; - cursor: pointer; + > a { + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - &:hover { - background-color: rgba(100, 100, 100, 0.15); - color: $color-white; - text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - } - - .avatar { - float: left; - margin-right: 0.9em; - - &:before { - color: inherit; - border-color: inherit; - } - } - - em { - box-sizing: border-box; - padding-right: 1.8em; - margin-top: 1.2em; - font-style: normal; - font-weight: 700; - width: 110px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - float: left; - - &:after { - font-size: 1.5em; - position: absolute; - right: 0.25em; - } - } - } + &:hover { + background-color: transparent; + } } .nav-submenu { - transform: translate3d(0, 0, 0); - position: fixed; - height: 100vh; - width: 0; - padding: 0; - top: 0; - left: $menu-width; - overflow: hidden; - display: flex; - flex-direction: column; + @include transition(width 0.2s ease); + box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35); + width: $menu-width; - h2, - &__list { - width: $menu-width; - } + a { + padding-left: 3.5em; + } + } + } - h2 { - display: block; - padding: 0.2em 0; - font-size: 1.2em; - font-weight: 500; - text-transform: none; - text-align: center; - color: $color-menu-text; + body.nav-open { + .content-wrapper { + position: relative; + } + } - &:before { - font-size: 4em; - display: block; - text-align: center; - margin: 0 0 0.2em; - width: 100%; - opacity: 0.15; - } - } - - &__list { - overflow: auto; - flex-grow: 1; - } - - &__footer { - line-height: $nav-footer-closed-height; - padding: 0; - } + body.explorer-open { + overflow: hidden; + &:after { + opacity: 1; + visibility: visible; } - li.submenu-active { - background: $nav-submenu-bg; - - > a { - text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - - &:hover { - background-color: transparent; - } - } - - .nav-submenu { - @include transition(width 0.2s ease); - box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35); - width: $menu-width; - - a { - padding-left: 3.5em; - } - } + .wrapper { + transform: none; } - body.nav-open { - .content-wrapper { - position: relative; - } + .nav-wrapper { + margin-left: 0; + width: $menu-width; } - body.explorer-open { - overflow: hidden; - - &:after { - opacity: 1; - visibility: visible; - } - - .wrapper { - transform: none; - } - - .nav-wrapper { - margin-left: 0; - width: $menu-width; - } - - .nav-main { - display: block; - } + .nav-main { + display: block; } + } } /////////////// // Z-indexes // /////////////// .nav-toggle { - z-index: 5; + z-index: 5; } // stylelint-disable-next-line no-duplicate-selectors .nav-wrapper { - z-index: 2; + z-index: 2; } // Avoiding a stacking context for the content-wrapper saves us a world @@ -525,80 +523,78 @@ body.explorer-open { // } // stylelint-disable-next-line no-duplicate-selectors .nav-submenu { - z-index: 6; + z-index: 6; } footer, .logo { - z-index: 100; + z-index: 100; } @include media-breakpoint-up(sm) { - .nav-main { - .nav-footer { - z-index: 2; - } + .nav-main { + .nav-footer { + z-index: 2; } + } - .nav-submenu { - z-index: 500; - } + .nav-submenu { + z-index: 500; + } - // Allows overspill of messages banner onto left menu, but also explorer - // to spill over main content - .nav-wrapper { - z-index: auto; - } + // Allows overspill of messages banner onto left menu, but also explorer + // to spill over main content + .nav-wrapper { + z-index: auto; + } - // footer is z-index: 100, so ensure the navigation sits on top of it. - .nav-wrapper.submenu-active { - z-index: 200; - } + // footer is z-index: 100, so ensure the navigation sits on top of it. + .nav-wrapper.submenu-active { + z-index: 200; + } } - /////////////////////// // Media query hacks // /////////////////////// // to detect IE10+ which doesn't support 3d transform of static elements and needs a fallback // stylelint-disable scss/media-feature-value-dollar-variable -@media all and (-ms-high-contrast: none), - all and (-ms-high-contrast: active) { +@media all and (-ms-high-contrast: none), all and (-ms-high-contrast: active) { + .wrapper { + @include transition(left 0.2s ease); + left: 0; + } + + body.nav-open { .wrapper { - @include transition(left 0.2s ease); - left: 0; + transform: none; + left: $menu-width; + position: relative; + } + } + + body.explorer-open { + .wrapper { + transform: none; + left: $menu-width-max; } - body.nav-open { - .wrapper { - transform: none; - left: $menu-width; - position: relative; - } - } - - body.explorer-open { - .wrapper { - transform: none; - left: $menu-width-max; - } - - .nav-wrapper { - width: $menu-width-max; - } + .nav-wrapper { + width: $menu-width-max; } + } } @media all and (min-width: breakpoint-min(sm)) and (-ms-high-contrast: none), - all and (min-width: breakpoint-min(sm)) and (-ms-high-contrast: active) { - body.explorer-open { - .wrapper { - left: 0; - } - - .nav-wrapper { - width: $menu-width; - } + all and (min-width: breakpoint-min(sm)) and (-ms-high-contrast: active) { + body.explorer-open { + .wrapper { + left: 0; } + + .nav-wrapper { + width: $menu-width; + } + } } diff --git a/client/scss/components/_media-placeholder.scss b/client/scss/components/_media-placeholder.scss index 96830daa36..afa54a0d13 100644 --- a/client/scss/components/_media-placeholder.scss +++ b/client/scss/components/_media-placeholder.scss @@ -1,17 +1,17 @@ .media-placeholder { - width: 600px; - height: 400px; - background-color: #ccc; - padding: 5px; + width: 600px; + height: 400px; + background-color: #ccc; + padding: 5px; - h3, - p { - margin: 0; - } + h3, + p { + margin: 0; + } - img { - max-width: 350px; - max-height: 350px; - margin: 20px; - } + img { + max-width: 350px; + max-height: 350px; + margin: 20px; + } } diff --git a/client/scss/components/_messages.capability.scss b/client/scss/components/_messages.capability.scss index 9122c1ab75..e5fb2275f9 100644 --- a/client/scss/components/_messages.capability.scss +++ b/client/scss/components/_messages.capability.scss @@ -1,14 +1,14 @@ .capabilitymessage { - display: block; - background-color: $color-red; - color: $color-white; - padding: 1em 2em; - margin: 0; - position: relative; - text-align: center; + display: block; + background-color: $color-red; + color: $color-white; + padding: 1em 2em; + margin: 0; + position: relative; + text-align: center; - a { - color: $color-white; - text-decoration: underline; - } + a { + color: $color-white; + text-decoration: underline; + } } diff --git a/client/scss/components/_messages.scss b/client/scss/components/_messages.scss index 2b22a1b2dc..503e92e14d 100644 --- a/client/scss/components/_messages.scss +++ b/client/scss/components/_messages.scss @@ -2,85 +2,85 @@ // for display on the next page visited. These appear as an animated banner at the top of the page. // For inline help text, see typography.scss .messages { + position: relative; + background-color: $color-grey-1; + + .buttons { + margin-left: 1em; + } + + > ul { + @include unlistimmediate(); position: relative; - background-color: $color-grey-1; + top: -100px; + opacity: 0; + } - .buttons { - margin-left: 1em; + > ul > li { + // @include nice-padding; + padding: 1.6em 3em 1.6em 1.6em; + color: $color-white; + } + + > ul > li:before { + @include font-smoothing; + margin-right: 0.5em; + font-size: 1.5em; + vertical-align: middle; + } + + &-icon { + vertical-align: text-top; + margin-right: 0.5em; + width: 1.5em; + height: 1.5em; + } + + .error { + background-color: $color-red-dark; + } + + .warning { + background-color: $color-orange-dark; + } + + .success { + background-color: $color-green-dark; + } + + .success .button:hover { + background-color: $color-teal-dark; + } + + .button-secondary { + border-color: rgba(255, 255, 255, 0.5); + color: $color-white; + + &:hover { + border-color: transparent; } + } - > ul { - @include unlistimmediate(); - position: relative; - top: -100px; - opacity: 0; - } - - > ul > li { - // @include nice-padding; - padding: 1.6em 3em 1.6em 1.6em; - color: $color-white; - } - - > ul > li:before { - @include font-smoothing; - margin-right: 0.5em; - font-size: 1.5em; - vertical-align: middle; - } - - &-icon { - vertical-align: text-top; - margin-right: 0.5em; - width: 1.5em; - height: 1.5em; - } - - .error { - background-color: $color-red-dark; - } - - .warning { - background-color: $color-orange-dark; - } - - .success { - background-color: $color-green-dark; - } - - .success .button:hover { - background-color: $color-teal-dark; - } - - .button-secondary { - border-color: rgba(255, 255, 255, 0.5); - color: $color-white; - - &:hover { - border-color: transparent; - } - } - - .errorlist { - margin: 0.5em 0 0 1em; - } + .errorlist { + margin: 0.5em 0 0 1em; + } } .messages.new > ul { - transition: none; - top: -100px; + transition: none; + top: -100px; } .ready .messages > ul, .messages.appear > ul { - transition: top 0.5s ease, opacity 0.5s ease, max-height 1.2s ease; - opacity: 1; - top: 0; + transition: top 0.5s ease, opacity 0.5s ease, max-height 1.2s ease; + opacity: 1; + top: 0; } @include media-breakpoint-up(sm) { - .messages > ul > li { - padding-left: 1.6em; - padding-right: 3em; - } + .messages > ul > li { + padding-left: 1.6em; + padding-right: 3em; + } } diff --git a/client/scss/components/_messages.status.scss b/client/scss/components/_messages.status.scss index 0217396341..5065006bc8 100644 --- a/client/scss/components/_messages.status.scss +++ b/client/scss/components/_messages.status.scss @@ -1,9 +1,9 @@ .status-msg { - &.success { - color: $color-green-dark; - } + &.success { + color: $color-green-dark; + } - &.failure { - color: $color-red-dark; - } + &.failure { + color: $color-red-dark; + } } diff --git a/client/scss/components/_modals.scss b/client/scss/components/_modals.scss index e0a9c2d393..36afe1c502 100644 --- a/client/scss/components/_modals.scss +++ b/client/scss/components/_modals.scss @@ -1,136 +1,140 @@ $zindex-modal-background: 500; .fade { - @include transition(opacity 0.15s linear); - opacity: 0; + @include transition(opacity 0.15s linear); + opacity: 0; - &.in { - opacity: 1; - } + &.in { + opacity: 1; + } } // Kill the scroll on the body .modal-open { - overflow: hidden; + overflow: hidden; - .content-wrapper { - transform: none; - } + .content-wrapper { + transform: none; + } } // Container that the modal scrolls within .modal { - box-sizing: border-box; - display: none; - overflow: auto; - overflow-y: scroll; - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: $zindex-modal-background; + box-sizing: border-box; + display: none; + overflow: auto; + overflow-y: scroll; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-modal-background; } // Shell div to position the modal with bottom padding .modal-dialog { - box-sizing: border-box; - margin-left: auto; - margin-right: auto; - padding: 0; - z-index: ($zindex-modal-background + 10); - height: 90%; - width: 85%; + box-sizing: border-box; + margin-left: auto; + margin-right: auto; + padding: 0; + z-index: ($zindex-modal-background + 10); + height: 90%; + width: 85%; - &:before { - content: ''; - display: inline-block; - height: 100%; - vertical-align: middle; - margin-right: -0.25em; - } + &:before { + content: ''; + display: inline-block; + height: 100%; + vertical-align: middle; + margin-right: -0.25em; + } } // Actual modal .modal-content { - box-sizing: border-box; - border-radius: 3px; - width: 98.7%; - position: relative; - background-color: $color-white; - margin-top: 2em; - padding-bottom: 3em; - display: inline-block; - vertical-align: middle; - overflow: hidden; + box-sizing: border-box; + border-radius: 3px; + width: 98.7%; + position: relative; + background-color: $color-white; + margin-top: 2em; + padding-bottom: 3em; + display: inline-block; + vertical-align: middle; + overflow: hidden; } // Modal background .modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: ($zindex-modal-background - 10); - background-color: $color-black; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: ($zindex-modal-background - 10); + background-color: $color-black; - // Fade for backdrop - &.fade { opacity: 0; } + // Fade for backdrop + &.fade { + opacity: 0; + } - &.in { opacity: 0.5; } + &.in { + opacity: 0.5; + } } .modal .close { - padding: 0; - position: absolute; - width: 50px; - height: 50px; - top: 10px; - right: 10px; - z-index: 1; + padding: 0; + position: absolute; + width: 50px; + height: 50px; + top: 10px; + right: 10px; + z-index: 1; } // Where all modal content resides .modal-body { - position: relative; - padding-bottom: 2em; + position: relative; + padding-bottom: 2em; - header { - padding-left: 2em; - padding-right: 100px; + header { + padding-left: 2em; + padding-right: 100px; - &.tab-merged { - padding-left: 1.6em; - } + &.tab-merged { + padding-left: 1.6em; } + } - .header-title { - // stylelint-disable-next-line declaration-no-important - padding-left: 0 !important; - margin-left: -36px; - } + .header-title { + // stylelint-disable-next-line declaration-no-important + padding-left: 0 !important; + margin-left: -36px; + } - .tab-merged .header-title { - margin-left: 0; - } + .tab-merged .header-title { + margin-left: 0; + } } @include media-breakpoint-up(sm) { - .modal-dialog { - padding: 0 0 2em $menu-width; - } + .modal-dialog { + padding: 0 0 2em $menu-width; + } - .modal-body { - header.tab-merged { - padding-left: $desktop-nice-padding; - } + .modal-body { + header.tab-merged { + padding-left: $desktop-nice-padding; } + } } @include media-breakpoint-up(xl) { - .modal-dialog { - max-width: 100em; - padding: 0 0 2em; - } + .modal-dialog { + max-width: 100em; + padding: 0 0 2em; + } } diff --git a/client/scss/components/_privacy-indicator.scss b/client/scss/components/_privacy-indicator.scss index f0bccf50d8..3c1373227d 100644 --- a/client/scss/components/_privacy-indicator.scss +++ b/client/scss/components/_privacy-indicator.scss @@ -1,20 +1,20 @@ .privacy-indicator { - .label-private, + .label-private, + .label-public { + &:before { + font-size: 1.5em; + } + } + + &.public { + .label-private { + display: none; + } + } + + &.private { .label-public { - &:before { - font-size: 1.5em; - } - } - - &.public { - .label-private { - display: none; - } - } - - &.private { - .label-public { - display: none; - } + display: none; } + } } diff --git a/client/scss/components/_progressbar.scss b/client/scss/components/_progressbar.scss index e90dafea16..74e1dbfc40 100644 --- a/client/scss/components/_progressbar.scss +++ b/client/scss/components/_progressbar.scss @@ -1,25 +1,25 @@ .progress { - border-radius: 1.2em; - background-color: $color-teal-dark; - border: 1px solid $color-teal; - opacity: 0; + border-radius: 1.2em; + background-color: $color-teal-dark; + border: 1px solid $color-teal; + opacity: 0; - &.active { - @include transition(opacity 0.3s ease); - opacity: 1; - } + &.active { + @include transition(opacity 0.3s ease); + opacity: 1; + } - .bar { - @include transition(width 0.3s ease); - border-radius: 1.5em; - overflow: hidden; - box-sizing: border-box; - text-align: right; - line-height: 1.2em; - color: $color-white; - font-size: 0.85em; - background-color: $color-teal; - height: 1.2em; - padding-right: 1em; - } + .bar { + @include transition(width 0.3s ease); + border-radius: 1.5em; + overflow: hidden; + box-sizing: border-box; + text-align: right; + line-height: 1.2em; + color: $color-white; + font-size: 0.85em; + background-color: $color-teal; + height: 1.2em; + padding-right: 1em; + } } diff --git a/client/scss/components/_skiplink.scss b/client/scss/components/_skiplink.scss index 1e3ff069d1..0d36b2a4ab 100644 --- a/client/scss/components/_skiplink.scss +++ b/client/scss/components/_skiplink.scss @@ -1,16 +1,16 @@ .skiplink { - display: block; - position: fixed; - top: -1000rem; - left: 1rem; - z-index: 3000; + display: block; + position: fixed; + top: -1000rem; + left: 1rem; + z-index: 3000; - &:focus { - top: 1rem; - } + &:focus { + top: 1rem; + } - &.button { - background: $color-green-darker; - border: $color-green-darker; - } + &.button { + background: $color-green-darker; + border: $color-green-darker; + } } diff --git a/client/scss/components/_status-tag.scss b/client/scss/components/_status-tag.scss index 774ae59c7c..d9d1ea6cc3 100644 --- a/client/scss/components/_status-tag.scss +++ b/client/scss/components/_status-tag.scss @@ -1,50 +1,50 @@ -@use "sass:color"; +@use 'sass:color'; .status-tag { - border-radius: 2px; - text-align: center; - display: inline-block; - text-transform: uppercase; - padding: 0 0.5em; - border: 1px solid color.adjust($color-grey-2, $lightness: 30%); - color: color.adjust($color-grey-2, $lightness: 30%); - -webkit-font-smoothing: auto; - line-height: 19px; - font-size: 0.8em; - margin: 0 0.5em 0.5em; - background: #fff url('#{$images-root}bg-dark-diag.svg'); + border-radius: 2px; + text-align: center; + display: inline-block; + text-transform: uppercase; + padding: 0 0.5em; + border: 1px solid color.adjust($color-grey-2, $lightness: 30%); + color: color.adjust($color-grey-2, $lightness: 30%); + -webkit-font-smoothing: auto; + line-height: 19px; + font-size: 0.8em; + margin: 0 0.5em 0.5em; + background: #fff url('#{$images-root}bg-dark-diag.svg'); - &.primary { - color: $color-grey-2; - border: 1px solid $color-grey-2; - background: #fff; - } + &.primary { + color: $color-grey-2; + border: 1px solid $color-grey-2; + background: #fff; + } - &.disabled { - pointer-events: none; - } + &.disabled { + pointer-events: none; + } - &--label { - color: $color-grey-2; - background: $color-grey-4; - border: $color-grey-4; - font-weight: 500; - } + &--label { + color: $color-grey-2; + background: $color-grey-4; + border: $color-grey-4; + font-weight: 500; + } } .listing .index .status-tag--label { - border: 1px solid; + border: 1px solid; } button.status-tag:hover, a.status-tag:hover, a.status-tag.primary:hover { - border-color: $color-teal; - color: $color-teal; + border-color: $color-teal; + color: $color-teal; } button.status-tag:hover { - border-color: $color-teal-dark; - background-color: $color-teal-darker; - color: $color-white; + border-color: $color-teal-dark; + background-color: $color-teal-darker; + color: $color-white; } diff --git a/client/scss/components/_switch.scss b/client/scss/components/_switch.scss index 65a7ac219b..815909f03f 100644 --- a/client/scss/components/_switch.scss +++ b/client/scss/components/_switch.scss @@ -1,4 +1,4 @@ -@use "sass:math"; +@use 'sass:math'; $switch-width: 40px; $switch-height: 20px; @@ -12,98 +12,101 @@ $switch-outline-radius: $switch-border-radius + $switch-outline; $switch-color-middle-grey: #777; .switch { - display: inline-flex; - align-items: center; - margin: 5px 0; + display: inline-flex; + align-items: center; + margin: 5px 0; - // Disable forms styling that's applied to the <label> tag - width: unset; - float: unset; + // Disable forms styling that's applied to the <label> tag + width: unset; + float: unset; - &__toggle { - position: relative; - cursor: pointer; + &__toggle { + position: relative; + cursor: pointer; - &::before, - &::after { - content: ''; - transition: all 100ms cubic-bezier(0.4, 0, 0.2, 1); - display: block; - } - - &::before { - height: $switch-height; - width: $switch-width; - border-radius: $switch-border-radius; - background: $switch-color-middle-grey; - border: $switch-border solid $switch-color-middle-grey; - } - - &::after { - box-sizing: border-box; - position: absolute; - top: 50%; - transform: translate($switch-border, -50%); - height: $switch-height; - width: $switch-height; - border: $switch-border solid $color-white; - border-radius: 50%; - background-color: $color-white; - } + &::before, + &::after { + content: ''; + transition: all 100ms cubic-bezier(0.4, 0, 0.2, 1); + display: block; } - [type=checkbox]:checked + &__toggle::before { - background: $color-teal; - border-color: $color-teal; + &::before { + height: $switch-height; + width: $switch-width; + border-radius: $switch-border-radius; + background: $switch-color-middle-grey; + border: $switch-border solid $switch-color-middle-grey; } - [type=checkbox]:checked + &__toggle::after { - transform: translate(calc(#{$switch-width} + #{$switch-border} - 100%), -50%); + &::after { + box-sizing: border-box; + position: absolute; + top: 50%; + transform: translate($switch-border, -50%); + height: $switch-height; + width: $switch-height; + border: $switch-border solid $color-white; + border-radius: 50%; + background-color: $color-white; + } + } + + [type='checkbox']:checked + &__toggle::before { + background: $color-teal; + border-color: $color-teal; + } + + [type='checkbox']:checked + &__toggle::after { + transform: translate( + calc(#{$switch-width} + #{$switch-border} - 100%), + -50% + ); + } + + [type='checkbox']:disabled + &__toggle { + cursor: not-allowed; + filter: grayscale(100%); + opacity: 0.3; + } + + [type='checkbox']:disabled + &__toggle::after { + opacity: 0.5; + box-shadow: none; + } + + [type='checkbox']:focus + &__toggle { + outline: $color-focus-outline solid $switch-outline; + -moz-outline-radius: $switch-outline-radius; + } + + [type='checkbox'] { + position: absolute; + opacity: 0; + pointer-events: none; + } + + // Colour changes for when displaying on teal background + &--teal-background { + $background: #03b0b1; + + .switch__toggle { + &::before { + background-color: #b9b9b9; + border: $switch-border solid #b9b9b9; + opacity: 0.6; + } + + &::after { + background-color: $color-white; + } } - [type=checkbox]:disabled + &__toggle { - cursor: not-allowed; - filter: grayscale(100%); - opacity: 0.3; - } - - [type=checkbox]:disabled + &__toggle::after { - opacity: 0.5; - box-shadow: none; - } - - [type=checkbox]:focus + &__toggle { - outline: $color-focus-outline solid $switch-outline; - -moz-outline-radius: $switch-outline-radius; - } - - [type=checkbox] { - position: absolute; - opacity: 0; - pointer-events: none; - } - - // Colour changes for when displaying on teal background - &--teal-background { - $background: #03b0b1; - - .switch__toggle { - &::before { - background-color: #b9b9b9; - border: $switch-border solid #b9b9b9; - opacity: 0.6; - } - - &::after { - background-color: $color-white; - } - } - - [type=checkbox]:checked + .switch__toggle::before { - // Override the white-background styling - background-color: $background; - border-color: $background; - opacity: 1; - } + [type='checkbox']:checked + .switch__toggle::before { + // Override the white-background styling + background-color: $background; + border-color: $background; + opacity: 1; } + } } diff --git a/client/scss/components/_tabs.scss b/client/scss/components/_tabs.scss index 9fbc4a1d9a..9b2263e8b1 100644 --- a/client/scss/components/_tabs.scss +++ b/client/scss/components/_tabs.scss @@ -1,161 +1,161 @@ .tab-nav { - @include row(); + @include row(); + padding: 0; + background: $color-grey-4; + + li { + list-style-type: none; + width: 33%; + float: left; padding: 0; - background: $color-grey-4; + position: relative; + margin-right: 2px; - li { - list-style-type: none; - width: 33%; - float: left; - padding: 0; - position: relative; - margin-right: 2px; - - &:first-of-type { - padding-left: $desktop-nice-padding; - margin-left: 0; - } + &:first-of-type { + padding-left: $desktop-nice-padding; + margin-left: 0; } + } - h2 { - margin: 0; - font-size: inherit; + h2 { + margin: 0; + font-size: inherit; + } + + a { + @include transition(border-color 0.2s ease); + background-color: $color-teal-darker; + text-transform: uppercase; + font-weight: 600; + text-decoration: none; + display: block; + padding: 0.6em 0.7em 0.8em; + color: $color-white; + border-top: 0.3em solid $color-teal-darker; + max-height: 1.44em; + overflow: hidden; + + &:hover { + color: $color-white; + border-top-color: rgba(0, 0, 0, 0.35); } + } - a { - @include transition(border-color 0.2s ease); - background-color: $color-teal-darker; - text-transform: uppercase; - font-weight: 600; - text-decoration: none; - display: block; - padding: 0.6em 0.7em 0.8em; - color: $color-white; - border-top: 0.3em solid $color-teal-darker; - max-height: 1.44em; - overflow: hidden; - - &:hover { - color: $color-white; - border-top-color: rgba(0, 0, 0, 0.35); - } + a.errors { + &:after { + border-radius: 50px; + box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.1); + position: absolute; + right: -0.5em; + top: -0.5em; + z-index: 5; + min-width: 0.9em; + color: $color-white; + background: $color-red; + content: attr(data-count); + padding: 0 0.3em; + line-height: 1.4em; + text-align: center; + font-size: 0.8em; } + } - a.errors { - &:after { - border-radius: 50px; - box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.1); - position: absolute; - right: -0.5em; - top: -0.5em; - z-index: 5; - min-width: 0.9em; - color: $color-white; - background: $color-red; - content: attr(data-count); - padding: 0 0.3em; - line-height: 1.4em; - text-align: center; - font-size: 0.8em; - } - } + li.active a { + box-shadow: none; + color: $color-grey-1; + background-color: $color-white; + border-top: 0.3em solid $color-grey-1; + } - li.active a { - box-shadow: none; - color: $color-grey-1; - background-color: $color-white; - border-top: 0.3em solid $color-grey-1; - } + // For cases where tab-nav should merge with header + &.merged { + margin-top: 0; + background-color: $color-header-bg; + } - // For cases where tab-nav should merge with header - &.merged { - margin-top: 0; - background-color: $color-header-bg; - } + li.right { + float: right; + margin-right: 0; + margin-left: 2px; + } - li.right { - float: right; - margin-right: 0; - margin-left: 2px; - } + li.wide { + width: unset; + } - li.wide { - width: unset; - } - - .right { - max-height: 1.44em; - overflow: visible; - } + .right { + max-height: 1.44em; + overflow: visible; + } } .tab-content { - > section { - display: none; - padding-top: 1em; + > section { + display: none; + padding-top: 1em; - &.active { - display: block; - } + &.active { + display: block; } + } - .page-locked & { - cursor: not-allowed; - user-select: none; + .page-locked & { + cursor: not-allowed; + user-select: none; - > * { - pointer-events: none; - } + > * { + pointer-events: none; } + } } @include media-breakpoint-up(sm) { - .tab-nav { - // For cases where tab-nav should merge with header - &.merged { - background-color: $color-header-bg; - } - - li { - width: auto; - padding: 0; - } - - a { - padding-left: $mobile-nice-padding; - padding-right: $mobile-nice-padding; - } - - li.settings a { - padding-left: 2em; - padding-right: 2em; - } + .tab-nav { + // For cases where tab-nav should merge with header + &.merged { + background-color: $color-header-bg; } - .modal-content .tab-nav li { - padding: 0; - min-width: 0; - - &:first-of-type { - padding-left: $desktop-nice-padding; - } + li { + width: auto; + padding: 0; } + + a { + padding-left: $mobile-nice-padding; + padding-right: $mobile-nice-padding; + } + + li.settings a { + padding-left: 2em; + padding-right: 2em; + } + } + + .modal-content .tab-nav li { + padding: 0; + min-width: 0; + + &:first-of-type { + padding-left: $desktop-nice-padding; + } + } } @include media-breakpoint-down(xs) { - // To allow tabs on the edit page to be editable - .tab-nav li:first-of-type { - padding-left: 1.6em; - } + // To allow tabs on the edit page to be editable + .tab-nav li:first-of-type { + padding-left: 1.6em; + } - .tab-nav li { - width: auto; - } + .tab-nav li { + width: auto; + } } // Media for Windows High Contrast @media (forced-colors: $media-forced-colours) { - .tab-nav li.active a { - border-bottom: 0.3em solid $system-color-link-text; - } + .tab-nav li.active a { + border-bottom: 0.3em solid $system-color-link-text; + } } diff --git a/client/scss/components/_tag.scss b/client/scss/components/_tag.scss index a17815551f..fc2b34f4d6 100644 --- a/client/scss/components/_tag.scss +++ b/client/scss/components/_tag.scss @@ -1,61 +1,61 @@ -@use "sass:map"; +@use 'sass:map'; // free tagging tags from taggit .tag { - border-radius: 2px; - background-color: $color-teal; - padding: 0.2em 0.5em; + border-radius: 2px; + background-color: $color-teal; + padding: 0.2em 0.5em; + color: $color-white; + line-height: 2em; + white-space: nowrap; + + &:before { + font-family: $font-wagtail-icons; + display: inline-block; color: $color-white; - line-height: 2em; - white-space: nowrap; + content: map.get($icons, 'tag'); + padding-right: 0.5em; + } - &:before { - font-family: $font-wagtail-icons; - display: inline-block; - color: $color-white; - content: map.get($icons, 'tag'); - padding-right: 0.5em; - } - - .taglist & { - margin-right: 0.8em; - } + .taglist & { + margin-right: 0.8em; + } } a.tag:hover { - background-color: $color-teal-darker; - color: $color-white; + background-color: $color-teal-darker; + color: $color-white; } .taglist { - font-size: 0.9em; - line-height: 2.4em; + font-size: 0.9em; + line-height: 2.4em; } .tagfilter { - legend { - @include visuallyvisible; + legend { + @include visuallyvisible; - @include media-breakpoint-up(sm) { - @include column(2); - padding-left: 0; - } - - font-weight: 700; - color: #333; - font-size: 1.1em; - display: block; - padding: 0 0 0.8em; + @include media-breakpoint-up(sm) { + @include column(2); + padding-left: 0; } - a { - font-size: 0.9em; - } + font-weight: 700; + color: #333; + font-size: 1.1em; + display: block; + padding: 0 0 0.8em; + } - .button.bicolor.icon-cross { - padding-left: 2em; + a { + font-size: 0.9em; + } - &:before { - background-color: transparent; - } + .button.bicolor.icon-cross { + padding-left: 2em; + + &:before { + background-color: transparent; } + } } diff --git a/client/scss/components/_tooltips.scss b/client/scss/components/_tooltips.scss index 9c9eef055e..27b9959e49 100644 --- a/client/scss/components/_tooltips.scss +++ b/client/scss/components/_tooltips.scss @@ -1,108 +1,108 @@ // From Bootstrap v3.0.0 .tooltip { - position: absolute; - z-index: 1030; - display: block; - font-size: 12px; - line-height: 1.4; - opacity: 0; - visibility: visible; + position: absolute; + z-index: 1030; + display: block; + font-size: 12px; + line-height: 1.4; + opacity: 0; + visibility: visible; } .tooltip.in { - opacity: 0.9; + opacity: 0.9; } .tooltip.top { - padding: 5px 0; + padding: 5px 0; } .tooltip.right { - padding: 0 5px; + padding: 0 5px; } .tooltip.bottom { - padding: 5px 0; + padding: 5px 0; } .tooltip.left { - padding: 0 5px; + padding: 0 5px; } .tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #fff; - text-align: center; - text-decoration: none; - background-color: #000; - border-radius: 4px; + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px; } .tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; } .tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-top-color: #000; - border-width: 5px 5px 0; + bottom: 0; + left: 50%; + margin-left: -5px; + border-top-color: #000; + border-width: 5px 5px 0; } .tooltip.top-left .tooltip-arrow { - bottom: 0; - left: 5px; - border-top-color: #000; - border-width: 5px 5px 0; + bottom: 0; + left: 5px; + border-top-color: #000; + border-width: 5px 5px 0; } .tooltip.top-right .tooltip-arrow { - right: 5px; - bottom: 0; - border-top-color: #000; - border-width: 5px 5px 0; + right: 5px; + bottom: 0; + border-top-color: #000; + border-width: 5px 5px 0; } .tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-right-color: #000; - border-width: 5px 5px 5px 0; + top: 50%; + left: 0; + margin-top: -5px; + border-right-color: #000; + border-width: 5px 5px 5px 0; } .tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-left-color: #000; - border-width: 5px 0 5px 5px; + top: 50%; + right: 0; + margin-top: -5px; + border-left-color: #000; + border-width: 5px 0 5px 5px; } .tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-bottom-color: #000; - border-width: 0 5px 5px; + top: 0; + left: 50%; + margin-left: -5px; + border-bottom-color: #000; + border-width: 0 5px 5px; } .tooltip.bottom-left .tooltip-arrow { - top: 0; - left: 5px; - border-bottom-color: #000; - border-width: 0 5px 5px; + top: 0; + left: 5px; + border-bottom-color: #000; + border-width: 0 5px 5px; } .tooltip.bottom-right .tooltip-arrow { - top: 0; - right: 5px; - border-bottom-color: #000; - border-width: 0 5px 5px; + top: 0; + right: 5px; + border-bottom-color: #000; + border-width: 0 5px 5px; } diff --git a/client/scss/components/_workflow-tasks.scss b/client/scss/components/_workflow-tasks.scss index 5a5ea9627a..fe06655265 100644 --- a/client/scss/components/_workflow-tasks.scss +++ b/client/scss/components/_workflow-tasks.scss @@ -1,42 +1,42 @@ .workflow-tasks { - $task-width: 117px; - $task-height: 56px; + $task-width: 117px; + $task-height: 56px; - list-style-type: none; + list-style-type: none; - &__task { - display: inline-block; - background-color: #f8ffff; - border: 2px solid #7ebebe; - border-radius: 5px; - color: #007d7e; - box-sizing: border-box; - width: $task-width; - height: $task-height; - margin: 7px; - padding-left: 9px; - padding-right: 9px; - } + &__task { + display: inline-block; + background-color: #f8ffff; + border: 2px solid #7ebebe; + border-radius: 5px; + color: #007d7e; + box-sizing: border-box; + width: $task-width; + height: $task-height; + margin: 7px; + padding-left: 9px; + padding-right: 9px; + } - &__step { - font-size: 10px; - text-transform: uppercase; - margin-top: 3px; - } + &__step { + font-size: 10px; + text-transform: uppercase; + margin-top: 3px; + } - &__name { - font-size: 16px; - font-weight: bold; - margin: 0; + &__name { + font-size: 16px; + font-weight: bold; + margin: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } - &__extra-tasks { - display: inline-block; - height: $task-height; - vertical-align: middle; - } + &__extra-tasks { + display: inline-block; + height: $task-height; + vertical-align: middle; + } } diff --git a/client/scss/components/browser-message.scss b/client/scss/components/browser-message.scss index d141489b7d..696bf9370e 100644 --- a/client/scss/components/browser-message.scss +++ b/client/scss/components/browser-message.scss @@ -1,5 +1,5 @@ @include media-breakpoint-up(sm) { - .browsermessage { - margin: 0 0 0 -150px; - } + .browsermessage { + margin: 0 0 0 -150px; + } } diff --git a/client/scss/core.scss b/client/scss/core.scss index 395bf6ea4b..5560c0c5d2 100644 --- a/client/scss/core.scss +++ b/client/scss/core.scss @@ -41,7 +41,6 @@ OTHER PREFIXES ==============================================================================*/ - /* SETTINGS These are variables, maps, and fonts. * No CSS should be produced by these files @@ -49,7 +48,6 @@ These are variables, maps, and fonts. @import 'settings'; - /* TOOLS These are functions and mixins. * No CSS should be produced by these files. @@ -57,7 +55,6 @@ These are functions and mixins. @import 'tools'; - /* GENERIC This is for resets and other rules that affect large collections of bare elements. * Changes to them should be very rare. @@ -65,7 +62,6 @@ This is for resets and other rules that affect large collections of bare element // @import 'generic/generic'; - /* ELEMENTS These are base styles for bare HTML elements. * Changes to them should be very rare. @@ -76,7 +72,6 @@ These are base styles for bare HTML elements. @import 'elements/forms'; @import 'elements/root'; - /* OBJECTS These are classes related to layout, known as 'objects' in ITCSS or OOCSS. * This is for grids, wrappers, and other non-consmetic layout utilities. @@ -86,7 +81,6 @@ These are classes related to layout, known as 'objects' in ITCSS or OOCSS. @import 'objects/objects'; @import 'objects/avatar'; - /* COMPONENTS These are classes for components. * These classes (unless legacy) are prefixed with `.c-`. @@ -147,7 +141,6 @@ These are classes that provide overrides. @import 'overrides/vendor.datetimepicker'; @import 'overrides/vendor.tagit'; - // UTILITIES: classes that do one simple thing. @import 'overrides/utilities.hidden'; @import 'overrides/utilities.text'; @@ -159,7 +152,6 @@ These are classes that provide overrides. @import 'overrides/utilities.text.legacy'; @import 'overrides/utilities.legacy'; - // PAGES: page-specific overrides @import 'overrides/pages.homepage'; @import 'overrides/pages.page-explorer'; diff --git a/client/scss/elements/_elements.scss b/client/scss/elements/_elements.scss index 30613c959d..475911d5ba 100644 --- a/client/scss/elements/_elements.scss +++ b/client/scss/elements/_elements.scss @@ -1,35 +1,35 @@ html { - background: $color-grey-4; - height: 100%; + background: $color-grey-4; + height: 100%; } body { - overflow-x: hidden; - position: relative; + overflow-x: hidden; + position: relative; - &:after { - content: ''; - position: fixed; - transition: visibility 0s linear 0s, opacity 0.2s ease-out; - background: rgba(255, 255, 255, 0.5); - width: 100%; - height: 100%; - top: 0; - left: 0; - z-index: 5; - opacity: 0; - visibility: hidden; - } + &:after { + content: ''; + position: fixed; + transition: visibility 0s linear 0s, opacity 0.2s ease-out; + background: rgba(255, 255, 255, 0.5); + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 5; + opacity: 0; + visibility: hidden; + } } hr { - border: 1px solid $color-grey-4; - border-width: 1px 0 0; - margin: 1.5em 0; + border: 1px solid $color-grey-4; + border-width: 1px 0 0; + margin: 1.5em 0; } // general image style img { - max-width: 100%; - height: auto; + max-width: 100%; + height: auto; } diff --git a/client/scss/elements/_forms.scss b/client/scss/elements/_forms.scss index 1d6bb7a8eb..ad06450ae3 100644 --- a/client/scss/elements/_forms.scss +++ b/client/scss/elements/_forms.scss @@ -1,242 +1,239 @@ -@use "sass:map"; +@use 'sass:map'; // These are the generic stylings for forms of any type. // If you're styling something specific to the page editing interface, // it probably ought to go in layouts/page-editor.scss form { - ul, + ul, + li { + list-style-type: none; + } - li { - list-style-type: none; - } - - ul { - margin: 0; - padding: 0; - } + ul { + margin: 0; + padding: 0; + } } fieldset { - border: 0; - padding: 0 0 2em; - margin: 0; + border: 0; + padding: 0 0 2em; + margin: 0; } legend { - @include visuallyhidden(); + @include visuallyhidden(); } label, .label { - text-transform: none; - font-weight: bold; - color: $color-grey-1; - font-size: 1.1em; - display: block; - padding: 0 0 0.8em; - margin: 0; - line-height: 1.3em; + text-transform: none; + font-weight: bold; + color: $color-grey-1; + font-size: 1.1em; + display: block; + padding: 0 0 0.8em; + margin: 0; + line-height: 1.3em; - .checkbox &, - .radio & { - display: inline; + .checkbox &, + .radio & { + display: inline; + } + + &.no-float { + float: none; + } + + &.disabled { + opacity: 0.7; + cursor: not-allowed; + } + + @include media-breakpoint-up(sm) { + @include column(2); + padding-top: 1.2em; + padding-left: 0; + + .radio_select &, + .multiple_choice_field &, + .model_multiple_choice_field &, + .checkbox_select_multiple &, + .boolean_field &, + .model_choice_field &, + .image_field & { + padding-top: 0; } - &.no-float { - float: none; - } - - &.disabled { - opacity: 0.7; - cursor: not-allowed; - } - - @include media-breakpoint-up(sm) { - @include column(2); - padding-top: 1.2em; - padding-left: 0; - - .radio_select &, - .multiple_choice_field &, - .model_multiple_choice_field &, - .checkbox_select_multiple &, - .boolean_field &, - .model_choice_field &, - .image_field & { - padding-top: 0; - } - - // Horrid specificity war - .model_choice_field.select & { - padding-top: 1.2em; - } + // Horrid specificity war + .model_choice_field.select & { + padding-top: 1.2em; } + } } -input:not([type=submit]), +input:not([type='submit']), textarea, select, .halloeditor, .tagit { - appearance: none; - box-sizing: border-box; - border-radius: 6px; - width: 100%; - font-family: $font-sans; - border: 1px solid $color-input-border; - padding: 0.9em 1.2em; - background-color: $color-fieldset-hover; - color: $color-text-input; - font-size: 1.2em; - font-weight: 300; + appearance: none; + box-sizing: border-box; + border-radius: 6px; + width: 100%; + font-family: $font-sans; + border: 1px solid $color-input-border; + padding: 0.9em 1.2em; + background-color: $color-fieldset-hover; + color: $color-text-input; + font-size: 1.2em; + font-weight: 300; - &:hover { - background-color: $color-white; - } + &:hover { + background-color: $color-white; + } - &:focus { - background-color: $color-input-focus; - border-color: $color-input-focus-border; - } + &:focus { + background-color: $color-input-focus; + border-color: $color-input-focus-border; + } - &:disabled, - &[disabled], - &:disabled:hover, - &[disabled]:hover { - background-color: $color-grey-4; - cursor: not-allowed; - color: $color-grey-2; - } + &:disabled, + &[disabled], + &:disabled:hover, + &[disabled]:hover { + background-color: $color-grey-4; + cursor: not-allowed; + color: $color-grey-2; + } } @media (forced-colors: $media-forced-colours) { - .tagit, - .field-content .tagit .tagit-choice, - .tagit .tagit-new .ui-widget-content { - box-shadow: inset 1000px 0 0 0 $color-black; - color: $color-white; - forced-color-adjust: none; - } + .tagit, + .field-content .tagit .tagit-choice, + .tagit .tagit-new .ui-widget-content { + box-shadow: inset 1000px 0 0 0 $color-black; + color: $color-white; + forced-color-adjust: none; + } - .tagit span.tagit-label:before, - .tagit span.tagit-label { - color: $color-black; - forced-color-adjust: none; - } + .tagit span.tagit-label:before, + .tagit span.tagit-label { + color: $color-black; + forced-color-adjust: none; + } } // Reset the arrow on `<select>`s in IE10+. select::-ms-expand { - display: none; + display: none; } .file_field { - .input { - label { - float: none; - display: inline; - padding: 0; - } - - input[type=checkbox] { - margin-top: 5px; - } - - a { - &:after { - content: ' '; - display: block; - } - } + .input { + label { + float: none; + display: inline; + padding: 0; } -} + input[type='checkbox'] { + margin-top: 5px; + } + + a { + &:after { + content: ' '; + display: block; + } + } + } +} // radio and check boxes -input[type=radio], -input[type=checkbox] { - border-radius: 0; - cursor: pointer; - border: 0; - padding: 0; +input[type='radio'], +input[type='checkbox'] { + border-radius: 0; + cursor: pointer; + border: 0; + padding: 0; } -input[type=radio] { - height: 12px; - width: auto; - position: relative; - margin-right: 27px; +input[type='radio'] { + height: 12px; + width: auto; + position: relative; + margin-right: 27px; } -input[type=radio]:before { - border-radius: 100%; - font-family: $font-wagtail-icons; - font-style: normal; - text-align: center; - position: absolute; - top: -5px; - left: -2px; - cursor: pointer; - display: block; - content: map.get($icons, 'radio-full'); - width: 1em; - height: 1em; - line-height: 1.1em; - padding: 4px; - background-color: $color-white; - color: $color-grey-4; - border: 1px solid $color-grey-4; +input[type='radio']:before { + border-radius: 100%; + font-family: $font-wagtail-icons; + font-style: normal; + text-align: center; + position: absolute; + top: -5px; + left: -2px; + cursor: pointer; + display: block; + content: map.get($icons, 'radio-full'); + width: 1em; + height: 1em; + line-height: 1.1em; + padding: 4px; + background-color: $color-white; + color: $color-grey-4; + border: 1px solid $color-grey-4; } -input[type=radio]:checked:before { - content: map.get($icons, 'radio-full'); - color: $color-teal; +input[type='radio']:checked:before { + content: map.get($icons, 'radio-full'); + color: $color-teal; } -input[type=checkbox] { - height: 12px; - width: 22px; - position: relative; - margin-right: 5px; +input[type='checkbox'] { + height: 12px; + width: 22px; + position: relative; + margin-right: 5px; } -input[type=checkbox]:before { - font-family: $font-wagtail-icons; - font-style: normal; - text-align: center; - position: absolute; - top: -5px; - cursor: pointer; - display: block; - content: ''; - line-height: 20px; - width: 20px; - height: 20px; - background-color: $color-white; - border: 1px solid $color-grey-4; - color: $color-teal; +input[type='checkbox']:before { + font-family: $font-wagtail-icons; + font-style: normal; + text-align: center; + position: absolute; + top: -5px; + cursor: pointer; + display: block; + content: ''; + line-height: 20px; + width: 20px; + height: 20px; + background-color: $color-white; + border: 1px solid $color-grey-4; + color: $color-teal; } -input[type=checkbox]:checked:before { - content: map.get($icons, 'tick'); +input[type='checkbox']:checked:before { + content: map.get($icons, 'tick'); } -input[type=checkbox][disabled]:before { - cursor: not-allowed; +input[type='checkbox'][disabled]:before { + cursor: not-allowed; } - // Special styles to counteract Firefox's completely unwarranted assumptions about button styles -input[type=submit], -input[type=reset], -input[type=button], +input[type='submit'], +input[type='reset'], +input[type='button'], button { - padding: 0 1em; + padding: 0 1em; - @include media-breakpoint-up(sm) { - &.button-small { - height: 2em; - } + @include media-breakpoint-up(sm) { + &.button-small { + height: 2em; } + } } // Transitions @@ -244,5 +241,5 @@ fieldset, input, textarea, select { - @include transition(background-color 0.2s ease); + @include transition(background-color 0.2s ease); } diff --git a/client/scss/elements/_root.scss b/client/scss/elements/_root.scss index 485f40a4f4..a6fd336583 100644 --- a/client/scss/elements/_root.scss +++ b/client/scss/elements/_root.scss @@ -1,10 +1,34 @@ :root { - @include define-color('color-primary', #007d7e); - @include define-color('color-primary-darker', css-darken(css-adjust-hue(get-color('color-primary'), 1), 4%)); - @include define-color('color-primary-dark', css-darken(css-adjust-hue(get-color('color-primary'), 1), 7%)); - @include define-color('color-primary-lighter', css-lighten(css-desaturate(css-adjust-hue(get-color('color-primary'), 1), 46%), 48%)); - @include define-color('color-primary-light', css-lighten(css-desaturate(css-adjust-hue(get-color('color-primary'), 1), 44%), 58%)); + @include define-color('color-primary', #007d7e); + @include define-color( + 'color-primary-darker', + css-darken(css-adjust-hue(get-color('color-primary'), 1), 4%) + ); + @include define-color( + 'color-primary-dark', + css-darken(css-adjust-hue(get-color('color-primary'), 1), 7%) + ); + @include define-color( + 'color-primary-lighter', + css-lighten( + css-desaturate(css-adjust-hue(get-color('color-primary'), 1), 46%), + 48% + ) + ); + @include define-color( + 'color-primary-light', + css-lighten( + css-desaturate(css-adjust-hue(get-color('color-primary'), 1), 44%), + 58% + ) + ); - @include define-color('color-input-focus', css-lighten(css-desaturate(get-color('color-primary'), 40%), 72%)); - @include define-color('color-input-focus-border', css-lighten(css-saturate(get-color('color-primary'), 12%), 10%)); + @include define-color( + 'color-input-focus', + css-lighten(css-desaturate(get-color('color-primary'), 40%), 72%) + ); + @include define-color( + 'color-input-focus-border', + css-lighten(css-saturate(get-color('color-primary'), 12%), 10%) + ); } diff --git a/client/scss/elements/_typography.scss b/client/scss/elements/_typography.scss index ae93f03e22..3c642da7de 100644 --- a/client/scss/elements/_typography.scss +++ b/client/scss/elements/_typography.scss @@ -1,9 +1,9 @@ body { - -webkit-font-smoothing: antialiased; // Do not remove! - font-family: $font-sans; - font-size: 85%; - line-height: 1.5em; - color: $color-text-base; + -webkit-font-smoothing: antialiased; // Do not remove! + font-family: $font-sans; + font-size: 85%; + line-height: 1.5em; + color: $color-text-base; } h1, @@ -12,75 +12,75 @@ h3, h4, h5, h6 { - font-weight: normal; + font-weight: normal; } h1 { - line-height: 1.3em; - font-size: 1.5em; - text-transform: uppercase; - color: $color-grey-1; - font-weight: 700; + line-height: 1.3em; + font-size: 1.5em; + text-transform: uppercase; + color: $color-grey-1; + font-weight: 700; - span { - text-transform: none; - font-weight: 300; - } + span { + text-transform: none; + font-weight: 300; + } } h2 { - text-transform: uppercase; - font-size: 1.3em; - font-family: $font-sans; - font-weight: 600; - color: $color-grey-2; + text-transform: uppercase; + font-size: 1.3em; + font-family: $font-sans; + font-weight: 600; + color: $color-grey-2; } p { - margin-top: 0; + margin-top: 0; } a { - // @include transition(color 0.2s ease, background-color 0.2s ease); - color: $color-link; - text-decoration: none; + // @include transition(color 0.2s ease, background-color 0.2s ease); + color: $color-link; + text-decoration: none; - &:hover { - color: $color-link-hover; - } + &:hover { + color: $color-link-hover; + } } code { - box-shadow: inset 0 0 4px 0 rgba(0, 0, 0, 0.2); - background-color: $color-fieldset-hover; - padding: 2px 5px; + box-shadow: inset 0 0 4px 0 rgba(0, 0, 0, 0.2); + background-color: $color-fieldset-hover; + padding: 2px 5px; } kbd { - border-radius: 3px; - font-family: $font-sans; - border: 1px solid $color-grey-2; - border-color: rgba(0, 0, 0, 0.2); - padding: 0.3em 0.5em; + border-radius: 3px; + font-family: $font-sans; + border: 1px solid $color-grey-2; + border-color: rgba(0, 0, 0, 0.2); + padding: 0.3em 0.5em; } dl, dt, dd { - padding: 0; - margin: 0; + padding: 0; + margin: 0; } dl { - margin-top: 1em; + margin-top: 1em; } dt { - color: $color-grey-2; - text-transform: uppercase; - font-size: 0.9em; + color: $color-grey-2; + text-transform: uppercase; + font-size: 0.9em; } dd { - margin-bottom: 1em; + margin-bottom: 1em; } diff --git a/client/scss/objects/_avatar.scss b/client/scss/objects/_avatar.scss index 5099885244..703b18b36c 100644 --- a/client/scss/objects/_avatar.scss +++ b/client/scss/objects/_avatar.scss @@ -1,40 +1,40 @@ // user avatars .avatar { - border-radius: 100%; - position: relative; - display: inline-block; + border-radius: 100%; + position: relative; + display: inline-block; + vertical-align: middle; + text-align: center; + overflow: hidden; + width: 50px; + height: 50px; + + img { + position: absolute; + z-index: 2; + top: 0; + left: 0; + right: 0; + border: 0; + } + + &.small { vertical-align: middle; - text-align: center; - overflow: hidden; - width: 50px; - height: 50px; + margin: 0 0.5em; + width: 25px; + height: 25px; + } - img { - position: absolute; - z-index: 2; - top: 0; - left: 0; - right: 0; - border: 0; - } - - &.small { - vertical-align: middle; - margin: 0 0.5em; - width: 25px; - height: 25px; - } - - &.large { - width: 100px; - height: 100px; - } - - &.square { - border-radius: 0; - - &:before { - border-radius: 0; - } + &.large { + width: 100px; + height: 100px; + } + + &.square { + border-radius: 0; + + &:before { + border-radius: 0; } + } } diff --git a/client/scss/objects/_objects.scss b/client/scss/objects/_objects.scss index b1ff5f7e58..9f54ce5cd5 100644 --- a/client/scss/objects/_objects.scss +++ b/client/scss/objects/_objects.scss @@ -1,16 +1,15 @@ .o-pill { - display: inline-block; - padding: 0.2em 0.5em; - border-radius: 0.25em; - vertical-align: middle; - line-height: 1.5; + display: inline-block; + padding: 0.2em 0.5em; + border-radius: 0.25em; + vertical-align: middle; + line-height: 1.5; } - // For dropdowns .o-icon { - display: inline-block; - vertical-align: middle; - line-height: 1; - margin-top: -0.25rem; + display: inline-block; + vertical-align: middle; + line-height: 1; + margin-top: -0.25rem; } diff --git a/client/scss/overrides/_pages.homepage.scss b/client/scss/overrides/_pages.homepage.scss index f903223a8f..aa40eb87f2 100644 --- a/client/scss/overrides/_pages.homepage.scss +++ b/client/scss/overrides/_pages.homepage.scss @@ -1,3 +1,3 @@ .homepage h1 { - text-transform: none; + text-transform: none; } diff --git a/client/scss/overrides/_pages.page-explorer.scss b/client/scss/overrides/_pages.page-explorer.scss index 6579e27f4a..e1aba9d9d9 100644 --- a/client/scss/overrides/_pages.page-explorer.scss +++ b/client/scss/overrides/_pages.page-explorer.scss @@ -1,3 +1,3 @@ .page-explorer h2 { - text-transform: none; + text-transform: none; } diff --git a/client/scss/overrides/_utilities.dropdowns.scss b/client/scss/overrides/_utilities.dropdowns.scss index 0643637139..65cdf42d51 100644 --- a/client/scss/overrides/_utilities.dropdowns.scss +++ b/client/scss/overrides/_utilities.dropdowns.scss @@ -2,20 +2,20 @@ // Arrows // ============================================================================= .u-arrow:before { - content: ''; - border: solid 0.35rem transparent; - display: block; - position: absolute; + content: ''; + border: solid 0.35rem transparent; + display: block; + position: absolute; } .u-arrow--tl:before { - bottom: 100%; - left: 1rem; + bottom: 100%; + left: 1rem; } .dropup .u-arrow--tl:before { - top: 100%; - transform: rotateZ(180deg); + top: 100%; + transform: rotateZ(180deg); } // ============================================================================= @@ -26,85 +26,84 @@ // } .t-default .u-btn-current { - border-color: rgba(0, 0, 0, 0.15); - color: $color-teal; + border-color: rgba(0, 0, 0, 0.15); + color: $color-teal; } .t-default .u-btn-current:hover { - background: $color-teal; - color: #fff; - border-color: $color-teal; + background: $color-teal; + color: #fff; + border-color: $color-teal; } .t-default .u-btn-current:active { - background: #333; - color: #fff; - border-color: #333; + background: #333; + color: #fff; + border-color: #333; } .t-inverted .u-btn-current { - border-color: rgba(0, 0, 0, 0.35); - color: #fff; + border-color: rgba(0, 0, 0, 0.35); + color: #fff; } .t-inverted .u-btn-current:hover { - background-color: $color-teal-darker; - border-color: rgba(0, 0, 0, 0.35); + background-color: $color-teal-darker; + border-color: rgba(0, 0, 0, 0.35); } .t-inverted .u-btn-current:active { - border-color: rgba(0, 0, 0, 0.35); - background: #333; - color: #fff; + border-color: rgba(0, 0, 0, 0.35); + background: #333; + color: #fff; } - // ============================================================================= // Dark theme // ============================================================================= .t-dark .u-link { - color: #fff; + color: #fff; } .t-dark .u-link:hover { - color: #aaa; + color: #aaa; } .t-dark .u-background { - background: #333; + background: #333; } .t-dark .u-arrow:before { - border-bottom-color: #333; + border-bottom-color: #333; } // ============================================================================= // Light theme // ============================================================================= .t-light .u-link { - color: #333; + color: #333; } .t-light .u-link:hover { - color: #aaa; + color: #aaa; } .t-light .u-background { - background: #fff; - border-color: #ccc; + background: #fff; + border-color: #ccc; } .t-light .u-arrow:before { - border-bottom-color: #fff; + border-bottom-color: #fff; } // ============================================================================= // States // ============================================================================= .u-toggle { - display: none; + display: none; } .is-open .u-toggle { - display: block; + display: block; } diff --git a/client/scss/overrides/_utilities.focus.scss b/client/scss/overrides/_utilities.focus.scss index e53f1aaf69..1b3c28f927 100644 --- a/client/scss/overrides/_utilities.focus.scss +++ b/client/scss/overrides/_utilities.focus.scss @@ -3,9 +3,9 @@ // without individual components having to explicitly define focus styles. // Using !important because we want to enforce only one style is used across the UI. .focus-outline-on *:focus { - outline: $focus-outline-width solid $color-focus-outline !important; + outline: $focus-outline-width solid $color-focus-outline !important; } .focus-outline-off *:focus { - outline: none !important; + outline: none !important; } diff --git a/client/scss/overrides/_utilities.hidden.scss b/client/scss/overrides/_utilities.hidden.scss index b2b697b0e3..dd14dd6ecf 100644 --- a/client/scss/overrides/_utilities.hidden.scss +++ b/client/scss/overrides/_utilities.hidden.scss @@ -1,40 +1,40 @@ // stylelint-disable declaration-no-important .u-hidden { - display: none !important; + display: none !important; } .u-hidden\@sm { - @include media-breakpoint-up(sm) { - display: none !important; - } + @include media-breakpoint-up(sm) { + display: none !important; + } } .u-hidden\@xs { - @include media-breakpoint-down(xs) { - display: none !important; - } + @include media-breakpoint-down(xs) { + display: none !important; + } } .u-inline\@sm { - @include media-breakpoint-up(sm) { - display: inline !important; - } + @include media-breakpoint-up(sm) { + display: inline !important; + } } .u-inline\@xs { - @include media-breakpoint-down(xs) { - display: inline !important; - } + @include media-breakpoint-down(xs) { + display: inline !important; + } } .u-block\@sm { - @include media-breakpoint-up(sm) { - display: block !important; - } + @include media-breakpoint-up(sm) { + display: block !important; + } } .u-block\@xs { - @include media-breakpoint-down(xs) { - display: block !important; - } + @include media-breakpoint-down(xs) { + display: block !important; + } } diff --git a/client/scss/overrides/_utilities.legacy.scss b/client/scss/overrides/_utilities.legacy.scss index 9719d97cf0..a7ce504b84 100644 --- a/client/scss/overrides/_utilities.legacy.scss +++ b/client/scss/overrides/_utilities.legacy.scss @@ -1,55 +1,54 @@ .clearfix { - @include clearfix(); + @include clearfix(); } .nice-padding { - padding-left: $mobile-nice-padding; - padding-right: $mobile-nice-padding; + padding-left: $mobile-nice-padding; + padding-right: $mobile-nice-padding; - @include media-breakpoint-up(sm) { - padding-left: $desktop-nice-padding; - padding-right: $desktop-nice-padding; - } + @include media-breakpoint-up(sm) { + padding-left: $desktop-nice-padding; + padding-right: $desktop-nice-padding; + } } @include media-breakpoint-up(sm) { - .divider-before { - border-left: 1px solid $color-grey-4; - } - - .divider-after { - border-right: 1px solid $color-grey-4; - } + .divider-before { + border-left: 1px solid $color-grey-4; + } + .divider-after { + border-right: 1px solid $color-grey-4; + } } body.reordering { - overflow: visible; + overflow: visible; } // Show a transparency grid in background .show-transparency { - background: url('#{$images-root}transparency.svg'); + background: url('#{$images-root}transparency.svg'); } // make a block-level element inline .inline { - display: inline; + display: inline; } .inline-block { - display: inline-block; + display: inline-block; } .block { - display: block; + display: block; } .unlist { - @include unlist(); + @include unlist(); } // utility class to allow things to be scrollable if their contents can't wrap more nicely .overflow { - overflow: auto; + overflow: auto; } diff --git a/client/scss/overrides/_utilities.text.legacy.scss b/client/scss/overrides/_utilities.text.legacy.scss index 84e7d3602c..f03d0e6a12 100644 --- a/client/scss/overrides/_utilities.text.legacy.scss +++ b/client/scss/overrides/_utilities.text.legacy.scss @@ -1,3 +1,3 @@ .unbold { - font-weight: normal; + font-weight: normal; } diff --git a/client/scss/overrides/_utilities.text.scss b/client/scss/overrides/_utilities.text.scss index 9f97d0099a..52d2014a36 100644 --- a/client/scss/overrides/_utilities.text.scss +++ b/client/scss/overrides/_utilities.text.scss @@ -1,11 +1,11 @@ .u-text-transform-uppercase { - text-transform: uppercase; + text-transform: uppercase; } .u-text-weight-normal { - font-weight: normal; + font-weight: normal; } .u-para { - margin-bottom: 1rem; + margin-bottom: 1rem; } diff --git a/client/scss/overrides/_utilities.visuallyhidden.scss b/client/scss/overrides/_utilities.visuallyhidden.scss index 41166b92a5..adcc6f19e9 100644 --- a/client/scss/overrides/_utilities.visuallyhidden.scss +++ b/client/scss/overrides/_utilities.visuallyhidden.scss @@ -1,7 +1,7 @@ .visuallyvisible { - @include visuallyvisible; + @include visuallyvisible; } .visuallyhidden { - @include visuallyhidden; + @include visuallyhidden; } diff --git a/client/scss/overrides/_vendor.datetimepicker.scss b/client/scss/overrides/_vendor.datetimepicker.scss index 939f570ced..0ea314fc99 100644 --- a/client/scss/overrides/_vendor.datetimepicker.scss +++ b/client/scss/overrides/_vendor.datetimepicker.scss @@ -1,328 +1,335 @@ -@use "sass:map"; +@use 'sass:map'; .xdsoft_datetimepicker { - box-shadow: 0 5px 10px -5px rgba(0, 0, 0, 0.4); - background: $color-white; - border: 1px solid $color-input-focus-border; - padding: 8px; - padding-left: 0; - padding-top: 2px; - position: absolute; - z-index: 5; + box-shadow: 0 5px 10px -5px rgba(0, 0, 0, 0.4); + background: $color-white; + border: 1px solid $color-input-focus-border; + padding: 8px; + padding-left: 0; + padding-top: 2px; + position: absolute; + z-index: 5; + box-sizing: border-box; + display: none; + + * { box-sizing: border-box; + padding: 0; + margin: 0; + } + + iframe { + position: absolute; + left: 0; + top: 0; + width: 75px; + height: 210px; + background: transparent; + border: 0; + } + + .xdsoft_datepicker, + .xdsoft_timepicker { display: none; - * { - box-sizing: border-box; - padding: 0; - margin: 0; + &.active { + display: block; + } + } + + .xdsoft_datepicker { + float: left; + margin-left: 8px; + } + + .xdsoft_datepicker.active + .xdsoft_timepicker { + margin-top: 8px; + margin-bottom: 3px; + } + + .xdsoft_mounthpicker { + position: relative; + text-align: center; + } + + .xdsoft_next, + .xdsoft_prev, + .xdsoft_today_button { + background-color: transparent; + cursor: pointer; + display: block; + border: 0; + overflow: hidden; + padding: 5px 0; + position: relative; + white-space: nowrap; + width: 2em; + color: $color-teal; + text-transform: none; + text-align: center; + + &:before { + font-size: 1.5em; + font-family: $font-wagtail-icons; + width: 1em; + line-height: 1.3em; + text-align: center; + margin: 0; } - iframe { - position: absolute; - left: 0; - top: 0; - width: 75px; - height: 210px; - background: transparent; - border: 0; + &:hover { + color: $color-teal-darker; } + } - .xdsoft_datepicker, - .xdsoft_timepicker { - display: none; + .xdsoft_prev { + float: left; - &.active { - display: block; - } + &:before { + content: map.get($icons, 'arrow-left'); } + } - .xdsoft_datepicker { - float: left; - margin-left: 8px; + .xdsoft_today_button { + float: left; + margin-left: 5px; + + &:before { + content: map.get($icons, 'home'); } + } - .xdsoft_datepicker.active + .xdsoft_timepicker { - margin-top: 8px; - margin-bottom: 3px; + .xdsoft_next { + float: right; + + &:before { + content: map.get($icons, 'arrow-right'); } + } - .xdsoft_mounthpicker { - position: relative; - text-align: center; - } + .xdsoft_timepicker { + min-width: 70px; + float: left; + text-align: center; + margin-left: 8px; + margin-top: 0; - .xdsoft_next, .xdsoft_prev, - .xdsoft_today_button { - background-color: transparent; - cursor: pointer; - display: block; - border: 0; - overflow: hidden; - padding: 5px 0; - position: relative; - white-space: nowrap; - width: 2em; - color: $color-teal; - text-transform: none; - text-align: center; - - &:before { - font-size: 1.5em; - font-family: $font-wagtail-icons; - width: 1em; - line-height: 1.3em; - text-align: center; - margin: 0; - } - - &:hover { - color: $color-teal-darker; - } - } - - .xdsoft_prev { - float: left; - - &:before { - content: map.get($icons, 'arrow-left'); - } - } - - .xdsoft_today_button { - float: left; - margin-left: 5px; - - &:before { - content: map.get($icons, 'home'); - } - } - .xdsoft_next { - float: right; + float: none; + height: 1.5em; + display: block; + text-align: center; + width: 100%; + padding: 0; - &:before { - content: map.get($icons, 'arrow-right'); - } + &:before { + width: 100%; + } } - .xdsoft_timepicker { - min-width: 70px; - float: left; - text-align: center; - margin-left: 8px; - margin-top: 0; - - .xdsoft_prev, - .xdsoft_next { - float: none; - height: 1.5em; - display: block; - text-align: center; - width: 100%; - padding: 0; - - &:before { - width: 100%; - } - } - - .xdsoft_prev:before { - content: map.get($icons, 'arrow-up'); - } - - .xdsoft_next:before { - content: map.get($icons, 'arrow-down'); - } - - .xdsoft_time_box { - position: relative; - border: 1px solid #ccc; - height: 170px; - overflow: hidden; - border-bottom: 1px solid #ddd; - - > div > div { - background: #f5f5f5; - border-top: 1px solid #ddd; - color: #666; - font-size: 1em; - text-align: center; - border-collapse: collapse; - cursor: pointer; - border-bottom-width: 0; - height: 2.3em; - line-height: 2.3em; - padding-left: 0.6em; - padding-right: 0.6em; - - // stylelint-disable-next-line max-nesting-depth - &:first-child { - border-top-width: 0; - } - } - } + .xdsoft_prev:before { + content: map.get($icons, 'arrow-up'); } - .xdsoft_label { - display: inline; - position: relative; - z-index: 9999; - margin: 0; - padding: 5px 3px; - font-size: 14px; - line-height: 20px; - font-weight: bold; - background-color: $color-white; - float: left; - width: 182px; + .xdsoft_next:before { + content: map.get($icons, 'arrow-down'); + } + + .xdsoft_time_box { + position: relative; + border: 1px solid #ccc; + height: 170px; + overflow: hidden; + border-bottom: 1px solid #ddd; + + > div > div { + background: #f5f5f5; + border-top: 1px solid #ddd; + color: #666; + font-size: 1em; text-align: center; + border-collapse: collapse; cursor: pointer; + border-bottom-width: 0; + height: 2.3em; + line-height: 2.3em; + padding-left: 0.6em; + padding-right: 0.6em; - &:hover { - text-decoration: underline; + // stylelint-disable-next-line max-nesting-depth + &:first-child { + border-top-width: 0; } + } + } + } - > .xdsoft_select { - border: 1px solid #ccc; - position: absolute; - right: 0; - top: 30px; - z-index: 101; - display: none; - background: $color-white; - max-height: 160px; - overflow-y: hidden; - - &.xdsoft_monthselect {right: -7px;} - - &.xdsoft_yearselect {right: 2px;} - - > div > .xdsoft_option:hover { - color: $color-white; - background: #ff8000; - } - - > div > .xdsoft_option { - padding: 2px 15px 2px 5px; - } - - > div > .xdsoft_option.xdsoft_current { - background: #3af; - color: $color-white; - font-weight: 700; - } - } + .xdsoft_label { + display: inline; + position: relative; + z-index: 9999; + margin: 0; + padding: 5px 3px; + font-size: 14px; + line-height: 20px; + font-weight: bold; + background-color: $color-white; + float: left; + width: 182px; + text-align: center; + cursor: pointer; + &:hover { + text-decoration: underline; } - .xdsoft_month { - width: 90px; - text-align: right; - } + > .xdsoft_select { + border: 1px solid #ccc; + position: absolute; + right: 0; + top: 30px; + z-index: 101; + display: none; + background: $color-white; + max-height: 160px; + overflow-y: hidden; - .xdsoft_year { - width: 56px; - } + &.xdsoft_monthselect { + right: -7px; + } - .xdsoft_calendar { - clear: both; + &.xdsoft_yearselect { + right: 2px; + } - table { - border-collapse: collapse; - } + > div > .xdsoft_option:hover { + color: $color-white; + background: #ff8000; + } - td > div { - padding-right: 5px; - } + > div > .xdsoft_option { + padding: 2px 15px 2px 5px; + } - td, - th { - width: 14.285%; - border: 1px solid #ddd; - color: #666; - font-size: 12px; - text-align: right; - padding: 5px 7px; - border-collapse: collapse; - cursor: pointer; - height: 25px; - } - - td { - background-color: $color-white; - } - - th { - background: #f1f1f1; - font-weight: 700; - font-size: 0.85em; - text-align: center; - cursor: default; - } - } - - .xdsoft_calendar td.xdsoft_default, - .xdsoft_calendar td.xdsoft_current, - .xdsoft_timepicker .xdsoft_time_box > div > div.xdsoft_current { - background: $color-salmon; + > div > .xdsoft_option.xdsoft_current { + background: #3af; color: $color-white; font-weight: 700; + } + } + } + + .xdsoft_month { + width: 90px; + text-align: right; + } + + .xdsoft_year { + width: 56px; + } + + .xdsoft_calendar { + clear: both; + + table { + border-collapse: collapse; } - .xdsoft_calendar td.xdsoft_other_month, - .xdsoft_calendar td.xdsoft_disabled, - .xdsoft_time_box > div > div.xdsoft_disabled { - opacity: 0.5; - background: $color-grey-3; + td > div { + padding-right: 5px; } - .xdsoft_calendar td.xdsoft_other_month.xdsoft_disabled { - opacity: 0.2; + td, + th { + width: 14.285%; + border: 1px solid #ddd; + color: #666; + font-size: 12px; + text-align: right; + padding: 5px 7px; + border-collapse: collapse; + cursor: pointer; + height: 25px; } - .xdsoft_calendar td:hover, - .xdsoft_timepicker .xdsoft_time_box > div > div:hover { - color: $color-white; - background: $color-teal; + td { + background-color: $color-white; } - .xdsoft_calendar td.xdsoft_today { - font-weight: 700; + th { + background: #f1f1f1; + font-weight: 700; + font-size: 0.85em; + text-align: center; + cursor: default; } + } + + .xdsoft_calendar td.xdsoft_default, + .xdsoft_calendar td.xdsoft_current, + .xdsoft_timepicker .xdsoft_time_box > div > div.xdsoft_current { + background: $color-salmon; + color: $color-white; + font-weight: 700; + } + + .xdsoft_calendar td.xdsoft_other_month, + .xdsoft_calendar td.xdsoft_disabled, + .xdsoft_time_box > div > div.xdsoft_disabled { + opacity: 0.5; + background: $color-grey-3; + } + + .xdsoft_calendar td.xdsoft_other_month.xdsoft_disabled { + opacity: 0.2; + } + + .xdsoft_calendar td:hover, + .xdsoft_timepicker .xdsoft_time_box > div > div:hover { + color: $color-white; + background: $color-teal; + } + + .xdsoft_calendar td.xdsoft_today { + font-weight: 700; + } } .xdsoft_noselect { - user-select: none; + user-select: none; } -.xdsoft_noselect::selection { background: transparent; } +.xdsoft_noselect::selection { + background: transparent; +} -.xdsoft_noselect::-moz-selection { background: transparent; } +.xdsoft_noselect::-moz-selection { + background: transparent; +} .xdsoft_datetimepicker.xdsoft_inline { - display: inline-block; - position: static; - box-shadow: none; + display: inline-block; + position: static; + box-shadow: none; } .xdsoft_scroller_box { - position: relative; + position: relative; } .xdsoft_scrollbar { - position: absolute; - width: 7px; - right: 0; - top: 0; - bottom: 0; - cursor: pointer; + position: absolute; + width: 7px; + right: 0; + top: 0; + bottom: 0; + cursor: pointer; - > .xdsoft_scroller { - // stylelint-disable-next-line declaration-no-important - background: #ccc !important; - height: 20px; - border-radius: 3px; - } + > .xdsoft_scroller { + // stylelint-disable-next-line declaration-no-important + background: #ccc !important; + height: 20px; + border-radius: 3px; + } } diff --git a/client/scss/overrides/_vendor.tagit.scss b/client/scss/overrides/_vendor.tagit.scss index a991a83a4c..1bcc755d35 100644 --- a/client/scss/overrides/_vendor.tagit.scss +++ b/client/scss/overrides/_vendor.tagit.scss @@ -1,44 +1,45 @@ -@use "sass:map"; +@use 'sass:map'; // taggit tagging .tagit { - padding: 0.6em 1.2em; + padding: 0.6em 1.2em; - .tagit-choice { - border: 0; - } + .tagit-choice { + border: 0; + } } // Additional specificity (.admin_tag_widget ) required to override tagit stylesheets, // which get added after the core CSS, and otherwise trump our styles. .admin_tag_widget ul.tagit input[type='text'] { - padding: 0.2em 0.5em; + padding: 0.2em 0.5em; } // Additional specificity (.admin_tag_widget ) required to override tagit stylesheets, // which get added after the core CSS, and otherwise trump our styles. .admin_tag_widget ul.tagit li.tagit-choice-editable { - padding: 0 23px 0 0; + padding: 0 23px 0 0; } -.ui-front { // provided by jqueryui but not high enough an index - z-index: 1000; +.ui-front { + // provided by jqueryui but not high enough an index + z-index: 1000; } .tagit-close { - .ui-icon-close { - margin-left: 1em; - text-indent: 0; - background: none; - } + .ui-icon-close { + margin-left: 1em; + text-indent: 0; + background: none; + } - .ui-icon-close:before { - font-family: $font-wagtail-icons; - display: block; - color: $color-grey-3; - content: map.get($icons, 'cross'); - } + .ui-icon-close:before { + font-family: $font-wagtail-icons; + display: block; + color: $color-grey-3; + content: map.get($icons, 'cross'); + } - .ui-icon-close:hover:before { - color: $color-red; - } + .ui-icon-close:hover:before { + color: $color-red; + } } diff --git a/client/scss/settings/_variables.icons.scss b/client/scss/settings/_variables.icons.scss index cd2a8697cb..82852fb148 100644 --- a/client/scss/settings/_variables.icons.scss +++ b/client/scss/settings/_variables.icons.scss @@ -1,91 +1,91 @@ -@use "sass:map"; +@use 'sass:map'; $icons: ( - 'arrow-down-big': '\e030', - 'arrow-down': '\e01a', - 'arrow-left': '\e022', - 'arrow-right': '\e017', - 'arrow-up-big': '\e02f', - 'arrow-up': '\e010', - 'arrows-up-down': '\e016', - 'bin': '\e038', - 'bold': '\e026', - 'chain-broken': '\e047', - 'code': '\e001', - 'cog': '\e020', - 'cogs': '\e00c', - 'collapse-down': '\e03f', - 'collapse-up': '\e03e', - 'cross': '\e012', - 'date': '\e045', - 'doc-empty-inverse': '\e00d', - 'doc-empty': '\e00e', - 'doc-full-inverse': '\e01b', - 'doc-full': '\e018', - 'download': '\e044', - 'duplicate': '\e902', - 'edit': '\e00f', - 'folder-inverse': '\e014', - 'folder-open-1': '\e013', - 'folder-open-inverse': '\e01f', - 'folder': '\e01c', - 'form': '\e00b', - 'grip': '\e03b', - 'group': '\e031', - 'help': '\e041', - // help-inverse directly renders the corresponding character. - 'help-inverse': '?', - 'home': '\e035', - // horizontalrule is not rendered as an icon font – it uses a unicode dash character rendered with a fallback font. - 'horizontalrule': '\2014', - 'image': '\e019', - 'italic': '\e027', - 'link': '\e02c', - 'list-ol': '\e029', - 'list-ul': '\e028', - 'locked': '\e009', - 'logout': '\e049', - 'mail': '\e015', - 'media': '\e032', - 'no-view': '\e006', - 'openquote': '\e000', - 'order-down': '\e036', - 'order-up': '\e037', - 'order': '\e034', - 'password': '\e033', - 'pick': '\e03d', - 'pilcrow': '\e002', - 'placeholder': '\e003', - 'plus-inverse': '\e024', - 'plus': '\e01d', - 'radio-empty': '\e02e', - 'radio-full': '\e02d', - 'redirect': '\e03c', - 'repeat': '\e02b', - 'search': '\e011', - 'site': '\e007', - 'snippet': '\e025', - 'spinner': '\e03a', - 'strikethrough': '\e04a', - 'subscript': '\e04c', - 'success': '\e043', - 'superscript': '\e04b', - 'table': '\e048', - 'tag': '\e01e', - 'tick-inverse': '\e023', - 'tick': '\e021', - 'time': '\e008', - 'title': '\e046', - 'undo': '\e02a', - 'unlocked': '\e00a', - 'user': '\e004', - 'view': '\e005', - 'wagtail-inverse': '\e040', - 'wagtail': '\e039', - 'warning': '\e042', + 'arrow-down-big': '\e030', + 'arrow-down': '\e01a', + 'arrow-left': '\e022', + 'arrow-right': '\e017', + 'arrow-up-big': '\e02f', + 'arrow-up': '\e010', + 'arrows-up-down': '\e016', + 'bin': '\e038', + 'bold': '\e026', + 'chain-broken': '\e047', + 'code': '\e001', + 'cog': '\e020', + 'cogs': '\e00c', + 'collapse-down': '\e03f', + 'collapse-up': '\e03e', + 'cross': '\e012', + 'date': '\e045', + 'doc-empty-inverse': '\e00d', + 'doc-empty': '\e00e', + 'doc-full-inverse': '\e01b', + 'doc-full': '\e018', + 'download': '\e044', + 'duplicate': '\e902', + 'edit': '\e00f', + 'folder-inverse': '\e014', + 'folder-open-1': '\e013', + 'folder-open-inverse': '\e01f', + 'folder': '\e01c', + 'form': '\e00b', + 'grip': '\e03b', + 'group': '\e031', + 'help': '\e041', + // help-inverse directly renders the corresponding character. + 'help-inverse': '?', + 'home': '\e035', + // horizontalrule is not rendered as an icon font – it uses a unicode dash character rendered with a fallback font. + 'horizontalrule': '\2014', + 'image': '\e019', + 'italic': '\e027', + 'link': '\e02c', + 'list-ol': '\e029', + 'list-ul': '\e028', + 'locked': '\e009', + 'logout': '\e049', + 'mail': '\e015', + 'media': '\e032', + 'no-view': '\e006', + 'openquote': '\e000', + 'order-down': '\e036', + 'order-up': '\e037', + 'order': '\e034', + 'password': '\e033', + 'pick': '\e03d', + 'pilcrow': '\e002', + 'placeholder': '\e003', + 'plus-inverse': '\e024', + 'plus': '\e01d', + 'radio-empty': '\e02e', + 'radio-full': '\e02d', + 'redirect': '\e03c', + 'repeat': '\e02b', + 'search': '\e011', + 'site': '\e007', + 'snippet': '\e025', + 'spinner': '\e03a', + 'strikethrough': '\e04a', + 'subscript': '\e04c', + 'success': '\e043', + 'superscript': '\e04b', + 'table': '\e048', + 'tag': '\e01e', + 'tick-inverse': '\e023', + 'tick': '\e021', + 'time': '\e008', + 'title': '\e046', + 'undo': '\e02a', + 'unlocked': '\e00a', + 'user': '\e004', + 'view': '\e005', + 'wagtail-inverse': '\e040', + 'wagtail': '\e039', + 'warning': '\e042', ); $icons-after: ( - 'arrow-down-after': map.get($icons, 'arrow-down'), - 'arrow-right-after': map.get($icons, 'arrow-right'), - 'arrow-up-after': map.get($icons, 'arrow-up'), + 'arrow-down-after': map.get($icons, 'arrow-down'), + 'arrow-right-after': map.get($icons, 'arrow-right'), + 'arrow-up-after': map.get($icons, 'arrow-up'), ); diff --git a/client/scss/settings/_variables.scss b/client/scss/settings/_variables.scss index ced9760292..93aeaa2f4a 100644 --- a/client/scss/settings/_variables.scss +++ b/client/scss/settings/_variables.scss @@ -1,4 +1,4 @@ -@use "sass:color"; +@use 'sass:color'; // paths // We can't use absolute paths here, because those are dependent on Django's @@ -19,11 +19,15 @@ $desktop-nice-padding: 50px; // screen breakpoints $breakpoints: ( - xs: 0, - sm: 50em, // 800px - md: 56.25em, // 900px - lg: 75em, // 1200px - xl: 100em, // 1440px + xs: 0, + sm: 50em, + // 800px + md: 56.25em, + // 900px + lg: 75em, + // 1200px + xl: 100em, + // 1440px ); // colours @@ -62,7 +66,10 @@ $color-fieldset-hover: $color-grey-5; $color-input-border: $color-grey-4; $color-input-focus: var(--color-input-focus); $color-input-focus-border: var(--color-input-focus-border); -$color-input-error-bg: color.adjust(color.adjust($color-red, $saturation: 28%), $lightness: 45%); +$color-input-error-bg: color.adjust( + color.adjust($color-red, $saturation: 28%), + $lightness: 45% +); $color-button: $color-teal; $color-button-hover: $color-teal-darker; @@ -71,7 +78,10 @@ $color-button-yes-hover: color.adjust($color-button-yes, $lightness: -8%); $color-button-no: $color-red-dark; $color-button-no-hover: color.adjust($color-button-no, $lightness: -20%); $color-button-warning: $color-orange-dark; -$color-button-warning-hover: color.adjust($color-button-warning, $lightness: -20%); +$color-button-warning-hover: color.adjust( + $color-button-warning, + $lightness: -20% +); $color-link: $color-teal-darker; $color-link-hover: $color-teal-dark; @@ -95,7 +105,8 @@ $system-color-link-text: LinkText; $system-color-button-text: ButtonText; // Fonts -$font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; +$font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, + sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; // Legacy icon font, to be removed in the near future. $font-wagtail-icons: wagtail; @@ -145,5 +156,13 @@ $nav-search-focus-bg: $nav-item-hover-bg; // Form Errors $color-text-error: color.change($color-red, $saturation: 69%, $lightness: 52%); -$color-text-error-forced-color: color.change($color-red, $saturation: 100%, $lightness: 50%); -$color-text-warning-forced-color: color.change($color-orange, $saturation: 100%, $lightness: 70%); +$color-text-error-forced-color: color.change( + $color-red, + $saturation: 100%, + $lightness: 50% +); +$color-text-warning-forced-color: color.change( + $color-orange, + $saturation: 100%, + $lightness: 70% +); diff --git a/client/scss/sidebar.scss b/client/scss/sidebar.scss index 54fdf1401e..be26b01906 100644 --- a/client/scss/sidebar.scss +++ b/client/scss/sidebar.scss @@ -10,7 +10,6 @@ see core.scss for details. ==============================================================================*/ - /* SETTINGS These are variables, maps, and fonts. * No CSS should be produced by these files @@ -18,7 +17,6 @@ These are variables, maps, and fonts. @import 'settings'; - /* TOOLS These are functions and mixins. * No CSS should be produced by these files. @@ -26,7 +24,6 @@ These are functions and mixins. @import 'tools'; - /* GENERIC This is for resets and other rules that affect large collections of bare elements. * Changes to them should be very rare. @@ -34,7 +31,6 @@ This is for resets and other rules that affect large collections of bare element @import 'generic/generic'; - /* ELEMENTS These are base styles for bare HTML elements. * Changes to them should be very rare. @@ -45,7 +41,6 @@ These are base styles for bare HTML elements. @import 'elements/forms'; @import 'elements/root'; - /* OBJECTS These are classes related to layout, known as 'objects' in ITCSS or OOCSS. * This is for grids, wrappers, and other non-consmetic layout utilities. @@ -55,7 +50,6 @@ These are classes related to layout, known as 'objects' in ITCSS or OOCSS. @import 'objects/objects'; @import 'objects/avatar'; - /* COMPONENTS These are classes for components. * These classes (unless legacy) are prefixed with `.c-`. @@ -69,6 +63,5 @@ These are classes for components. @import '../src/components/PageExplorer/PageExplorer'; @import '../src/components/Sidebar/Sidebar'; - // Legacy @import 'components/icons'; diff --git a/client/scss/tools/_functions.breakpoints.scss b/client/scss/tools/_functions.breakpoints.scss index a3d4cf4315..4640e49e2e 100644 --- a/client/scss/tools/_functions.breakpoints.scss +++ b/client/scss/tools/_functions.breakpoints.scss @@ -1,5 +1,5 @@ -@use "sass:list"; -@use "sass:map"; +@use 'sass:list'; +@use 'sass:map'; // Based upon the fine work and thoughts from Bootstrap v4. // Copyright 2011-2018 The Bootstrap Authors // Copyright 2011-2018 Twitter, Inc. @@ -9,23 +9,27 @@ // >> breakpoint-next(sm) // md @function breakpoint-next($name) { - $breakpoint-names: map.keys($breakpoints); - $n: list.index($breakpoint-names, $name); - @return if($n < list.length($breakpoint-names), list.nth($breakpoint-names, $n + 1), null); + $breakpoint-names: map.keys($breakpoints); + $n: list.index($breakpoint-names, $name); + @return if( + $n < list.length($breakpoint-names), + list.nth($breakpoint-names, $n + 1), + null + ); } // Minimum breakpoint width. Null for the smallest (first) breakpoint. // >> breakpoint-min(sm) // 50em @function breakpoint-min($name) { - $min: map.get($breakpoints, $name); - @return if($min != 0, $min, null); + $min: map.get($breakpoints, $name); + @return if($min != 0, $min, null); } // Maximum breakpoint width. Null for the largest (last) breakpoint. // >> breakpoint-max(sm) // 56.1875em @function breakpoint-max($name) { - $next: breakpoint-next($name); - @return if($next, breakpoint-min($next) - 0.0625em, null); + $next: breakpoint-next($name); + @return if($next, breakpoint-min($next) - 0.0625em, null); } diff --git a/client/scss/tools/_mixins.breakpoints.scss b/client/scss/tools/_mixins.breakpoints.scss index fd2c91de26..a5b743a544 100644 --- a/client/scss/tools/_mixins.breakpoints.scss +++ b/client/scss/tools/_mixins.breakpoints.scss @@ -3,29 +3,28 @@ // Copyright 2011-2018 Twitter, Inc. // Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - // Media of at least the minimum breakpoint width. No query for the smallest breakpoint. // Makes the @content apply to the given breakpoint and wider. @mixin media-breakpoint-up($name) { - $min: breakpoint-min($name); - @if $min { - @media screen and (min-width: $min) { - @content; - } - } @else { - @content; + $min: breakpoint-min($name); + @if $min { + @media screen and (min-width: $min) { + @content; } + } @else { + @content; + } } // Media of at most the maximum breakpoint width. No query for the largest breakpoint. // Makes the @content apply to the given breakpoint and narrower. @mixin media-breakpoint-down($name) { - $max: breakpoint-max($name); - @if $max { - @media screen and (max-width: $max) { - @content; - } - } @else { - @content; + $max: breakpoint-max($name); + @if $max { + @media screen and (max-width: $max) { + @content; } + } @else { + @content; + } } diff --git a/client/scss/tools/_mixins.general.scss b/client/scss/tools/_mixins.general.scss index 1d872d5bda..d7e4dd14cc 100644 --- a/client/scss/tools/_mixins.general.scss +++ b/client/scss/tools/_mixins.general.scss @@ -6,111 +6,109 @@ // Turns on font-smoothing when used. Use sparingly. @mixin font-smoothing { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } @mixin clearfix() { - &:before, - &:after { - content: ' '; - display: table; - } + &:before, + &:after { + content: ' '; + display: table; + } - &:after { - clear: both; - } + &:after { + clear: both; + } } @mixin unlist() { - margin-top: 0; - margin-bottom: 0; - padding-left: 0; + margin-top: 0; + margin-bottom: 0; + padding-left: 0; + list-style-type: none; + font-style: normal; + + li { list-style-type: none; font-style: normal; - - li { - list-style-type: none; - font-style: normal; - } + } } - // remove list styles, but only for the immediate element - // allow nested lists inside it to keep the default style @mixin unlistimmediate() { - margin-top: 0; - margin-bottom: 0; - padding-left: 0; + margin-top: 0; + margin-bottom: 0; + padding-left: 0; + list-style-type: none; + font-style: normal; + + > li { list-style-type: none; font-style: normal; - - > li { - list-style-type: none; - font-style: normal; - } + } } @mixin transition($transition...) { - body.ready & { - transition: $transition; - } + body.ready & { + transition: $transition; + } } @mixin visuallyhidden { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } - @mixin visuallyvisible { - clip: auto; - height: auto; - width: auto; - margin: initial; - overflow: visible; - position: initial; + clip: auto; + height: auto; + width: auto; + margin: initial; + overflow: visible; + position: initial; } -@mixin svg-icon ($size: 1.5em, $position: text-top) { - width: $size; - height: $size; - vertical-align: $position; +@mixin svg-icon($size: 1.5em, $position: text-top) { + width: $size; + height: $size; + vertical-align: $position; } -@mixin icon () { - @include font-smoothing; - font-family: $font-wagtail-icons; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - speak: none; - text-decoration: none; - width: 1.3em; - line-height: 1em; - text-align: left; - vertical-align: middle; - margin-right: 0.2em; +@mixin icon() { + @include font-smoothing; + font-family: $font-wagtail-icons; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + speak: none; + text-decoration: none; + width: 1.3em; + line-height: 1em; + text-align: left; + vertical-align: middle; + margin-right: 0.2em; } // Applies given rules on hover, except for touch screens. // Relies on feature detection to add a no-touch class on the html element. @mixin hover { - .no-touch &:hover { - @content; - } + .no-touch &:hover { + @content; + } } // Where included, show the focus outline within focusable items instead of around them. // This is useful when focusable items are tightly packed and there is no space in-between. @mixin show-focus-outline-inside { - outline-offset: -1 * $focus-outline-width; + outline-offset: -1 * $focus-outline-width; } diff --git a/client/scss/tools/_mixins.grid.scss b/client/scss/tools/_mixins.grid.scss index f0a379c9e0..2c1899e2e2 100644 --- a/client/scss/tools/_mixins.grid.scss +++ b/client/scss/tools/_mixins.grid.scss @@ -1,75 +1,75 @@ -@use "sass:math"; +@use 'sass:math'; // Utility variable - you should never need to modify this $padding: math.div($grid-gutter-width, 2); // Our row container @mixin row($padding: 0) { - @include clearfix(); - box-sizing: border-box; - display: block; - margin-right: auto; - margin-left: auto; - padding-right: $padding; - padding-left: $padding; + @include clearfix(); + box-sizing: border-box; + display: block; + margin-right: auto; + margin-left: auto; + padding-right: $padding; + padding-left: $padding; } @mixin row-flush() { - margin-left: -$padding; - margin-right: -$padding; + margin-left: -$padding; + margin-right: -$padding; } // Our column container @mixin column($x, $padding: $padding, $grid-columns: $grid-columns) { - box-sizing: border-box; - display: inline; - float: left; - width: 100% * math.div($x, $grid-columns); - padding-right: $padding; - padding-left: $padding; + box-sizing: border-box; + display: inline; + float: left; + width: 100% * math.div($x, $grid-columns); + padding-right: $padding; + padding-left: $padding; } @mixin table-column($x, $padding: $padding, $grid-columns: $grid-columns) { - box-sizing: border-box; - width: 100% * math.div($x, $grid-columns); + box-sizing: border-box; + width: 100% * math.div($x, $grid-columns); } // Push adds left padding @mixin push($offset: 1, $grid-columns: $grid-columns) { - margin-left: 100% * math.div($offset, $grid-columns); + margin-left: 100% * math.div($offset, $grid-columns); } @mixin push-padding($offset: 1, $grid-columns: $grid-columns) { - padding-left: 100% * math.div($offset, $grid-columns); + padding-left: 100% * math.div($offset, $grid-columns); } // Pull adds right padding @mixin pull($offset: 1, $grid-columns: $grid-columns) { - margin-right: 100% * math.div($offset, $grid-columns); + margin-right: 100% * math.div($offset, $grid-columns); } @mixin pull-padding($offset: 1, $grid-columns: $grid-columns) { - padding-right: 100% * math.div($offset, $grid-columns); + padding-right: 100% * math.div($offset, $grid-columns); } // only used in places where padding not applied to same elements as row or row-flush // most of the time this class should be applied directly to the html elements @mixin nice-padding { - padding-left: $mobile-nice-padding; - padding-right: $mobile-nice-padding; + padding-left: $mobile-nice-padding; + padding-right: $mobile-nice-padding; - @include media-breakpoint-up(sm) { - padding-left: $desktop-nice-padding; - padding-right: $desktop-nice-padding; - } + @include media-breakpoint-up(sm) { + padding-left: $desktop-nice-padding; + padding-right: $desktop-nice-padding; + } } @mixin nice-margin { - margin-left: $mobile-nice-padding; - margin-right: $mobile-nice-padding; + margin-left: $mobile-nice-padding; + margin-right: $mobile-nice-padding; - @include media-breakpoint-up(sm) { - margin-left: $desktop-nice-padding; - margin-right: $desktop-nice-padding; - } + @include media-breakpoint-up(sm) { + margin-left: $desktop-nice-padding; + margin-right: $desktop-nice-padding; + } } diff --git a/client/scss/tools/_various.colors.scss b/client/scss/tools/_various.colors.scss index 2658018674..b9bdbd96c0 100644 --- a/client/scss/tools/_various.colors.scss +++ b/client/scss/tools/_various.colors.scss @@ -1,67 +1,73 @@ -@use "sass:math"; -@use "sass:color"; -@use "sass:list"; -@use "sass:meta"; +@use 'sass:math'; +@use 'sass:color'; +@use 'sass:list'; +@use 'sass:meta'; // $color is either a color or an hsl tuple @mixin define-color($name, $color) { - $h: null; - $s: null; - $l: null; + $h: null; + $s: null; + $l: null; - @if meta.type-of($color) == color { - $h: math.div(color.hue($color), 1deg); // Cast to unitless - $s: color.saturation($color); - $l: color.lightness($color); - } @else { - $h: list.nth($color, 1); - $s: list.nth($color, 2); - $l: list.nth($color, 3); - } + @if meta.type-of($color) == color { + $h: math.div(color.hue($color), 1deg); // Cast to unitless + $s: color.saturation($color); + $l: color.lightness($color); + } @else { + $h: list.nth($color, 1); + $s: list.nth($color, 2); + $l: list.nth($color, 3); + } - --#{$name}-hue: #{$h}; - --#{$name}-saturation: #{$s}; - --#{$name}-lightness: #{$l}; - --#{$name}: hsl(#{ var(--#{$name}-hue), var(--#{$name}-saturation), var(--#{$name}-lightness) }); + --#{$name}-hue: #{$h}; + --#{$name}-saturation: #{$s}; + --#{$name}-lightness: #{$l}; + // Prettier causes a linting issue when reformatting this. + /* prettier-ignore */ + --#{$name}: hsl(#{ var(--#{$name}-hue), var(--#{$name}-saturation), var(--#{$name}-lightness) }); } @function get-color($name) { - @return (var(--#{$name}-hue), var(--#{$name}-saturation), var(--#{$name}-lightness)); + @return ( + var(--#{$name}-hue), + var(--#{$name}-saturation), + var(--#{$name}-lightness) + ); } @function css-darken($hsl-tuple, $darken-by) { - $h: list.nth($hsl-tuple, 1); - $s: list.nth($hsl-tuple, 2); - $l: list.nth($hsl-tuple, 3); - @return ($h, $s, calc(#{$l} - #{$darken-by + 0%})); + $h: list.nth($hsl-tuple, 1); + $s: list.nth($hsl-tuple, 2); + $l: list.nth($hsl-tuple, 3); + @return ($h, $s, calc(#{$l} - #{$darken-by + 0%})); } @function css-lighten($hsl-tuple, $lighten-by) { - $h: list.nth($hsl-tuple, 1); - $s: list.nth($hsl-tuple, 2); - $l: list.nth($hsl-tuple, 3); - @return ($h, $s, calc(#{$l} + #{$lighten-by + 0%})); + $h: list.nth($hsl-tuple, 1); + $s: list.nth($hsl-tuple, 2); + $l: list.nth($hsl-tuple, 3); + @return ($h, $s, calc(#{$l} + #{$lighten-by + 0%})); } @function css-saturate($hsl-tuple, $saturate-by) { - $h: list.nth($hsl-tuple, 1); - $s: list.nth($hsl-tuple, 2); - $l: list.nth($hsl-tuple, 3); - @return ($h, calc(#{$s} + #{$saturate-by + 0%}), $l); + $h: list.nth($hsl-tuple, 1); + $s: list.nth($hsl-tuple, 2); + $l: list.nth($hsl-tuple, 3); + @return ($h, calc(#{$s} + #{$saturate-by + 0%}), $l); } @function css-desaturate($hsl-tuple, $desaturate-by) { - $h: list.nth($hsl-tuple, 1); - $s: list.nth($hsl-tuple, 2); - $l: list.nth($hsl-tuple, 3); - @return ($h, calc(#{$s} - #{$desaturate-by + 0%}), $l); + $h: list.nth($hsl-tuple, 1); + $s: list.nth($hsl-tuple, 2); + $l: list.nth($hsl-tuple, 3); + @return ($h, calc(#{$s} - #{$desaturate-by + 0%}), $l); } @function css-adjust-hue($hsl-tuple, $adjust-by) { - $h: list.nth($hsl-tuple, 1); - $s: list.nth($hsl-tuple, 2); - $l: list.nth($hsl-tuple, 3); - @return (calc(#{$h} + #{$adjust-by}), $s, $l); + $h: list.nth($hsl-tuple, 1); + $s: list.nth($hsl-tuple, 2); + $l: list.nth($hsl-tuple, 3); + @return (calc(#{$h} + #{$adjust-by}), $s, $l); } @function css-transparentize($hsl-tuple, $alpha) { - $h: list.nth($hsl-tuple, 1); - $s: list.nth($hsl-tuple, 2); - $l: list.nth($hsl-tuple, 3); - @return ($h, $s, $l, $alpha); + $h: list.nth($hsl-tuple, 1); + $s: list.nth($hsl-tuple, 2); + $l: list.nth($hsl-tuple, 3); + @return ($h, $s, $l, $alpha); } diff --git a/client/src/api/admin.test.js b/client/src/api/admin.test.js index 90bde5dfb0..579eeea638 100644 --- a/client/src/api/admin.test.js +++ b/client/src/api/admin.test.js @@ -8,10 +8,7 @@ const stubResult = { verbose_name: 'Test', }, }, - items: [ - { meta: { type: 'test' } }, - { meta: { type: 'foo' } }, - ], + items: [{ meta: { type: 'test' } }, { meta: { type: 'foo' } }], }; client.get = jest.fn(() => Promise.resolve(stubResult)); @@ -20,27 +17,29 @@ describe('admin API', () => { describe('getPageChildren', () => { it('works', () => { getPageChildren(3); - expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=parent`); + expect(client.get).toBeCalledWith( + `${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=parent`, + ); }); it('#fields', () => { getPageChildren(3, { fields: ['title', 'latest_revision_created_at'] }); expect(client.get).toBeCalledWith( - `${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=parent,title%2Clatest_revision_created_at` + `${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=parent,title%2Clatest_revision_created_at`, ); }); it('#onlyWithChildren', () => { getPageChildren(3, { onlyWithChildren: true }); expect(client.get).toBeCalledWith( - `${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=parent&has_children=1` + `${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=parent&has_children=1`, ); }); it('#offset', () => { getPageChildren(3, { offset: 5 }); expect(client.get).toBeCalledWith( - `${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=parent&offset=5` + `${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=parent&offset=5`, ); }); }); diff --git a/client/src/api/admin.ts b/client/src/api/admin.ts index 81968b1e40..80626e9dbf 100644 --- a/client/src/api/admin.ts +++ b/client/src/api/admin.ts @@ -8,9 +8,9 @@ export interface WagtailPageAPI { status: { status: string; live: boolean; - + has_unpublished_changes: boolean; - } + }; children: any; parent: { id: number; @@ -18,13 +18,12 @@ export interface WagtailPageAPI { locale?: string; translations?: any; }; - + admin_display_title?: string; } interface WagtailPageListAPI { meta: { - total_count: number; }; items: WagtailPageAPI[]; @@ -42,12 +41,17 @@ interface GetPageChildrenOptions { offset?: number; } -type GetPageChildren = (id: number, options: GetPageChildrenOptions) => Promise<WagtailPageListAPI>; +type GetPageChildren = ( + id: number, + options: GetPageChildrenOptions, +) => Promise<WagtailPageListAPI>; export const getPageChildren: GetPageChildren = (id, options = {}) => { let url = `${ADMIN_API.PAGES}?child_of=${id}&for_explorer=1`; if (options.fields) { - url += `&fields=parent,${window.encodeURIComponent(options.fields.join(','))}`; + url += `&fields=parent,${window.encodeURIComponent( + options.fields.join(','), + )}`; } else { url += '&fields=parent'; } @@ -70,12 +74,17 @@ interface GetPageTranslationsOptions { onlyWithChildren?: boolean; offset?: number; } -type GetPageTranslations = (id: number, options: GetPageTranslationsOptions) => Promise<WagtailPageListAPI>; +type GetPageTranslations = ( + id: number, + options: GetPageTranslationsOptions, +) => Promise<WagtailPageListAPI>; export const getPageTranslations: GetPageTranslations = (id, options = {}) => { let url = `${ADMIN_API.PAGES}?translation_of=${id}&limit=20`; if (options.fields) { - url += `&fields=parent,${global.encodeURIComponent(options.fields.join(','))}`; + url += `&fields=parent,${global.encodeURIComponent( + options.fields.join(','), + )}`; } else { url += '&fields=parent'; } @@ -96,14 +105,20 @@ interface GetAllPageTranslationsOptions { onlyWithChildren?: boolean; } -export const getAllPageTranslations = async (id: number, options: GetAllPageTranslationsOptions) => { +export const getAllPageTranslations = async ( + id: number, + options: GetAllPageTranslationsOptions, +) => { const items: WagtailPageAPI[] = []; let iterLimit = 100; for (;;) { - const page = await getPageTranslations(id, { offset: items.length, ...options }); + const page = await getPageTranslations(id, { + offset: items.length, + ...options, + }); - page.items.forEach(item => items.push(item)); + page.items.forEach((item) => items.push(item)); if (items.length >= page.meta.total_count || iterLimit-- <= 0) { return items; diff --git a/client/src/api/client.js b/client/src/api/client.js index 9e7301dc89..9ce60d7a49 100644 --- a/client/src/api/client.js +++ b/client/src/api/client.js @@ -3,7 +3,7 @@ const Headers = global.Headers; const REQUEST_TIMEOUT = 15000; -const checkStatus = (response) => { +const checkStatus = (response) => { if (response.status >= 200 && response.status < 300) { return response; } @@ -13,7 +13,7 @@ const checkStatus = (response) => { throw error; }; -const parseJSON = response => response.json(); +const parseJSON = (response) => response.json(); // Response timeout cancelling the promise (not the request). // See https://github.com/github/fetch/issues/175#issuecomment-216791333. @@ -23,13 +23,16 @@ const timeout = (ms, promise) => { reject(new Error('Response timeout')); }, ms); - promise.then((res) => { - clearTimeout(timeoutId); - resolve(res); - }, (err) => { - clearTimeout(timeoutId); - reject(err); - }); + promise.then( + (res) => { + clearTimeout(timeoutId); + resolve(res); + }, + (err) => { + clearTimeout(timeoutId); + reject(err); + }, + ); }); return race; @@ -46,7 +49,7 @@ const request = (method, url) => { 'Accept': 'application/json', 'Content-Type': 'application/json', }), - method: method + method: method, }; return timeout(REQUEST_TIMEOUT, fetch(url, options)) @@ -54,4 +57,4 @@ const request = (method, url) => { .then(parseJSON); }; -export const get = url => request('GET', url); +export const get = (url) => request('GET', url); diff --git a/client/src/components/Button/Button.test.js b/client/src/components/Button/Button.test.js index be14fabac2..57f0121771 100644 --- a/client/src/components/Button/Button.test.js +++ b/client/src/components/Button/Button.test.js @@ -17,7 +17,9 @@ describe('Button', () => { }); it('#accessibleLabel', () => { - expect(shallow(<Button accessibleLabel="I am here in the shadows" />)).toMatchSnapshot(); + expect( + shallow(<Button accessibleLabel="I am here in the shadows" />), + ).toMatchSnapshot(); }); it('#dialogTrigger', () => { @@ -25,7 +27,9 @@ describe('Button', () => { }); it('#target', () => { - expect(shallow(<Button target="_blank" rel="noopener noreferrer" />)).toMatchSnapshot(); + expect( + shallow(<Button target="_blank" rel="noopener noreferrer" />), + ).toMatchSnapshot(); }); it('is clickable', () => { diff --git a/client/src/components/Button/Button.tsx b/client/src/components/Button/Button.tsx index 2a4f45aa60..66b440ddd3 100644 --- a/client/src/components/Button/Button.tsx +++ b/client/src/components/Button/Button.tsx @@ -1,5 +1,3 @@ - - import * as React from 'react'; const handleClick = ( @@ -7,7 +5,7 @@ const handleClick = ( onClick: ((e: React.MouseEvent) => void) | undefined, preventDefault: boolean, navigate: (url: string) => Promise<void>, - e: React.MouseEvent + e: React.MouseEvent, ) => { if (preventDefault && href === '#') { e.preventDefault(); @@ -52,9 +50,7 @@ const Button: React.FunctionComponent<ButtonProps> = ({ }) => { const hasText = React.Children.count(children) > 0; const accessibleElt = accessibleLabel ? ( - <span className="visuallyhidden"> - {accessibleLabel} - </span> + <span className="visuallyhidden">{accessibleLabel}</span> ) : null; return ( diff --git a/client/src/components/CommentApp/__fixtures__/state.tsx b/client/src/components/CommentApp/__fixtures__/state.tsx index 91616423f4..4b05d9ef15 100644 --- a/client/src/components/CommentApp/__fixtures__/state.tsx +++ b/client/src/components/CommentApp/__fixtures__/state.tsx @@ -1,6 +1,5 @@ import type { Comment, CommentReply, CommentsState } from '../state/comments'; - const remoteReply: CommentReply = { localId: 2, remoteId: 2, @@ -41,7 +40,10 @@ const remoteComment: Comment = { newReply: '', newText: '', remoteReplyCount: 1, - replies: new Map([[remoteReply.localId, remoteReply], [localReply.localId, localReply]]), + replies: new Map([ + [remoteReply.localId, remoteReply], + [localReply.localId, localReply], + ]), }; const localComment: Comment = { @@ -68,5 +70,8 @@ export const basicCommentsState: CommentsState = { forceFocus: false, pinnedComment: 1, remoteCommentCount: 1, - comments: new Map([[remoteComment.localId, remoteComment], [localComment.localId, localComment]]), + comments: new Map([ + [remoteComment.localId, remoteComment], + [localComment.localId, localComment], + ]), }; diff --git a/client/src/components/CommentApp/actions/comments.ts b/client/src/components/CommentApp/actions/comments.ts index cf70f7b661..285355b71e 100644 --- a/client/src/components/CommentApp/actions/comments.ts +++ b/client/src/components/CommentApp/actions/comments.ts @@ -90,7 +90,7 @@ export function addComment(comment: Comment): AddCommentAction { export function updateComment( commentId: number, - update: CommentUpdate + update: CommentUpdate, ): UpdateCommentAction { return { type: UPDATE_COMMENT, @@ -113,22 +113,24 @@ export function resolveComment(commentId: number): ResolveCommentAction { }; } - export function setFocusedComment( commentId: number | null, - { updatePinnedComment, forceFocus } = { updatePinnedComment: false, forceFocus: false } + { updatePinnedComment, forceFocus } = { + updatePinnedComment: false, + forceFocus: false, + }, ): SetFocusedCommentAction { return { type: SET_FOCUSED_COMMENT, commentId, updatePinnedComment, - forceFocus + forceFocus, }; } export function addReply( commentId: number, - reply: CommentReply + reply: CommentReply, ): AddReplyAction { return { type: ADD_REPLY, @@ -140,7 +142,7 @@ export function addReply( export function updateReply( commentId: number, replyId: number, - update: CommentReplyUpdate + update: CommentReplyUpdate, ): UpdateReplyAction { return { type: UPDATE_REPLY, @@ -152,7 +154,7 @@ export function updateReply( export function deleteReply( commentId: number, - replyId: number + replyId: number, ): DeleteReplyAction { return { type: DELETE_REPLY, @@ -161,7 +163,9 @@ export function deleteReply( }; } -export function invalidateContentPath(contentPath: string): InvalidateContentPathAction { +export function invalidateContentPath( + contentPath: string, +): InvalidateContentPathAction { return { type: INVALIDATE_CONTENT_PATH, contentPath, diff --git a/client/src/components/CommentApp/actions/settings.ts b/client/src/components/CommentApp/actions/settings.ts index 7366af3523..4596e57709 100644 --- a/client/src/components/CommentApp/actions/settings.ts +++ b/client/src/components/CommentApp/actions/settings.ts @@ -10,7 +10,7 @@ export interface UpdateGlobalSettingsAction { export type Action = UpdateGlobalSettingsAction; export function updateGlobalSettings( - update: SettingsStateUpdate + update: SettingsStateUpdate, ): UpdateGlobalSettingsAction { return { type: UPDATE_GLOBAL_SETTINGS, diff --git a/client/src/components/CommentApp/components/Comment/index.stories.tsx b/client/src/components/CommentApp/components/Comment/index.stories.tsx index a6df04a61e..657384151c 100644 --- a/client/src/components/CommentApp/components/Comment/index.stories.tsx +++ b/client/src/components/CommentApp/components/Comment/index.stories.tsx @@ -41,7 +41,8 @@ export function commentFromSomeoneElse() { author: { id: 2, name: 'Someone else', - avatarUrl: 'https://gravatar.com/avatar/31c3d5cc27d1faa321c2413589e8a53f?s=200&d=robohash&r=x', + avatarUrl: + 'https://gravatar.com/avatar/31c3d5cc27d1faa321c2413589e8a53f?s=200&d=robohash&r=x', }, }); @@ -72,8 +73,8 @@ export function commentFromSomeoneWithAReallyLongName() { author: { id: 1, name: 'This person has a really long name and it should wrap to the next line', - avatarUrl: 'https://gravatar.com/avatar/31c3d5cc27d1faa321c2413589e8a53f?s=200&d=robohash&r=x', - + avatarUrl: + 'https://gravatar.com/avatar/31c3d5cc27d1faa321c2413589e8a53f?s=200&d=robohash&r=x', }, }); diff --git a/client/src/components/CommentApp/components/Comment/index.tsx b/client/src/components/CommentApp/components/Comment/index.tsx index 4e96376eeb..efa5470092 100644 --- a/client/src/components/CommentApp/components/Comment/index.tsx +++ b/client/src/components/CommentApp/components/Comment/index.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import ReactDOM from 'react-dom'; import FocusTrap from 'focus-trap-react'; @@ -12,20 +10,20 @@ import { deleteComment, resolveComment, setFocusedComment, - addReply + addReply, } from '../../actions/comments'; import { LayoutController } from '../../utils/layout'; import { getNextReplyId } from '../../utils/sequences'; import CommentReplyComponent from '../CommentReply'; import type { TranslatableStrings } from '../../main'; -import { CommentHeader } from '../CommentHeader'; +import { CommentHeader } from '../CommentHeader'; import TextArea from '../TextArea'; async function saveComment(comment: Comment, store: Store) { store.dispatch( updateComment(comment.localId, { mode: 'saving', - }) + }), ); try { @@ -36,7 +34,7 @@ async function saveComment(comment: Comment, store: Store) { remoteId: comment.remoteId, author: comment.author, date: comment.date, - }) + }), ); } catch (err) { /* eslint-disable-next-line no-console */ @@ -44,7 +42,7 @@ async function saveComment(comment: Comment, store: Store) { store.dispatch( updateComment(comment.localId, { mode: 'save_error', - }) + }), ); } } @@ -53,7 +51,7 @@ async function doDeleteComment(comment: Comment, store: Store) { store.dispatch( updateComment(comment.localId, { mode: 'deleting', - }) + }), ); try { @@ -64,15 +62,13 @@ async function doDeleteComment(comment: Comment, store: Store) { store.dispatch( updateComment(comment.localId, { mode: 'delete_error', - }) + }), ); } } function doResolveComment(comment: Comment, store: Store) { - store.dispatch( - resolveComment(comment.localId) - ); + store.dispatch(resolveComment(comment.localId)); } export interface CommentProps { @@ -99,7 +95,7 @@ export default class CommentComponent extends React.Component<CommentProps> { store.dispatch( updateComment(comment.localId, { newReply: value, - }) + }), ); }; @@ -116,7 +112,7 @@ export default class CommentComponent extends React.Component<CommentProps> { store.dispatch( updateComment(comment.localId, { newReply: '', - }) + }), ); }; @@ -126,7 +122,7 @@ export default class CommentComponent extends React.Component<CommentProps> { store.dispatch( updateComment(comment.localId, { newReply: '', - }) + }), ); store.dispatch(setFocusedComment(null)); @@ -152,7 +148,7 @@ export default class CommentComponent extends React.Component<CommentProps> { reply={reply} strings={strings} isFocused={isFocused} - /> + />, ); } } @@ -210,7 +206,7 @@ export default class CommentComponent extends React.Component<CommentProps> { store.dispatch( updateComment(comment.localId, { newText: value, - }) + }), ); }; @@ -244,7 +240,7 @@ export default class CommentComponent extends React.Component<CommentProps> { onChange={onChangeText} placeholder="Enter your comments..." additionalAttributes={{ - 'aria-describedby': descriptionId + 'aria-describedby': descriptionId, }} /> <div className="comment__actions"> @@ -275,7 +271,7 @@ export default class CommentComponent extends React.Component<CommentProps> { store.dispatch( updateComment(comment.localId, { newText: value, - }) + }), ); }; @@ -292,7 +288,7 @@ export default class CommentComponent extends React.Component<CommentProps> { updateComment(comment.localId, { mode: 'default', newText: comment.text, - }) + }), ); }; @@ -313,7 +309,7 @@ export default class CommentComponent extends React.Component<CommentProps> { className="comment__input" value={comment.newText} additionalAttributes={{ - 'aria-describedby': descriptionId + 'aria-describedby': descriptionId, }} onChange={onChangeText} /> @@ -368,7 +364,12 @@ export default class CommentComponent extends React.Component<CommentProps> { return ( <> - <CommentHeader commentReply={comment} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={comment} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment__text">{comment.text}</p> {this.renderReplies({ hideNewReply: true })} <div className="comment__error"> @@ -400,13 +401,18 @@ export default class CommentComponent extends React.Component<CommentProps> { store.dispatch( updateComment(comment.localId, { mode: 'default', - }) + }), ); }; return ( <> - <CommentHeader commentReply={comment} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={comment} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment__text">{comment.text}</p> <div className="comment__confirm-delete"> {strings.CONFIRM_DELETE_COMMENT} @@ -435,7 +441,12 @@ export default class CommentComponent extends React.Component<CommentProps> { return ( <> - <CommentHeader commentReply={comment} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={comment} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment__text">{comment.text}</p> <div className="comment__progress">{strings.DELETING}</div> {this.renderReplies({ hideNewReply: true })} @@ -458,13 +469,18 @@ export default class CommentComponent extends React.Component<CommentProps> { store.dispatch( updateComment(comment.localId, { mode: 'default', - }) + }), ); }; return ( <> - <CommentHeader commentReply={comment} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={comment} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment__text">{comment.text}</p> {this.renderReplies({ hideNewReply: true })} <div className="comment__error"> @@ -494,13 +510,16 @@ export default class CommentComponent extends React.Component<CommentProps> { // Show edit/delete buttons if this comment was authored by the current user let onEdit; let onDelete; - if (comment.author === null || this.props.user && this.props.user.id === comment.author.id) { + if ( + comment.author === null || + (this.props.user && this.props.user.id === comment.author.id) + ) { onEdit = () => { store.dispatch( updateComment(comment.localId, { mode: 'editing', newText: comment.text, - }) + }), ); }; @@ -508,7 +527,7 @@ export default class CommentComponent extends React.Component<CommentProps> { store.dispatch( updateComment(comment.localId, { mode: 'delete_confirm', - }) + }), ); }; } @@ -534,14 +553,14 @@ export default class CommentComponent extends React.Component<CommentProps> { focused={isFocused} /> <p className="comment__text">{comment.text}</p> - {notice && + {notice && ( <div className="comment__notice-placeholder"> <div className="comment__notice" role="status"> <Icon name="info-circle" /> {notice} </div> </div> - } + )} {this.renderReplies()} </> ); @@ -551,78 +570,90 @@ export default class CommentComponent extends React.Component<CommentProps> { let inner: React.ReactFragment; switch (this.props.comment.mode) { - case 'creating': - inner = this.renderCreating(); - break; + case 'creating': + inner = this.renderCreating(); + break; - case 'editing': - inner = this.renderEditing(); - break; + case 'editing': + inner = this.renderEditing(); + break; - case 'saving': - inner = this.renderSaving(); - break; + case 'saving': + inner = this.renderSaving(); + break; - case 'save_error': - inner = this.renderSaveError(); - break; + case 'save_error': + inner = this.renderSaveError(); + break; - case 'delete_confirm': - inner = this.renderDeleteConfirm(); - break; + case 'delete_confirm': + inner = this.renderDeleteConfirm(); + break; - case 'deleting': - inner = this.renderDeleting(); - break; + case 'deleting': + inner = this.renderDeleting(); + break; - case 'delete_error': - inner = this.renderDeleteError(); - break; + case 'delete_error': + inner = this.renderDeleteError(); + break; - default: - inner = this.renderDefault(); - break; + default: + inner = this.renderDefault(); + break; } const onClick = () => { this.props.store.dispatch( - setFocusedComment(this.props.comment.localId, - { updatePinnedComment: false, forceFocus: this.props.isFocused && this.props.forceFocus } - ) + setFocusedComment(this.props.comment.localId, { + updatePinnedComment: false, + forceFocus: this.props.isFocused && this.props.forceFocus, + }), ); }; const onDoubleClick = () => { this.props.store.dispatch( - setFocusedComment(this.props.comment.localId, { updatePinnedComment: true, forceFocus: true }) + setFocusedComment(this.props.comment.localId, { + updatePinnedComment: true, + forceFocus: true, + }), ); }; const top = this.props.layout.getCommentPosition( - this.props.comment.localId + this.props.comment.localId, ); return ( <FocusTrap - focusTrapOptions={{ - preventScroll: true, - clickOutsideDeactivates: true, - onDeactivate: () => { - this.props.store.dispatch( - setFocusedComment(null, { updatePinnedComment: true, forceFocus: false }) - ); - }, - initialFocus: '[data-focus-target="true"]', - } as any} // For some reason, the types for FocusTrap props don't yet include preventScroll. + focusTrapOptions={ + { + preventScroll: true, + clickOutsideDeactivates: true, + onDeactivate: () => { + this.props.store.dispatch( + setFocusedComment(null, { + updatePinnedComment: true, + forceFocus: false, + }), + ); + }, + initialFocus: '[data-focus-target="true"]', + } as any + } // For some reason, the types for FocusTrap props don't yet include preventScroll. active={this.props.isFocused && this.props.forceFocus} > <li tabIndex={-1} - data-focus-target={this.props.isFocused && !['creating', 'editing'].includes(this.props.comment.mode)} - key={this.props.comment.localId} - className={ - `comment comment--mode-${this.props.comment.mode} ${this.props.isFocused ? 'comment--focused' : ''}` + data-focus-target={ + this.props.isFocused && + !['creating', 'editing'].includes(this.props.comment.mode) } + key={this.props.comment.localId} + className={`comment comment--mode-${this.props.comment.mode} ${ + this.props.isFocused ? 'comment--focused' : '' + }`} style={{ position: 'absolute', top: `${top}px`, @@ -648,7 +679,7 @@ export default class CommentComponent extends React.Component<CommentProps> { if (this.props.isVisible) { this.props.layout.setCommentHeight( this.props.comment.localId, - element.offsetHeight + element.offsetHeight, ); } } @@ -666,7 +697,7 @@ export default class CommentComponent extends React.Component<CommentProps> { if (this.props.isVisible && element instanceof HTMLElement) { this.props.layout.setCommentHeight( this.props.comment.localId, - element.offsetHeight + element.offsetHeight, ); } } diff --git a/client/src/components/CommentApp/components/Comment/style.scss b/client/src/components/CommentApp/components/Comment/style.scss index 06dcf5a947..6929eff788 100644 --- a/client/src/components/CommentApp/components/Comment/style.scss +++ b/client/src/components/CommentApp/components/Comment/style.scss @@ -1,151 +1,151 @@ .comment { - @include box; + @include box; + width: calc(100vw - 40px); + max-width: calc(100vw - 19%); + display: block; + transition: top 0.5s ease 0s, right 0.5s ease 0s, height 0.5s ease 0s; + pointer-events: auto; + box-sizing: border-box; + padding-bottom: 0; + right: -2000px; + + @include media-breakpoint-up(sm) { width: calc(100vw - 40px); - max-width: calc(100vw - 19%); - display: block; - transition: top 0.5s ease 0s, right 0.5s ease 0s, height 0.5s ease 0s; - pointer-events: auto; - box-sizing: border-box; - padding-bottom: 0; - right: -2000px; + max-width: 400px; + left: initial; + } - @include media-breakpoint-up(sm) { - width: calc(100vw - 40px); - max-width: 400px; - left: initial; - } + @include media-breakpoint-up(md) { + max-width: 200px; + right: 0; + } + + @include media-breakpoint-up(lg) { + max-width: 275px; + } + + &--focused { + right: 35px; @include media-breakpoint-up(md) { - max-width: 200px; - right: 0; + right: 50px; + } + } + + &__text { + color: $color-box-text; + font-size: 13px; + line-height: 19px; + margin-bottom: 0; + padding-top: 10px; + padding-bottom: 10px; + + &--mode-deleting { + color: $color-grey-1; + } + } + + form { + border-top: 1px solid $color-comment-separator; + } + + &--mode-creating form { + border-top: 0; + margin-top: 10px; + } + + &--mode-editing form { + margin-top: 10px; + } + + &--mode-deleting &__text { + color: $color-grey-3; + } + + &__replies { + list-style-type: none; + padding: 0; + margin: 0; + } + + &__button { + @include button; + } + + &__actions, + &__reply-actions { + padding-bottom: 10px; + } + + &__actions &__button, + &__reply-actions &__button { + margin-right: 10px; + margin-top: 10px; + } + + &__confirm-delete &__button { + margin-left: 10px; + margin-bottom: 10px; + } + + &__confirm-delete, + &__error { + color: $color-box-text; + font-weight: bold; + font-size: 13px; + margin-top: 10px; + + button { + float: right; } - @include media-breakpoint-up(lg) { - max-width: 275px; + &::after { + display: block; + content: ' '; + clear: both; + } + } + + &__error { + color: $color-white; + background-color: $color-red-dark; + border-radius: 3px; + padding: 5px; + padding-left: 10px; + height: 26px; + line-height: 26px; + vertical-align: middle; + + button { + height: 26px; + float: right; + margin-left: 5px; + color: $color-white; + background-color: $color-red-very-dark; + border-color: $color-red-very-dark; + padding: 2px; + padding-left: 10px; + padding-right: 10px; + font-size: 0.65em; + font-weight: bold; } - &--focused { - right: 35px; - - @include media-breakpoint-up(md) { - right: 50px; - } + &::after { + display: block; + content: ''; + clear: both; } + } - &__text { - color: $color-box-text; - font-size: 13px; - line-height: 19px; - margin-bottom: 0; - padding-top: 10px; - padding-bottom: 10px; + &__progress { + margin-top: 20px; + font-weight: bold; + font-size: 13px; + } - &--mode-deleting { - color: $color-grey-1; - } - } - - form { - border-top: 1px solid $color-comment-separator; - } - - &--mode-creating form { - border-top: 0; - margin-top: 10px; - } - - &--mode-editing form { - margin-top: 10px; - } - - &--mode-deleting &__text { - color: $color-grey-3; - } - - &__replies { - list-style-type: none; - padding: 0; - margin: 0; - } - - &__button { - @include button; - } - - &__actions, - &__reply-actions { - padding-bottom: 10px; - } - - &__actions &__button, - &__reply-actions &__button { - margin-right: 10px; - margin-top: 10px; - } - - &__confirm-delete &__button { - margin-left: 10px; - margin-bottom: 10px; - } - - &__confirm-delete, - &__error { - color: $color-box-text; - font-weight: bold; - font-size: 13px; - margin-top: 10px; - - button { - float: right; - } - - &::after { - display: block; - content: ' '; - clear: both; - } - } - - &__error { - color: $color-white; - background-color: $color-red-dark; - border-radius: 3px; - padding: 5px; - padding-left: 10px; - height: 26px; - line-height: 26px; - vertical-align: middle; - - button { - height: 26px; - float: right; - margin-left: 5px; - color: $color-white; - background-color: $color-red-very-dark; - border-color: $color-red-very-dark; - padding: 2px; - padding-left: 10px; - padding-right: 10px; - font-size: 0.65em; - font-weight: bold; - } - - &::after { - display: block; - content: ''; - clear: both; - } - } - - &__progress { - margin-top: 20px; - font-weight: bold; - font-size: 13px; - } - - &__reply-input { - /* stylelint-disable-next-line declaration-no-important */ - margin-top: 20px !important; - } + &__reply-input { + /* stylelint-disable-next-line declaration-no-important */ + margin-top: 20px !important; + } } diff --git a/client/src/components/CommentApp/components/CommentHeader/index.tsx b/client/src/components/CommentApp/components/CommentHeader/index.tsx index e170ee577f..40bb6352d8 100644 --- a/client/src/components/CommentApp/components/CommentHeader/index.tsx +++ b/client/src/components/CommentApp/components/CommentHeader/index.tsx @@ -1,5 +1,3 @@ - - import dateFormat from 'dateformat'; import React, { FunctionComponent, useState, useEffect, useRef } from 'react'; import Icon from '../../../Icon/Icon'; @@ -11,41 +9,39 @@ import { Author } from '../../state/comments'; // Details/Summary components that just become <details>/<summary> tags // except for IE11 where they become <div> tags to allow us to style them -const Details: React.FunctionComponent<React.ComponentPropsWithoutRef<'details'>> = ( - ({ children, open, ...extraProps }) => { - if (IS_IE11) { - return ( - <div className={'details-fallback' + (open ? ' details-fallback--open' : '')} {...extraProps}> - {children} - </div> - ); - } - - return ( - <details open={open} {...extraProps}> - {children} - </details> - ); - } -); - -const Summary: React.FunctionComponent<React.ComponentPropsWithoutRef<'summary'>> = ({ children, ...extraProps }) => { +const Details: React.FunctionComponent< + React.ComponentPropsWithoutRef<'details'> +> = ({ children, open, ...extraProps }) => { if (IS_IE11) { return ( - <button - className="details-fallback__summary" + <div + className={'details-fallback' + (open ? ' details-fallback--open' : '')} {...extraProps} > {children} + </div> + ); + } + + return ( + <details open={open} {...extraProps}> + {children} + </details> + ); +}; + +const Summary: React.FunctionComponent< + React.ComponentPropsWithoutRef<'summary'> +> = ({ children, ...extraProps }) => { + if (IS_IE11) { + return ( + <button className="details-fallback__summary" {...extraProps}> + {children} </button> ); } - return ( - <summary {...extraProps}> - {children} - </summary> - ); + return <summary {...extraProps}>{children}</summary>; }; interface CommentReply { @@ -65,7 +61,14 @@ interface CommentHeaderProps { } export const CommentHeader: FunctionComponent<CommentHeaderProps> = ({ - commentReply, store, strings, onResolve, onEdit, onDelete, descriptionId, focused + commentReply, + store, + strings, + onResolve, + onEdit, + onDelete, + descriptionId, + focused, }) => { const { author, date } = commentReply; @@ -115,7 +118,11 @@ export const CommentHeader: FunctionComponent<CommentHeaderProps> = ({ }, [menuOpen]); const handleClickOutside = (e: MouseEvent) => { - if (menuContainerRef.current && e.target instanceof Node && !menuContainerRef.current.contains(e.target)) { + if ( + menuContainerRef.current && + e.target instanceof Node && + !menuContainerRef.current.contains(e.target) + ) { setMenuOpen(false); } }; @@ -130,8 +137,11 @@ export const CommentHeader: FunctionComponent<CommentHeaderProps> = ({ return ( <div className="comment-header"> <div className="comment-header__actions"> - {(onEdit || onDelete || onResolve) && - <div className="comment-header__action comment-header__action--more" ref={menuContainerRef}> + {(onEdit || onDelete || onResolve) && ( + <div + className="comment-header__action comment-header__action--more" + ref={menuContainerRef} + > <Details open={menuOpen} onClick={toggleMenu}> <Summary aria-label={strings.MORE_ACTIONS} @@ -143,20 +153,47 @@ export const CommentHeader: FunctionComponent<CommentHeaderProps> = ({ <Icon name="ellipsis-v" /> </Summary> - <div className="comment-header__more-actions" role="menu" ref={menuRef}> - {onEdit && <button type="button" role="menuitem" onClick={onClickEdit}>{strings.EDIT}</button>} - {onDelete && <button type="button" role="menuitem" onClick={onClickDelete}>{strings.DELETE}</button>} - {onResolve && <button type="button" role="menuitem" onClick={onClickResolve}>{strings.RESOLVE}</button>} + <div + className="comment-header__more-actions" + role="menu" + ref={menuRef} + > + {onEdit && ( + <button type="button" role="menuitem" onClick={onClickEdit}> + {strings.EDIT} + </button> + )} + {onDelete && ( + <button type="button" role="menuitem" onClick={onClickDelete}> + {strings.DELETE} + </button> + )} + {onResolve && ( + <button + type="button" + role="menuitem" + onClick={onClickResolve} + > + {strings.RESOLVE} + </button> + )} </div> </Details> </div> - } + )} </div> - {author && author.avatarUrl && - <img className="comment-header__avatar" src={author.avatarUrl} role="presentation" />} + {author && author.avatarUrl && ( + <img + className="comment-header__avatar" + src={author.avatarUrl} + role="presentation" + /> + )} <span id={descriptionId}> <p className="comment-header__author">{author ? author.name : ''}</p> - <p className="comment-header__date">{dateFormat(date, 'd mmm yyyy HH:MM')}</p> + <p className="comment-header__date"> + {dateFormat(date, 'd mmm yyyy HH:MM')} + </p> </span> </div> ); diff --git a/client/src/components/CommentApp/components/CommentHeader/style.scss b/client/src/components/CommentApp/components/CommentHeader/style.scss index e2ef112abc..f096711e69 100644 --- a/client/src/components/CommentApp/components/CommentHeader/style.scss +++ b/client/src/components/CommentApp/components/CommentHeader/style.scss @@ -1,144 +1,151 @@ .comment-header { - position: relative; + position: relative; - &__avatar { + &__avatar { + position: absolute; + width: 30px; + height: 30px; + border-radius: 15px; + } + + &__author, + &__date { + max-width: calc( + 100% - 110px + ); // Leave room for actions to the right and avatar to the left + margin: 0; + margin-left: 45px; + font-size: 11px; + line-height: 15px; + } + + &__date { + color: $color-grey-25; + } + + &__actions { + position: absolute; + right: 0; + } + + &__action { + float: left; + margin-left: 5px; + border-radius: 5px; + width: 30px; + height: 30px; + + &:hover { + background-color: $color-grey-7; + } + + > button, + > details > summary, + .details-fallback > .details-fallback__summary { + // IE11 uses divs instead with these classes + // Hides triangle on Firefox + list-style-type: none; + // Hides triangle on Chrome + &::-webkit-details-marker { + display: none; + } + width: 30px; + height: 30px; + position: relative; + background-color: unset; + border: unset; + -moz-outline-radius: 10px; + padding: 0; + box-sizing: border-box; + + svg { position: absolute; - width: 30px; - height: 30px; - border-radius: 15px; + top: 7.5px; + left: 7.5px; + width: 15px; + height: 15px; + } + + &:hover { + cursor: pointer; + } } - &__author, - &__date { - max-width: calc(100% - 110px); // Leave room for actions to the right and avatar to the left - margin: 0; - margin-left: 45px; - font-size: 11px; - line-height: 15px; - } + > details, + > .details-fallback { + // IE11 uses divs instead with these classes + position: relative; - &__date { - color: $color-grey-25; - } - - &__actions { + > div { position: absolute; right: 0; + top: 35px; + } } - &__action { - float: left; - margin-left: 5px; - border-radius: 5px; - width: 30px; - height: 30px; + &--more { + > button, + > details > summary, + > .details-fallback > .details-fallback__summary { + // IE11 uses divs instead with these classes + color: #767676; + // stylelint-disable-next-line max-nesting-depth &:hover { - background-color: $color-grey-7; + color: $color-grey-25; } + } + } + } - > button, - > details > summary, - .details-fallback > .details-fallback__summary { // IE11 uses divs instead with these classes - // Hides triangle on Firefox - list-style-type: none; - // Hides triangle on Chrome - &::-webkit-details-marker { display: none; } - width: 30px; - height: 30px; - position: relative; - background-color: unset; - border: unset; - -moz-outline-radius: 10px; - padding: 0; - box-sizing: border-box; + &__more-actions { + background-color: #333; + color: $color-grey-5; + text-transform: none; + position: absolute; + z-index: 1000; + list-style: none; + text-align: left; + border-radius: 3px; - svg { - position: absolute; - top: 7.5px; - left: 7.5px; - width: 15px; - height: 15px; - } - - &:hover { - cursor: pointer; - } - } - - > details, - > .details-fallback { // IE11 uses divs instead with these classes - position: relative; - - > div { - position: absolute; - right: 0; - top: 35px; - } - } - - &--more { - > button, - > details > summary, - > .details-fallback > .details-fallback__summary { // IE11 uses divs instead with these classes - color: #767676; - - // stylelint-disable-next-line max-nesting-depth - &:hover { - color: $color-grey-25; - } - } - } + &:before { + content: ''; + border: 6px solid transparent; + border-bottom-color: #333; + display: block; + position: absolute; + bottom: 100%; + right: 9px; } - &__more-actions { - background-color: #333; - color: $color-grey-5; - text-transform: none; - position: absolute; - z-index: 1000; - list-style: none; - text-align: left; - border-radius: 3px; + button { + display: block; + background: none; + border: 0; + color: #fff; + padding: 5px 10px; + font-size: 13px; + width: 100px; + text-align: left; - &:before { - content: ''; - border: 6px solid transparent; - border-bottom-color: #333; - display: block; - position: absolute; - bottom: 100%; - right: 9px; - } - - button { - display: block; - background: none; - border: 0; - color: #fff; - padding: 5px 10px; - font-size: 13px; - width: 100px; - text-align: left; - - &:hover { - color: #aaa; - cursor: pointer; - } - } + &:hover { + color: #aaa; + cursor: pointer; + } } + } } .comment--mode-deleting .comment-header, .comment-reply--mode-deleting .comment-header { - opacity: 0.5; + opacity: 0.5; } // IE11 only uses these classes .details-fallback .comment-header__more-actions { - display: none; + display: none; } .details-fallback--open .comment-header__more-actions { - display: block; + display: block; } diff --git a/client/src/components/CommentApp/components/CommentReply/index.stories.tsx b/client/src/components/CommentApp/components/CommentReply/index.stories.tsx index 4af4e7ba22..1eba91d0c8 100644 --- a/client/src/components/CommentApp/components/CommentReply/index.stories.tsx +++ b/client/src/components/CommentApp/components/CommentReply/index.stories.tsx @@ -41,7 +41,8 @@ export function replyFromSomeoneElse() { author: { id: 2, name: 'Someone else', - avatarUrl: 'https://gravatar.com/avatar/31c3d5cc27d1faa321c2413589e8a53f?s=200&d=robohash&r=x', + avatarUrl: + 'https://gravatar.com/avatar/31c3d5cc27d1faa321c2413589e8a53f?s=200&d=robohash&r=x', }, }); diff --git a/client/src/components/CommentApp/components/CommentReply/index.tsx b/client/src/components/CommentApp/components/CommentReply/index.tsx index 2eca4b3fce..8401ca1a4c 100644 --- a/client/src/components/CommentApp/components/CommentReply/index.tsx +++ b/client/src/components/CommentApp/components/CommentReply/index.tsx @@ -1,24 +1,22 @@ - - import React from 'react'; import type { Store } from '../../state'; import type { Comment, CommentReply, Author } from '../../state/comments'; import { updateReply, deleteReply } from '../../actions/comments'; import type { TranslatableStrings } from '../../main'; -import { CommentHeader } from '../CommentHeader'; +import { CommentHeader } from '../CommentHeader'; import TextArea from '../TextArea'; import Icon from '../../../Icon/Icon'; export async function saveCommentReply( comment: Comment, reply: CommentReply, - store: Store + store: Store, ) { store.dispatch( updateReply(comment.localId, reply.localId, { mode: 'saving', - }) + }), ); try { @@ -27,7 +25,7 @@ export async function saveCommentReply( mode: 'default', text: reply.newText, author: reply.author, - }) + }), ); } catch (err) { /* eslint-disable-next-line no-console */ @@ -35,7 +33,7 @@ export async function saveCommentReply( store.dispatch( updateReply(comment.localId, reply.localId, { mode: 'save_error', - }) + }), ); } } @@ -43,12 +41,12 @@ export async function saveCommentReply( async function deleteCommentReply( comment: Comment, reply: CommentReply, - store: Store + store: Store, ) { store.dispatch( updateReply(comment.localId, reply.localId, { mode: 'deleting', - }) + }), ); try { @@ -57,7 +55,7 @@ async function deleteCommentReply( store.dispatch( updateReply(comment.localId, reply.localId, { mode: 'delete_error', - }) + }), ); } } @@ -79,7 +77,7 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP store.dispatch( updateReply(comment.localId, reply.localId, { newText: value, - }) + }), ); }; @@ -95,7 +93,7 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP updateReply(comment.localId, reply.localId, { mode: 'default', newText: reply.text, - }) + }), ); }; @@ -139,7 +137,12 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP return ( <> - <CommentHeader commentReply={reply} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={reply} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment-reply__text">{reply.text}</p> <div className="comment-reply__progress">{strings.SAVING}</div> </> @@ -157,7 +160,12 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP return ( <> - <CommentHeader commentReply={reply} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={reply} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment-reply__text">{reply.text}</p> <div className="comment-reply__error"> {strings.SAVE_ERROR} @@ -188,13 +196,18 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP store.dispatch( updateReply(comment.localId, reply.localId, { mode: 'default', - }) + }), ); }; return ( <> - <CommentHeader commentReply={reply} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={reply} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment-reply__text">{reply.text}</p> <div className="comment-reply__confirm-delete"> {strings.CONFIRM_DELETE_COMMENT} @@ -222,7 +235,12 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP return ( <> - <CommentHeader commentReply={reply} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={reply} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment-reply__text">{reply.text}</p> <div className="comment-reply__progress">{strings.DELETING}</div> </> @@ -244,13 +262,18 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP store.dispatch( updateReply(comment.localId, reply.localId, { mode: 'default', - }) + }), ); }; return ( <> - <CommentHeader commentReply={reply} store={store} strings={strings} focused={isFocused} /> + <CommentHeader + commentReply={reply} + store={store} + strings={strings} + focused={isFocused} + /> <p className="comment-reply__text">{reply.text}</p> <div className="comment-reply__error"> {strings.DELETE_ERROR} @@ -279,13 +302,16 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP // Show edit/delete buttons if this reply was authored by the current user let onEdit; let onDelete; - if (reply.author === null || this.props.user && this.props.user.id === reply.author.id) { + if ( + reply.author === null || + (this.props.user && this.props.user.id === reply.author.id) + ) { onEdit = () => { store.dispatch( updateReply(comment.localId, reply.localId, { mode: 'editing', newText: reply.text, - }) + }), ); }; @@ -293,7 +319,7 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP store.dispatch( updateReply(comment.localId, reply.localId, { mode: 'delete_confirm', - }) + }), ); }; } @@ -315,14 +341,14 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP focused={isFocused} /> <p className="comment-reply__text">{reply.text}</p> - {notice && + {notice && ( <div className="comment__notice-placeholder"> <div className="comment__notice" role="status"> <Icon name="info-circle" /> {notice} </div> </div> - } + )} </> ); } @@ -331,33 +357,33 @@ export default class CommentReplyComponent extends React.Component<CommentReplyP let inner: React.ReactFragment; switch (this.props.reply.mode) { - case 'editing': - inner = this.renderEditing(); - break; + case 'editing': + inner = this.renderEditing(); + break; - case 'saving': - inner = this.renderSaving(); - break; + case 'saving': + inner = this.renderSaving(); + break; - case 'save_error': - inner = this.renderSaveError(); - break; + case 'save_error': + inner = this.renderSaveError(); + break; - case 'delete_confirm': - inner = this.renderDeleteConfirm(); - break; + case 'delete_confirm': + inner = this.renderDeleteConfirm(); + break; - case 'deleting': - inner = this.renderDeleting(); - break; + case 'deleting': + inner = this.renderDeleting(); + break; - case 'delete_error': - inner = this.renderDeleteError(); - break; + case 'delete_error': + inner = this.renderDeleteError(); + break; - default: - inner = this.renderDefault(); - break; + default: + inner = this.renderDefault(); + break; } return ( diff --git a/client/src/components/CommentApp/components/CommentReply/style.scss b/client/src/components/CommentApp/components/CommentReply/style.scss index 9bad36377c..7eb7766b1a 100644 --- a/client/src/components/CommentApp/components/CommentReply/style.scss +++ b/client/src/components/CommentApp/components/CommentReply/style.scss @@ -1,109 +1,109 @@ .comment-reply { - padding-top: 20px; - pointer-events: auto; - position: relative; - border-top: 1px solid $color-comment-separator; + padding-top: 20px; + pointer-events: auto; + position: relative; + border-top: 1px solid $color-comment-separator; - &__text { - color: $color-box-text; - font-size: 13px; - line-height: 19px; - margin-bottom: 0; - padding-top: 10px; - padding-bottom: 10px; + &__text { + color: $color-box-text; + font-size: 13px; + line-height: 19px; + margin-bottom: 0; + padding-top: 10px; + padding-bottom: 10px; - &--mode-deleting { - color: $color-grey-1; - } + &--mode-deleting { + color: $color-grey-1; + } + } + + &--mode-deleting &__avatar { + opacity: 0.5; + } + + &--mode-deleting &__text { + color: $color-grey-3; + } + + form { + margin-top: 10px; + } + + &__button { + @include button; + } + + &__actions, + &__confirm-delete, + &__progress, + &__error { + &::after { + display: block; + content: ''; + clear: both; + } + } + + &__actions { + padding-bottom: 10px; + } + + &__actions &__button { + margin-right: 10px; + margin-top: 10px; + } + + &__confirm-delete &__button { + margin-left: 10px; + margin-bottom: 10px; + } + + &__confirm-delete, + &__error { + color: $color-box-text; + font-weight: bold; + font-size: 13px; + margin-top: 10px; + + button { + float: right; } - &--mode-deleting &__avatar { - opacity: 0.5; + &::after { + display: block; + content: ' '; + clear: both; } + } - &--mode-deleting &__text { - color: $color-grey-3; + &__error { + color: $color-white; + background-color: $color-red-dark; + border-radius: 3px; + padding: 5px; + padding-left: 10px; + height: 26px; + line-height: 26px; + vertical-align: middle; + + button { + height: 26px; + float: right; + margin-left: 5px; + color: $color-white; + background-color: $color-red-very-dark; + border-color: $color-red-very-dark; + padding: 2px; + padding-left: 10px; + padding-right: 10px; + font-size: 0.65em; + font-weight: bold; } + } - form { - margin-top: 10px; - } - - &__button { - @include button; - } - - &__actions, - &__confirm-delete, - &__progress, - &__error { - &::after { - display: block; - content: ''; - clear: both; - } - } - - &__actions { - padding-bottom: 10px; - } - - &__actions &__button { - margin-right: 10px; - margin-top: 10px; - } - - &__confirm-delete &__button { - margin-left: 10px; - margin-bottom: 10px; - } - - &__confirm-delete, - &__error { - color: $color-box-text; - font-weight: bold; - font-size: 13px; - margin-top: 10px; - - button { - float: right; - } - - &::after { - display: block; - content: ' '; - clear: both; - } - } - - &__error { - color: $color-white; - background-color: $color-red-dark; - border-radius: 3px; - padding: 5px; - padding-left: 10px; - height: 26px; - line-height: 26px; - vertical-align: middle; - - button { - height: 26px; - float: right; - margin-left: 5px; - color: $color-white; - background-color: $color-red-very-dark; - border-color: $color-red-very-dark; - padding: 2px; - padding-left: 10px; - padding-right: 10px; - font-size: 0.65em; - font-weight: bold; - } - } - - &__progress { - margin-top: 20px; - font-weight: bold; - font-size: 13px; - } + &__progress { + margin-top: 20px; + font-weight: bold; + font-size: 13px; + } } diff --git a/client/src/components/CommentApp/components/TextArea/index.tsx b/client/src/components/CommentApp/components/TextArea/index.tsx index 13266c2e8e..9dbe1c0a23 100644 --- a/client/src/components/CommentApp/components/TextArea/index.tsx +++ b/client/src/components/CommentApp/components/TextArea/index.tsx @@ -7,56 +7,64 @@ export interface TextAreaProps { onChange?(newValue: string): void; focusOnMount?: boolean; focusTarget?: boolean; - additionalAttributes?: React.ComponentPropsWithoutRef<'textarea'> + additionalAttributes?: React.ComponentPropsWithoutRef<'textarea'>; } -const TextArea = React.forwardRef<HTMLTextAreaElement | null, TextAreaProps>(({ - value, - className, - placeholder, - onChange, - focusOnMount, - focusTarget = false, - additionalAttributes = {} -}, ref) => { - const onChangeValue = (e: React.ChangeEvent<HTMLTextAreaElement>) => { - if (onChange) { - onChange(e.target.value); - } - }; +const TextArea = React.forwardRef<HTMLTextAreaElement | null, TextAreaProps>( + ( + { + value, + className, + placeholder, + onChange, + focusOnMount, + focusTarget = false, + additionalAttributes = {}, + }, + ref, + ) => { + const onChangeValue = (e: React.ChangeEvent<HTMLTextAreaElement>) => { + if (onChange) { + onChange(e.target.value); + } + }; - // Resize the textarea whenever the value is changed - const textAreaElement = React.useRef<HTMLTextAreaElement>(null); - React.useImperativeHandle<HTMLTextAreaElement | null, HTMLTextAreaElement | null>(ref, () => textAreaElement.current); + // Resize the textarea whenever the value is changed + const textAreaElement = React.useRef<HTMLTextAreaElement>(null); + React.useImperativeHandle< + HTMLTextAreaElement | null, + HTMLTextAreaElement | null + >(ref, () => textAreaElement.current); - React.useEffect(() => { - if (textAreaElement.current) { - textAreaElement.current.style.height = ''; - textAreaElement.current.style.height = - textAreaElement.current.scrollHeight + 'px'; - } - }, [value, textAreaElement]); + React.useEffect(() => { + if (textAreaElement.current) { + textAreaElement.current.style.height = ''; + textAreaElement.current.style.height = + textAreaElement.current.scrollHeight + 'px'; + } + }, [value, textAreaElement]); - // Focus the textarea when it is mounted - React.useEffect(() => { - if (focusOnMount && textAreaElement.current) { - textAreaElement.current.focus(); - } - }, [textAreaElement]); + // Focus the textarea when it is mounted + React.useEffect(() => { + if (focusOnMount && textAreaElement.current) { + textAreaElement.current.focus(); + } + }, [textAreaElement]); - return ( - <textarea - data-focus-target={focusTarget} - rows={1} - style={{ resize: 'none', overflowY: 'hidden' }} - className={className} - placeholder={placeholder} - ref={textAreaElement} - onChange={onChangeValue} - value={value} - {...additionalAttributes} - /> - ); -}); + return ( + <textarea + data-focus-target={focusTarget} + rows={1} + style={{ resize: 'none', overflowY: 'hidden' }} + className={className} + placeholder={placeholder} + ref={textAreaElement} + onChange={onChangeValue} + value={value} + {...additionalAttributes} + /> + ); + }, +); export default TextArea; diff --git a/client/src/components/CommentApp/main.scss b/client/src/components/CommentApp/main.scss index 2cc11ccfff..d34fa9a561 100644 --- a/client/src/components/CommentApp/main.scss +++ b/client/src/components/CommentApp/main.scss @@ -23,125 +23,125 @@ $box-border-radius: 5px; $box-padding: 10px; @mixin focus-outline { - outline: $color-focus-outline solid 3px; + outline: $color-focus-outline solid 3px; } @mixin box { - background-color: $color-box-background; - border: 1px solid $color-box-border; - padding: $box-padding; - font-size: 11px; - border-radius: $box-border-radius; + background-color: $color-box-background; + border: 1px solid $color-box-border; + padding: $box-padding; + font-size: 11px; + border-radius: $box-border-radius; + color: $color-box-text; + + &--focused { + border-color: #bbb; + box-shadow: 3px 2px 3px -1px rgba(0, 0, 0, 0.1); + } + + textarea { + font-family: $font-sans; + margin: 0; + padding: 10px; + width: 100%; + background-color: $color-textarea-background; + border: 1px solid $color-textarea-border; + box-sizing: border-box; + border-radius: 5px; + -moz-outline-radius: 8px; color: $color-box-text; - &--focused { - border-color: #bbb; - box-shadow: 3px 2px 3px -1px rgba(0, 0, 0, 0.1); + &::placeholder { + color: $color-textarea-placeholder-text; + opacity: 1; } - textarea { - font-family: $font-sans; - margin: 0; - padding: 10px; - width: 100%; - background-color: $color-textarea-background; - border: 1px solid $color-textarea-border; - box-sizing: border-box; - border-radius: 5px; - -moz-outline-radius: 8px; - color: $color-box-text; - - &::placeholder { - color: $color-textarea-placeholder-text; - opacity: 1; - } - - &:focus { - background-color: $color-textarea-background-focused; - border-color: $color-textarea-border-focused; - outline: unset; - } + &:focus { + background-color: $color-textarea-background-focused; + border-color: $color-textarea-border-focused; + outline: unset; } + } - *:focus { - @include focus-outline; + *:focus { + @include focus-outline; + } + + &__notice-placeholder { + position: relative; + padding-bottom: 40px; + } + + &__notice { + background-color: $color-amber-1; + position: absolute; + left: -$box-padding; + bottom: 0; + width: calc(100% + #{$box-padding} * 2); + padding: 5px 10px; + box-sizing: border-box; + + svg.icon { + color: $color-amber-0; + width: 14px; + height: 14px; + margin-right: 10px; + vertical-align: text-bottom; } + } - &__notice-placeholder { - position: relative; - padding-bottom: 40px; - } - - &__notice { - background-color: $color-amber-1; - position: absolute; - left: -$box-padding; - bottom: 0; - width: calc(100% + #{$box-padding} * 2); - padding: 5px 10px; - box-sizing: border-box; - - svg.icon { - color: $color-amber-0; - width: 14px; - height: 14px; - margin-right: 10px; - vertical-align: text-bottom; - } - } - - > :last-child &__notice { - bottom: -$box-padding; - border-bottom-left-radius: $box-border-radius; - border-bottom-right-radius: $box-border-radius; - } + > :last-child &__notice { + bottom: -$box-padding; + border-bottom-left-radius: $box-border-radius; + border-bottom-right-radius: $box-border-radius; + } } @mixin button { - background-color: inherit; - border: 1px solid $color-grey-3; - border-radius: 3px; - -moz-outline-radius: 6px; - color: $color-teal; - cursor: pointer; - text-transform: uppercase; - font-family: inherit; - font-size: 12px; - font-weight: bold; - height: 25px; - padding-left: 5px; - padding-right: 5px; + background-color: inherit; + border: 1px solid $color-grey-3; + border-radius: 3px; + -moz-outline-radius: 6px; + color: $color-teal; + cursor: pointer; + text-transform: uppercase; + font-family: inherit; + font-size: 12px; + font-weight: bold; + height: 25px; + padding-left: 5px; + padding-right: 5px; - &--primary { - color: $color-white; - border: 1px solid $color-teal; - background-color: $color-teal; - } + &--primary { + color: $color-white; + border: 1px solid $color-teal; + background-color: $color-teal; + } - &--red { - color: $color-white; - border: 1px solid $color-red-very-dark; - background-color: $color-red-very-dark; - } + &--red { + color: $color-white; + border: 1px solid $color-red-very-dark; + background-color: $color-red-very-dark; + } - &:disabled { - opacity: 0.3; - } + &:disabled { + opacity: 0.3; + } - // Disable Firefox's focus styling because we add our own. - &::-moz-focus-inner { - border: 0; - } + // Disable Firefox's focus styling because we add our own. + &::-moz-focus-inner { + border: 0; + } } .comments-list { - width: 400px; - position: absolute; - top: 30px; - right: 30px; - z-index: 50; - font-family: $font-sans; - pointer-events: none; + width: 400px; + position: absolute; + top: 30px; + right: 30px; + z-index: 50; + font-family: $font-sans; + pointer-events: none; } // stylelint-disable no-invalid-position-at-import-rule diff --git a/client/src/components/CommentApp/main.tsx b/client/src/components/CommentApp/main.tsx index a7053b37a7..5233305aaa 100644 --- a/client/src/components/CommentApp/main.tsx +++ b/client/src/components/CommentApp/main.tsx @@ -14,7 +14,7 @@ import { setFocusedComment, updateComment, commentActionFunctions, - invalidateContentPath + invalidateContentPath, } from './actions/comments'; import { updateGlobalSettings } from './actions/settings'; import { @@ -24,7 +24,7 @@ import { selectEnabled, selectFocused, selectIsDirty, - selectCommentCount + selectCommentCount, } from './selectors'; import CommentComponent from './components/Comment'; import { CommentFormSetComponent } from './components/Form'; @@ -72,7 +72,6 @@ export const defaultStrings = { SAVE_PAGE_TO_SAVE_REPLY: 'Save the page to save this reply', }; - // This is done as this is serialized pretty directly from the Django model export interface InitialCommentReply { pk: number; @@ -97,9 +96,14 @@ export interface InitialComment { } /* eslint-enable */ - -const getAuthor = (authors: Map<string, {name: string, avatar_url: string}>, id: any): Author => { - const authorData = getOrDefault(authors, String(id), { name: '', avatar_url: '' }); +const getAuthor = ( + authors: Map<string, { name: string; avatar_url: string }>, + id: any, +): Author => { + const authorData = getOrDefault(authors, String(id), { + name: '', + avatar_url: '', + }); return { id, @@ -112,7 +116,7 @@ function renderCommentsUi( store: Store, layout: LayoutController, comments: Comment[], - strings: TranslatableStrings + strings: TranslatableStrings, ): React.ReactElement { const state = store.getState(); const { commentsEnabled, user, currentTab } = state.settings; @@ -123,7 +127,9 @@ function renderCommentsUi( commentsToRender = []; } // Hide all resolved/deleted comments - commentsToRender = commentsToRender.filter(({ deleted, resolved }) => !(deleted || resolved)); + commentsToRender = commentsToRender.filter( + ({ deleted, resolved }) => !(deleted || resolved), + ); const commentsRendered = commentsToRender.map((comment) => ( <CommentComponent key={comment.localId} @@ -137,9 +143,7 @@ function renderCommentsUi( strings={strings} /> )); - return ( - <ol className="comments-list">{commentsRendered}</ol> - ); + return <ol className="comments-list">{commentsRendered}</ol>; /* eslint-enable react/no-danger */ } @@ -148,47 +152,39 @@ export class CommentApp { layout: LayoutController; utils = { selectCommentsForContentPathFactory, - selectCommentFactory - } + selectCommentFactory, + }; selectors = { selectComments, selectEnabled, selectFocused, selectIsDirty, - selectCommentCount - } + selectCommentCount, + }; actions = commentActionFunctions; constructor() { this.store = createStore(reducer, { - settings: INITIAL_SETTINGS_STATE + settings: INITIAL_SETTINGS_STATE, }); this.layout = new LayoutController(); } - - setUser(userId: any, authors: Map<string, {name: string, avatar_url: string}>) { + + setUser( + userId: any, + authors: Map<string, { name: string; avatar_url: string }>, + ) { this.store.dispatch( updateGlobalSettings({ - user: getAuthor(authors, userId) - }) + user: getAuthor(authors, userId), + }), ); } - updateAnnotation( - annotation: Annotation, - commentId: number - ) { + updateAnnotation(annotation: Annotation, commentId: number) { this.attachAnnotationLayout(annotation, commentId); - this.store.dispatch( - updateComment( - commentId, - { annotation: annotation } - ) - ); + this.store.dispatch(updateComment(commentId, { annotation: annotation })); } - attachAnnotationLayout( - annotation: Annotation, - commentId: number - ) { + attachAnnotationLayout(annotation: Annotation, commentId: number) { // Attach an annotation to an existing comment in the layout // const layout engine know the annotation so it would position the comment correctly @@ -214,19 +210,26 @@ export class CommentApp { Date.now(), { mode: 'creating', - } - ) - ) + }, + ), + ), ); // Focus and pin the comment - this.store.dispatch(setFocusedComment(commentId, { updatePinnedComment: true, forceFocus: true })); + this.store.dispatch( + setFocusedComment(commentId, { + updatePinnedComment: true, + forceFocus: true, + }), + ); return commentId; } setVisible(visible: boolean) { - this.store.dispatch(updateGlobalSettings({ - commentsEnabled: visible, - })); + this.store.dispatch( + updateGlobalSettings({ + commentsEnabled: visible, + }), + ); } invalidateContentPath(contentPath: string) { // Called when a given content path on the form is no longer valid (eg, a block has been deleted) @@ -237,9 +240,9 @@ export class CommentApp { outputElement: HTMLElement, userId: any, initialComments: InitialComment[], - - authors: Map<string, {name: string, avatar_url: string}>, - translationStrings: TranslatableStrings | null + + authors: Map<string, { name: string; avatar_url: string }>, + translationStrings: TranslatableStrings | null, ) { let pinnedComment: number | null = null; this.setUser(userId, authors); @@ -258,14 +261,18 @@ export class CommentApp { const render = () => { const state = this.store.getState(); - const commentList: Comment[] = Array.from(state.comments.comments.values()); + const commentList: Comment[] = Array.from( + state.comments.comments.values(), + ); ReactDOM.render( <CommentFormSetComponent - comments={commentList.filter(comment => comment.mode !== 'creating')} + comments={commentList.filter( + (comment) => comment.mode !== 'creating', + )} remoteCommentCount={state.comments.remoteCommentCount} />, - outputElement + outputElement, ); // Check if the pinned comment has changed @@ -287,10 +294,10 @@ export class CommentApp { if (this.layout.refreshLayout()) { ReactDOM.render( renderCommentsUi(this.store, this.layout, commentList, strings), - element + element, ); } - } + }, ); }; @@ -312,10 +319,10 @@ export class CommentApp { remoteId: comment.pk, text: comment.text, deleted: comment.deleted, - resolved: comment.resolved - } - ) - ) + resolved: comment.resolved, + }, + ), + ), ); // Create replies @@ -330,18 +337,23 @@ export class CommentApp { { remoteId: reply.pk, text: reply.text, - deleted: reply.deleted - } - ) - ) + deleted: reply.deleted, + }, + ), + ), ); } // If this is the initial focused comment. Focus and pin it - + // TODO: Scroll to this comment if (initialFocusedCommentId && comment.pk === initialFocusedCommentId) { - this.store.dispatch(setFocusedComment(commentId, { updatePinnedComment: true, forceFocus: true })); + this.store.dispatch( + setFocusedComment(commentId, { + updatePinnedComment: true, + forceFocus: true, + }), + ); } } @@ -353,10 +365,17 @@ export class CommentApp { document.body.addEventListener('mousedown', (e) => { if (e.target instanceof HTMLElement) { // ignore if click target is a comment or an annotation - if (!e.target.closest('#comments, [data-annotation], [data-comment-add]')) { + if ( + !e.target.closest('#comments, [data-annotation], [data-comment-add]') + ) { // Running store.dispatch directly here seems to prevent the event from being handled anywhere else setTimeout(() => { - this.store.dispatch(setFocusedComment(null, { updatePinnedComment: true, forceFocus: false })); + this.store.dispatch( + setFocusedComment(null, { + updatePinnedComment: true, + forceFocus: false, + }), + ); }, 200); } } @@ -365,7 +384,9 @@ export class CommentApp { document.body.addEventListener('commentAnchorVisibilityChange', () => { // If any streamfield blocks or panels have collapsed or expanded // check if we need to rerender - this.layout.refreshDesiredPositions(this.store.getState().settings.currentTab); + this.layout.refreshDesiredPositions( + this.store.getState().settings.currentTab, + ); if (this.layout.refreshLayout()) { render(); } diff --git a/client/src/components/CommentApp/selectors/index.ts b/client/src/components/CommentApp/selectors/index.ts index 108fcd067f..163344c6ce 100644 --- a/client/src/components/CommentApp/selectors/index.ts +++ b/client/src/components/CommentApp/selectors/index.ts @@ -4,14 +4,16 @@ import type { State } from '../state'; export const selectComments = (state: State) => state.comments.comments; export const selectFocused = (state: State) => state.comments.focusedComment; -export const selectRemoteCommentCount = (state: State) => state.comments.remoteCommentCount; +export const selectRemoteCommentCount = (state: State) => + state.comments.remoteCommentCount; export function selectCommentsForContentPathFactory(contentpath: string) { return createSelector(selectComments, (comments) => [...comments.values()].filter( (comment: Comment) => - comment.contentpath === contentpath && !(comment.deleted || comment.resolved) - ) + comment.contentpath === contentpath && + !(comment.deleted || comment.resolved), + ), ); } @@ -22,9 +24,7 @@ export function selectCommentFactory(localId: number) { return undefined; } return comment; - } - - ); + }); } export const selectEnabled = (state: State) => state.settings.commentsEnabled; @@ -36,20 +36,23 @@ export const selectIsDirty = createSelector( if (remoteCommentCount !== comments.size) { return true; } - return Array.from(comments.values()).some(comment => { - if (comment.deleted || + return Array.from(comments.values()).some((comment) => { + if ( + comment.deleted || comment.resolved || comment.replies.size !== comment.remoteReplyCount || comment.originalText !== comment.text ) { return true; } - return Array.from(comment.replies.values()).some(reply => reply.deleted || reply.originalText !== reply.text); + return Array.from(comment.replies.values()).some( + (reply) => reply.deleted || reply.originalText !== reply.text, + ); }); - }); - -export const selectCommentCount = (state: State) => ( - [...state.comments.comments.values()].filter( - (comment: Comment) => !comment.deleted && !comment.resolved - ).length + }, ); + +export const selectCommentCount = (state: State) => + [...state.comments.comments.values()].filter( + (comment: Comment) => !comment.deleted && !comment.resolved, + ).length; diff --git a/client/src/components/CommentApp/selectors/selectors.test.ts b/client/src/components/CommentApp/selectors/selectors.test.ts index 120d878ad9..e41c934f6b 100644 --- a/client/src/components/CommentApp/selectors/selectors.test.ts +++ b/client/src/components/CommentApp/selectors/selectors.test.ts @@ -18,12 +18,10 @@ test('Select comments for contentpath', () => { comments: basicCommentsState, settings: INITIAL_SETTINGS_STATE, }; - const testContentPathSelector = selectCommentsForContentPathFactory( - 'test_contentpath' - ); - const testContentPathSelector2 = selectCommentsForContentPathFactory( - 'test_contentpath_2' - ); + const testContentPathSelector = + selectCommentsForContentPathFactory('test_contentpath'); + const testContentPathSelector2 = + selectCommentsForContentPathFactory('test_contentpath_2'); const selectedComments = testContentPathSelector(state); expect(selectedComments.length).toBe(1); expect(selectedComments[0].contentpath).toBe('test_contentpath'); @@ -35,79 +33,90 @@ test('Select comments for contentpath', () => { test('Select is dirty', () => { const state = { comments: INITIAL_COMMENTS_STATE, - settings: INITIAL_SETTINGS_STATE + settings: INITIAL_SETTINGS_STATE, }; - const stateWithUnsavedComment = reducer(state, actions.addComment(newComment( - 'test_contentpath', - 'test_position', - 1, - null, - null, - 0, - { - remoteId: null, - text: 'my new comment' - } - ))); + const stateWithUnsavedComment = reducer( + state, + actions.addComment( + newComment('test_contentpath', 'test_position', 1, null, null, 0, { + remoteId: null, + text: 'my new comment', + }), + ), + ); expect(selectIsDirty(stateWithUnsavedComment)).toBe(true); - const stateWithSavedComment = reducer(state, actions.addComment(newComment( - 'test_contentpath', - 'test_position', - 1, - null, - null, - 0, - { - remoteId: 1, - text: 'my saved comment' - } - ))); + const stateWithSavedComment = reducer( + state, + actions.addComment( + newComment('test_contentpath', 'test_position', 1, null, null, 0, { + remoteId: 1, + text: 'my saved comment', + }), + ), + ); expect(selectIsDirty(stateWithSavedComment)).toBe(false); - const stateWithDeletedComment = reducer(stateWithSavedComment, actions.deleteComment(1)); + const stateWithDeletedComment = reducer( + stateWithSavedComment, + actions.deleteComment(1), + ); expect(selectIsDirty(stateWithDeletedComment)).toBe(true); - const stateWithResolvedComment = reducer(stateWithSavedComment, actions.updateComment(1, { resolved: true })); + const stateWithResolvedComment = reducer( + stateWithSavedComment, + actions.updateComment(1, { resolved: true }), + ); expect(selectIsDirty(stateWithResolvedComment)).toBe(true); - const stateWithEditedComment = reducer(stateWithSavedComment, actions.updateComment(1, { text: 'edited_text' })); + const stateWithEditedComment = reducer( + stateWithSavedComment, + actions.updateComment(1, { text: 'edited_text' }), + ); expect(selectIsDirty(stateWithEditedComment)).toBe(true); - const stateWithUnsavedReply = reducer(stateWithSavedComment, actions.addReply(1, newCommentReply( - 2, - null, - 0, - { - remoteId: null, - text: 'new reply' - } - ))); + const stateWithUnsavedReply = reducer( + stateWithSavedComment, + actions.addReply( + 1, + newCommentReply(2, null, 0, { + remoteId: null, + text: 'new reply', + }), + ), + ); expect(selectIsDirty(stateWithUnsavedReply)).toBe(true); - const stateWithSavedReply = reducer(stateWithSavedComment, actions.addReply(1, newCommentReply( - 2, - null, - 0, - { - remoteId: 2, - text: 'new saved reply' - } - ))); + const stateWithSavedReply = reducer( + stateWithSavedComment, + actions.addReply( + 1, + newCommentReply(2, null, 0, { + remoteId: 2, + text: 'new saved reply', + }), + ), + ); expect(selectIsDirty(stateWithSavedReply)).toBe(false); - const stateWithDeletedReply = reducer(stateWithSavedReply, actions.deleteReply(1, 2)); + const stateWithDeletedReply = reducer( + stateWithSavedReply, + actions.deleteReply(1, 2), + ); expect(selectIsDirty(stateWithDeletedReply)).toBe(true); - const stateWithEditedReply = reducer(stateWithSavedReply, actions.updateReply(1, 2, { text: 'edited_text' })); + const stateWithEditedReply = reducer( + stateWithSavedReply, + actions.updateReply(1, 2, { text: 'edited_text' }), + ); expect(selectIsDirty(stateWithEditedReply)).toBe(true); }); diff --git a/client/src/components/CommentApp/state/comments.test.ts b/client/src/components/CommentApp/state/comments.test.ts index 410cd8fb79..744871b866 100644 --- a/client/src/components/CommentApp/state/comments.test.ts +++ b/client/src/components/CommentApp/state/comments.test.ts @@ -41,7 +41,7 @@ test('New comment added to state', () => { const newState = reducer(basicCommentsState, commentAction); expect(newState.comments.get(newComment.localId)).toBe(newComment); expect(newState.remoteCommentCount).toBe( - basicCommentsState.remoteCommentCount + basicCommentsState.remoteCommentCount, ); }); @@ -68,13 +68,13 @@ test('Remote comment added to state', () => { const newState = reducer(basicCommentsState, commentAction); expect(newState.comments.get(newComment.localId)).toBe(newComment); expect(newState.remoteCommentCount).toBe( - basicCommentsState.remoteCommentCount + 1 + basicCommentsState.remoteCommentCount + 1, ); }); test('Existing comment updated', () => { const commentUpdate: CommentUpdate = { - mode: 'editing' + mode: 'editing', }; const updateAction = actions.updateComment(1, commentUpdate); const newState = reducer(basicCommentsState, updateAction); @@ -111,7 +111,7 @@ test('Remote comment deleted', () => { expect(newState.focusedComment).toBe(null); expect(newState.pinnedComment).toBe(null); expect(newState.remoteCommentCount).toBe( - basicCommentsState.remoteCommentCount + basicCommentsState.remoteCommentCount, ); }); @@ -127,12 +127,15 @@ test('Remote comment resolved', () => { expect(newState.focusedComment).toBe(null); expect(newState.pinnedComment).toBe(null); expect(newState.remoteCommentCount).toBe( - basicCommentsState.remoteCommentCount + basicCommentsState.remoteCommentCount, ); }); test('Comment focused', () => { - const focusAction = actions.setFocusedComment(4, { updatePinnedComment: true, forceFocus: true }); + const focusAction = actions.setFocusedComment(4, { + updatePinnedComment: true, + forceFocus: true, + }); const newState = reducer(basicCommentsState, focusAction); expect(newState.focusedComment).toBe(4); expect(newState.pinnedComment).toBe(4); @@ -140,7 +143,10 @@ test('Comment focused', () => { }); test('Invalid comment not focused', () => { - const focusAction = actions.setFocusedComment(9000, { updatePinnedComment: true, forceFocus: true }); + const focusAction = actions.setFocusedComment(9000, { + updatePinnedComment: true, + forceFocus: true, + }); const newState = reducer(basicCommentsState, focusAction); expect(newState.focusedComment).toBe(basicCommentsState.focusedComment); expect(newState.pinnedComment).toBe(basicCommentsState.pinnedComment); @@ -194,7 +200,9 @@ test('Remote reply added', () => { expect(stateReply).toBeDefined(); expect(stateReply).toBe(reply); if (originalComment) { - expect(comment.remoteReplyCount).toBe(originalComment.remoteReplyCount + 1); + expect(comment.remoteReplyCount).toBe( + originalComment.remoteReplyCount + 1, + ); } } }); diff --git a/client/src/components/CommentApp/state/comments.ts b/client/src/components/CommentApp/state/comments.ts index 445a900a12..705ab08910 100644 --- a/client/src/components/CommentApp/state/comments.ts +++ b/client/src/components/CommentApp/state/comments.ts @@ -54,8 +54,8 @@ export function newCommentReply( remoteId = null, mode = 'default', text = '', - deleted = false - }: NewReplyOptions + deleted = false, + }: NewReplyOptions, ): CommentReply { return { localId, @@ -130,7 +130,7 @@ export function newComment( resolved = false, deleted = false, replies = new Map(), - }: NewCommentOptions + }: NewCommentOptions, ): Comment { return { contentpath, @@ -150,7 +150,7 @@ export function newComment( resolved, remoteReplyCount: Array.from(replies.values()).reduce( (n, reply) => (reply.remoteId !== null ? n + 1 : n), - 0 + 0, ), }; } @@ -174,142 +174,145 @@ export const INITIAL_STATE: CommentsState = { remoteCommentCount: 0, }; -export const reducer = produce((draft: CommentsState, action: actions.Action) => { - /* eslint-disable no-param-reassign */ - const deleteComment = (comment: Comment) => { - if (!comment.remoteId) { - // If the comment doesn't exist in the database, there's no need to keep it around locally - draft.comments.delete(comment.localId); - } else { - comment.deleted = true; - } +export const reducer = produce( + (draft: CommentsState, action: actions.Action) => { + /* eslint-disable no-param-reassign */ + const deleteComment = (comment: Comment) => { + if (!comment.remoteId) { + // If the comment doesn't exist in the database, there's no need to keep it around locally + draft.comments.delete(comment.localId); + } else { + comment.deleted = true; + } - // Unset focusedComment if the focused comment is the one being deleted - if (draft.focusedComment === comment.localId) { - draft.focusedComment = null; - draft.forceFocus = false; - } - if (draft.pinnedComment === comment.localId) { - draft.pinnedComment = null; - } - }; + // Unset focusedComment if the focused comment is the one being deleted + if (draft.focusedComment === comment.localId) { + draft.focusedComment = null; + draft.forceFocus = false; + } + if (draft.pinnedComment === comment.localId) { + draft.pinnedComment = null; + } + }; - const resolveComment = (comment: Comment) => { - if (!comment.remoteId) { - // If the comment doesn't exist in the database, there's no need to keep it around locally - draft.comments.delete(comment.localId); - } else { - comment.resolved = true; - } - // Unset focusedComment if the focused comment is the one being resolved - if (draft.focusedComment === comment.localId) { - draft.focusedComment = null; - } - if (draft.pinnedComment === comment.localId) { - draft.pinnedComment = null; - } - }; + const resolveComment = (comment: Comment) => { + if (!comment.remoteId) { + // If the comment doesn't exist in the database, there's no need to keep it around locally + draft.comments.delete(comment.localId); + } else { + comment.resolved = true; + } + // Unset focusedComment if the focused comment is the one being resolved + if (draft.focusedComment === comment.localId) { + draft.focusedComment = null; + } + if (draft.pinnedComment === comment.localId) { + draft.pinnedComment = null; + } + }; - switch (action.type) { - case actions.ADD_COMMENT: { - draft.comments.set(action.comment.localId, action.comment); - if (action.comment.remoteId) { - draft.remoteCommentCount += 1; - } - break; - } - case actions.UPDATE_COMMENT: { - const comment = draft.comments.get(action.commentId); - if (comment) { - if (action.update.newText && action.update.newText.length === 0) { + switch (action.type) { + case actions.ADD_COMMENT: { + draft.comments.set(action.comment.localId, action.comment); + if (action.comment.remoteId) { + draft.remoteCommentCount += 1; + } break; } - update(comment, action.update); - } - break; - } - case actions.DELETE_COMMENT: { - const comment = draft.comments.get(action.commentId); - if (!comment) { - break; - } - - deleteComment(comment); - break; - } - case actions.RESOLVE_COMMENT: { - const comment = draft.comments.get(action.commentId); - if (!comment) { - break; - } - - resolveComment(comment); - break; - } - case actions.SET_FOCUSED_COMMENT: { - if ((action.commentId === null) || (draft.comments.has(action.commentId))) { - draft.focusedComment = action.commentId; - if (action.updatePinnedComment) { - draft.pinnedComment = action.commentId; + case actions.UPDATE_COMMENT: { + const comment = draft.comments.get(action.commentId); + if (comment) { + if (action.update.newText && action.update.newText.length === 0) { + break; + } + update(comment, action.update); + } + break; } - draft.forceFocus = action.forceFocus; - } - break; - } - case actions.ADD_REPLY: { - const comment = draft.comments.get(action.commentId); - if ((!comment) || action.reply.text.length === 0) { - break; - } - if (action.reply.remoteId) { - comment.remoteReplyCount += 1; - } - comment.replies.set(action.reply.localId, action.reply); - break; - } - case actions.UPDATE_REPLY: { - const comment = draft.comments.get(action.commentId); - if (!comment) { - break; - } - const reply = comment.replies.get(action.replyId); - if (!reply) { - break; - } - if (action.update.newText && action.update.newText.length === 0) { - break; - } - update(reply, action.update); - break; - } - case actions.DELETE_REPLY: { - const comment = draft.comments.get(action.commentId); - if (!comment) { - break; - } - const reply = comment.replies.get(action.replyId); - if (!reply) { - break; - } - if (!reply.remoteId) { - // The reply doesn't exist in the database, so we don't need to store it locally - comment.replies.delete(reply.localId); - } else { - reply.deleted = true; - } - break; - } - case actions.INVALIDATE_CONTENT_PATH: { - // Delete any comments that exist in the contentpath - const comments = draft.comments; - for (const comment of comments.values()) { - if (comment.contentpath.startsWith(action.contentPath)) { + case actions.DELETE_COMMENT: { + const comment = draft.comments.get(action.commentId); + if (!comment) { + break; + } + + deleteComment(comment); + break; + } + case actions.RESOLVE_COMMENT: { + const comment = draft.comments.get(action.commentId); + if (!comment) { + break; + } + resolveComment(comment); + break; } + case actions.SET_FOCUSED_COMMENT: { + if (action.commentId === null || draft.comments.has(action.commentId)) { + draft.focusedComment = action.commentId; + if (action.updatePinnedComment) { + draft.pinnedComment = action.commentId; + } + draft.forceFocus = action.forceFocus; + } + break; + } + case actions.ADD_REPLY: { + const comment = draft.comments.get(action.commentId); + if (!comment || action.reply.text.length === 0) { + break; + } + if (action.reply.remoteId) { + comment.remoteReplyCount += 1; + } + comment.replies.set(action.reply.localId, action.reply); + break; + } + case actions.UPDATE_REPLY: { + const comment = draft.comments.get(action.commentId); + if (!comment) { + break; + } + const reply = comment.replies.get(action.replyId); + if (!reply) { + break; + } + if (action.update.newText && action.update.newText.length === 0) { + break; + } + update(reply, action.update); + break; + } + case actions.DELETE_REPLY: { + const comment = draft.comments.get(action.commentId); + if (!comment) { + break; + } + const reply = comment.replies.get(action.replyId); + if (!reply) { + break; + } + if (!reply.remoteId) { + // The reply doesn't exist in the database, so we don't need to store it locally + comment.replies.delete(reply.localId); + } else { + reply.deleted = true; + } + break; + } + case actions.INVALIDATE_CONTENT_PATH: { + // Delete any comments that exist in the contentpath + const comments = draft.comments; + for (const comment of comments.values()) { + if (comment.contentpath.startsWith(action.contentPath)) { + resolveComment(comment); + } + } + break; + } + default: + break; } - break; - } - default: - break; - } -}, INITIAL_STATE); + }, + INITIAL_STATE, +); diff --git a/client/src/components/CommentApp/state/settings.ts b/client/src/components/CommentApp/state/settings.ts index fc08b602fd..b34c249fca 100644 --- a/client/src/components/CommentApp/state/settings.ts +++ b/client/src/components/CommentApp/state/settings.ts @@ -18,12 +18,15 @@ export const INITIAL_STATE: SettingsState = { currentTab: null, }; -export const reducer = produce((draft: SettingsState, action: actions.Action) => { - switch (action.type) { - case actions.UPDATE_GLOBAL_SETTINGS: - update(draft, action.update); - break; - default: - break; - } -}, INITIAL_STATE); +export const reducer = produce( + (draft: SettingsState, action: actions.Action) => { + switch (action.type) { + case actions.UPDATE_GLOBAL_SETTINGS: + update(draft, action.update); + break; + default: + break; + } + }, + INITIAL_STATE, +); diff --git a/client/src/components/CommentApp/utils/layout.ts b/client/src/components/CommentApp/utils/layout.ts index 09ac4ce9bc..6dfa5c8ce0 100644 --- a/client/src/components/CommentApp/utils/layout.ts +++ b/client/src/components/CommentApp/utils/layout.ts @@ -51,11 +51,15 @@ export class LayoutController { return; } - const currentNodeTop = annotation.getAnchorNode(commentId === this.pinnedComment).getBoundingClientRect().top; + const currentNodeTop = annotation + .getAnchorNode(commentId === this.pinnedComment) + .getBoundingClientRect().top; this.commentDesiredPositions.set( commentId, - currentNodeTop !== 0 ? currentNodeTop + document.documentElement.scrollTop + OFFSET : 0 + currentNodeTop !== 0 + ? currentNodeTop + document.documentElement.scrollTop + OFFSET + : 0, ); } @@ -102,32 +106,35 @@ export class LayoutController { height: getOrDefault(this.commentHeights, commentId, 0), comments: [commentId], containsPinnedComment: - this.pinnedComment !== null && commentId === this.pinnedComment, + this.pinnedComment !== null && commentId === this.pinnedComment, pinnedCommentPosition: 0, - }) + }), ); // Group blocks by tabs const blocksByTab: Map<string | null, Block[]> = new Map(); - allBlocks.forEach(block => { + allBlocks.forEach((block) => { const blocks = blocksByTab.get(block.tab) || []; blocks.push(block); blocksByTab.set(block.tab, blocks); }); // Get location of pinned comment - const pinnedCommentPosition = this.pinnedComment ? - this.commentDesiredPositions.get(this.pinnedComment) : undefined; - const pinnedCommentTab = this.pinnedComment ? - this.commentTabs.get(this.pinnedComment) : undefined; + const pinnedCommentPosition = this.pinnedComment + ? this.commentDesiredPositions.get(this.pinnedComment) + : undefined; + const pinnedCommentTab = this.pinnedComment + ? this.commentTabs.get(this.pinnedComment) + : undefined; // For each tab, resolve positions of all the comments Array.from(blocksByTab.entries()).forEach(([tab, blocks]) => { - const pinnedCommentOnThisTab = this.pinnedComment && pinnedCommentTab === tab; + const pinnedCommentOnThisTab = + this.pinnedComment && pinnedCommentTab === tab; // Sort blocks blocks.sort( - (block, comparisonBlock) => block.position - comparisonBlock.position + (block, comparisonBlock) => block.position - comparisonBlock.position, ); // Resolve overlapping blocks @@ -173,8 +180,7 @@ export class LayoutController { previousBlock.containsPinnedComment ) { previousBlock.position = - pinnedCommentPosition - - previousBlock.pinnedCommentPosition; + pinnedCommentPosition - previousBlock.pinnedCommentPosition; } continue; @@ -212,7 +218,10 @@ export class LayoutController { } getCommentVisible(tab: string | null, commentId: number): boolean { - return this.getCommentTabVisible(tab, commentId) && getOrDefault(this.commentDesiredPositions, commentId, 1) > 0; + return ( + this.getCommentTabVisible(tab, commentId) && + getOrDefault(this.commentDesiredPositions, commentId, 1) > 0 + ); } getCommentPosition(commentId: number) { diff --git a/client/src/components/CommentApp/utils/storybook.tsx b/client/src/components/CommentApp/utils/storybook.tsx index 1f88d334ac..8565d49700 100644 --- a/client/src/components/CommentApp/utils/storybook.tsx +++ b/client/src/components/CommentApp/utils/storybook.tsx @@ -1,11 +1,7 @@ import React from 'react'; import { Store } from '../state'; -import { - addComment, - setFocusedComment, - addReply, -} from '../actions/comments'; +import { addComment, setFocusedComment, addReply } from '../actions/comments'; import { Author, Comment, @@ -35,7 +31,7 @@ export function RenderCommentsForStorybook({ const layout = new LayoutController(); const commentsToRender: Comment[] = Array.from( - state.comments.comments.values() + state.comments.comments.values(), ); const commentsRendered = commentsToRender.map((comment) => ( @@ -47,7 +43,8 @@ export function RenderCommentsForStorybook({ author || { id: 1, name: 'Admin', - avatarUrl: 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', + avatarUrl: + 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', } } comment={comment} @@ -57,9 +54,7 @@ export function RenderCommentsForStorybook({ /> )); - return ( - <ol className="comments-list">{commentsRendered}</ol> - ); + return <ol className="comments-list">{commentsRendered}</ol>; } interface AddTestCommentOptions extends NewCommentOptions { @@ -69,7 +64,7 @@ interface AddTestCommentOptions extends NewCommentOptions { export function addTestComment( store: Store, - options: AddTestCommentOptions + options: AddTestCommentOptions, ): number { const commentId = getNextCommentId(); @@ -78,7 +73,8 @@ export function addTestComment( const author = options.author || { id: 1, name: 'Admin', - avatarUrl: 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', + avatarUrl: + 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', }; // We must have a remoteId unless the comment is being created @@ -93,8 +89,16 @@ export function addTestComment( store.dispatch( addComment( - newComment('test', '', commentId, null, author, Date.now(), addCommentOptions) - ) + newComment( + 'test', + '', + commentId, + null, + author, + Date.now(), + addCommentOptions, + ), + ), ); if (options.focused) { @@ -112,13 +116,14 @@ interface AddTestReplyOptions extends NewReplyOptions { export function addTestReply( store: Store, commentId: number, - options: AddTestReplyOptions + options: AddTestReplyOptions, ) { const addReplyOptions = options; const author = options.author || { id: 1, name: 'Admin', - avatarUrl: 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', + avatarUrl: + 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', }; if (!options.remoteId) { @@ -126,6 +131,9 @@ export function addTestReply( } store.dispatch( - addReply(commentId, newCommentReply(1, author, Date.now(), addReplyOptions)) + addReply( + commentId, + newCommentReply(1, author, Date.now(), addReplyOptions), + ), ); } diff --git a/client/src/components/Draftail/CommentableEditor/CommentableEditor.scss b/client/src/components/Draftail/CommentableEditor/CommentableEditor.scss index bb081ea4e4..1e649f8d49 100644 --- a/client/src/components/Draftail/CommentableEditor/CommentableEditor.scss +++ b/client/src/components/Draftail/CommentableEditor/CommentableEditor.scss @@ -1,35 +1,35 @@ .Draftail-Toolbar { - display: flex; - flex-wrap: wrap; + display: flex; + flex-wrap: wrap; - .Draftail-ToolbarGroup:last-child { - flex-grow: 1; - } + .Draftail-ToolbarGroup:last-child { + flex-grow: 1; + } - .Draftail-CommentControl { - float: right; - color: $color-teal; - } + .Draftail-CommentControl { + float: right; + color: $color-teal; + } } .Draftail-CommentControl .Draftail-ToolbarButton { + .icon-comment-large-outline { + display: block; + } + + .icon-comment-large-reversed { + display: none; + } + + &:hover { + border-color: transparent; + .icon-comment-large-outline { - display: block; + display: none; } .icon-comment-large-reversed { - display: none; - } - - &:hover { - border-color: transparent; - - .icon-comment-large-outline { - display: none; - } - - .icon-comment-large-reversed { - display: block; - } + display: block; } + } } diff --git a/client/src/components/Draftail/CommentableEditor/CommentableEditor.test.tsx b/client/src/components/Draftail/CommentableEditor/CommentableEditor.test.tsx index 6c3f4ddfda..b32b34aea4 100644 --- a/client/src/components/Draftail/CommentableEditor/CommentableEditor.test.tsx +++ b/client/src/components/Draftail/CommentableEditor/CommentableEditor.test.tsx @@ -77,7 +77,7 @@ describe('CommentableEditor', () => { const contentpath = 'test-contentpath'; const getComments = (app: CommentApp) => app.utils.selectCommentsForContentPathFactory(contentpath)( - app.store.getState() + app.store.getState(), ); beforeAll(() => { const commentsElement = document.createElement('div'); @@ -113,7 +113,7 @@ describe('CommentableEditor', () => { commentApp.setVisible(true); const editor = mount(getEditorComponent(commentApp)); const controls = editor.findWhere( - (n) => n.name() === 'ToolbarButton' && n.prop('name') === 'comment' + (n) => n.name() === 'ToolbarButton' && n.prop('name') === 'comment', ); expect(controls).toHaveLength(1); editor.unmount(); @@ -122,7 +122,7 @@ describe('CommentableEditor', () => { commentApp.store.dispatch(updateGlobalSettings({ commentsEnabled: false })); const editor = mount(getEditorComponent(commentApp)); const controls = editor.findWhere( - (n) => n.name() === 'ToolbarButton' && n.prop('name') === 'comment' + (n) => n.name() === 'ToolbarButton' && n.prop('name') === 'comment', ); expect(controls).toHaveLength(0); editor.unmount(); @@ -130,8 +130,8 @@ describe('CommentableEditor', () => { it('can update comment positions', () => { commentApp.store.dispatch( commentApp.actions.addComment( - newComment('test-contentpath', 'old_position', 1, null, null, 0, {}) - ) + newComment('test-contentpath', 'old_position', 1, null, null, 0, {}), + ), ); // Test that a comment with no annotation will not have its position updated updateCommentPositions({ @@ -140,7 +140,7 @@ describe('CommentableEditor', () => { commentApp: commentApp, }); expect(commentApp.store.getState().comments.comments.get(1)?.position).toBe( - 'old_position' + 'old_position', ); commentApp.updateAnnotation(new DraftailInlineAnnotation(fieldNode), 1); @@ -152,7 +152,7 @@ describe('CommentableEditor', () => { commentApp: commentApp, }); expect(commentApp.store.getState().comments.comments.get(1)?.position).toBe( - '[]' + '[]', ); // Test that a comment with a style range has that style range recorded accurately in the state @@ -162,7 +162,7 @@ describe('CommentableEditor', () => { commentApp: commentApp, }); expect(commentApp.store.getState().comments.comments.get(1)?.position).toBe( - '[{"key":"a","start":0,"end":1}]' + '[{"key":"a","start":0,"end":1}]', ); }); it('can add comments to editor', () => { @@ -175,9 +175,9 @@ describe('CommentableEditor', () => { null, null, 0, - {} - ) - ) + {}, + ), + ), ); // Test that comment styles are correctly added to the editor, // and the comments in the state have annotations assigned @@ -185,7 +185,7 @@ describe('CommentableEditor', () => { createEditorStateFromRaw(content).getCurrentContent(), getComments(commentApp), commentApp, - () => new DraftailInlineAnnotation(fieldNode) + () => new DraftailInlineAnnotation(fieldNode), ); newContentState.getFirstBlock().findStyleRanges( (metadata) => !metadata.getStyle().isEmpty(), @@ -194,14 +194,14 @@ describe('CommentableEditor', () => { newContentState .getFirstBlock() .getInlineStyleAt(start) - .has('COMMENT-1') + .has('COMMENT-1'), ).toBe(true); expect(start).toBe(0); expect(end).toBe(1); - } + }, ); expect( - commentApp.store.getState().comments.comments.get(1)?.annotation + commentApp.store.getState().comments.comments.get(1)?.annotation, ).not.toBe(null); }); it('can find the least common comment id', () => { diff --git a/client/src/components/Draftail/CommentableEditor/CommentableEditor.tsx b/client/src/components/Draftail/CommentableEditor/CommentableEditor.tsx index 0068e7a8e1..06a51b887a 100644 --- a/client/src/components/Draftail/CommentableEditor/CommentableEditor.tsx +++ b/client/src/components/Draftail/CommentableEditor/CommentableEditor.tsx @@ -17,11 +17,19 @@ import { Modifier, RawDraftContentState, RichUtils, - SelectionState + SelectionState, } from 'draft-js'; import type { DraftEditorLeaf } from 'draft-js/lib/DraftEditorLeaf.react'; import { filterInlineStyles } from 'draftjs-filters'; -import React, { MutableRefObject, ReactNode, ReactText, useEffect, useMemo, useRef, useState } from 'react'; +import React, { + MutableRefObject, + ReactNode, + ReactText, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useSelector, shallowEqual } from 'react-redux'; import { STRINGS } from '../../../config/wagtailConfig'; @@ -56,10 +64,10 @@ export class DraftailInlineAnnotation implements Annotation { * Create an inline annotation * @param {Element} field - an element to provide the fallback position for comments without any inline decorators */ - field: Element - decoratorRefs: Map<DecoratorRef, BlockKey> - focusedBlockKey: BlockKey - cachedMedianRef: DecoratorRef | null + field: Element; + decoratorRefs: Map<DecoratorRef, BlockKey>; + focusedBlockKey: BlockKey; + cachedMedianRef: DecoratorRef | null; constructor(field: Element) { this.field = field; @@ -90,7 +98,8 @@ export class DraftailInlineAnnotation implements Annotation { } static getMedianRef(refArray: Array<DecoratorRef>) { const refs = refArray.sort( - (firstRef, secondRef) => this.getHeightForRef(firstRef) - this.getHeightForRef(secondRef) + (firstRef, secondRef) => + this.getHeightForRef(firstRef) - this.getHeightForRef(secondRef), ); const length = refs.length; if (length > 0) { @@ -111,13 +120,13 @@ export class DraftailInlineAnnotation implements Annotation { // if the highlight has somehow been split up medianRef = DraftailInlineAnnotation.getMedianRef( Array.from(this.decoratorRefs.keys()).filter( - (ref) => this.decoratorRefs.get(ref) === this.focusedBlockKey - ) + (ref) => this.decoratorRefs.get(ref) === this.focusedBlockKey, + ), ); } else if (!this.cachedMedianRef) { // Our cache is empty - try to update it medianRef = DraftailInlineAnnotation.getMedianRef( - Array.from(this.decoratorRefs.keys()) + Array.from(this.decoratorRefs.keys()), ); this.cachedMedianRef = medianRef; } else { @@ -130,22 +139,28 @@ export class DraftailInlineAnnotation implements Annotation { } } - -function applyInlineStyleToRange({ contentState, style, blockKey, start, end }: - {contentState: ContentState, - style: string, - blockKey: BlockKey, - start: number, - end: number} -) { - return Modifier.applyInlineStyle(contentState, +function applyInlineStyleToRange({ + contentState, + style, + blockKey, + start, + end, +}: { + contentState: ContentState; + style: string; + blockKey: BlockKey; + start: number; + end: number; +}) { + return Modifier.applyInlineStyle( + contentState, new SelectionState({ anchorKey: blockKey, anchorOffset: start, focusKey: blockKey, - focusOffset: end + focusOffset: end, }), - style + style, ); } @@ -158,11 +173,16 @@ function getFullSelectionState(contentState: ContentState) { anchorKey: contentState.getFirstBlock().getKey(), anchorOffset: 0, focusKey: lastBlock.getKey(), - focusOffset: lastBlock.getLength() + focusOffset: lastBlock.getLength(), }); } -function addNewComment(editorState: EditorState, fieldNode: Element, commentApp: CommentApp, contentPath: string) { +function addNewComment( + editorState: EditorState, + fieldNode: Element, + commentApp: CommentApp, + contentPath: string, +) { let state = editorState; const annotation = new DraftailInlineAnnotation(fieldNode); const commentId = commentApp.makeComment(annotation, contentPath, '[]'); @@ -170,40 +190,47 @@ function addNewComment(editorState: EditorState, fieldNode: Element, commentApp: // If the selection is collapsed, add the comment highlight on the whole field state = EditorState.acceptSelection( editorState, - selection.isCollapsed() ? getFullSelectionState(editorState.getCurrentContent()) : selection + selection.isCollapsed() + ? getFullSelectionState(editorState.getCurrentContent()) + : selection, ); - return ( - EditorState.acceptSelection( - RichUtils.toggleInlineStyle( - state, - `${COMMENT_STYLE_IDENTIFIER}${commentId}` - ), - selection - ) + return EditorState.acceptSelection( + RichUtils.toggleInlineStyle( + state, + `${COMMENT_STYLE_IDENTIFIER}${commentId}`, + ), + selection, ); } interface ControlProps { - getEditorState: () => EditorState, - onChange: (editorState: EditorState) => void + getEditorState: () => EditorState; + onChange: (editorState: EditorState) => void; } -function getCommentControl(commentApp: CommentApp, contentPath: string, fieldNode: Element) { +function getCommentControl( + commentApp: CommentApp, + contentPath: string, + fieldNode: Element, +) { return ({ getEditorState, onChange }: ControlProps) => ( <span className="Draftail-CommentControl" data-comment-add> <ToolbarButton name="comment" active={false} - title={`${STRINGS.ADD_A_COMMENT}\n${IS_MAC_OS ? '⌘ + Alt + M' : 'Ctrl + Alt + M'}`} + title={`${STRINGS.ADD_A_COMMENT}\n${ + IS_MAC_OS ? '⌘ + Alt + M' : 'Ctrl + Alt + M' + }`} icon={ <> - <Icon name="comment-large-outline" /> <Icon name="comment-large-reversed" /> + <Icon name="comment-large-outline" />{' '} + <Icon name="comment-large-reversed" /> </> } onClick={() => { onChange( - addNewComment(getEditorState(), fieldNode, commentApp, contentPath) + addNewComment(getEditorState(), fieldNode, commentApp, contentPath), ); }} /> @@ -222,50 +249,59 @@ function getIdForCommentStyle(style: string) { function findCommentStyleRanges( contentBlock: ContentBlock, callback: (start: number, end: number) => void, - filterFn?: (metadata: CharacterMetadata) => boolean) { + filterFn?: (metadata: CharacterMetadata) => boolean, +) { // Find comment style ranges that do not overlap an existing entity - const filterFunction = filterFn || ((metadata: CharacterMetadata) => metadata.getStyle().some(styleIsComment)); + const filterFunction = + filterFn || + ((metadata: CharacterMetadata) => metadata.getStyle().some(styleIsComment)); const entityRanges: Array<[number, number]> = []; contentBlock.findEntityRanges( - character => character.getEntity() !== null, - (start, end) => entityRanges.push([start, end]) + (character) => character.getEntity() !== null, + (start, end) => entityRanges.push([start, end]), ); - contentBlock.findStyleRanges( - filterFunction, - (start, end) => { - const interferingEntityRanges = entityRanges.filter(value => value[1] > start).filter(value => value[0] < end); - let currentPosition = start; - interferingEntityRanges.forEach((value) => { - const [entityStart, entityEnd] = value; - if (entityStart > currentPosition) { - callback(currentPosition, entityStart); - } - currentPosition = entityEnd; - }); - if (currentPosition < end) { - callback(start, end); + contentBlock.findStyleRanges(filterFunction, (start, end) => { + const interferingEntityRanges = entityRanges + .filter((value) => value[1] > start) + .filter((value) => value[0] < end); + let currentPosition = start; + interferingEntityRanges.forEach((value) => { + const [entityStart, entityEnd] = value; + if (entityStart > currentPosition) { + callback(currentPosition, entityStart); } + currentPosition = entityEnd; + }); + if (currentPosition < end) { + callback(start, end); } - ); + }); } - -export function updateCommentPositions({ editorState, comments, commentApp }: - { - editorState: EditorState, - comments: Array<Comment>, - commentApp: CommentApp - }) { +export function updateCommentPositions({ + editorState, + comments, + commentApp, +}: { + editorState: EditorState; + comments: Array<Comment>; + commentApp: CommentApp; +}) { // Construct a map of comment id -> array of style ranges const commentPositions = new Map(); - editorState.getCurrentContent().getBlocksAsArray().forEach( - (block) => { + editorState + .getCurrentContent() + .getBlocksAsArray() + .forEach((block) => { const key = block.getKey(); - block.findStyleRanges((metadata) => metadata.getStyle().some(styleIsComment), + block.findStyleRanges( + (metadata) => metadata.getStyle().some(styleIsComment), (start, end) => { - block.getInlineStyleAt(start).filter(styleIsComment).forEach( - (style) => { + block + .getInlineStyleAt(start) + .filter(styleIsComment) + .forEach((style) => { // We have already filtered out any undefined styles, so cast here const id = getIdForCommentStyle(style as string); let existingPosition = commentPositions.get(id); @@ -275,29 +311,30 @@ export function updateCommentPositions({ editorState, comments, commentApp }: existingPosition.push({ key: key, start: start, - end: end + end: end, }); commentPositions.set(id, existingPosition); - } - ); - }); - } - ); - - - comments.filter(comment => comment.annotation).forEach((comment) => { - // if a comment has an annotation - ie the field has it inserted - update its position - const newPosition = commentPositions.get(comment.localId); - const serializedNewPosition = newPosition ? JSON.stringify(newPosition) : '[]'; - if (comment.position !== serializedNewPosition) { - commentApp.store.dispatch( - commentApp.actions.updateComment( - comment.localId, - { position: serializedNewPosition } - ) + }); + }, ); - } - }); + }); + + comments + .filter((comment) => comment.annotation) + .forEach((comment) => { + // if a comment has an annotation - ie the field has it inserted - update its position + const newPosition = commentPositions.get(comment.localId); + const serializedNewPosition = newPosition + ? JSON.stringify(newPosition) + : '[]'; + if (comment.position !== serializedNewPosition) { + commentApp.store.dispatch( + commentApp.actions.updateComment(comment.localId, { + position: serializedNewPosition, + }), + ); + } + }); } /** @@ -305,7 +342,9 @@ export function updateCommentPositions({ editorState, comments, commentApp }: * has the fewest style ranges within the block, or null if no comment exists at the offset */ export function findLeastCommonCommentId(block: ContentBlock, offset: number) { - const styles = block.getInlineStyleAt(offset).filter(styleIsComment) as Immutable.OrderedSet<string>; + const styles = block + .getInlineStyleAt(offset) + .filter(styleIsComment) as Immutable.OrderedSet<string>; let styleToUse: string; const styleCount = styles.count(); if (styleCount === 0) { @@ -322,15 +361,20 @@ export function findLeastCommonCommentId(block: ContentBlock, offset: number) { // this casting should be removed let styleFreq = styles.map((style) => { let counter = 0; - findCommentStyleRanges(block, - () => { counter = counter + 1; }, - (metadata) => metadata.getStyle().some(rangeStyle => rangeStyle === style) + findCommentStyleRanges( + block, + () => { + counter = counter + 1; + }, + (metadata) => + metadata.getStyle().some((rangeStyle) => rangeStyle === style), ); return [style, counter]; }) as unknown as Immutable.OrderedSet<[string, number]>; - styleFreq = styleFreq.sort( - (firstStyleCount, secondStyleCount) => firstStyleCount[1] - secondStyleCount[1] + styleFreq = styleFreq.sort( + (firstStyleCount, secondStyleCount) => + firstStyleCount[1] - secondStyleCount[1], ) as Immutable.OrderedSet<[string, number]>; styleToUse = styleFreq.first()[0]; @@ -341,8 +385,8 @@ export function findLeastCommonCommentId(block: ContentBlock, offset: number) { } interface DecoratorProps { - contentState: ContentState, - children?: Array<DraftEditorLeaf> + contentState: ContentState; + children?: Array<DraftEditorLeaf>; } function getCommentDecorator(commentApp: CommentApp) { @@ -358,11 +402,10 @@ function getCommentDecorator(commentApp: CommentApp) { const blockKey: BlockKey = children[0].props.block.getKey(); const start: number = children[0].props.start; - const commentId = useMemo( - () => { - const block = contentState.getBlockForKey(blockKey); - return findLeastCommonCommentId(block, start); - }, [blockKey, start]); + const commentId = useMemo(() => { + const block = contentState.getBlockForKey(blockKey); + return findLeastCommonCommentId(block, start); + }, [blockKey, start]); const annotationNode = useRef(null); useEffect(() => { // Add a ref to the annotation, allowing the comment to float alongside the attached text. @@ -379,9 +422,7 @@ function getCommentDecorator(commentApp: CommentApp) { }, [commentId, annotationNode, blockKey]); if (!enabled) { - return <> - {children} - </>; + return <>{children}</>; } const onClick = () => { @@ -390,7 +431,11 @@ function getCommentDecorator(commentApp: CommentApp) { return; } const annotation = commentApp.layout.commentAnnotations.get(commentId); - if (annotation && annotation instanceof DraftailInlineAnnotation && annotationNode) { + if ( + annotation && + annotation instanceof DraftailInlineAnnotation && + annotationNode + ) { annotation.setFocusedBlockKey(blockKey); } @@ -398,8 +443,8 @@ function getCommentDecorator(commentApp: CommentApp) { commentApp.store.dispatch( commentApp.actions.setFocusedComment(commentId, { updatePinnedComment: true, - forceFocus: false - }) + forceFocus: false, + }), ); }; return ( @@ -417,7 +462,10 @@ function getCommentDecorator(commentApp: CommentApp) { return CommentDecorator; } -function forceResetEditorState(editorState: EditorState, replacementContent?: ContentState) { +function forceResetEditorState( + editorState: EditorState, + replacementContent?: ContentState, +) { const content = replacementContent || editorState.getCurrentContent(); const state = EditorState.set( EditorState.createWithContent(content, editorState.getDecorator()), @@ -425,8 +473,8 @@ function forceResetEditorState(editorState: EditorState, replacementContent?: Co selection: editorState.getSelection(), undoStack: editorState.getUndoStack(), redoStack: editorState.getRedoStack(), - inlineStyleOverride: editorState.getInlineStyleOverride() - } + inlineStyleOverride: editorState.getInlineStyleOverride(), + }, ); return EditorState.acceptSelection(state, state.getSelection()); } @@ -435,39 +483,43 @@ export function addCommentsToEditor( contentState: ContentState, comments: Comment[], commentApp: CommentApp, - getAnnotation: () => Annotation + getAnnotation: () => Annotation, ) { let newContentState = contentState; - comments.filter(comment => !comment.annotation).forEach((comment) => { - commentApp.updateAnnotation(getAnnotation(), comment.localId); - const style = `${COMMENT_STYLE_IDENTIFIER}${comment.localId}`; - try { - const positions = JSON.parse(comment.position); - positions.forEach((position) => { - newContentState = applyInlineStyleToRange({ - contentState: newContentState, - blockKey: position.key, - start: position.start, - end: position.end, - style + comments + .filter((comment) => !comment.annotation) + .forEach((comment) => { + commentApp.updateAnnotation(getAnnotation(), comment.localId); + const style = `${COMMENT_STYLE_IDENTIFIER}${comment.localId}`; + try { + const positions = JSON.parse(comment.position); + positions.forEach((position) => { + newContentState = applyInlineStyleToRange({ + contentState: newContentState, + blockKey: position.key, + start: position.start, + end: position.end, + style, + }); }); - }); - } catch (err) { - /* eslint-disable no-console */ - console.error(`Error loading comment position for comment ${comment.localId}`); - console.error(err); - /* esline-enable no-console */ - } - }); + } catch (err) { + /* eslint-disable no-console */ + console.error( + `Error loading comment position for comment ${comment.localId}`, + ); + console.error(err); + /* esline-enable no-console */ + } + }); return newContentState; } -type Direction = 'RTL' | 'LTR' +type Direction = 'RTL' | 'LTR'; function handleArrowAtContentEnd( state: EditorState, setEditorState: (newState: EditorState) => void, - direction: Direction + direction: Direction, ) { // If at the end of content and pressing in the same direction as the text, remove the comment style from // further typing @@ -476,49 +528,53 @@ function handleArrowAtContentEnd( const lastBlock = newState.getCurrentContent().getLastBlock(); const textDirection = newState.getDirectionMap().get(lastBlock.getKey()); - if (!( - textDirection === direction - && selection.isCollapsed() - && selection.getAnchorKey() === lastBlock.getKey() - && selection.getAnchorOffset() === lastBlock.getLength() - )) { + if ( + !( + textDirection === direction && + selection.isCollapsed() && + selection.getAnchorKey() === lastBlock.getKey() && + selection.getAnchorOffset() === lastBlock.getLength() + ) + ) { return; } setEditorState( EditorState.setInlineStyleOverride( newState, - newState.getCurrentInlineStyle().filter(style => !styleIsComment(style)) as DraftInlineStyle - ) + newState + .getCurrentInlineStyle() + .filter((style) => !styleIsComment(style)) as DraftInlineStyle, + ), ); } interface InlineStyle { - label?: string, - description?: string, - icon?: string | string[] | Node, - type: string, - style?: Record<string, string | number | ReactText | undefined > + label?: string; + description?: string; + icon?: string | string[] | Node; + type: string; + style?: Record<string, string | number | ReactText | undefined>; } interface ColorConfigProp { - standardHighlight: string, - overlappingHighlight: string, - focusedHighlight: string + standardHighlight: string; + overlappingHighlight: string; + focusedHighlight: string; } interface CommentableEditorProps { - commentApp: CommentApp, - fieldNode: Element, - contentPath: string, - rawContentState: RawDraftContentState, - onSave: (rawContent: RawDraftContentState) => void, - inlineStyles: Array<InlineStyle>, - editorRef: (editor: ReactNode) => void - colorConfig: ColorConfigProp - isCommentShortcut: (e: React.KeyboardEvent) => boolean + commentApp: CommentApp; + fieldNode: Element; + contentPath: string; + rawContentState: RawDraftContentState; + onSave: (rawContent: RawDraftContentState) => void; + inlineStyles: Array<InlineStyle>; + editorRef: (editor: ReactNode) => void; + colorConfig: ColorConfigProp; + isCommentShortcut: (e: React.KeyboardEvent) => boolean; // Unfortunately the EditorPlugin type isn't exported in our version of 'draft-js-plugins-editor' - plugins?: Record<string, unknown>[] - controls?: Array<(props: ControlProps) => JSX.Element> + plugins?: Record<string, unknown>[]; + controls?: Array<(props: ControlProps) => JSX.Element>; } function CommentableEditor({ @@ -536,33 +592,35 @@ function CommentableEditor({ ...options }: CommentableEditorProps) { const [editorState, setEditorState] = useState(() => - createEditorStateFromRaw(rawContentState) + createEditorStateFromRaw(rawContentState), ); const CommentControl = useMemo( () => getCommentControl(commentApp, contentPath, fieldNode), - [commentApp, contentPath, fieldNode] + [commentApp, contentPath, fieldNode], ); const commentsSelector = useMemo( () => commentApp.utils.selectCommentsForContentPathFactory(contentPath), - [contentPath, commentApp] + [contentPath, commentApp], + ); + const CommentDecorator = useMemo( + () => getCommentDecorator(commentApp), + [commentApp], ); - const CommentDecorator = useMemo(() => getCommentDecorator(commentApp), [ - commentApp, - ]); const comments = useSelector(commentsSelector, shallowEqual); const enabled = useSelector(commentApp.selectors.selectEnabled); const focusedId = useSelector(commentApp.selectors.selectFocused); - const ids = useMemo(() => comments.map((comment) => comment.localId), [ - comments, - ]); + const ids = useMemo( + () => comments.map((comment) => comment.localId), + [comments], + ); const commentStyles: Array<InlineStyle> = useMemo( () => ids.map((id) => ({ - type: `${COMMENT_STYLE_IDENTIFIER}${id}` + type: `${COMMENT_STYLE_IDENTIFIER}${id}`, })), - [ids] + [ids], ); const [uniqueStyleId, setUniqueStyleId] = useState(0); @@ -574,16 +632,17 @@ function CommentableEditor({ // Only trigger a focus-related rerender if the current focused comment is inside the field, or the previous one was const validFocusChange = previousFocused !== focusedId && - ((previousFocused && previousIds && previousIds.includes(previousFocused)) || - focusedId && ids.includes(focusedId)); + ((previousFocused && + previousIds && + previousIds.includes(previousFocused)) || + (focusedId && ids.includes(focusedId))); if ( !validFocusChange && previousEnabled === enabled && - ( - previousIds === ids || - (previousIds.length === ids.length && previousIds.every((value, index) => value === ids[index])) - ) + (previousIds === ids || + (previousIds.length === ids.length && + previousIds.every((value, index) => value === ids[index]))) ) { return; } @@ -593,7 +652,7 @@ function CommentableEditor({ inlineStyles .map((style) => style.type) .concat(ids.map((id) => `${COMMENT_STYLE_IDENTIFIER}${id}`)), - editorState.getCurrentContent() + editorState.getCurrentContent(), ); // Force reset the editor state to ensure redecoration, and apply a new (blank) inline style to force // inline style rerender. This must be entirely new for the rerender to trigger, hence the unique @@ -606,9 +665,9 @@ function CommentableEditor({ Modifier.applyInlineStyle( filteredContent, getFullSelectionState(filteredContent), - `STYLE_RERENDER_${uniqueStyleId}` - ) - ) + `STYLE_RERENDER_${uniqueStyleId}`, + ), + ), ); setUniqueStyleId((id) => (id + 1) % 200); }, [focusedId, enabled, inlineStyles, ids, editorState]); @@ -617,7 +676,10 @@ function CommentableEditor({ // if there are any comments without annotations, we need to add them to the EditorState const contentState = editorState.getCurrentContent(); const newContentState = addCommentsToEditor( - contentState, comments, commentApp, () => new DraftailInlineAnnotation(fieldNode) + contentState, + comments, + commentApp, + () => new DraftailInlineAnnotation(fieldNode), ); if (contentState !== newContentState) { setEditorState(forceResetEditorState(editorState, newContentState)); @@ -633,19 +695,16 @@ function CommentableEditor({ editorState, filterInlineStyles( inlineStyles.map((style) => style.type), - editorState.getCurrentContent() + editorState.getCurrentContent(), ), - 'change-inline-style' + 'change-inline-style', ); - timeoutRef.current = window.setTimeout( - () => { - onSave(serialiseEditorStateToRaw(filteredEditorState)); + timeoutRef.current = window.setTimeout(() => { + onSave(serialiseEditorStateToRaw(filteredEditorState)); - // Next, update comment positions in the redux store - updateCommentPositions({ editorState, comments, commentApp }); - }, - 250 - ); + // Next, update comment positions in the redux store + updateCommentPositions({ editorState, comments, commentApp }); + }, 250); return () => { window.clearTimeout(timeoutRef.current); }; @@ -659,21 +718,27 @@ function CommentableEditor({ if (['undo', 'redo'].includes(state.getLastChangeType())) { const filteredContent = filterInlineStyles( inlineStyles - .map(style => style.type) - .concat(ids.map(id => `${COMMENT_STYLE_IDENTIFIER}${id}`)), - state.getCurrentContent() + .map((style) => style.type) + .concat(ids.map((id) => `${COMMENT_STYLE_IDENTIFIER}${id}`)), + state.getCurrentContent(), ); newEditorState = forceResetEditorState(state, filteredContent); } else if (state.getLastChangeType() === 'split-block') { const content = newEditorState.getCurrentContent(); const selection = newEditorState.getSelection(); - const style = content.getBlockForKey(selection.getAnchorKey()).getInlineStyleAt(selection.getAnchorOffset()); + const style = content + .getBlockForKey(selection.getAnchorKey()) + .getInlineStyleAt(selection.getAnchorOffset()); // If starting a new paragraph (and not splitting an existing comment) // ensure any new text entered doesn't get a comment style - if (!style.some(styleName => styleIsComment(styleName))) { + if (!style.some((styleName) => styleIsComment(styleName))) { newEditorState = EditorState.setInlineStyleOverride( newEditorState, - newEditorState.getCurrentInlineStyle().filter(styleName => !styleIsComment(styleName)) as DraftInlineStyle + newEditorState + .getCurrentInlineStyle() + .filter( + (styleName) => !styleIsComment(styleName), + ) as DraftInlineStyle, ); } } @@ -682,89 +747,101 @@ function CommentableEditor({ editorState={editorState} controls={enabled ? controls.concat([CommentControl]) : controls} inlineStyles={inlineStyles.concat(commentStyles)} - plugins={plugins.concat([{ - decorators: [{ - strategy: ( - block: ContentBlock, callback: (start: number, end: number) => void - ) => findCommentStyleRanges(block, callback), - component: CommentDecorator, - }], - keyBindingFn: (e: React.KeyboardEvent) => { - if (isCommentShortcut(e)) { - return 'comment'; - } - return undefined; - }, - onRightArrow: (_: React.KeyboardEvent, { getEditorState }) => { - // In later versions of draft-js, this is deprecated and can be handled via handleKeyCommand instead - // when draftail upgrades, this logic can be moved there - - handleArrowAtContentEnd(getEditorState(), setEditorState, 'LTR'); - }, - onLeftArrow: (_: React.KeyboardEvent, { getEditorState }) => { - // In later versions of draft-js, this is deprecated and can be handled via handleKeyCommand instead - // when draftail upgrades, this logic can be moved there - - handleArrowAtContentEnd(getEditorState(), setEditorState, 'RTL'); - }, - handleKeyCommand: (command: string, state: EditorState) => { - if (enabled && command === 'comment') { - const selection = state.getSelection(); - const content = state.getCurrentContent(); - if (selection.isCollapsed()) { - // We might be trying to focus an existing comment - check if we're in a comment range - const id = findLeastCommonCommentId( - content.getBlockForKey(selection.getAnchorKey()), - selection.getAnchorOffset() - ); - if (id) { - // Focus the comment - commentApp.store.dispatch( - commentApp.actions.setFocusedComment(id, { updatePinnedComment: true, forceFocus: true }) - ); - return 'handled'; - } + plugins={plugins.concat([ + { + decorators: [ + { + strategy: ( + block: ContentBlock, + callback: (start: number, end: number) => void, + ) => findCommentStyleRanges(block, callback), + component: CommentDecorator, + }, + ], + keyBindingFn: (e: React.KeyboardEvent) => { + if (isCommentShortcut(e)) { + return 'comment'; } - // Otherwise, add a new comment - setEditorState(addNewComment(state, fieldNode, commentApp, contentPath)); - return 'handled'; - } - return 'not-handled'; - }, - customStyleFn: (styleSet: DraftInlineStyle) => { - if (!enabled) { return undefined; - } - // Use of casting in this function is due to issue #1563 in immutable-js, which causes operations like - // map and filter to lose type information on the results. It should be fixed in v4: when we upgrade, - // this casting should be removed - const localCommentStyles = styleSet.filter(styleIsComment) as Immutable.OrderedSet<string>; - const numStyles = localCommentStyles.count(); - if (numStyles > 0) { - // There is at least one comment in the range - const commentIds = localCommentStyles.map( - style => getIdForCommentStyle(style as string) - ) as unknown as Immutable.OrderedSet<number>; - let background = standardHighlight; - if (focusedId && commentIds.has(focusedId)) { - // Use the focused colour if one of the comments is focused - background = focusedHighlight; + }, + onRightArrow: (_: React.KeyboardEvent, { getEditorState }) => { + // In later versions of draft-js, this is deprecated and can be handled via handleKeyCommand instead + // when draftail upgrades, this logic can be moved there + + handleArrowAtContentEnd(getEditorState(), setEditorState, 'LTR'); + }, + onLeftArrow: (_: React.KeyboardEvent, { getEditorState }) => { + // In later versions of draft-js, this is deprecated and can be handled via handleKeyCommand instead + // when draftail upgrades, this logic can be moved there + + handleArrowAtContentEnd(getEditorState(), setEditorState, 'RTL'); + }, + handleKeyCommand: (command: string, state: EditorState) => { + if (enabled && command === 'comment') { + const selection = state.getSelection(); + const content = state.getCurrentContent(); + if (selection.isCollapsed()) { + // We might be trying to focus an existing comment - check if we're in a comment range + const id = findLeastCommonCommentId( + content.getBlockForKey(selection.getAnchorKey()), + selection.getAnchorOffset(), + ); + if (id) { + // Focus the comment + commentApp.store.dispatch( + commentApp.actions.setFocusedComment(id, { + updatePinnedComment: true, + forceFocus: true, + }), + ); + return 'handled'; + } + } + // Otherwise, add a new comment + setEditorState( + addNewComment(state, fieldNode, commentApp, contentPath), + ); + return 'handled'; + } + return 'not-handled'; + }, + customStyleFn: (styleSet: DraftInlineStyle) => { + if (!enabled) { + return undefined; + } + // Use of casting in this function is due to issue #1563 in immutable-js, which causes operations like + // map and filter to lose type information on the results. It should be fixed in v4: when we upgrade, + // this casting should be removed + const localCommentStyles = styleSet.filter( + styleIsComment, + ) as Immutable.OrderedSet<string>; + const numStyles = localCommentStyles.count(); + if (numStyles > 0) { + // There is at least one comment in the range + const commentIds = localCommentStyles.map((style) => + getIdForCommentStyle(style as string), + ) as unknown as Immutable.OrderedSet<number>; + let background = standardHighlight; + if (focusedId && commentIds.has(focusedId)) { + // Use the focused colour if one of the comments is focused + background = focusedHighlight; + return { + 'background-color': background, + 'color': standardHighlight, + }; + } else if (numStyles > 1) { + // Otherwise if we're in a region with overlapping comments, use a slightly darker colour than usual + // to indicate that + background = overlappingHighlight; + } return { 'background-color': background, - 'color': standardHighlight }; - } else if (numStyles > 1) { - // Otherwise if we're in a region with overlapping comments, use a slightly darker colour than usual - // to indicate that - background = overlappingHighlight; } - return { - 'background-color': background - }; - } - return undefined; - } - }])} + return undefined; + }, + }, + ])} {...options} /> ); diff --git a/client/src/components/Draftail/DraftUtils.js b/client/src/components/Draftail/DraftUtils.js index dac2f85942..ce6cd7a5ab 100644 --- a/client/src/components/Draftail/DraftUtils.js +++ b/client/src/components/Draftail/DraftUtils.js @@ -1,15 +1,14 @@ - /** -* Returns collection of currently selected blocks. -* See https://github.com/jpuri/draftjs-utils/blob/e81c0ae19c3b0fdef7e0c1b70d924398956be126/js/block.js#L19. -*/ + * Returns collection of currently selected blocks. + * See https://github.com/jpuri/draftjs-utils/blob/e81c0ae19c3b0fdef7e0c1b70d924398956be126/js/block.js#L19. + */ const getSelectedBlocksList = (editorState) => { const selectionState = editorState.getSelection(); const content = editorState.getCurrentContent(); const startKey = selectionState.getStartKey(); const endKey = selectionState.getEndKey(); const blockMap = content.getBlockMap(); - const blocks = blockMap + const blocks = blockMap .toSeq() .skipUntil((_, k) => k === startKey) .takeUntil((_, k) => k === endKey) @@ -18,9 +17,9 @@ const getSelectedBlocksList = (editorState) => { }; /** -* Returns the currently selected text in the editor. -* See https://github.com/jpuri/draftjs-utils/blob/e81c0ae19c3b0fdef7e0c1b70d924398956be126/js/block.js#L106. -*/ + * Returns the currently selected text in the editor. + * See https://github.com/jpuri/draftjs-utils/blob/e81c0ae19c3b0fdef7e0c1b70d924398956be126/js/block.js#L106. + */ export const getSelectionText = (editorState) => { const selection = editorState.getSelection(); let start = selection.getAnchorOffset(); @@ -36,7 +35,10 @@ export const getSelectionText = (editorState) => { let selectedText = ''; for (let i = 0; i < selectedBlocks.size; i += 1) { const blockStart = i === 0 ? start : 0; - const blockEnd = i === (selectedBlocks.size - 1) ? end : selectedBlocks.get(i).getText().length; + const blockEnd = + i === selectedBlocks.size - 1 + ? end + : selectedBlocks.get(i).getText().length; selectedText += selectedBlocks.get(i).getText().slice(blockStart, blockEnd); } diff --git a/client/src/components/Draftail/DraftUtils.test.js b/client/src/components/Draftail/DraftUtils.test.js index 5c5b0f57e3..307d5b4252 100644 --- a/client/src/components/Draftail/DraftUtils.test.js +++ b/client/src/components/Draftail/DraftUtils.test.js @@ -1,7 +1,4 @@ -import { - EditorState, - convertFromRaw, -} from 'draft-js'; +import { EditorState, convertFromRaw } from 'draft-js'; import { getSelectionText } from './DraftUtils'; @@ -69,7 +66,7 @@ describe('DraftUtils', () => { { key: 'b', text: 'multiblock', - } + }, ], }); let editorState = EditorState.createWithContent(content); @@ -99,7 +96,7 @@ describe('DraftUtils', () => { { key: 'b', text: 'multiblock', - } + }, ], }); let editorState = EditorState.createWithContent(content); diff --git a/client/src/components/Draftail/Draftail.scss b/client/src/components/Draftail/Draftail.scss index 375067564a..31663113cc 100644 --- a/client/src/components/Draftail/Draftail.scss +++ b/client/src/components/Draftail/Draftail.scss @@ -28,104 +28,104 @@ $draftail-editor-font-family: $font-sans; @import './blocks/EmbedBlock'; @include draftail-richtext-styles { - h1, - h2, - h3, - h4, - h5, - h6 { - // Overrides for other parts of the Wagtail admin. - text-transform: none; - font-family: inherit; - clear: both; - line-height: 1; - } + h1, + h2, + h3, + h4, + h5, + h6 { + // Overrides for other parts of the Wagtail admin. + text-transform: none; + font-family: inherit; + clear: both; + line-height: 1; + } - h1, - h2, - h3 { - line-height: 1.1; - } + h1, + h2, + h3 { + line-height: 1.1; + } - h1 { - font-size: 2em; - } + h1 { + font-size: 2em; + } - h2 { - font-size: 1.8em; - } + h2 { + font-size: 1.8em; + } - h3 { - font-size: 1.5em; - } + h3 { + font-size: 1.5em; + } - h4 { - font-size: 1.2em; - } + h4 { + font-size: 1.2em; + } - h5 { - font-size: 1.1em; - } + h5 { + font-size: 1.1em; + } } .Draftail-Editor { - border-radius: 0; + border-radius: 0; - &__wrapper { - // Ensure elements within the editor are positioned according to this container. - position: relative; - } + &__wrapper { + // Ensure elements within the editor are positioned according to this container. + position: relative; + } } .Draftail-ToolbarButton .icon { - width: $draftail-toolbar-icon-size; - height: $draftail-toolbar-icon-size; - vertical-align: middle; + width: $draftail-toolbar-icon-size; + height: $draftail-toolbar-icon-size; + vertical-align: middle; } // When in a .full container, the editor has a specific appearance // so the whole area appears like a focusable area, and the editor // receives focus on click. .full .Draftail-Editor { - padding-top: 2em; - padding-bottom: 2em; - background-color: $color-fieldset-hover; - border-right: 1px solid $color-input-border; + padding-top: 2em; + padding-bottom: 2em; + background-color: $color-fieldset-hover; + border-right: 1px solid $color-input-border; - &--focus { - background-color: $color-input-focus; - border-color: $color-input-focus-border; - } + &--focus { + background-color: $color-input-focus; + border-color: $color-input-focus-border; + } } .full .Draftail-Editor .public-DraftEditor-content, .full .Draftail-Editor .public-DraftEditorPlaceholder-root { - @include nice-padding; + @include nice-padding; } .full .Draftail-Toolbar { - @include nice-margin; + @include nice-margin; } .Draftail-Toolbar { - border: 1px solid $color-grey-3; + border: 1px solid $color-grey-3; } .Draftail-ToolbarButton { - &:hover, - &:active { - border: 1px solid $color-grey-3; - } + &:hover, + &:active { + border: 1px solid $color-grey-3; + } } .title .Draftail-Editor .public-DraftEditor-content, .title .Draftail-Editor .public-DraftEditorPlaceholder-root { - font-size: 2em; + font-size: 2em; } .Draftail-block--blockquote { - border-left: 0.25em solid $color-grey-3; - color: $color-grey-2; - margin: 1em 0; - padding: 1em 2em; + border-left: 0.25em solid $color-grey-3; + color: $color-grey-2; + margin: 1em 0; + padding: 1em 2em; } diff --git a/client/src/components/Draftail/EditorFallback/EditorFallback.scss b/client/src/components/Draftail/EditorFallback/EditorFallback.scss index 959f442ce7..d603d75779 100644 --- a/client/src/components/Draftail/EditorFallback/EditorFallback.scss +++ b/client/src/components/Draftail/EditorFallback/EditorFallback.scss @@ -1,22 +1,22 @@ .EditorFallback__textarea { - min-height: 150px; - margin-top: 1rem; - resize: vertical; + min-height: 150px; + margin-top: 1rem; + resize: vertical; } // Override over-reaching styles definition for all textareas in full fields. @mixin wagtail-textarea-overrides { - padding: 1rem; - border-width: 1px; - border-radius: 6px; + padding: 1rem; + border-width: 1px; + border-radius: 6px; } .EditorFallback__textarea, .object.full .EditorFallback__textarea { - @include wagtail-textarea-overrides; + @include wagtail-textarea-overrides; } .EditorFallback__error { - background: none; - box-shadow: none; + background: none; + box-shadow: none; } diff --git a/client/src/components/Draftail/EditorFallback/EditorFallback.test.js b/client/src/components/Draftail/EditorFallback/EditorFallback.test.js index c4b098bc90..759a8ccc1d 100644 --- a/client/src/components/Draftail/EditorFallback/EditorFallback.test.js +++ b/client/src/components/Draftail/EditorFallback/EditorFallback.test.js @@ -9,8 +9,8 @@ describe('EditorFallback', () => { shallow( <EditorFallback field={document.createElement('input')}> test - </EditorFallback> - ) + </EditorFallback>, + ), ).toMatchSnapshot(); }); @@ -19,7 +19,7 @@ describe('EditorFallback', () => { field.value = 'test value'; const wrapper = shallow( - <EditorFallback field={field}>test</EditorFallback> + <EditorFallback field={field}>test</EditorFallback>, ); field.value = 'new test value'; @@ -39,7 +39,7 @@ describe('EditorFallback', () => { const wrapper = shallow( <EditorFallback field={document.createElement('input')}> test - </EditorFallback> + </EditorFallback>, ); wrapper.setState({ @@ -53,7 +53,7 @@ describe('EditorFallback', () => { const wrapper = shallow( <EditorFallback field={document.createElement('input')}> test - </EditorFallback> + </EditorFallback>, ); wrapper @@ -74,7 +74,7 @@ describe('EditorFallback', () => { const wrapper = shallow( <EditorFallback field={document.createElement('input')}> test - </EditorFallback> + </EditorFallback>, ); wrapper @@ -93,7 +93,7 @@ describe('EditorFallback', () => { const wrapper = shallow( <EditorFallback field={document.createElement('input')}> test - </EditorFallback> + </EditorFallback>, ); const error = new Error('test'); @@ -125,7 +125,7 @@ describe('EditorFallback', () => { }; const wrapper = shallow( - <EditorFallback field={field}>test</EditorFallback> + <EditorFallback field={field}>test</EditorFallback>, ); const error = new Error('test'); diff --git a/client/src/components/Draftail/Tooltip/Tooltip.js b/client/src/components/Draftail/Tooltip/Tooltip.js index 2829271f62..eacdd3f6e0 100644 --- a/client/src/components/Draftail/Tooltip/Tooltip.js +++ b/client/src/components/Draftail/Tooltip/Tooltip.js @@ -9,22 +9,22 @@ const getTooltipStyles = (target, direction) => { const top = window.pageYOffset + target.top; const left = window.pageXOffset + target.left; switch (direction) { - case TOP: - return { - top: top + target.height, - left: left + target.width / 2, - }; - case LEFT: - return { - top: top + target.height / 2, - left: left + target.width, - }; - case TOP_LEFT: - default: - return { - top: top + target.height, - left: left, - }; + case TOP: + return { + top: top + target.height, + left: left + target.width / 2, + }; + case LEFT: + return { + top: top + target.height / 2, + left: left + target.width, + }; + case TOP_LEFT: + default: + return { + top: top + target.height, + left: left, + }; } }; diff --git a/client/src/components/Draftail/Tooltip/Tooltip.scss b/client/src/components/Draftail/Tooltip/Tooltip.scss index bd5129dcab..21ef20607e 100644 --- a/client/src/components/Draftail/Tooltip/Tooltip.scss +++ b/client/src/components/Draftail/Tooltip/Tooltip.scss @@ -11,86 +11,86 @@ $tooltip-z-index: $draftail-tooltip-z-index; $tooltip-color-no: #f48880; @mixin arrow--top { - margin-top: $tooltip-arrow-spacing; - transform: translateX(-50%); + margin-top: $tooltip-arrow-spacing; + transform: translateX(-50%); - &::before { - bottom: 100%; - left: 50%; - transform: translateX(-50%); - border-bottom-color: $tooltip-chrome; - } + &::before { + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border-bottom-color: $tooltip-chrome; + } } @mixin arrow--left { - margin-left: $tooltip-arrow-spacing; - transform: translateY(-50%); + margin-left: $tooltip-arrow-spacing; + transform: translateY(-50%); - &::before { - top: 50%; - right: 100%; - transform: translateY(-50%); - border-right-color: $tooltip-chrome; - } + &::before { + top: 50%; + right: 100%; + transform: translateY(-50%); + border-right-color: $tooltip-chrome; + } } @mixin arrow--top-left { - margin-top: $tooltip-arrow-spacing; + margin-top: $tooltip-arrow-spacing; - &::before { - bottom: 100%; - left: $tooltip-arrow-spacing; - border-bottom-color: $tooltip-chrome; - } + &::before { + bottom: 100%; + left: $tooltip-arrow-spacing; + border-bottom-color: $tooltip-chrome; + } } .Tooltip { + position: absolute; + padding: $tooltip-spacing; + background-color: $tooltip-chrome; + color: $tooltip-chrome-text; + z-index: $tooltip-z-index; + border-radius: $tooltip-radius; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + + &::before { + content: ''; position: absolute; - padding: $tooltip-spacing; - background-color: $tooltip-chrome; - color: $tooltip-chrome-text; - z-index: $tooltip-z-index; - border-radius: $tooltip-radius; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + border: $tooltip-arrow-height solid transparent; + } - &::before { - content: ''; - position: absolute; - border: $tooltip-arrow-height solid transparent; + &--top { + @include arrow--top; + } + + &--left { + @include arrow--left; + } + + &--top-left { + @include arrow--top-left; + } + + &__link { + @include font-smoothing; + font-size: 0.875rem; + margin-right: $controls-spacing * 4; + display: inline-block; + + &, + &:hover { + color: $color-white; } + } - &--top { - @include arrow--top; - } - - &--left { - @include arrow--left; - } - - &--top-left { - @include arrow--top-left; - } - - &__link { - @include font-smoothing; - font-size: 0.875rem; - margin-right: $controls-spacing * 4; - display: inline-block; - - &, - &:hover { - color: $color-white; - } - } - - &__button { - &.no.button-secondary { - color: $tooltip-color-no; - border-color: currentColor; - - &:hover { - color: $color-button-no; - } - } + &__button { + &.no.button-secondary { + color: $tooltip-color-no; + border-color: currentColor; + + &:hover { + color: $color-button-no; + } } + } } diff --git a/client/src/components/Draftail/Tooltip/Tooltip.test.js b/client/src/components/Draftail/Tooltip/Tooltip.test.js index 35ec135b1a..9da1b6c7d2 100644 --- a/client/src/components/Draftail/Tooltip/Tooltip.test.js +++ b/client/src/components/Draftail/Tooltip/Tooltip.test.js @@ -16,8 +16,8 @@ describe('Tooltip', () => { shallow( <Tooltip target={target} direction="top"> Test - </Tooltip> - ) + </Tooltip>, + ), ).toMatchSnapshot(); }); @@ -26,8 +26,8 @@ describe('Tooltip', () => { shallow( <Tooltip target={target} direction="left"> Test - </Tooltip> - ) + </Tooltip>, + ), ).toMatchSnapshot(); }); @@ -36,8 +36,8 @@ describe('Tooltip', () => { shallow( <Tooltip target={target} direction="top-left"> Test - </Tooltip> - ) + </Tooltip>, + ), ).toMatchSnapshot(); }); }); diff --git a/client/src/components/Draftail/blocks/EmbedBlock.js b/client/src/components/Draftail/blocks/EmbedBlock.js index a42e49ef1c..d91d589b16 100644 --- a/client/src/components/Draftail/blocks/EmbedBlock.js +++ b/client/src/components/Draftail/blocks/EmbedBlock.js @@ -8,7 +8,7 @@ import MediaBlock from '../blocks/MediaBlock'; /** * Editor block to display media and edit content. */ -const EmbedBlock = props => { +const EmbedBlock = (props) => { const { entity, onEditEntity, onRemoveEntity } = props.blockProps; const { url, title, thumbnail } = entity.getData(); @@ -25,10 +25,17 @@ const EmbedBlock = props => { {title} </a> ) : null} - <button className="button Tooltip__button" type="button" onClick={onEditEntity}> + <button + className="button Tooltip__button" + type="button" + onClick={onEditEntity} + > {STRINGS.EDIT} </button> - <button className="button button-secondary no Tooltip__button" onClick={onRemoveEntity}> + <button + className="button button-secondary no Tooltip__button" + onClick={onRemoveEntity} + > {STRINGS.DELETE} </button> </MediaBlock> diff --git a/client/src/components/Draftail/blocks/EmbedBlock.scss b/client/src/components/Draftail/blocks/EmbedBlock.scss index b4dcffec11..b5f95c9639 100644 --- a/client/src/components/Draftail/blocks/EmbedBlock.scss +++ b/client/src/components/Draftail/blocks/EmbedBlock.scss @@ -1,4 +1,4 @@ .EmbedBlock__link { - display: block; - margin-bottom: $button-spacing; + display: block; + margin-bottom: $button-spacing; } diff --git a/client/src/components/Draftail/blocks/EmbedBlock.test.js b/client/src/components/Draftail/blocks/EmbedBlock.test.js index 0aa9c52fdd..9d814715ca 100644 --- a/client/src/components/Draftail/blocks/EmbedBlock.test.js +++ b/client/src/components/Draftail/blocks/EmbedBlock.test.js @@ -21,8 +21,8 @@ describe('EmbedBlock', () => { }, onChange: () => {}, }} - /> - ) + />, + ), ).toMatchSnapshot(); }); @@ -39,8 +39,8 @@ describe('EmbedBlock', () => { }, onChange: () => {}, }} - /> - ) + />, + ), ).toMatchSnapshot(); }); }); diff --git a/client/src/components/Draftail/blocks/ImageBlock.js b/client/src/components/Draftail/blocks/ImageBlock.js index ac3aff3b1f..0a3296ad71 100644 --- a/client/src/components/Draftail/blocks/ImageBlock.js +++ b/client/src/components/Draftail/blocks/ImageBlock.js @@ -8,7 +8,7 @@ import MediaBlock from '../blocks/MediaBlock'; /** * Editor block to preview and edit images. */ -const ImageBlock = props => { +const ImageBlock = (props) => { const { blockProps } = props; const { entity, onEditEntity, onRemoveEntity } = blockProps; const { src, alt } = entity.getData(); @@ -21,10 +21,17 @@ const ImageBlock = props => { <MediaBlock {...props} src={src} alt=""> <p className="ImageBlock__alt">{altLabel}</p> - <button className="button Tooltip__button" type="button" onClick={onEditEntity}> + <button + className="button Tooltip__button" + type="button" + onClick={onEditEntity} + > {STRINGS.EDIT} </button> - <button className="button button-secondary no Tooltip__button" onClick={onRemoveEntity}> + <button + className="button button-secondary no Tooltip__button" + onClick={onRemoveEntity} + > {STRINGS.DELETE} </button> </MediaBlock> diff --git a/client/src/components/Draftail/blocks/ImageBlock.scss b/client/src/components/Draftail/blocks/ImageBlock.scss index 9d1121d744..fbfa678034 100644 --- a/client/src/components/Draftail/blocks/ImageBlock.scss +++ b/client/src/components/Draftail/blocks/ImageBlock.scss @@ -1,35 +1,35 @@ -@use "sass:color"; +@use 'sass:color'; @mixin wagtail-label-overrides { - float: none; - width: initial; - padding: 0; + float: none; + width: initial; + padding: 0; } .ImageBlock__field { - @include wagtail-label-overrides; - display: block; - margin-bottom: $button-spacing * 2; - color: $color-white; - cursor: pointer; + @include wagtail-label-overrides; + display: block; + margin-bottom: $button-spacing * 2; + color: $color-white; + cursor: pointer; } @mixin wagtail-input-overrides { - border: 0; - font-size: inherit; + border: 0; + font-size: inherit; } .ImageBlock__field__input { - @include wagtail-input-overrides; - font-size: 1rem; - background-color: $color-white; - color: $color-text-base; + @include wagtail-input-overrides; + font-size: 1rem; + background-color: $color-white; + color: $color-text-base; - &[readonly] { - color: color.adjust($color-text-base, $alpha: -0.5); - } + &[readonly] { + color: color.adjust($color-text-base, $alpha: -0.5); + } } .ImageBlock__alt { - @include font-smoothing; - font-size: 0.875rem; + @include font-smoothing; + font-size: 0.875rem; } diff --git a/client/src/components/Draftail/blocks/ImageBlock.test.js b/client/src/components/Draftail/blocks/ImageBlock.test.js index 269a5d442f..46198d5621 100644 --- a/client/src/components/Draftail/blocks/ImageBlock.test.js +++ b/client/src/components/Draftail/blocks/ImageBlock.test.js @@ -19,8 +19,8 @@ describe('ImageBlock', () => { }, onChange: () => {}, }} - /> - ) + />, + ), ).toMatchSnapshot(); }); @@ -37,8 +37,8 @@ describe('ImageBlock', () => { }, onChange: () => {}, }} - /> - ) + />, + ), ).toMatchSnapshot(); }); @@ -58,8 +58,8 @@ describe('ImageBlock', () => { }, onChange: () => {}, }} - /> - ) + />, + ), ).toMatchSnapshot(); }); }); diff --git a/client/src/components/Draftail/blocks/MediaBlock.js b/client/src/components/Draftail/blocks/MediaBlock.js index d8cbcc3747..0278416d34 100644 --- a/client/src/components/Draftail/blocks/MediaBlock.js +++ b/client/src/components/Draftail/blocks/MediaBlock.js @@ -63,8 +63,14 @@ class MediaBlock extends Component { this.setState({ showTooltipAt: { container: container, - top: rect.top - containerRect.top - (document.documentElement.scrollTop || document.body.scrollTop), - left: rect.left - containerRect.left - (document.documentElement.scrollLeft || document.body.scrollLeft), + top: + rect.top - + containerRect.top - + (document.documentElement.scrollTop || document.body.scrollTop), + left: + rect.left - + containerRect.left - + (document.documentElement.scrollLeft || document.body.scrollLeft), width: rect.width, height: rect.height, direction: maxWidth >= TOOLTIP_MAX_WIDTH ? 'left' : 'top-left', diff --git a/client/src/components/Draftail/blocks/MediaBlock.scss b/client/src/components/Draftail/blocks/MediaBlock.scss index 63687068ea..6c14e4d1c6 100644 --- a/client/src/components/Draftail/blocks/MediaBlock.scss +++ b/client/src/components/Draftail/blocks/MediaBlock.scss @@ -1,44 +1,44 @@ .MediaBlock { - display: inline-block; - position: relative; - border: 0; - padding: 0; - cursor: pointer; + display: inline-block; + position: relative; + border: 0; + padding: 0; + cursor: pointer; - &__icon-wrapper { - position: absolute; - top: 0; - right: 0; - background: $draftail-editor-chrome; - color: $draftail-editor-chrome-text; - line-height: 1; - padding: $controls-spacing * 2 $controls-spacing * 3; - pointer-events: none; + &__icon-wrapper { + position: absolute; + top: 0; + right: 0; + background: $draftail-editor-chrome; + color: $draftail-editor-chrome-text; + line-height: 1; + padding: $controls-spacing * 2 $controls-spacing * 3; + pointer-events: none; - .icon { - @include svg-icon(); - } + .icon { + @include svg-icon(); } + } - &__icon { - $icon-size: 1.5rem; - width: $icon-size; - height: $icon-size; - font-size: $icon-size; - } + &__icon { + $icon-size: 1.5rem; + width: $icon-size; + height: $icon-size; + font-size: $icon-size; + } - @mixin invalid-image-fallback { - min-width: 256px; - min-height: 50px; - object-fit: contain; - background-color: $color-grey-1; - } + @mixin invalid-image-fallback { + min-width: 256px; + min-height: 50px; + object-fit: contain; + background-color: $color-grey-1; + } - &__img { - @include invalid-image-fallback; - display: block; - width: 256px; - height: auto; - pointer-events: none; - } + &__img { + @include invalid-image-fallback; + display: block; + width: 256px; + height: auto; + pointer-events: none; + } } diff --git a/client/src/components/Draftail/blocks/MediaBlock.test.js b/client/src/components/Draftail/blocks/MediaBlock.test.js index b06fd8e669..a7abc887e1 100644 --- a/client/src/components/Draftail/blocks/MediaBlock.test.js +++ b/client/src/components/Draftail/blocks/MediaBlock.test.js @@ -26,8 +26,8 @@ describe('MediaBlock', () => { }} > Test - </MediaBlock> - ) + </MediaBlock>, + ), ).toMatchSnapshot(); }); @@ -50,8 +50,8 @@ describe('MediaBlock', () => { }} > Test - </MediaBlock> - ) + </MediaBlock>, + ), ).toMatchSnapshot(); }); @@ -88,7 +88,7 @@ describe('MediaBlock', () => { blockProps={blockProps} > <div id="test">Test</div> - </MediaBlock> + </MediaBlock>, ); }); @@ -113,8 +113,7 @@ describe('MediaBlock', () => { wrapper.simulate('click', { target }); expect( - wrapper - .find('Portal > Portal').prop('containerInfo') + wrapper.find('Portal > Portal').prop('containerInfo'), ).toMatchSnapshot(); }); @@ -138,11 +137,9 @@ describe('MediaBlock', () => { wrapper.simulate('click', { target }); - expect( - wrapper - .find('.Tooltip') - .prop('className') - ).toBe('Tooltip Tooltip--left'); + expect(wrapper.find('.Tooltip').prop('className')).toBe( + 'Tooltip Tooltip--left', + ); }); it('tooltip closes', () => { diff --git a/client/src/components/Draftail/decorators/Document.js b/client/src/components/Draftail/decorators/Document.js index 9889f23734..6a760b8e50 100644 --- a/client/src/components/Draftail/decorators/Document.js +++ b/client/src/components/Draftail/decorators/Document.js @@ -10,7 +10,7 @@ import { STRINGS } from '../../../config/wagtailConfig'; const documentIcon = <Icon name="doc-full" />; const missingDocumentIcon = <Icon name="warning" />; -const Document = props => { +const Document = (props) => { const { entityKey, contentState } = props; const data = contentState.getEntity(entityKey).getData(); const url = data.url || null; @@ -25,14 +25,7 @@ const Document = props => { label = data.filename || ''; } - return ( - <TooltipEntity - {...props} - icon={icon} - label={label} - url={url} - /> - ); + return <TooltipEntity {...props} icon={icon} label={label} url={url} />; }; Document.propTypes = { diff --git a/client/src/components/Draftail/decorators/Document.test.js b/client/src/components/Draftail/decorators/Document.test.js index e724ebe1ed..4003e308b4 100644 --- a/client/src/components/Draftail/decorators/Document.test.js +++ b/client/src/components/Draftail/decorators/Document.test.js @@ -14,7 +14,7 @@ describe('Document', () => { url: '/example.pdf', filename: 'example.pdf', }, - } + }, }, blocks: [ { @@ -25,21 +25,23 @@ describe('Document', () => { offset: 0, length: 4, key: 0, - } - ] - } - ] + }, + ], + }, + ], }); - expect(shallow(( - <Document - contentState={content} - entityKey="1" - onEdit={() => {}} - onRemove={() => {}} - > - test - </Document> - ))).toMatchSnapshot(); + expect( + shallow( + <Document + contentState={content} + entityKey="1" + onEdit={() => {}} + onRemove={() => {}} + > + test + </Document>, + ), + ).toMatchSnapshot(); }); it('no data', () => { @@ -47,7 +49,7 @@ describe('Document', () => { entityMap: { 2: { type: 'DOCUMENT', - } + }, }, blocks: [ { @@ -58,20 +60,22 @@ describe('Document', () => { offset: 0, length: 4, key: 0, - } - ] - } - ] + }, + ], + }, + ], }); - expect(shallow(( - <Document - contentState={content} - entityKey="2" - onEdit={() => {}} - onRemove={() => {}} - > - test - </Document> - ))).toMatchSnapshot(); + expect( + shallow( + <Document + contentState={content} + entityKey="2" + onEdit={() => {}} + onRemove={() => {}} + > + test + </Document>, + ), + ).toMatchSnapshot(); }); }); diff --git a/client/src/components/Draftail/decorators/Link.js b/client/src/components/Draftail/decorators/Link.js index 9e22bb5ad9..920cb26ddc 100644 --- a/client/src/components/Draftail/decorators/Link.js +++ b/client/src/components/Draftail/decorators/Link.js @@ -11,9 +11,9 @@ const LINK_ICON = <Icon name="link" />; const BROKEN_LINK_ICON = <Icon name="warning" />; const MAIL_ICON = <Icon name="mail" />; -const getEmailAddress = mailto => mailto.replace('mailto:', '').split('?')[0]; -const getPhoneNumber = tel => tel.replace('tel:', '').split('?')[0]; -const getDomainName = url => url.replace(/(^\w+:|^)\/\//, '').split('/')[0]; +const getEmailAddress = (mailto) => mailto.replace('mailto:', '').split('?')[0]; +const getPhoneNumber = (tel) => tel.replace('tel:', '').split('?')[0]; +const getDomainName = (url) => url.replace(/(^\w+:|^)\/\//, '').split('/')[0]; // Determines how to display the link based on its type: page, mail, anchor or external. export const getLinkAttributes = (data) => { @@ -51,16 +51,11 @@ export const getLinkAttributes = (data) => { /** * Represents a link within the editor's content. */ -const Link = props => { +const Link = (props) => { const { entityKey, contentState } = props; const data = contentState.getEntity(entityKey).getData(); - return ( - <TooltipEntity - {...props} - {...getLinkAttributes(data)} - /> - ); + return <TooltipEntity {...props} {...getLinkAttributes(data)} />; }; Link.propTypes = { diff --git a/client/src/components/Draftail/decorators/Link.test.js b/client/src/components/Draftail/decorators/Link.test.js index 3fceaff4bd..34b05b9ec2 100644 --- a/client/src/components/Draftail/decorators/Link.test.js +++ b/client/src/components/Draftail/decorators/Link.test.js @@ -13,7 +13,7 @@ describe('Link', () => { data: { url: 'http://www.example.com/', }, - } + }, }, blocks: [ { @@ -24,21 +24,23 @@ describe('Link', () => { offset: 0, length: 4, key: 0, - } - ] - } - ] + }, + ], + }, + ], }); - expect(shallow(( - <Link - contentState={content} - entityKey="1" - onEdit={() => {}} - onRemove={() => {}} - > - test - </Link> - ))).toMatchSnapshot(); + expect( + shallow( + <Link + contentState={content} + entityKey="1" + onEdit={() => {}} + onRemove={() => {}} + > + test + </Link>, + ), + ).toMatchSnapshot(); }); describe('getLinkAttributes', () => { @@ -78,7 +80,10 @@ describe('Link', () => { }); it('no data', () => { - expect(getLinkAttributes({})).toMatchObject({ url: null, label: 'Broken link' }); + expect(getLinkAttributes({})).toMatchObject({ + url: null, + label: 'Broken link', + }); }); }); }); diff --git a/client/src/components/Draftail/decorators/TooltipEntity.js b/client/src/components/Draftail/decorators/TooltipEntity.js index debf6a73f5..c67ffee05a 100644 --- a/client/src/components/Draftail/decorators/TooltipEntity.js +++ b/client/src/components/Draftail/decorators/TooltipEntity.js @@ -59,8 +59,14 @@ class TooltipEntity extends Component { this.setState({ showTooltipAt: { container: container, - top: rect.top - containerRect.top - (document.documentElement.scrollTop || document.body.scrollTop), - left: rect.left - containerRect.left - (document.documentElement.scrollLeft || document.body.scrollLeft), + top: + rect.top - + containerRect.top - + (document.documentElement.scrollTop || document.body.scrollTop), + left: + rect.left - + containerRect.left - + (document.documentElement.scrollLeft || document.body.scrollLeft), width: rect.width, height: rect.height, }, @@ -72,12 +78,7 @@ class TooltipEntity extends Component { } render() { - const { - children, - icon, - label, - url, - } = this.props; + const { children, icon, label, url } = this.props; const { showTooltipAt } = this.state; return ( @@ -112,10 +113,7 @@ class TooltipEntity extends Component { </a> ) : null} - <button - className="button Tooltip__button" - onClick={this.onEdit} - > + <button className="button Tooltip__button" onClick={this.onEdit}> Edit </button> diff --git a/client/src/components/Draftail/decorators/TooltipEntity.scss b/client/src/components/Draftail/decorators/TooltipEntity.scss index 6b0b82ddaf..0af7d40abf 100644 --- a/client/src/components/Draftail/decorators/TooltipEntity.scss +++ b/client/src/components/Draftail/decorators/TooltipEntity.scss @@ -1,20 +1,20 @@ $icon-size: 1em; .TooltipEntity { - cursor: pointer; + cursor: pointer; - &:hover { - color: $color-teal; - } + &:hover { + color: $color-teal; + } - .icon-warning { - color: $color-red; - } + .icon-warning { + color: $color-red; + } - .icon { - width: $icon-size; - height: $icon-size; - margin-right: 0.2em; - vertical-align: middle; - } + .icon { + width: $icon-size; + height: $icon-size; + margin-right: 0.2em; + vertical-align: middle; + } } diff --git a/client/src/components/Draftail/decorators/TooltipEntity.test.js b/client/src/components/Draftail/decorators/TooltipEntity.test.js index 2e12ff90bf..89108bdd2d 100644 --- a/client/src/components/Draftail/decorators/TooltipEntity.test.js +++ b/client/src/components/Draftail/decorators/TooltipEntity.test.js @@ -5,57 +5,67 @@ import TooltipEntity from './TooltipEntity'; describe('TooltipEntity', () => { it('works', () => { - expect(shallow(( - <TooltipEntity - entityKey="1" - onEdit={() => {}} - onRemove={() => {}} - icon="#icon-test" - url="https://www.example.com/" - label="www.example.com" - > - test - </TooltipEntity> - ))).toMatchSnapshot(); + expect( + shallow( + <TooltipEntity + entityKey="1" + onEdit={() => {}} + onRemove={() => {}} + icon="#icon-test" + url="https://www.example.com/" + label="www.example.com" + > + test + </TooltipEntity>, + ), + ).toMatchSnapshot(); }); it('shortened label', () => { - expect(shallow(( - <TooltipEntity - entityKey="1" - onEdit={() => {}} - onRemove={() => {}} - icon="#icon-test" - url="https://www.example.com/" - label="www.example.example.example.com" - > - test - </TooltipEntity> - )).setState({ - showTooltipAt: document.createElement('div').getBoundingClientRect(), - }).find('Tooltip a') - .text()).toBe('www.example.example.…'); + expect( + shallow( + <TooltipEntity + entityKey="1" + onEdit={() => {}} + onRemove={() => {}} + icon="#icon-test" + url="https://www.example.com/" + label="www.example.example.example.com" + > + test + </TooltipEntity>, + ) + .setState({ + showTooltipAt: document.createElement('div').getBoundingClientRect(), + }) + .find('Tooltip a') + .text(), + ).toBe('www.example.example.…'); }); it('empty label', () => { - expect(shallow(( - <TooltipEntity - entityKey="1" - onEdit={() => {}} - onRemove={() => {}} - icon="#icon-test" - url="https://www.example.com/" - label="" - > - test - </TooltipEntity> - )).setState({ - showTooltipAt: document.createElement('div').getBoundingClientRect(), - }).find('Tooltip a').length).toBe(0); + expect( + shallow( + <TooltipEntity + entityKey="1" + onEdit={() => {}} + onRemove={() => {}} + icon="#icon-test" + url="https://www.example.com/" + label="" + > + test + </TooltipEntity>, + ) + .setState({ + showTooltipAt: document.createElement('div').getBoundingClientRect(), + }) + .find('Tooltip a').length, + ).toBe(0); }); it('#openTooltip', () => { - const wrapper = shallow(( + const wrapper = shallow( <TooltipEntity entityKey="1" onEdit={() => {}} @@ -65,8 +75,8 @@ describe('TooltipEntity', () => { label="www.example.com" > test - </TooltipEntity> - )); + </TooltipEntity>, + ); const target = document.createElement('div'); target.setAttribute('data-draftail-trigger', true); @@ -81,7 +91,7 @@ describe('TooltipEntity', () => { }); it('#closeTooltip', () => { - const wrapper = shallow(( + const wrapper = shallow( <TooltipEntity entityKey="1" onEdit={() => {}} @@ -91,8 +101,8 @@ describe('TooltipEntity', () => { label="www.example.com" > test - </TooltipEntity> - )); + </TooltipEntity>, + ); wrapper.find('.TooltipEntity').simulate('mouseup', { target: document.createElement('div'), @@ -108,7 +118,7 @@ describe('TooltipEntity', () => { it('#onEdit', () => { const onEdit = jest.fn(); - const wrapper = shallow(( + const wrapper = shallow( <TooltipEntity entityKey="1" onEdit={onEdit} @@ -118,8 +128,8 @@ describe('TooltipEntity', () => { label="www.example.com" > test - </TooltipEntity> - )); + </TooltipEntity>, + ); wrapper.instance().onEdit(new Event('click')); @@ -129,7 +139,7 @@ describe('TooltipEntity', () => { it('#onRemove', () => { const onRemove = jest.fn(); - const wrapper = shallow(( + const wrapper = shallow( <TooltipEntity entityKey="1" onEdit={() => {}} @@ -139,8 +149,8 @@ describe('TooltipEntity', () => { label="www.example.com" > test - </TooltipEntity> - )); + </TooltipEntity>, + ); wrapper.instance().onRemove(new Event('click')); diff --git a/client/src/components/Draftail/index.js b/client/src/components/Draftail/index.js index d9f472c2b6..3879e85f67 100644 --- a/client/src/components/Draftail/index.js +++ b/client/src/components/Draftail/index.js @@ -17,7 +17,7 @@ import { ImageModalWorkflowSource, EmbedModalWorkflowSource, LinkModalWorkflowSource, - DocumentModalWorkflowSource + DocumentModalWorkflowSource, } from './sources/ModalWorkflowSource'; import Tooltip from './Tooltip/Tooltip'; import TooltipEntity from './decorators/TooltipEntity'; @@ -25,7 +25,8 @@ import EditorFallback from './EditorFallback/EditorFallback'; import CommentableEditor from './CommentableEditor/CommentableEditor'; // 1024x1024 SVG path rendering of the "↵" character, that renders badly in MS Edge. -const BR_ICON = 'M.436 633.471l296.897-296.898v241.823h616.586V94.117h109.517v593.796H297.333v242.456z'; +const BR_ICON = + 'M.436 633.471l296.897-296.898v241.823h616.586V94.117h109.517v593.796H297.333v242.456z'; /** * Registry for client-side code of Draftail plugins. @@ -41,7 +42,7 @@ const registerPlugin = (plugin) => { * Wraps a style/block/entity type’s icon with an icon font implementation, * so Draftail can use icon fonts in its toolbar. */ -export const wrapWagtailIcon = type => { +export const wrapWagtailIcon = (type) => { const isIconFont = type.icon && typeof type.icon === 'string'; if (isIconFont) { return Object.assign(type, { @@ -63,7 +64,8 @@ const initEditor = (selector, options, currentScript) => { const context = currentScript ? currentScript.parentNode : document.body; // If the field is not in the current context, look for it in the whole body. // Fallback for sequence.js jQuery eval-ed scripts running in document.head. - const field = context.querySelector(selector) || document.body.querySelector(selector); + const field = + context.querySelector(selector) || document.body.querySelector(selector); const editorWrapper = document.createElement('div'); editorWrapper.className = 'Draftail-Editor__wrapper'; @@ -71,7 +73,7 @@ const initEditor = (selector, options, currentScript) => { field.parentNode.appendChild(editorWrapper); - const serialiseInputValue = rawContentState => { + const serialiseInputValue = (rawContentState) => { field.rawContentState = rawContentState; field.value = JSON.stringify(rawContentState); }; @@ -87,9 +89,11 @@ const initEditor = (selector, options, currentScript) => { return Object.assign({}, plugin, type); }); - const enableHorizontalRule = options.enableHorizontalRule ? { - description: STRINGS.HORIZONTAL_LINE, - } : false; + const enableHorizontalRule = options.enableHorizontalRule + ? { + description: STRINGS.HORIZONTAL_LINE, + } + : false; const rawContentState = JSON.parse(field.value); field.rawContentState = rawContentState; @@ -117,37 +121,40 @@ const initEditor = (selector, options, currentScript) => { blockTypes: blockTypes.map(wrapWagtailIcon), inlineStyles: inlineStyles.map(wrapWagtailIcon), entityTypes, - enableHorizontalRule + enableHorizontalRule, }; const styles = getComputedStyle(document.documentElement); const colors = { standardHighlight: styles.getPropertyValue('--color-primary-light'), overlappingHighlight: styles.getPropertyValue('--color-primary-lighter'), - focusedHighlight: styles.getPropertyValue('--color-primary') + focusedHighlight: styles.getPropertyValue('--color-primary'), }; // If the field has a valid contentpath - ie is not an InlinePanel or under a ListBlock - // and the comments system is initialized then use CommentableEditor, otherwise plain DraftailEditor const contentPath = window.comments?.getContentPath(field) || ''; - const editor = (window.comments?.commentApp && contentPath !== '') ? - <Provider store={window.comments.commentApp.store}> - <CommentableEditor - editorRef={editorRef} - commentApp={window.comments.commentApp} - fieldNode={field.parentNode} - contentPath={contentPath} - colorConfig={colors} - isCommentShortcut={window.comments.isCommentShortcut} - {...sharedProps} - /> - </Provider> - : <DraftailEditor - ref={editorRef} - {...sharedProps} - />; + const editor = + window.comments?.commentApp && contentPath !== '' ? ( + <Provider store={window.comments.commentApp.store}> + <CommentableEditor + editorRef={editorRef} + commentApp={window.comments.commentApp} + fieldNode={field.parentNode} + contentPath={contentPath} + colorConfig={colors} + isCommentShortcut={window.comments.isCommentShortcut} + {...sharedProps} + /> + </Provider> + ) : ( + <DraftailEditor ref={editorRef} {...sharedProps} /> + ); - ReactDOM.render(<EditorFallback field={field}>{editor}</EditorFallback>, editorWrapper); + ReactDOM.render( + <EditorFallback field={field}>{editor}</EditorFallback>, + editorWrapper, + ); }; export default { diff --git a/client/src/components/Draftail/index.test.js b/client/src/components/Draftail/index.test.js index 03f51a19f4..5225ec6b22 100644 --- a/client/src/components/Draftail/index.test.js +++ b/client/src/components/Draftail/index.test.js @@ -48,9 +48,7 @@ describe('Draftail', () => { }); draftail.initEditor('#test', { - entityTypes: [ - { type: 'IMAGE' }, - ], + entityTypes: [{ type: 'IMAGE' }], enableHorizontalRule: true, }); @@ -59,7 +57,8 @@ describe('Draftail', () => { describe('selector conflicts', () => { it('fails to instantiate on the right field', () => { - document.body.innerHTML = '<meta name="description" content="null" /><input name="description" value="null" />'; + document.body.innerHTML = + '<meta name="description" content="null" /><input name="description" value="null" />'; expect(() => { draftail.initEditor('[name="description"]', {}, document.body); @@ -78,7 +77,9 @@ describe('Draftail', () => { draftail.initEditor('#description', {}); - expect(document.querySelector('[name="last"]').draftailEditor).not.toBeDefined(); + expect( + document.querySelector('[name="last"]').draftailEditor, + ).not.toBeDefined(); }); it('has no conflict when currentScript is used', () => { @@ -91,9 +92,15 @@ describe('Draftail', () => { </div> `; - draftail.initEditor('#description', {}, document.querySelector('[data-draftail-script]')); + draftail.initEditor( + '#description', + {}, + document.querySelector('[data-draftail-script]'), + ); - expect(document.querySelector('[name="last"]').draftailEditor).toBeDefined(); + expect( + document.querySelector('[name="last"]').draftailEditor, + ).toBeDefined(); }); it('uses fallback document.body when currentScript context is wrong', () => { @@ -103,9 +110,15 @@ describe('Draftail', () => { <div><script data-draftail-script></script></div> `; - draftail.initEditor('#description', {}, document.querySelector('[data-draftail-script]')); + draftail.initEditor( + '#description', + {}, + document.querySelector('[data-draftail-script]'), + ); - expect(document.querySelector('#description').draftailEditor).toBeDefined(); + expect( + document.querySelector('#description').draftailEditor, + ).toBeDefined(); }); }); }); @@ -145,7 +158,8 @@ describe('Draftail', () => { it('#ImageBlock', () => expect(ImageBlock).toBeDefined()); it('#EmbedBlock', () => expect(EmbedBlock).toBeDefined()); - it('#ModalWorkflowSource', () => expect(draftail.ModalWorkflowSource).toBeDefined()); + it('#ModalWorkflowSource', () => + expect(draftail.ModalWorkflowSource).toBeDefined()); it('#Tooltip', () => expect(draftail.Tooltip).toBeDefined()); it('#TooltipEntity', () => expect(draftail.TooltipEntity).toBeDefined()); }); diff --git a/client/src/components/Draftail/sources/ModalWorkflowSource.js b/client/src/components/Draftail/sources/ModalWorkflowSource.js index a71ccae32a..4939fdc9bc 100644 --- a/client/src/components/Draftail/sources/ModalWorkflowSource.js +++ b/client/src/components/Draftail/sources/ModalWorkflowSource.js @@ -31,22 +31,28 @@ class ModalWorkflowSource extends Component { // eslint-disable-next-line @typescript-eslint/no-unused-vars getChooserConfig(entity, selectedText) { - throw new TypeError('Subclasses of ModalWorkflowSource must implement getChooserConfig'); + throw new TypeError( + 'Subclasses of ModalWorkflowSource must implement getChooserConfig', + ); } // eslint-disable-next-line @typescript-eslint/no-unused-vars filterEntityData(data) { - throw new TypeError('Subclasses of ModalWorkflowSource must implement filterEntityData'); + throw new TypeError( + 'Subclasses of ModalWorkflowSource must implement filterEntityData', + ); } componentDidMount() { const { onClose, entity, editorState } = this.props; const selectedText = getSelectionText(editorState); - const { url, urlParams, onload, responses } = this.getChooserConfig(entity, selectedText); + const { url, urlParams, onload, responses } = this.getChooserConfig( + entity, + selectedText, + ); $(document.body).on('hidden.bs.modal', this.onClose); - this.workflow = global.ModalWorkflow({ url, urlParams, @@ -67,7 +73,8 @@ class ModalWorkflowSource extends Component { } onChosen(data) { - const { editorState, entity, entityKey, entityType, onComplete } = this.props; + const { editorState, entity, entityKey, entityType, onComplete } = + this.props; const content = editorState.getCurrentContent(); const selection = editorState.getSelection(); const entityData = this.filterEntityData(data); @@ -79,24 +86,51 @@ class ModalWorkflowSource extends Component { // Replace the data for the currently selected block const blockKey = selection.getAnchorKey(); const block = content.getBlockForKey(blockKey); - nextState = DraftUtils.updateBlockEntity(editorState, block, entityData); + nextState = DraftUtils.updateBlockEntity( + editorState, + block, + entityData, + ); } else { // Add new entity if there is none selected - const contentWithEntity = content.createEntity(entityType.type, mutability, entityData); + const contentWithEntity = content.createEntity( + entityType.type, + mutability, + entityData, + ); const newEntityKey = contentWithEntity.getLastCreatedEntityKey(); - nextState = AtomicBlockUtils.insertAtomicBlock(editorState, newEntityKey, ' '); + nextState = AtomicBlockUtils.insertAtomicBlock( + editorState, + newEntityKey, + ' ', + ); } } else { - const contentWithEntity = content.createEntity(entityType.type, mutability, entityData); + const contentWithEntity = content.createEntity( + entityType.type, + mutability, + entityData, + ); const newEntityKey = contentWithEntity.getLastCreatedEntityKey(); // Replace text if the chooser demands it, or if there is no selected text in the first place. - const shouldReplaceText = data.prefer_this_title_as_link_text || selection.isCollapsed(); + const shouldReplaceText = + data.prefer_this_title_as_link_text || selection.isCollapsed(); if (shouldReplaceText) { // If there is a title attribute, use it. Otherwise we inject the URL. const newText = data.title || data.url; - const newContent = Modifier.replaceText(content, selection, newText, null, newEntityKey); - nextState = EditorState.push(editorState, newContent, 'insert-characters'); + const newContent = Modifier.replaceText( + content, + selection, + newText, + null, + newEntityKey, + ); + nextState = EditorState.push( + editorState, + newContent, + 'insert-characters', + ); } else { nextState = RichUtils.toggleLink(editorState, selection, newEntityKey); } @@ -185,7 +219,7 @@ class EmbedModalWorkflowSource extends ModalWorkflowSource { responses: { // Discard the first parameter (HTML) to only transmit the data. embedChosen: (_, data) => this.onChosen(data), - } + }, }; } @@ -243,7 +277,7 @@ class LinkModalWorkflowSource extends ModalWorkflowSource { onload: global.PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS, responses: { pageChosen: this.onChosen, - } + }, }; } diff --git a/client/src/components/Draftail/sources/ModalWorkflowSource.test.js b/client/src/components/Draftail/sources/ModalWorkflowSource.test.js index b287e3b166..12f4d2117c 100644 --- a/client/src/components/Draftail/sources/ModalWorkflowSource.test.js +++ b/client/src/components/Draftail/sources/ModalWorkflowSource.test.js @@ -5,11 +5,17 @@ import { ImageModalWorkflowSource, EmbedModalWorkflowSource, LinkModalWorkflowSource, - DocumentModalWorkflowSource + DocumentModalWorkflowSource, } from './ModalWorkflowSource'; import * as DraftUtils from '../DraftUtils'; import { DraftUtils as DraftailUtils } from 'draftail'; -import { EditorState, convertFromRaw, AtomicBlockUtils, RichUtils, Modifier } from 'draft-js'; +import { + EditorState, + convertFromRaw, + AtomicBlockUtils, + RichUtils, + Modifier, +} from 'draft-js'; global.ModalWorkflow = () => {}; @@ -24,15 +30,17 @@ describe('ModalWorkflowSource', () => { }); it('works', () => { - expect(shallow(( - <ImageModalWorkflowSource - editorState={{}} - entityType={{}} - entity={null} - onComplete={() => {}} - onClose={() => {}} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <ImageModalWorkflowSource + editorState={{}} + entityType={{}} + entity={null} + onComplete={() => {}} + onClose={() => {}} + />, + ), + ).toMatchSnapshot(); }); describe('#getChooserConfig', () => { @@ -94,27 +102,47 @@ describe('ModalWorkflowSource', () => { }); it('page', () => { - expect(linkSource.getChooserConfig({ - getData: () => ({ id: 2, parentId: 1 }) - }, '')).toMatchSnapshot(); + expect( + linkSource.getChooserConfig( + { + getData: () => ({ id: 2, parentId: 1 }), + }, + '', + ), + ).toMatchSnapshot(); }); it('root page', () => { - expect(linkSource.getChooserConfig({ - getData: () => ({ id: 1, parentId: null }) - }, '')).toMatchSnapshot(); + expect( + linkSource.getChooserConfig( + { + getData: () => ({ id: 1, parentId: null }), + }, + '', + ), + ).toMatchSnapshot(); }); it('mail', () => { - expect(linkSource.getChooserConfig({ - getData: () => ({ url: 'mailto:test@example.com' }) - }, '')).toMatchSnapshot(); + expect( + linkSource.getChooserConfig( + { + getData: () => ({ url: 'mailto:test@example.com' }), + }, + '', + ), + ).toMatchSnapshot(); }); it('external', () => { - expect(linkSource.getChooserConfig({ - getData: () => ({ url: 'https://www.example.com/' }) - }, '')).toMatchSnapshot(); + expect( + linkSource.getChooserConfig( + { + getData: () => ({ url: 'https://www.example.com/' }), + }, + '', + ), + ).toMatchSnapshot(); }); }); }); @@ -122,90 +150,104 @@ describe('ModalWorkflowSource', () => { describe('#filterEntityData', () => { const imageSource = new ImageModalWorkflowSource(); it('IMAGE', () => { - expect(imageSource.filterEntityData({ - id: 53, - title: 'Test', - alt: 'Test', - class: 'richtext-image right', - edit_link: '/admin/images/53/', - format: 'right', - preview: { - url: '/media/images/test.width-500.jpg', - } - })).toMatchSnapshot(); + expect( + imageSource.filterEntityData({ + id: 53, + title: 'Test', + alt: 'Test', + class: 'richtext-image right', + edit_link: '/admin/images/53/', + format: 'right', + preview: { + url: '/media/images/test.width-500.jpg', + }, + }), + ).toMatchSnapshot(); }); const embedSource = new EmbedModalWorkflowSource(); it('EMBED', () => { - expect(embedSource.filterEntityData({ - authorName: 'Test', - embedType: 'video', - providerName: 'YouTube', - thumbnail: 'https://i.ytimg.com/vi/pSlVtxLOYiM/hqdefault.jpg', - title: 'Test', - url: 'https://www.youtube.com/watch?v=pSlVtxLOYiM', - })).toMatchSnapshot(); + expect( + embedSource.filterEntityData({ + authorName: 'Test', + embedType: 'video', + providerName: 'YouTube', + thumbnail: 'https://i.ytimg.com/vi/pSlVtxLOYiM/hqdefault.jpg', + title: 'Test', + url: 'https://www.youtube.com/watch?v=pSlVtxLOYiM', + }), + ).toMatchSnapshot(); }); const documentSource = new DocumentModalWorkflowSource(); it('DOCUMENT', () => { - expect(documentSource.filterEntityData({ - edit_link: '/admin/documents/edit/1/', - filename: 'test.pdf', - id: 1, - title: 'Test', - url: '/documents/1/test.pdf', - })).toMatchSnapshot(); + expect( + documentSource.filterEntityData({ + edit_link: '/admin/documents/edit/1/', + filename: 'test.pdf', + id: 1, + title: 'Test', + url: '/documents/1/test.pdf', + }), + ).toMatchSnapshot(); }); const linkSource = new LinkModalWorkflowSource(); describe('LINK', () => { it('page', () => { - expect(linkSource.filterEntityData({ - id: 60, - parentId: 1, - url: '/', - editUrl: '/admin/pages/60/edit/', - title: 'Welcome to the Wagtail Bakery!', - })).toMatchSnapshot(); + expect( + linkSource.filterEntityData({ + id: 60, + parentId: 1, + url: '/', + editUrl: '/admin/pages/60/edit/', + title: 'Welcome to the Wagtail Bakery!', + }), + ).toMatchSnapshot(); }); it('mail', () => { - expect(linkSource.filterEntityData({ - prefer_this_title_as_link_text: false, - title: 'test@example.com', - url: 'mailto:test@example.com', - })).toMatchSnapshot(); + expect( + linkSource.filterEntityData({ + prefer_this_title_as_link_text: false, + title: 'test@example.com', + url: 'mailto:test@example.com', + }), + ).toMatchSnapshot(); }); it('anchor', () => { - expect(linkSource.filterEntityData({ - prefer_this_title_as_link_text: false, - title: 'testanchor', - url: '#testanchor', - })).toMatchSnapshot(); + expect( + linkSource.filterEntityData({ + prefer_this_title_as_link_text: false, + title: 'testanchor', + url: '#testanchor', + }), + ).toMatchSnapshot(); }); it('external', () => { - expect(linkSource.filterEntityData({ - prefer_this_title_as_link_text: false, - title: 'https://www.example.com/', - url: 'https://www.example.com/', - })).toMatchSnapshot(); + expect( + linkSource.filterEntityData({ + prefer_this_title_as_link_text: false, + title: 'https://www.example.com/', + url: 'https://www.example.com/', + }), + ).toMatchSnapshot(); }); }); }); it('#componentDidMount', () => { - const wrapper = shallow(( + const wrapper = shallow( <EmbedModalWorkflowSource editorState={EditorState.createEmpty()} entityType={{}} entity={null} onComplete={() => {}} onClose={() => {}} - /> - )); + />, + ); wrapper.instance().onChosen = jest.fn(); @@ -222,15 +264,15 @@ describe('ModalWorkflowSource', () => { window.alert = jest.fn(); const onClose = jest.fn(); - const wrapper = shallow(( + const wrapper = shallow( <EmbedModalWorkflowSource editorState={EditorState.createEmpty()} entityType={{}} entity={null} onComplete={() => {}} onClose={onClose} - /> - )); + />, + ); wrapper.instance().componentDidMount(); @@ -243,15 +285,15 @@ describe('ModalWorkflowSource', () => { }); it('#componentWillUnmount', () => { - const wrapper = shallow(( + const wrapper = shallow( <EmbedModalWorkflowSource editorState={EditorState.createEmpty()} entityType={{}} entity={null} onComplete={() => {}} onClose={() => {}} - /> - )); + />, + ); wrapper.instance().componentWillUnmount(); @@ -265,29 +307,31 @@ describe('ModalWorkflowSource', () => { const onComplete = jest.fn(); const close = jest.fn(); - let editorState = EditorState.createWithContent(convertFromRaw({ - entityMap: {}, - blocks: [ - { - key: 'a', - text: 'test', - } - ] - })); + let editorState = EditorState.createWithContent( + convertFromRaw({ + entityMap: {}, + blocks: [ + { + key: 'a', + text: 'test', + }, + ], + }), + ); let selection = editorState.getSelection(); selection = selection.merge({ focusOffset: 4, }); editorState = EditorState.acceptSelection(editorState, selection); - const wrapper = shallow(( + const wrapper = shallow( <LinkModalWorkflowSource editorState={editorState} entityType={{}} entity={null} onComplete={onComplete} onClose={() => {}} - /> - )); + />, + ); wrapper.instance().workflow = { close }; wrapper.instance().onChosen({}); @@ -305,21 +349,23 @@ describe('ModalWorkflowSource', () => { const onComplete = jest.fn(); const close = jest.fn(); - let editorState = EditorState.createWithContent(convertFromRaw({ - entityMap: {}, - blocks: [ - { - key: 'a', - text: 'test', - } - ] - })); + let editorState = EditorState.createWithContent( + convertFromRaw({ + entityMap: {}, + blocks: [ + { + key: 'a', + text: 'test', + }, + ], + }), + ); let selection = editorState.getSelection(); selection = selection.merge({ focusOffset: 4, }); editorState = EditorState.acceptSelection(editorState, selection); - const wrapper = shallow(( + const wrapper = shallow( <LinkModalWorkflowSource editorState={editorState} entityType={{ @@ -328,8 +374,8 @@ describe('ModalWorkflowSource', () => { entity={null} onComplete={onComplete} onClose={() => {}} - /> - )); + />, + ); wrapper.instance().workflow = { close }; wrapper.instance().onChosen({}); @@ -347,30 +393,32 @@ describe('ModalWorkflowSource', () => { const close = jest.fn(); const entity = { getData: () => ({ id: 1, format: 'left', alt: 'alt' }) }; - let editorState = EditorState.createWithContent(convertFromRaw({ - blocks: [ - { - key: 'a', - text: ' ', - type: 'atomic', - entityRanges: [{ offset: 0, length: 1, key: 'first' }], - data: {}, - } - ], - entityMap: { - first: { - type: 'IMAGE', - mutability: 'IMMUTABLE', - data: {}, - } - } - })); + let editorState = EditorState.createWithContent( + convertFromRaw({ + blocks: [ + { + key: 'a', + text: ' ', + type: 'atomic', + entityRanges: [{ offset: 0, length: 1, key: 'first' }], + data: {}, + }, + ], + entityMap: { + first: { + type: 'IMAGE', + mutability: 'IMMUTABLE', + data: {}, + }, + }, + }), + ); let selection = editorState.getSelection(); selection = selection.merge({ anchorKey: 'a', }); editorState = EditorState.acceptSelection(editorState, selection); - const wrapper = shallow(( + const wrapper = shallow( <ImageModalWorkflowSource editorState={editorState} entityType={{ @@ -380,11 +428,16 @@ describe('ModalWorkflowSource', () => { entityKey={'first'} onComplete={onComplete} onClose={() => {}} - /> - )); + />, + ); wrapper.instance().workflow = { close }; - wrapper.instance().onChosen({ id: 2, preview: { url: '/foo' }, alt: 'new image', format: 'left' }); + wrapper.instance().onChosen({ + id: 2, + preview: { url: '/foo' }, + alt: 'new image', + format: 'left', + }); expect(onComplete).toHaveBeenCalled(); expect(DraftailUtils.updateBlockEntity).toHaveBeenCalled(); @@ -399,28 +452,30 @@ describe('ModalWorkflowSource', () => { const onComplete = jest.fn(); const close = jest.fn(); - let editorState = EditorState.createWithContent(convertFromRaw({ - entityMap: {}, - blocks: [ - { - key: 'a', - text: 'test', - } - ] - })); + let editorState = EditorState.createWithContent( + convertFromRaw({ + entityMap: {}, + blocks: [ + { + key: 'a', + text: 'test', + }, + ], + }), + ); let selection = editorState.getSelection(); selection = selection.merge({ focusOffset: 4, }); editorState = EditorState.acceptSelection(editorState, selection); - const wrapper = shallow(( + const wrapper = shallow( <LinkModalWorkflowSource editorState={editorState} entityType={{}} onComplete={onComplete} onClose={() => {}} - /> - )); + />, + ); wrapper.instance().workflow = { close }; wrapper.instance().onChosen({ @@ -438,15 +493,15 @@ describe('ModalWorkflowSource', () => { it('#onClose', () => { const onClose = jest.fn(); - const wrapper = shallow(( + const wrapper = shallow( <LinkModalWorkflowSource editorState={EditorState.createEmpty()} entityType={{}} entity={null} onComplete={() => {}} onClose={onClose} - /> - )); + />, + ); wrapper.instance().onClose({ preventDefault: () => {}, diff --git a/client/src/components/Explorer/Explorer.scss b/client/src/components/Explorer/Explorer.scss index d1fc7cbbcf..c897bf19e6 100644 --- a/client/src/components/Explorer/Explorer.scss +++ b/client/src/components/Explorer/Explorer.scss @@ -5,228 +5,228 @@ $c-explorer-secondary: #a5a5a5; $c-explorer-easing: cubic-bezier(0.075, 0.82, 0.165, 1); $menu-footer-height: 50px; -@use "sass:map"; +@use 'sass:map'; @import 'ExplorerItem'; .explorer__wrapper, .explorer__wrapper * { - box-sizing: border-box; + box-sizing: border-box; } .explorer__wrapper { - display: flex; - flex: 1; + display: flex; + flex: 1; } .explorer { - width: 100%; - display: flex; - flex-direction: column; + width: 100%; + display: flex; + flex-direction: column; - @include media-breakpoint-up(sm) { - width: 485px; - height: 100vh; - position: fixed; - z-index: 500; - left: $menu-width; - } + @include media-breakpoint-up(sm) { + width: 485px; + height: 100vh; + position: fixed; + z-index: 500; + left: $menu-width; + } - *:focus { - @include show-focus-outline-inside; - } + *:focus { + @include show-focus-outline-inside; + } } .c-explorer { - flex: 1; - position: relative; - overflow: hidden; - background: $c-explorer-bg; + flex: 1; + position: relative; + overflow: hidden; + background: $c-explorer-bg; - @include media-breakpoint-up(sm) { - box-shadow: 2px 2px 5px $c-explorer-bg-active; - } + @include media-breakpoint-up(sm) { + box-shadow: 2px 2px 5px $c-explorer-bg-active; + } } .c-explorer > .c-transition-group { - display: flex; - flex-direction: column; - height: 100%; - z-index: 150; + display: flex; + flex-direction: column; + height: 100%; + z-index: 150; } .c-explorer__close { - padding: 1em; + padding: 1em; + color: $c-explorer-secondary; + border-bottom: 1px solid rgba(200, 200, 200, 0.1); + cursor: pointer; + display: none; + + &:focus { + background-color: $c-explorer-bg-active; + color: $color-white; + } + + // Overrides for default link hover. + &:hover { color: $c-explorer-secondary; - border-bottom: 1px solid rgba(200, 200, 200, 0.1); - cursor: pointer; - display: none; + } - &:focus { - background-color: $c-explorer-bg-active; - color: $color-white; - } - - // Overrides for default link hover. - &:hover { - color: $c-explorer-secondary; - } - - @include media-breakpoint-down(xs) { - .explorer-open & { - display: block; - } + @include media-breakpoint-down(xs) { + .explorer-open & { + display: block; } + } } .c-explorer__drawer { - flex: 1; - overflow-y: auto; - -webkit-overflow-scrolling: touch; + flex: 1; + overflow-y: auto; + -webkit-overflow-scrolling: touch; } .c-explorer__header { - display: block; - background-color: $c-explorer-bg-dark; - border-bottom: 1px solid $c-explorer-bg-dark; - color: $color-white; + display: block; + background-color: $c-explorer-bg-dark; + border-bottom: 1px solid $c-explorer-bg-dark; + color: $color-white; } .c-explorer__header__title { - color: inherit; + color: inherit; - &:focus { - background-color: $c-explorer-bg-active; - color: $color-white; - } + &:focus { + background-color: $c-explorer-bg-active; + color: $color-white; + } - // Overrides for default link hover. - &:hover { - color: $color-white; - } + // Overrides for default link hover. + &:hover { + color: $color-white; + } - @include hover { - background-color: $c-explorer-bg-active; - } + @include hover { + background-color: $c-explorer-bg-active; + } } .c-explorer__header__title__inner { - width: 70%; - float: left; - padding: 1em 0.75em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + width: 70%; + float: left; + padding: 1em 0.75em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; - .icon { - color: $c-explorer-secondary; - margin-right: 0.25rem; - font-size: 1rem; - } + .icon { + color: $c-explorer-secondary; + margin-right: 0.25rem; + font-size: 1rem; + } - .icon--explorer-header { - width: 1.25em; - height: 1.25em; - margin-right: 0.25rem; - vertical-align: text-top; - color: $c-explorer-secondary; - } + .icon--explorer-header { + width: 1.25em; + height: 1.25em; + margin-right: 0.25rem; + vertical-align: text-top; + color: $c-explorer-secondary; + } - @include media-breakpoint-up(sm) { - padding: 1em 1.5em; - } + @include media-breakpoint-up(sm) { + padding: 1em 1.5em; + } } .c-explorer__header__select { - $margin: 10px; - position: relative; + $margin: 10px; + position: relative; - > select { - width: calc(30% - #{$margin * 2}); - height: calc(100% - #{$margin * 2}); - margin-top: $margin; - margin-right: $margin; - float: right; - padding: 0; - padding-left: 10px; + > select { + width: calc(30% - #{$margin * 2}); + height: calc(100% - #{$margin * 2}); + margin-top: $margin; + margin-right: $margin; + float: right; + padding: 0; + padding-left: 10px; - background-color: $c-explorer-bg-dark; - border-radius: 0; - border-color: #4c4e4d; - color: $color-white; + background-color: $c-explorer-bg-dark; + border-radius: 0; + border-color: #4c4e4d; + color: $color-white; - &:disabled { - border: 0; - } - - &:hover:enabled { - cursor: pointer; - } - - &:hover:disabled { - color: inherit; - background-color: inherit; - cursor: inherit; - } - - // Reset the arrow on `<select>`s in IE10+. - &::-ms-expand { - display: none; - } + &:disabled { + border: 0; } - // Add select arrow back on browsers where native ui has been removed - > span:after { - z-index: 0; - position: absolute; - right: $margin; - top: $margin + 3px; - bottom: 0; - width: 2em; - font-family: $font-wagtail-icons; - content: map.get($icons, 'arrow-down'); - text-align: center; - font-size: 1.2em; - pointer-events: none; - color: $color-grey-3; - - .ie & { - display: none; - } + &:hover:enabled { + cursor: pointer; } + + &:hover:disabled { + color: inherit; + background-color: inherit; + cursor: inherit; + } + + // Reset the arrow on `<select>`s in IE10+. + &::-ms-expand { + display: none; + } + } + + // Add select arrow back on browsers where native ui has been removed + > span:after { + z-index: 0; + position: absolute; + right: $margin; + top: $margin + 3px; + bottom: 0; + width: 2em; + font-family: $font-wagtail-icons; + content: map.get($icons, 'arrow-down'); + text-align: center; + font-size: 1.2em; + pointer-events: none; + color: $color-grey-3; + + .ie & { + display: none; + } + } } .c-explorer__placeholder { - padding: 1em; - color: $color-white; + padding: 1em; + color: $color-white; - @include media-breakpoint-up(sm) { - padding: 1em 1.75em; - } + @include media-breakpoint-up(sm) { + padding: 1em 1.75em; + } } .c-explorer__see-more { - display: block; - padding: 1em; - background: rgba(0, 0, 0, 0.3); + display: block; + padding: 1em; + background: rgba(0, 0, 0, 0.3); + color: $color-white; + + &:focus { + color: $c-explorer-secondary; + background: $c-explorer-bg-active; + } + + // Overrides for default link hover. + &:hover { color: $color-white; + } - &:focus { - color: $c-explorer-secondary; - background: $c-explorer-bg-active; - } + @include hover { + background: $c-explorer-bg-active; + } - // Overrides for default link hover. - &:hover { - color: $color-white; - } - - @include hover { - background: $c-explorer-bg-active; - } - - @include media-breakpoint-up(sm) { - padding: 1em 1.75em; - height: $menu-footer-height; - } + @include media-breakpoint-up(sm) { + padding: 1em 1.75em; + height: $menu-footer-height; + } } diff --git a/client/src/components/Explorer/Explorer.test.js b/client/src/components/Explorer/Explorer.test.js index 17244cbd95..6becc771e3 100644 --- a/client/src/components/Explorer/Explorer.test.js +++ b/client/src/components/Explorer/Explorer.test.js @@ -22,13 +22,23 @@ describe('Explorer', () => { it('renders', () => { expect(shallow(<Explorer store={store} />).dive()).toMatchSnapshot(); - expect(shallow(<Provider store={store}><Explorer /></Provider>).dive()).toMatchSnapshot(); + expect( + shallow( + <Provider store={store}> + <Explorer /> + </Provider>, + ).dive(), + ).toMatchSnapshot(); }); it('visible', () => { store.dispatch(actions.toggleExplorer(1)); expect(shallow(<Explorer store={store} />).dive()).toMatchSnapshot(); - expect(shallow(<Explorer store={store} />).dive().dive()).toMatchSnapshot(); + expect( + shallow(<Explorer store={store} />) + .dive() + .dive(), + ).toMatchSnapshot(); }); describe('actions', () => { diff --git a/client/src/components/Explorer/Explorer.tsx b/client/src/components/Explorer/Explorer.tsx index 452a9bfd4c..3ae19afb11 100644 --- a/client/src/components/Explorer/Explorer.tsx +++ b/client/src/components/Explorer/Explorer.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { connect } from 'react-redux'; @@ -11,9 +9,9 @@ import ExplorerPanel from './ExplorerPanel'; interface ExplorerProps { isVisible: boolean; - depth: number, - currentPageId: number | null, - nodes: NodeState, + depth: number; + currentPageId: number | null; + nodes: NodeState; onClose(): void; gotoPage(id: number, transition: number): void; } @@ -25,15 +23,16 @@ const Explorer: React.FunctionComponent<ExplorerProps> = ({ nodes, gotoPage, onClose, -}) => ((isVisible && currentPageId) ? ( - <ExplorerPanel - depth={depth} - page={nodes[currentPageId]} - nodes={nodes} - gotoPage={gotoPage} - onClose={onClose} - /> -) : null); +}) => + isVisible && currentPageId ? ( + <ExplorerPanel + depth={depth} + page={nodes[currentPageId]} + nodes={nodes} + gotoPage={gotoPage} + onClose={onClose} + /> + ) : null; const mapStateToProps = (state: State) => ({ isVisible: state.explorer.isVisible, @@ -43,7 +42,8 @@ const mapStateToProps = (state: State) => ({ }); const mapDispatchToProps = (dispatch) => ({ - gotoPage: (id: number, transition: number) => dispatch(actions.gotoPage(id, transition)), + gotoPage: (id: number, transition: number) => + dispatch(actions.gotoPage(id, transition)), onClose: () => dispatch(actions.closeExplorer()), }); diff --git a/client/src/components/Explorer/ExplorerHeader.test.js b/client/src/components/Explorer/ExplorerHeader.test.js index 2c76d392ce..f0aa4894f7 100644 --- a/client/src/components/Explorer/ExplorerHeader.test.js +++ b/client/src/components/Explorer/ExplorerHeader.test.js @@ -7,9 +7,9 @@ const mockProps = { page: { meta: { parent: { - id: 1 - } - } + id: 1, + }, + }, }, depth: 2, onClick: jest.fn(), @@ -25,12 +25,21 @@ describe('ExplorerHeader', () => { }); it('#depth at root', () => { - expect(shallow(<ExplorerHeader {...mockProps} depth={0} />)).toMatchSnapshot(); + expect( + shallow(<ExplorerHeader {...mockProps} depth={0} />), + ).toMatchSnapshot(); }); it('#page', () => { const wrapper = shallow( - <ExplorerHeader {...mockProps} page={{ id: 'a', admin_display_title: 'test', meta: { parent: { id: 1 } } }} /> + <ExplorerHeader + {...mockProps} + page={{ + id: 'a', + admin_display_title: 'test', + meta: { parent: { id: 1 } }, + }} + />, ); expect(wrapper).toMatchSnapshot(); }); diff --git a/client/src/components/Explorer/ExplorerHeader.tsx b/client/src/components/Explorer/ExplorerHeader.tsx index 3479d3f9f6..066c00c023 100644 --- a/client/src/components/Explorer/ExplorerHeader.tsx +++ b/client/src/components/Explorer/ExplorerHeader.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { ADMIN_URLS, STRINGS } from '../../config/wagtailConfig'; @@ -13,11 +11,20 @@ interface SelectLocaleProps { gotoPage(id: number, transition: number): void; } -const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({ locale, translations, gotoPage }) => { - const options = wagtailConfig.LOCALES - .filter(({ code }) => code === locale || translations.get(code)) - /* eslint-disable-next-line camelcase */ - .map(({ code, display_name }) => <option key={code} value={code}>{display_name}</option>); +const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({ + locale, + translations, + gotoPage, +}) => { + /* eslint-disable camelcase */ + const options = wagtailConfig.LOCALES.filter( + ({ code }) => code === locale || translations.get(code), + ).map(({ code, display_name }) => ( + <option key={code} value={code}> + {display_name} + </option> + )); + /* eslint-enable camelcase */ const onChange = (e) => { e.preventDefault(); @@ -29,7 +36,9 @@ const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({ locale, tran return ( <div className="c-explorer__header__select"> - <select value={locale} onChange={onChange} disabled={options.length < 2}>{options}</select> + <select value={locale} onChange={onChange} disabled={options.length < 2}> + {options} + </select> <span /> </div> ); @@ -38,7 +47,7 @@ const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({ locale, tran interface ExplorerHeaderProps { page: PageState; depth: number; - onClick(e: any): void + onClick(e: any): void; gotoPage(id: number, transition: number): void; } @@ -46,7 +55,12 @@ interface ExplorerHeaderProps { * The bar at the top of the explorer, displaying the current level * and allowing access back to the parent level. */ -const ExplorerHeader: React.FunctionComponent<ExplorerHeaderProps> = ({ page, depth, onClick, gotoPage }) => { +const ExplorerHeader: React.FunctionComponent<ExplorerHeaderProps> = ({ + page, + depth, + onClick, + gotoPage, +}) => { const isRoot = depth === 0; const isSiteRoot = page.id === 0; @@ -65,10 +79,16 @@ const ExplorerHeader: React.FunctionComponent<ExplorerHeaderProps> = ({ page, de <span>{page.admin_display_title || STRINGS.PAGES}</span> </div> </Button> - {!isSiteRoot && page.meta.locale && - page.translations && - page.translations.size > 0 && - <SelectLocale locale={page.meta.locale} translations={page.translations} gotoPage={gotoPage} />} + {!isSiteRoot && + page.meta.locale && + page.translations && + page.translations.size > 0 && ( + <SelectLocale + locale={page.meta.locale} + translations={page.translations} + gotoPage={gotoPage} + /> + )} </div> ); }; diff --git a/client/src/components/Explorer/ExplorerItem.scss b/client/src/components/Explorer/ExplorerItem.scss index 5eb6ed37b9..9a5c3947ca 100644 --- a/client/src/components/Explorer/ExplorerItem.scss +++ b/client/src/components/Explorer/ExplorerItem.scss @@ -1,93 +1,93 @@ .c-explorer__item { - display: flex; - flex-flow: row nowrap; - border-bottom: 1px solid $c-explorer-bg-dark; + display: flex; + flex-flow: row nowrap; + border-bottom: 1px solid $c-explorer-bg-dark; } .c-explorer__item__link { - display: inline-flex; - align-items: center; - flex-grow: 1; - padding: 1.45em 1em; - cursor: pointer; + display: inline-flex; + align-items: center; + flex-grow: 1; + padding: 1.45em 1em; + cursor: pointer; - &:focus { - background: $c-explorer-bg-active; - color: $color-white; - } + &:focus { + background: $c-explorer-bg-active; + color: $color-white; + } - // Overrides for default link hover. - &:hover { - color: $color-white; - } + // Overrides for default link hover. + &:hover { + color: $color-white; + } - @include hover { - background: $c-explorer-bg-active; - } + @include hover { + background: $c-explorer-bg-active; + } - @include media-breakpoint-up(sm) { - padding: 1.45em 1.75em; - } + @include media-breakpoint-up(sm) { + padding: 1.45em 1.75em; + } } .c-explorer__item__link .icon { - width: 2em; - height: 2em; - color: $c-explorer-secondary; - margin-right: 0.75rem; + width: 2em; + height: 2em; + color: $c-explorer-secondary; + margin-right: 0.75rem; } .c-explorer__item__title { - margin: 0; - color: $color-white; - display: inline-block; + margin: 0; + color: $color-white; + display: inline-block; } .c-explorer__item__action { - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 50px; - padding: 0 0.5em; - line-height: 1; - font-size: 2em; - cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 50px; + padding: 0 0.5em; + line-height: 1; + font-size: 2em; + cursor: pointer; + color: $c-explorer-secondary; + border: 0; + border-left: solid 1px $c-explorer-bg-dark; + + &:focus { + background: $c-explorer-bg-active; + color: $color-white; + } + + // Overrides for default link hover. + &:hover { color: $c-explorer-secondary; - border: 0; - border-left: solid 1px $c-explorer-bg-dark; + } - &:focus { - background: $c-explorer-bg-active; - color: $color-white; - } + @include hover { + background: $c-explorer-bg-active; + color: $color-white; + } - // Overrides for default link hover. - &:hover { - color: $c-explorer-secondary; - } - - @include hover { - background: $c-explorer-bg-active; - color: $color-white; - } - - .icon::before { - margin-right: 0; - } + .icon::before { + margin-right: 0; + } } .c-explorer__item__action--small { - font-size: 1.2em; + font-size: 1.2em; } .icon--item-action { - width: 1em; - height: 1em; + width: 1em; + height: 1em; } .c-explorer__meta { - margin-left: 0.5rem; - color: $c-explorer-secondary; - font-size: 12px; + margin-left: 0.5rem; + color: $c-explorer-secondary; + font-size: 12px; } diff --git a/client/src/components/Explorer/ExplorerItem.test.js b/client/src/components/Explorer/ExplorerItem.test.js index 4f6b1394cd..331dca9a0b 100644 --- a/client/src/components/Explorer/ExplorerItem.test.js +++ b/client/src/components/Explorer/ExplorerItem.test.js @@ -19,7 +19,7 @@ const mockProps = { }, children: { count: 0, - } + }, }, }, onClick: () => {}, diff --git a/client/src/components/Explorer/ExplorerItem.tsx b/client/src/components/Explorer/ExplorerItem.tsx index 53fcad9a72..9a4b439a1d 100644 --- a/client/src/components/Explorer/ExplorerItem.tsx +++ b/client/src/components/Explorer/ExplorerItem.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { ADMIN_URLS, STRINGS, LOCALE_NAMES } from '../../config/wagtailConfig'; @@ -9,9 +7,7 @@ import PublicationStatus from '../../components/PublicationStatus/PublicationSta import { PageState } from './reducers/nodes'; // Hoist icons in the explorer item, as it is re-rendered many times. -const childrenIcon = ( - <Icon name="folder-inverse" className="icon--menuitem" /> -); +const childrenIcon = <Icon name="folder-inverse" className="icon--menuitem" />; interface ExplorerItemProps { item: PageState; @@ -22,33 +18,46 @@ interface ExplorerItemProps { * One menu item in the page explorer, with different available actions * and information depending on the metadata of the page. */ -const ExplorerItem: React.FunctionComponent<ExplorerItemProps> = ({ item, onClick }) => { +const ExplorerItem: React.FunctionComponent<ExplorerItemProps> = ({ + item, + onClick, +}) => { const { id, admin_display_title: title, meta } = item; const hasChildren = meta.children.count > 0; const isPublished = meta.status.live && !meta.status.has_unpublished_changes; - const localeName = meta.parent?.id === 1 && meta.locale && (LOCALE_NAMES.get(meta.locale) || meta.locale); + const localeName = + meta.parent?.id === 1 && + meta.locale && + (LOCALE_NAMES.get(meta.locale) || meta.locale); return ( <div className="c-explorer__item"> - <Button href={`${ADMIN_URLS.PAGES}${id}/`} className="c-explorer__item__link"> + <Button + href={`${ADMIN_URLS.PAGES}${id}/`} + className="c-explorer__item__link" + > {hasChildren ? childrenIcon : null} - <h3 className="c-explorer__item__title"> - {title} - </h3> + <h3 className="c-explorer__item__title">{title}</h3> - {(!isPublished || localeName) && + {(!isPublished || localeName) && ( <span className="c-explorer__meta"> - {localeName && <span className="o-pill c-status">{localeName}</span>} + {localeName && ( + <span className="o-pill c-status">{localeName}</span> + )} {!isPublished && <PublicationStatus status={meta.status} />} </span> - } + )} </Button> <Button href={`${ADMIN_URLS.PAGES}${id}/edit/`} className="c-explorer__item__action c-explorer__item__action--small" > - <Icon name="edit" title={STRINGS.EDIT_PAGE.replace('{title}', title)} className="icon--item-action" /> + <Icon + name="edit" + title={STRINGS.EDIT_PAGE.replace('{title}', title)} + className="icon--item-action" + /> </Button> {hasChildren ? ( <Button diff --git a/client/src/components/Explorer/ExplorerPanel.test.js b/client/src/components/Explorer/ExplorerPanel.test.js index 10f6e6e54a..e361b1b4fa 100644 --- a/client/src/components/Explorer/ExplorerPanel.test.js +++ b/client/src/components/Explorer/ExplorerPanel.test.js @@ -8,8 +8,8 @@ const mockProps = { items: [], }, meta: { - parent: null - } + parent: null, + }, }, depth: 1, onClose: jest.fn(), @@ -32,43 +32,54 @@ describe('ExplorerPanel', () => { }); it('#isFetching', () => { - expect(shallow(( - <ExplorerPanel - {...mockProps} - page={Object.assign({ isFetching: true }, mockProps.page)} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <ExplorerPanel + {...mockProps} + page={Object.assign({ isFetching: true }, mockProps.page)} + />, + ), + ).toMatchSnapshot(); }); it('#isError', () => { - expect(shallow(( - <ExplorerPanel - {...mockProps} - page={Object.assign({ isError: true }, mockProps.page)} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <ExplorerPanel + {...mockProps} + page={Object.assign({ isError: true }, mockProps.page)} + />, + ), + ).toMatchSnapshot(); }); it('no children', () => { - expect(shallow(( - <ExplorerPanel - {...mockProps} - page={{ children: {} }} - /> - ))).toMatchSnapshot(); + expect( + shallow(<ExplorerPanel {...mockProps} page={{ children: {} }} />), + ).toMatchSnapshot(); }); it('#items', () => { - expect(shallow(( - <ExplorerPanel - {...mockProps} - page={{ children: { items: [1, 2] } }} - nodes={{ - 1: { id: 1, admin_display_title: 'Test', meta: { status: {}, type: 'test' } }, - 2: { id: 2, admin_display_title: 'Foo', meta: { status: {}, type: 'foo' } }, - }} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <ExplorerPanel + {...mockProps} + page={{ children: { items: [1, 2] } }} + nodes={{ + 1: { + id: 1, + admin_display_title: 'Test', + meta: { status: {}, type: 'test' }, + }, + 2: { + id: 2, + admin_display_title: 'Foo', + meta: { status: {}, type: 'foo' }, + }, + }} + />, + ), + ).toMatchSnapshot(); }); }); @@ -78,9 +89,15 @@ describe('ExplorerPanel', () => { }); it('calls gotoPage', () => { - shallow(( - <ExplorerPanel {...mockProps} depth={2} page={{ children: { items: [] }, meta: { parent: { id: 1 } } }} /> - )).find('ExplorerHeader').prop('onClick')({ + shallow( + <ExplorerPanel + {...mockProps} + depth={2} + page={{ children: { items: [] }, meta: { parent: { id: 1 } } }} + />, + ) + .find('ExplorerHeader') + .prop('onClick')({ preventDefault() {}, stopPropagation() {}, }); @@ -89,9 +106,15 @@ describe('ExplorerPanel', () => { }); it('does not call gotoPage for first page', () => { - shallow(( - <ExplorerPanel {...mockProps} depth={0} page={{ children: { items: [] }, meta: { parent: { id: 1 } } }} /> - )).find('ExplorerHeader').prop('onClick')({ + shallow( + <ExplorerPanel + {...mockProps} + depth={0} + page={{ children: { items: [] }, meta: { parent: { id: 1 } } }} + />, + ) + .find('ExplorerHeader') + .prop('onClick')({ preventDefault() {}, stopPropagation() {}, }); @@ -106,14 +129,22 @@ describe('ExplorerPanel', () => { }); it('calls gotoPage', () => { - shallow(( + shallow( <ExplorerPanel {...mockProps} path={[1]} page={{ children: { items: [1] } }} - nodes={{ 1: { id: 1, admin_display_title: 'Test', meta: { status: {}, type: 'test' } } }} - /> - )).find('ExplorerItem').prop('onClick')({ + nodes={{ + 1: { + id: 1, + admin_display_title: 'Test', + meta: { status: {}, type: 'test' }, + }, + }} + />, + ) + .find('ExplorerItem') + .prop('onClick')({ preventDefault() {}, stopPropagation() {}, }); @@ -137,15 +168,24 @@ describe('ExplorerPanel', () => { document.body.innerHTML = '<div data-explorer-menu-item></div>'; const wrapper = shallow(<ExplorerPanel {...mockProps} />); wrapper.instance().componentDidMount(); - expect(document.querySelector('[data-explorer-menu-item]').classList.contains('submenu-active')).toBe(true); + expect( + document + .querySelector('[data-explorer-menu-item]') + .classList.contains('submenu-active'), + ).toBe(true); expect(document.body.classList.contains('explorer-open')).toBe(true); }); it('componentWillUnmount', () => { - document.body.innerHTML = '<div class="submenu-active" data-explorer-menu-item></div>'; + document.body.innerHTML = + '<div class="submenu-active" data-explorer-menu-item></div>'; const wrapper = shallow(<ExplorerPanel {...mockProps} />); wrapper.instance().componentWillUnmount(); - expect(document.querySelector('[data-explorer-menu-item]').classList.contains('submenu-active')).toBe(false); + expect( + document + .querySelector('[data-explorer-menu-item]') + .classList.contains('submenu-active'), + ).toBe(false); expect(document.body.classList.contains('explorer-open')).toBe(false); }); }); @@ -156,7 +196,8 @@ describe('ExplorerPanel', () => { }); it('triggers onClose when click is outside', () => { - document.body.innerHTML = '<div data-explorer-menu-item></div><div data-explorer-menu></div><div id="t"></div>'; + document.body.innerHTML = + '<div data-explorer-menu-item></div><div data-explorer-menu></div><div id="t"></div>'; const wrapper = shallow(<ExplorerPanel {...mockProps} />); wrapper.instance().clickOutside({ target: document.querySelector('#t'), @@ -165,7 +206,8 @@ describe('ExplorerPanel', () => { }); it('does not trigger onClose when click is inside', () => { - document.body.innerHTML = '<div data-explorer-menu-item></div><div data-explorer-menu><div id="t"></div></div>'; + document.body.innerHTML = + '<div data-explorer-menu-item></div><div data-explorer-menu><div id="t"></div></div>'; const wrapper = shallow(<ExplorerPanel {...mockProps} />); wrapper.instance().clickOutside({ target: document.querySelector('#t'), @@ -174,7 +216,8 @@ describe('ExplorerPanel', () => { }); it('pauses focus trap inside toggle', () => { - document.body.innerHTML = '<div data-explorer-menu-item><div id="t"></div></div><div data-explorer-menu></div>'; + document.body.innerHTML = + '<div data-explorer-menu-item><div id="t"></div></div><div data-explorer-menu></div>'; const wrapper = shallow(<ExplorerPanel {...mockProps} />); wrapper.instance().clickOutside({ target: document.querySelector('#t'), diff --git a/client/src/components/Explorer/ExplorerPanel.tsx b/client/src/components/Explorer/ExplorerPanel.tsx index 5196b5f48d..337da37f01 100644 --- a/client/src/components/Explorer/ExplorerPanel.tsx +++ b/client/src/components/Explorer/ExplorerPanel.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import FocusTrap from 'focus-trap-react'; @@ -30,7 +28,10 @@ interface ExplorerPanelState { * The main panel of the page explorer menu, with heading, * menu items, and special states. */ -class ExplorerPanel extends React.Component<ExplorerPanelProps, ExplorerPanelState> { +class ExplorerPanel extends React.Component< + ExplorerPanelProps, + ExplorerPanelState +> { constructor(props) { super(props); @@ -54,14 +55,18 @@ class ExplorerPanel extends React.Component<ExplorerPanelProps, ExplorerPanelSta } componentDidMount() { - document.querySelector('[data-explorer-menu-item]')?.classList.add('submenu-active'); + document + .querySelector('[data-explorer-menu-item]') + ?.classList.add('submenu-active'); document.body.classList.add('explorer-open'); document.addEventListener('mousedown', this.clickOutside); document.addEventListener('touchend', this.clickOutside); } componentWillUnmount() { - document.querySelector('[data-explorer-menu-item]')?.classList.remove('submenu-active'); + document + .querySelector('[data-explorer-menu-item]') + ?.classList.remove('submenu-active'); document.body.classList.remove('explorer-open'); document.removeEventListener('mousedown', this.clickOutside); document.removeEventListener('touchend', this.clickOutside); @@ -157,20 +162,27 @@ class ExplorerPanel extends React.Component<ExplorerPanelProps, ExplorerPanelSta return ( <FocusTrap - paused={paused || !page || page.isFetchingChildren || page.isFetchingTranslations} + paused={ + paused || + !page || + page.isFetchingChildren || + page.isFetchingTranslations + } focusTrapOptions={{ initialFocus: '.c-explorer__header__title', onDeactivate: onClose, }} > - <div - role="dialog" - className="explorer" - > + <div role="dialog" className="explorer"> <Button className="c-explorer__close"> {STRINGS.CLOSE_EXPLORER} </Button> - <Transition name={transition} className="c-explorer" component="nav" label={STRINGS.PAGE_EXPLORER}> + <Transition + name={transition} + className="c-explorer" + component="nav" + label={STRINGS.PAGE_EXPLORER} + > <div key={depth} className="c-transition-group"> <ExplorerHeader depth={depth} @@ -181,7 +193,9 @@ class ExplorerPanel extends React.Component<ExplorerPanelProps, ExplorerPanelSta {this.renderChildren()} - {page.isError || page.children.items && page.children.count > MAX_EXPLORER_PAGES ? ( + {page.isError || + (page.children.items && + page.children.count > MAX_EXPLORER_PAGES) ? ( <PageCount page={page} /> ) : null} </div> diff --git a/client/src/components/Explorer/ExplorerToggle.test.js b/client/src/components/Explorer/ExplorerToggle.test.js index 9cf74fca62..c93b12de8e 100644 --- a/client/src/components/Explorer/ExplorerToggle.test.js +++ b/client/src/components/Explorer/ExplorerToggle.test.js @@ -12,13 +12,15 @@ describe('ExplorerToggle', () => { }); it('basic', () => { - expect(shallow(( - <ExplorerToggle store={store}> - <span> - To infinity and beyond! - </span> - </ExplorerToggle> - )).find('ExplorerToggle').dive()).toMatchSnapshot(); + expect( + shallow( + <ExplorerToggle store={store}> + <span>To infinity and beyond!</span> + </ExplorerToggle>, + ) + .find('ExplorerToggle') + .dive(), + ).toMatchSnapshot(); }); describe('actions', () => { diff --git a/client/src/components/Explorer/ExplorerToggle.tsx b/client/src/components/Explorer/ExplorerToggle.tsx index 0ce5591b7c..33946e4a53 100644 --- a/client/src/components/Explorer/ExplorerToggle.tsx +++ b/client/src/components/Explorer/ExplorerToggle.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { connect } from 'react-redux'; @@ -16,11 +14,11 @@ interface ExplorerToggleProps { /** * A Button which toggles the explorer. */ -const ExplorerToggle: React.FunctionComponent<ExplorerToggleProps> = ({ children, onToggle }) => ( - <Button - dialogTrigger={true} - onClick={onToggle} - > +const ExplorerToggle: React.FunctionComponent<ExplorerToggleProps> = ({ + children, + onToggle, +}) => ( + <Button dialogTrigger={true} onClick={onToggle}> <Icon name="folder-open-inverse" className="icon--menuitem" /> {children} <Icon name="arrow-right" className="icon--submenu-trigger" /> @@ -38,4 +36,8 @@ const mergeProps = (_stateProps, dispatchProps, ownProps) => ({ onToggle: dispatchProps.onToggle.bind(null, ownProps.startPage), }); -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ExplorerToggle); +export default connect( + mapStateToProps, + mapDispatchToProps, + mergeProps, +)(ExplorerToggle); diff --git a/client/src/components/Explorer/PageCount.tsx b/client/src/components/Explorer/PageCount.tsx index f2a6e64a30..7e9eebaaec 100644 --- a/client/src/components/Explorer/PageCount.tsx +++ b/client/src/components/Explorer/PageCount.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { ADMIN_URLS, STRINGS } from '../../config/wagtailConfig'; @@ -10,20 +8,19 @@ interface PageCountProps { id: number; children: { count: number; - } - } + }; + }; } const PageCount: React.FunctionComponent<PageCountProps> = ({ page }) => { const count = page.children.count; return ( - <a - href={`${ADMIN_URLS.PAGES}${page.id}/`} - className="c-explorer__see-more" - > + <a href={`${ADMIN_URLS.PAGES}${page.id}/`} className="c-explorer__see-more"> {STRINGS.SEE_ALL} - <span>{` ${count} ${count === 1 ? STRINGS.PAGE.toLowerCase() : STRINGS.PAGES.toLowerCase()}`}</span> + <span>{` ${count} ${ + count === 1 ? STRINGS.PAGE.toLowerCase() : STRINGS.PAGES.toLowerCase() + }`}</span> <Icon name="arrow-right" /> </a> ); diff --git a/client/src/components/Explorer/actions.ts b/client/src/components/Explorer/actions.ts index ef4c694268..695925bb15 100644 --- a/client/src/components/Explorer/actions.ts +++ b/client/src/components/Explorer/actions.ts @@ -8,26 +8,41 @@ import { State, Action } from './reducers'; type ThunkActionType = ThunkAction<void, State, unknown, Action>; -const getPageSuccess = createAction('GET_PAGE_SUCCESS', (id: number, data: admin.WagtailPageAPI) => ({ id, data })); -const getPageFailure = createAction('GET_PAGE_FAILURE', (id: number, error: Error) => ({ id, error })); +const getPageSuccess = createAction( + 'GET_PAGE_SUCCESS', + (id: number, data: admin.WagtailPageAPI) => ({ id, data }), +); +const getPageFailure = createAction( + 'GET_PAGE_FAILURE', + (id: number, error: Error) => ({ id, error }), +); /** * Gets a page from the API. */ function getPage(id: number): ThunkActionType { - return (dispatch) => admin.getPage(id).then((data) => { - dispatch(getPageSuccess(id, data)); - }, (error) => { - dispatch(getPageFailure(id, error)); - }); + return (dispatch) => + admin.getPage(id).then( + (data) => { + dispatch(getPageSuccess(id, data)); + }, + (error) => { + dispatch(getPageFailure(id, error)); + }, + ); } -const getChildrenStart = createAction('GET_CHILDREN_START', (id: number) => ({ id })); +const getChildrenStart = createAction('GET_CHILDREN_START', (id: number) => ({ + id, +})); const getChildrenSuccess = createAction( 'GET_CHILDREN_SUCCESS', - (id, items: admin.WagtailPageAPI[], meta: any) => ({ id, items, meta }) + (id, items: admin.WagtailPageAPI[], meta: any) => ({ id, items, meta }), +); +const getChildrenFailure = createAction( + 'GET_CHILDREN_FAILURE', + (id: number, error: Error) => ({ id, error }), ); -const getChildrenFailure = createAction('GET_CHILDREN_FAILURE', (id: number, error: Error) => ({ id, error })); /** * Gets the children of a node from the API. @@ -36,26 +51,39 @@ function getChildren(id: number, offset = 0): ThunkActionType { return (dispatch) => { dispatch(getChildrenStart(id)); - return admin.getPageChildren(id, { - offset: offset, - }).then(({ items, meta }) => { - const nbPages = offset + items.length; - dispatch(getChildrenSuccess(id, items, meta)); + return admin + .getPageChildren(id, { + offset: offset, + }) + .then( + ({ items, meta }) => { + const nbPages = offset + items.length; + dispatch(getChildrenSuccess(id, items, meta)); - // Load more pages if necessary. Only one request is created even though - // more might be needed, thus naturally throttling the loading. - if (nbPages < meta.total_count && nbPages < MAX_EXPLORER_PAGES) { - dispatch(getChildren(id, nbPages)); - } - }, (error) => { - dispatch(getChildrenFailure(id, error)); - }); + // Load more pages if necessary. Only one request is created even though + // more might be needed, thus naturally throttling the loading. + if (nbPages < meta.total_count && nbPages < MAX_EXPLORER_PAGES) { + dispatch(getChildren(id, nbPages)); + } + }, + (error) => { + dispatch(getChildrenFailure(id, error)); + }, + ); }; } -const getTranslationsStart = createAction('GET_TRANSLATIONS_START', id => ({ id })); -const getTranslationsSuccess = createAction('GET_TRANSLATIONS_SUCCESS', (id, items) => ({ id, items })); -const getTranslationsFailure = createAction('GET_TRANSLATIONS_FAILURE', (id, error) => ({ id, error })); +const getTranslationsStart = createAction('GET_TRANSLATIONS_START', (id) => ({ + id, +})); +const getTranslationsSuccess = createAction( + 'GET_TRANSLATIONS_SUCCESS', + (id, items) => ({ id, items }), +); +const getTranslationsFailure = createAction( + 'GET_TRANSLATIONS_FAILURE', + (id, error) => ({ id, error }), +); /** * Gets the translations of a node from the API. @@ -64,15 +92,18 @@ function getTranslations(id) { return (dispatch) => { dispatch(getTranslationsStart(id)); - return admin.getAllPageTranslations(id, { onlyWithChildren: true }).then(items => { - dispatch(getTranslationsSuccess(id, items)); - }, (error) => { - dispatch(getTranslationsFailure(id, error)); - }); + return admin.getAllPageTranslations(id, { onlyWithChildren: true }).then( + (items) => { + dispatch(getTranslationsSuccess(id, items)); + }, + (error) => { + dispatch(getTranslationsFailure(id, error)); + }, + ); }; } -const openExplorer = createAction('OPEN_EXPLORER', id => ({ id })); +const openExplorer = createAction('OPEN_EXPLORER', (id) => ({ id })); export const closeExplorer = createAction('CLOSE_EXPLORER'); export function toggleExplorer(id: number): ThunkActionType { @@ -103,7 +134,10 @@ export function toggleExplorer(id: number): ThunkActionType { }; } -const gotoPagePrivate = createAction('GOTO_PAGE', (id: number, transition: number) => ({ id, transition })); +const gotoPagePrivate = createAction( + 'GOTO_PAGE', + (id: number, transition: number) => ({ id, transition }), +); export function gotoPage(id: number, transition: number): ThunkActionType { return (dispatch, getState) => { @@ -112,7 +146,7 @@ export function gotoPage(id: number, transition: number): ThunkActionType { dispatch(gotoPagePrivate(id, transition)); - if (page && !page.isFetchingChildren && !(page.children.count > 0)) { + if (page && !page.isFetchingChildren && !(page.children.count > 0)) { dispatch(getChildren(id)); } diff --git a/client/src/components/Explorer/index.test.js b/client/src/components/Explorer/index.test.js index 14d2b2dbea..7ea4ff9155 100644 --- a/client/src/components/Explorer/index.test.js +++ b/client/src/components/Explorer/index.test.js @@ -17,7 +17,8 @@ describe('Explorer index', () => { }); it('works', () => { - document.body.innerHTML = '<div><div id="e"></div><div id="t">Test</div></div>'; + document.body.innerHTML = + '<div><div id="e"></div><div id="t">Test</div></div>'; const explorerNode = document.querySelector('#e'); const toggleNode = document.querySelector('#t'); diff --git a/client/src/components/Explorer/index.tsx b/client/src/components/Explorer/index.tsx index df477bda1a..de486fa727 100644 --- a/client/src/components/Explorer/index.tsx +++ b/client/src/components/Explorer/index.tsx @@ -19,39 +19,47 @@ const initExplorer = (explorerNode, toggleNode) => { nodes, }); - const middleware = [ - thunkMiddleware, - ]; + const middleware = [thunkMiddleware]; // Uncomment this to use performance measurements. // if (process.env.NODE_ENV !== 'production') { // middleware.push(perfMiddleware); // } - const store = createStore(rootReducer, {}, compose( - applyMiddleware(...middleware), - // Expose store to Redux DevTools extension. - window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : func => func - )); + const store = createStore( + rootReducer, + {}, + compose( + applyMiddleware(...middleware), + // Expose store to Redux DevTools extension. + window.__REDUX_DEVTOOLS_EXTENSION__ + ? window.__REDUX_DEVTOOLS_EXTENSION__() + : (func) => func, + ), + ); - const startPage = parseInt(toggleNode.getAttribute('data-explorer-start-page'), 10); + const startPage = parseInt( + toggleNode.getAttribute('data-explorer-start-page'), + 10, + ); - ReactDOM.render(( + ReactDOM.render( <Provider store={store}> - <ExplorerToggle startPage={startPage}>{toggleNode.textContent}</ExplorerToggle> - </Provider> - ), toggleNode.parentNode); + <ExplorerToggle startPage={startPage}> + {toggleNode.textContent} + </ExplorerToggle> + </Provider>, + toggleNode.parentNode, + ); - ReactDOM.render(( + ReactDOM.render( <Provider store={store}> <Explorer /> - </Provider> - ), explorerNode); + </Provider>, + explorerNode, + ); }; export default Explorer; -export { - ExplorerToggle, - initExplorer, -}; +export { ExplorerToggle, initExplorer }; diff --git a/client/src/components/Explorer/reducers/explorer.test.js b/client/src/components/Explorer/reducers/explorer.test.js index 32916f0299..9934bc40ea 100644 --- a/client/src/components/Explorer/reducers/explorer.test.js +++ b/client/src/components/Explorer/reducers/explorer.test.js @@ -7,7 +7,7 @@ describe('explorer', () => { expect(explorer).toBeDefined(); }); - it('returns the initial state if no input is provided', () => { + it('returns the initial state if no input is provided', () => { expect(explorer(undefined, {})).toEqual(initialState); }); @@ -17,15 +17,22 @@ describe('explorer', () => { }); it('CLOSE_EXPLORER', () => { - expect(explorer(initialState, { type: 'CLOSE_EXPLORER' })).toEqual(initialState); + expect(explorer(initialState, { type: 'CLOSE_EXPLORER' })).toEqual( + initialState, + ); }); it('PUSH_PAGE', () => { - expect(explorer(initialState, { type: 'PUSH_PAGE', payload: { id: 100 } })).toMatchSnapshot(); + expect( + explorer(initialState, { type: 'PUSH_PAGE', payload: { id: 100 } }), + ).toMatchSnapshot(); }); it('POP_PAGE', () => { - const state = explorer(initialState, { type: 'PUSH_PAGE', payload: { id: 100 } }); + const state = explorer(initialState, { + type: 'PUSH_PAGE', + payload: { id: 100 }, + }); const action = { type: 'POP_PAGE', payload: { id: 100 } }; expect(explorer(state, action)).toMatchSnapshot(); }); diff --git a/client/src/components/Explorer/reducers/explorer.ts b/client/src/components/Explorer/reducers/explorer.ts index 2fd4037bcc..ce73e18c4a 100644 --- a/client/src/components/Explorer/reducers/explorer.ts +++ b/client/src/components/Explorer/reducers/explorer.ts @@ -15,7 +15,7 @@ interface OpenExplorerAction { type: typeof OPEN_EXPLORER; payload: { id: number; - } + }; } export const CLOSE_EXPLORER = 'CLOSE_EXPLORER'; @@ -29,37 +29,40 @@ interface GotoPageAction { payload: { id: number; transition: number; - } + }; } -export type Action = OpenExplorerAction | CloseExplorerAction |GotoPageAction; +export type Action = OpenExplorerAction | CloseExplorerAction | GotoPageAction; /** * Oversees the state of the explorer. Defines: * - Where in the page tree the explorer is at. * - Whether the explorer is open or not. */ -export default function explorer(prevState = defaultState, action: Action): State { +export default function explorer( + prevState = defaultState, + action: Action, +): State { switch (action.type) { - case OPEN_EXPLORER: - // Provide a starting page when opening the explorer. - return { - isVisible: true, - depth: 0, - currentPageId: action.payload.id, - }; + case OPEN_EXPLORER: + // Provide a starting page when opening the explorer. + return { + isVisible: true, + depth: 0, + currentPageId: action.payload.id, + }; - case CLOSE_EXPLORER: - return defaultState; + case CLOSE_EXPLORER: + return defaultState; - case GOTO_PAGE: - return { - isVisible: prevState.isVisible, - depth: prevState.depth + action.payload.transition, - currentPageId: action.payload.id, - }; + case GOTO_PAGE: + return { + isVisible: prevState.isVisible, + depth: prevState.depth + action.payload.transition, + currentPageId: action.payload.id, + }; - default: - return prevState; + default: + return prevState; } } diff --git a/client/src/components/Explorer/reducers/index.ts b/client/src/components/Explorer/reducers/index.ts index 682711331c..c457ea4b2d 100644 --- a/client/src/components/Explorer/reducers/index.ts +++ b/client/src/components/Explorer/reducers/index.ts @@ -2,8 +2,8 @@ import { State as ExplorerState, Action as ExplorerAction } from './explorer'; import { State as NodeState, Action as NodeAction } from './nodes'; export interface State { - explorer: ExplorerState, - nodes: NodeState, + explorer: ExplorerState; + nodes: NodeState; } export type Action = ExplorerAction | NodeAction; diff --git a/client/src/components/Explorer/reducers/nodes.test.js b/client/src/components/Explorer/reducers/nodes.test.js index c68774b853..23c8064592 100644 --- a/client/src/components/Explorer/reducers/nodes.test.js +++ b/client/src/components/Explorer/reducers/nodes.test.js @@ -22,7 +22,10 @@ describe('nodes', () => { }); it('GET_PAGE_FAILURE', () => { - const state = nodes(initialState, { type: 'OPEN_EXPLORER', payload: { id: 1 } }); + const state = nodes(initialState, { + type: 'OPEN_EXPLORER', + payload: { id: 1 }, + }); const action = { type: 'GET_PAGE_FAILURE', payload: { id: 1 } }; expect(nodes(state, action)).toMatchSnapshot(); }); @@ -33,16 +36,15 @@ describe('nodes', () => { }); it('GET_CHILDREN_SUCCESS', () => { - const state = nodes(initialState, { type: 'OPEN_EXPLORER', payload: { id: 1 } }); + const state = nodes(initialState, { + type: 'OPEN_EXPLORER', + payload: { id: 1 }, + }); const action = { type: 'GET_CHILDREN_SUCCESS', payload: { id: 1, - items: [ - { id: 3 }, - { id: 4 }, - { id: 5 }, - ], + items: [{ id: 3 }, { id: 4 }, { id: 5 }], meta: { total_count: 3, }, @@ -52,7 +54,10 @@ describe('nodes', () => { }); it('GET_CHILDREN_FAILURE', () => { - const state = nodes(initialState, { type: 'OPEN_EXPLORER', payload: { id: 1 } }); + const state = nodes(initialState, { + type: 'OPEN_EXPLORER', + payload: { id: 1 }, + }); const action = { type: 'GET_CHILDREN_FAILURE', payload: { id: 1 } }; expect(nodes(state, action)).toMatchSnapshot(); }); diff --git a/client/src/components/Explorer/reducers/nodes.ts b/client/src/components/Explorer/reducers/nodes.ts index 48ea1b96ee..c50dd2cdbc 100644 --- a/client/src/components/Explorer/reducers/nodes.ts +++ b/client/src/components/Explorer/reducers/nodes.ts @@ -2,8 +2,8 @@ import { WagtailPageAPI } from '../../../api/admin'; import { OPEN_EXPLORER, CLOSE_EXPLORER } from './explorer'; export interface PageState extends WagtailPageAPI { - isFetchingChildren: boolean, - isFetchingTranslations: boolean, + isFetchingChildren: boolean; + isFetchingTranslations: boolean; isError: boolean; children: { items: any[]; @@ -25,7 +25,7 @@ const defaultPageState: PageState = { status: { status: '', live: false, - has_unpublished_changes: true + has_unpublished_changes: true, }, parent: null, children: {}, @@ -36,7 +36,7 @@ interface OpenExplorerAction { type: typeof OPEN_EXPLORER; payload: { id: number; - } + }; } interface CloseExplorerAction { @@ -66,7 +66,6 @@ interface GetChildrenSuccess { payload: { id: number; meta: { - total_count: number; }; items: WagtailPageAPI[]; @@ -87,7 +86,6 @@ interface GetTranslationsSuccess { payload: { id: number; meta: { - total_count: number; }; items: WagtailPageAPI[]; @@ -118,72 +116,75 @@ interface GetTranslationsFailure { }; } -export type Action = OpenExplorerAction - | CloseExplorerAction - | GetPageSuccess - | GetChildrenStart - | GetChildrenSuccess - | GetTranslationsStart - | GetTranslationsSuccess - | GetPageFailure - | GetChildrenFailure - | GetTranslationsFailure; +export type Action = + | OpenExplorerAction + | CloseExplorerAction + | GetPageSuccess + | GetChildrenStart + | GetChildrenSuccess + | GetTranslationsStart + | GetTranslationsSuccess + | GetPageFailure + | GetChildrenFailure + | GetTranslationsFailure; /** * A single page node in the explorer. */ const node = (state = defaultPageState, action: Action): PageState => { switch (action.type) { - case GET_PAGE_SUCCESS: - return Object.assign({}, state, action.payload.data, { - isError: false, - }); + case GET_PAGE_SUCCESS: + return Object.assign({}, state, action.payload.data, { + isError: false, + }); - case GET_CHILDREN_START: - return Object.assign({}, state, { - isFetchingChildren: true, - }); + case GET_CHILDREN_START: + return Object.assign({}, state, { + isFetchingChildren: true, + }); - case GET_TRANSLATIONS_START: - return Object.assign({}, state, { - isFetchingTranslations: true, - }); + case GET_TRANSLATIONS_START: + return Object.assign({}, state, { + isFetchingTranslations: true, + }); - case GET_CHILDREN_SUCCESS: - return Object.assign({}, state, { - isFetchingChildren: false, - isError: false, - children: { - items: state.children.items.slice().concat(action.payload.items.map(item => item.id)), - count: action.payload.meta.total_count, - }, - }); + case GET_CHILDREN_SUCCESS: + return Object.assign({}, state, { + isFetchingChildren: false, + isError: false, + children: { + items: state.children.items + .slice() + .concat(action.payload.items.map((item) => item.id)), + count: action.payload.meta.total_count, + }, + }); - case GET_TRANSLATIONS_SUCCESS: - // eslint-disable-next-line no-case-declarations - const translations = new Map(); + case GET_TRANSLATIONS_SUCCESS: + // eslint-disable-next-line no-case-declarations + const translations = new Map(); - action.payload.items.forEach(item => { - translations.set(item.meta.locale, item.id); - }); + action.payload.items.forEach((item) => { + translations.set(item.meta.locale, item.id); + }); - return Object.assign({}, state, { - isFetchingTranslations: false, - isError: false, - translations, - }); + return Object.assign({}, state, { + isFetchingTranslations: false, + isError: false, + translations, + }); - case GET_PAGE_FAILURE: - case GET_CHILDREN_FAILURE: - case GET_TRANSLATIONS_FAILURE: - return Object.assign({}, state, { - isFetchingChildren: false, - isFetchingTranslations: true, - isError: true, - }); + case GET_PAGE_FAILURE: + case GET_CHILDREN_FAILURE: + case GET_TRANSLATIONS_FAILURE: + return Object.assign({}, state, { + isFetchingChildren: false, + isFetchingTranslations: true, + isError: true, + }); - default: - return state; + default: + return state; } }; @@ -198,41 +199,41 @@ const defaultState: State = {}; */ export default function nodes(state = defaultState, action: Action) { switch (action.type) { - case OPEN_EXPLORER: { - return Object.assign({}, state, { - [action.payload.id]: Object.assign({}, defaultPageState), - }); - } + case OPEN_EXPLORER: { + return Object.assign({}, state, { + [action.payload.id]: Object.assign({}, defaultPageState), + }); + } - case GET_PAGE_SUCCESS: - case GET_CHILDREN_START: - case GET_TRANSLATIONS_START: - case GET_PAGE_FAILURE: - case GET_CHILDREN_FAILURE: - case GET_TRANSLATIONS_FAILURE: - return Object.assign({}, state, { - // Delegate logic to single-node reducer. - [action.payload.id]: node(state[action.payload.id], action), - }); + case GET_PAGE_SUCCESS: + case GET_CHILDREN_START: + case GET_TRANSLATIONS_START: + case GET_PAGE_FAILURE: + case GET_CHILDREN_FAILURE: + case GET_TRANSLATIONS_FAILURE: + return Object.assign({}, state, { + // Delegate logic to single-node reducer. + [action.payload.id]: node(state[action.payload.id], action), + }); - case GET_CHILDREN_SUCCESS: - case GET_TRANSLATIONS_SUCCESS: - // eslint-disable-next-line no-case-declarations - const newState = Object.assign({}, state, { - [action.payload.id]: node(state[action.payload.id], action), - }); + case GET_CHILDREN_SUCCESS: + case GET_TRANSLATIONS_SUCCESS: + // eslint-disable-next-line no-case-declarations + const newState = Object.assign({}, state, { + [action.payload.id]: node(state[action.payload.id], action), + }); - action.payload.items.forEach((item) => { - newState[item.id] = Object.assign({}, defaultPageState, item); - }); + action.payload.items.forEach((item) => { + newState[item.id] = Object.assign({}, defaultPageState, item); + }); - return newState; + return newState; - case CLOSE_EXPLORER: { - return defaultState; - } + case CLOSE_EXPLORER: { + return defaultState; + } - default: - return state; + default: + return state; } } diff --git a/client/src/components/Hallo/_halloeditor.scss b/client/src/components/Hallo/_halloeditor.scss index 11cdad2ac8..68ef0c436b 100644 --- a/client/src/components/Hallo/_halloeditor.scss +++ b/client/src/components/Hallo/_halloeditor.scss @@ -1,86 +1,86 @@ .halloeditor { - font-family: $font-sans; - padding-top: 4em; - min-height: 50px; - overflow: hidden; - line-height: 1.5em; + font-family: $font-sans; + padding-top: 4em; + min-height: 50px; + overflow: hidden; + line-height: 1.5em; - // Resetting various html tags that have been messed with by Wagtail's main css - h1 { - text-transform: none; - } + // Resetting various html tags that have been messed with by Wagtail's main css + h1 { + text-transform: none; + } - h2 { - text-transform: none; - display: block; - } + h2 { + text-transform: none; + display: block; + } - h1 span { - font-weight: normal; - color: inherit; - } + h1 span { + font-weight: normal; + color: inherit; + } - *::before, - *::after { - display: none; - } + *::before, + *::after { + display: none; + } - ol, - ul { - margin: 1em 0; - padding: 0 0 0 40px; - } + ol, + ul { + margin: 1em 0; + padding: 0 0 0 40px; + } - li { - display: list-item; - } + li { + display: list-item; + } - ul li { - list-style-type: disc; - } + ul li { + list-style-type: disc; + } - ol li { - list-style-type: decimal; - } + ol li { + list-style-type: decimal; + } - // Set some reasonable default heading styles. These can be overridden in site-specific custom CSS - // to make them better reflect their appearance on the front-end (however, it's arguably better for editors - // NOT to be thinking about a specific visual appearance when they choose heading levels...) - h1, - h2, - h3, - h4, - h5, - h6 { - font-family: inherit; - } + // Set some reasonable default heading styles. These can be overridden in site-specific custom CSS + // to make them better reflect their appearance on the front-end (however, it's arguably better for editors + // NOT to be thinking about a specific visual appearance when they choose heading levels...) + h1, + h2, + h3, + h4, + h5, + h6 { + font-family: inherit; + } - // stylelint-disable-next-line no-duplicate-selectors - h2 { - font-size: 2em; - line-height: 1.2em; - clear: both; - } + // stylelint-disable-next-line no-duplicate-selectors + h2 { + font-size: 2em; + line-height: 1.2em; + clear: both; + } - h3 { - font-size: 1.7em; - line-height: 1.194em; - } + h3 { + font-size: 1.7em; + line-height: 1.194em; + } - h4 { - font-size: 1.5em; - line-height: 1.267em; - } + h4 { + font-size: 1.5em; + line-height: 1.267em; + } - h5 { - // used for large body text, not really heading - font-size: 1.2em; - line-height: 1.27em; - } + h5 { + // used for large body text, not really heading + font-size: 1.2em; + line-height: 1.27em; + } - hr { - border-bottom: 1px solid #ccc; - border-top: 0; - border-left: 0; - } + hr { + border-bottom: 1px solid #ccc; + border-top: 0; + border-left: 0; + } } diff --git a/client/src/components/Hallo/_halloembed.scss b/client/src/components/Hallo/_halloembed.scss index 2e5505252f..7fa5373bfb 100644 --- a/client/src/components/Hallo/_halloembed.scss +++ b/client/src/components/Hallo/_halloembed.scss @@ -1,22 +1,22 @@ .halloembed { - position: relative; + position: relative; } .halloembed__delete { - position: absolute; - display: none; - right: 0; - cursor: pointer; + position: absolute; + display: none; + right: 0; + cursor: pointer; - &::before { - background-color: rgba(255, 255, 255, 0.8); - } + &::before { + background-color: rgba(255, 255, 255, 0.8); + } - &:hover::before { - background-color: $color-white; - } + &:hover::before { + background-color: $color-white; + } - .inEditMode & { - display: block; - } + .inEditMode & { + display: block; + } } diff --git a/client/src/components/Hallo/_hallotoolbar.scss b/client/src/components/Hallo/_hallotoolbar.scss index e4f9d72528..5e30769270 100644 --- a/client/src/components/Hallo/_hallotoolbar.scss +++ b/client/src/components/Hallo/_hallotoolbar.scss @@ -1,25 +1,25 @@ .hallotoolbar { - position: absolute; - z-index: 5; - margin-top: 4em; - margin-left: 1.2em; + position: absolute; + z-index: 5; + margin-top: 4em; + margin-left: 1.2em; - &.affixed { - position: fixed; - margin-top: 0; + &.affixed { + position: fixed; + margin-top: 0; + } + + // full is added to hallotoolbar when invoked on a field set to the full layout style + &.full { + margin-left: $mobile-nice-padding; + + @include media-breakpoint-up(sm) { + margin-left: $desktop-nice-padding; } + } - // full is added to hallotoolbar when invoked on a field set to the full layout style - &.full { - margin-left: $mobile-nice-padding; - - @include media-breakpoint-up(sm) { - margin-left: $desktop-nice-padding; - } - } - - button { - border-radius: 0; - height: 2.4em; - } + button { + border-radius: 0; + height: 2.4em; + } } diff --git a/client/src/components/Hallo/_richtext-image.scss b/client/src/components/Hallo/_richtext-image.scss index 5d980b9ead..f9ce72ce15 100644 --- a/client/src/components/Hallo/_richtext-image.scss +++ b/client/src/components/Hallo/_richtext-image.scss @@ -5,20 +5,20 @@ // should ideally use the insert_editor_css hook to pass in their own custom CSS to have those // images render within the rich text area in the same styles that would appear on the front-end. .richtext-image { - // close as possible to match line-height space above p - margin-top: 3px; + // close as possible to match line-height space above p + margin-top: 3px; - &.small { - max-width: 45%; - } + &.small { + max-width: 45%; + } - &.left { - float: left; - margin-right: 16px; - } + &.left { + float: left; + margin-right: 16px; + } - &.right { - float: right; - margin-left: 16px; - } + &.right { + float: right; + margin-left: 16px; + } } diff --git a/client/src/components/Icon/Icon.tsx b/client/src/components/Icon/Icon.tsx index b0b3f29db6..bc0d83c58f 100644 --- a/client/src/components/Icon/Icon.tsx +++ b/client/src/components/Icon/Icon.tsx @@ -9,16 +9,16 @@ export interface IconProps { /** * Provide a `title` as an accessible label intended for screen readers. */ -const Icon: React.FunctionComponent<IconProps> = ({ name, className, title }) => ( +const Icon: React.FunctionComponent<IconProps> = ({ + name, + className, + title, +}) => ( <> <svg className={`icon icon-${name} ${className || ''}`} aria-hidden="true"> <use href={`#icon-${name}`} /> </svg> - {title && - <span className="visuallyhidden"> - {title} - </span> - } + {title && <span className="visuallyhidden">{title}</span>} </> ); diff --git a/client/src/components/LoadingSpinner/LoadingSpinner.js b/client/src/components/LoadingSpinner/LoadingSpinner.js index 58d864a127..40c74a7ede 100644 --- a/client/src/components/LoadingSpinner/LoadingSpinner.js +++ b/client/src/components/LoadingSpinner/LoadingSpinner.js @@ -7,7 +7,8 @@ import Icon from '../../components/Icon/Icon'; */ const LoadingSpinner = () => ( <span> - <Icon name="spinner" className="c-spinner" />{` ${STRINGS.LOADING}`} + <Icon name="spinner" className="c-spinner" /> + {` ${STRINGS.LOADING}`} </span> ); diff --git a/client/src/components/LoadingSpinner/LoadingSpinner.scss b/client/src/components/LoadingSpinner/LoadingSpinner.scss index 0fc00d8430..17b056a619 100644 --- a/client/src/components/LoadingSpinner/LoadingSpinner.scss +++ b/client/src/components/LoadingSpinner/LoadingSpinner.scss @@ -1,7 +1,6 @@ - .c-spinner { - display: inline-block; - width: 1em; - height: 1em; - animation: spin-wag 0.5s infinite linear; + display: inline-block; + width: 1em; + height: 1em; + animation: spin-wag 0.5s infinite linear; } diff --git a/client/src/components/PageExplorer/PageCount.tsx b/client/src/components/PageExplorer/PageCount.tsx index c6dbbc18a7..95b5e18246 100644 --- a/client/src/components/PageExplorer/PageCount.tsx +++ b/client/src/components/PageExplorer/PageCount.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { ADMIN_URLS, STRINGS } from '../../config/wagtailConfig'; @@ -10,8 +8,8 @@ interface PageCountProps { id: number; children: { count: number; - } - } + }; + }; } const PageCount: React.FunctionComponent<PageCountProps> = ({ page }) => { @@ -23,7 +21,9 @@ const PageCount: React.FunctionComponent<PageCountProps> = ({ page }) => { className="c-page-explorer__see-more" > {STRINGS.SEE_ALL} - <span>{` ${count} ${count === 1 ? STRINGS.PAGE.toLowerCase() : STRINGS.PAGES.toLowerCase()}`}</span> + <span>{` ${count} ${ + count === 1 ? STRINGS.PAGE.toLowerCase() : STRINGS.PAGES.toLowerCase() + }`}</span> <Icon name="arrow-right" /> </a> ); diff --git a/client/src/components/PageExplorer/PageExplorer.scss b/client/src/components/PageExplorer/PageExplorer.scss index 0299a452e0..7f78398e23 100644 --- a/client/src/components/PageExplorer/PageExplorer.scss +++ b/client/src/components/PageExplorer/PageExplorer.scss @@ -5,188 +5,187 @@ $c-page-explorer-secondary: #a5a5a5; $c-page-explorer-easing: cubic-bezier(0.075, 0.82, 0.165, 1); $menu-footer-height: 50px; -@use "sass:map"; +@use 'sass:map'; @import 'PageExplorerItem'; .c-page-explorer { - max-width: 485px; - width: 90vw; - height: 100vh; - background: $c-page-explorer-bg; - overflow: hidden; - flex: 1; + max-width: 485px; + width: 90vw; + height: 100vh; + background: $c-page-explorer-bg; + overflow: hidden; + flex: 1; - *:focus { - @include show-focus-outline-inside; - } + *:focus { + @include show-focus-outline-inside; + } - - @include media-breakpoint-up(sm) { - width: 485px; - box-shadow: 2px 2px 5px $c-page-explorer-bg-active; - } + @include media-breakpoint-up(sm) { + width: 485px; + box-shadow: 2px 2px 5px $c-page-explorer-bg-active; + } } .c-page-explorer > .c-transition-group { - display: flex; - flex-direction: column; - height: 100%; - z-index: 350; + display: flex; + flex-direction: column; + height: 100%; + z-index: 350; } .c-page-explorer__drawer { - flex: 1; - overflow-y: auto; - -webkit-overflow-scrolling: touch; + flex: 1; + overflow-y: auto; + -webkit-overflow-scrolling: touch; } $explorer-header-horizontal-padding: 10px; .c-page-explorer__header { - display: grid; - grid-template-columns: 1fr auto; - align-items: center; - background-color: $c-page-explorer-bg-dark; - border-bottom: 1px solid $c-page-explorer-bg-dark; - color: $color-white; - margin-inline-start: $sidebar-toggle-spacing * 2 + $sidebar-toggle-size; - height: $sidebar-toggle-spacing * 2 + $sidebar-toggle-size; + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + background-color: $c-page-explorer-bg-dark; + border-bottom: 1px solid $c-page-explorer-bg-dark; + color: $color-white; + margin-inline-start: $sidebar-toggle-spacing * 2 + $sidebar-toggle-size; + height: $sidebar-toggle-spacing * 2 + $sidebar-toggle-size; - @include media-breakpoint-up(sm) { - margin-inline-start: initial; - height: initial; - } + @include media-breakpoint-up(sm) { + margin-inline-start: initial; + height: initial; + } } .c-page-explorer__header__title { - color: inherit; + color: inherit; - &:focus { - background-color: $c-page-explorer-bg-active; - color: $color-white; - } + &:focus { + background-color: $c-page-explorer-bg-active; + color: $color-white; + } - // Overrides for default link hover. - &:hover { - color: $color-white; - } + // Overrides for default link hover. + &:hover { + color: $color-white; + } - @include hover { - background-color: $c-page-explorer-bg-active; - } + @include hover { + background-color: $c-page-explorer-bg-active; + } } .c-page-explorer__header__title__inner { - padding: 1em $explorer-header-horizontal-padding; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + padding: 1em $explorer-header-horizontal-padding; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; - .icon { - color: $c-page-explorer-secondary; - margin-right: 0.25rem; - font-size: 1rem; - } + .icon { + color: $c-page-explorer-secondary; + margin-right: 0.25rem; + font-size: 1rem; + } - .icon--explorer-header { - width: 1.25em; - height: 1.25em; - margin-right: 0.25rem; - vertical-align: text-top; - color: $c-page-explorer-secondary; - } + .icon--explorer-header { + width: 1.25em; + height: 1.25em; + margin-right: 0.25rem; + vertical-align: text-top; + color: $c-page-explorer-secondary; + } - @include media-breakpoint-up(sm) { - padding: 1em 1.5em; - } + @include media-breakpoint-up(sm) { + padding: 1em 1.5em; + } } .c-page-explorer__header__select { - $margin: 10px; + $margin: 10px; - > select { - padding: 0; - padding-left: 10px; - padding-right: 30px; + > select { + padding: 0; + padding-left: 10px; + padding-right: 30px; - background-color: $c-page-explorer-bg-dark; - border-radius: 0; - border-color: #4c4e4d; - color: $color-white; + background-color: $c-page-explorer-bg-dark; + border-radius: 0; + border-color: #4c4e4d; + color: $color-white; - &:disabled { - border: 0; - } - - &:hover:enabled { - cursor: pointer; - } - - &:hover:disabled { - color: inherit; - background-color: inherit; - cursor: inherit; - } - - // Reset the arrow on `<select>`s in IE10+. - &::-ms-expand { - display: none; - } + &:disabled { + border: 0; } - // Add select arrow back on browsers where native ui has been removed - > span:after { - z-index: 0; - position: absolute; - right: $margin; - top: $margin + 3px; - bottom: 0; - width: 2em; - font-family: $font-wagtail-icons; - content: map.get($icons, 'arrow-down'); - text-align: center; - font-size: 1.2em; - pointer-events: none; - color: $color-grey-3; - - .ie & { - display: none; - } + &:hover:enabled { + cursor: pointer; } + + &:hover:disabled { + color: inherit; + background-color: inherit; + cursor: inherit; + } + + // Reset the arrow on `<select>`s in IE10+. + &::-ms-expand { + display: none; + } + } + + // Add select arrow back on browsers where native ui has been removed + > span:after { + z-index: 0; + position: absolute; + right: $margin; + top: $margin + 3px; + bottom: 0; + width: 2em; + font-family: $font-wagtail-icons; + content: map.get($icons, 'arrow-down'); + text-align: center; + font-size: 1.2em; + pointer-events: none; + color: $color-grey-3; + + .ie & { + display: none; + } + } } .c-page-explorer__placeholder { - padding: 1em; - color: $color-white; + padding: 1em; + color: $color-white; - @include media-breakpoint-up(sm) { - padding: 1em 1.75em; - } + @include media-breakpoint-up(sm) { + padding: 1em 1.75em; + } } .c-page-explorer__see-more { - display: block; - padding: 1em; - background: rgba(0, 0, 0, 0.3); + display: block; + padding: 1em; + background: rgba(0, 0, 0, 0.3); + color: $color-white; + + &:focus { + color: $c-page-explorer-secondary; + background: $c-page-explorer-bg-active; + } + + // Overrides for default link hover. + &:hover { color: $color-white; + } - &:focus { - color: $c-page-explorer-secondary; - background: $c-page-explorer-bg-active; - } + @include hover { + background: $c-page-explorer-bg-active; + } - // Overrides for default link hover. - &:hover { - color: $color-white; - } - - @include hover { - background: $c-page-explorer-bg-active; - } - - @include media-breakpoint-up(sm) { - padding: 1em 1.75em; - height: $menu-footer-height; - } + @include media-breakpoint-up(sm) { + padding: 1em 1.75em; + height: $menu-footer-height; + } } diff --git a/client/src/components/PageExplorer/PageExplorer.test.js b/client/src/components/PageExplorer/PageExplorer.test.js index 2c4aa38b11..e443562232 100644 --- a/client/src/components/PageExplorer/PageExplorer.test.js +++ b/client/src/components/PageExplorer/PageExplorer.test.js @@ -22,13 +22,23 @@ describe('PageExplorer', () => { it('renders', () => { expect(shallow(<PageExplorer store={store} />).dive()).toMatchSnapshot(); - expect(shallow(<Provider store={store}><PageExplorer /></Provider>).dive()).toMatchSnapshot(); + expect( + shallow( + <Provider store={store}> + <PageExplorer /> + </Provider>, + ).dive(), + ).toMatchSnapshot(); }); it('visible', () => { store.dispatch(actions.openPageExplorer(1)); expect(shallow(<PageExplorer store={store} />).dive()).toMatchSnapshot(); - expect(shallow(<PageExplorer store={store} />).dive().dive()).toMatchSnapshot(); + expect( + shallow(<PageExplorer store={store} />) + .dive() + .dive(), + ).toMatchSnapshot(); }); describe('actions', () => { diff --git a/client/src/components/PageExplorer/PageExplorer.tsx b/client/src/components/PageExplorer/PageExplorer.tsx index 2e264c805c..0e6929c33c 100644 --- a/client/src/components/PageExplorer/PageExplorer.tsx +++ b/client/src/components/PageExplorer/PageExplorer.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { connect } from 'react-redux'; @@ -11,9 +9,9 @@ import PageExplorerPanel from './PageExplorerPanel'; interface PageExplorerProps { isVisible: boolean; - depth: number, - currentPageId: number | null, - nodes: NodeState, + depth: number; + currentPageId: number | null; + nodes: NodeState; onClose(): void; gotoPage(id: number, transition: number): void; navigate(url: string): Promise<void>; @@ -27,16 +25,17 @@ const PageExplorer: React.FunctionComponent<PageExplorerProps> = ({ gotoPage, onClose, navigate, -}) => ((isVisible && currentPageId) ? ( - <PageExplorerPanel - depth={depth} - page={nodes[currentPageId]} - nodes={nodes} - gotoPage={gotoPage} - onClose={onClose} - navigate={navigate} - /> -) : null); +}) => + isVisible && currentPageId ? ( + <PageExplorerPanel + depth={depth} + page={nodes[currentPageId]} + nodes={nodes} + gotoPage={gotoPage} + onClose={onClose} + navigate={navigate} + /> + ) : null; const mapStateToProps = (state: State) => ({ depth: state.explorer.depth, @@ -45,7 +44,8 @@ const mapStateToProps = (state: State) => ({ }); const mapDispatchToProps = (dispatch) => ({ - gotoPage: (id: number, transition: number) => dispatch(actions.gotoPage(id, transition)), + gotoPage: (id: number, transition: number) => + dispatch(actions.gotoPage(id, transition)), onClose: () => dispatch(actions.closePageExplorer()), }); diff --git a/client/src/components/PageExplorer/PageExplorerHeader.test.js b/client/src/components/PageExplorer/PageExplorerHeader.test.js index b786a29cb8..d7f7245d0e 100644 --- a/client/src/components/PageExplorer/PageExplorerHeader.test.js +++ b/client/src/components/PageExplorer/PageExplorerHeader.test.js @@ -7,9 +7,9 @@ const mockProps = { page: { meta: { parent: { - id: 1 - } - } + id: 1, + }, + }, }, depth: 2, onClick: jest.fn(), @@ -25,12 +25,21 @@ describe('PageExplorerHeader', () => { }); it('#depth at root', () => { - expect(shallow(<PageExplorerHeader {...mockProps} depth={0} />)).toMatchSnapshot(); + expect( + shallow(<PageExplorerHeader {...mockProps} depth={0} />), + ).toMatchSnapshot(); }); it('#page', () => { const wrapper = shallow( - <PageExplorerHeader {...mockProps} page={{ id: 'a', admin_display_title: 'test', meta: { parent: { id: 1 } } }} /> + <PageExplorerHeader + {...mockProps} + page={{ + id: 'a', + admin_display_title: 'test', + meta: { parent: { id: 1 } }, + }} + />, ); expect(wrapper).toMatchSnapshot(); }); diff --git a/client/src/components/PageExplorer/PageExplorerHeader.tsx b/client/src/components/PageExplorer/PageExplorerHeader.tsx index 96494764ed..72769154ba 100644 --- a/client/src/components/PageExplorer/PageExplorerHeader.tsx +++ b/client/src/components/PageExplorer/PageExplorerHeader.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { ADMIN_URLS, STRINGS } from '../../config/wagtailConfig'; @@ -13,11 +11,20 @@ interface SelectLocaleProps { gotoPage(id: number, transition: number): void; } -const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({ locale, translations, gotoPage }) => { - const options = wagtailConfig.LOCALES - .filter(({ code }) => code === locale || translations.get(code)) - /* eslint-disable-next-line camelcase */ - .map(({ code, display_name }) => <option key={code} value={code}>{display_name}</option>); +const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({ + locale, + translations, + gotoPage, +}) => { + /* eslint-disable camelcase */ + const options = wagtailConfig.LOCALES.filter( + ({ code }) => code === locale || translations.get(code), + ).map(({ code, display_name }) => ( + <option key={code} value={code}> + {display_name} + </option> + )); + /* eslint-enable camelcase */ const onChange = (e) => { e.preventDefault(); @@ -29,7 +36,9 @@ const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({ locale, tran return ( <div className="c-page-explorer__header__select"> - <select value={locale} onChange={onChange} disabled={options.length < 2}>{options}</select> + <select value={locale} onChange={onChange} disabled={options.length < 2}> + {options} + </select> <span /> </div> ); @@ -38,7 +47,7 @@ const SelectLocale: React.FunctionComponent<SelectLocaleProps> = ({ locale, tran interface PageExplorerHeaderProps { page: PageState; depth: number; - onClick(e: any): void + onClick(e: any): void; gotoPage(id: number, transition: number): void; navigate(url: string): Promise<void>; } @@ -47,8 +56,13 @@ interface PageExplorerHeaderProps { * The bar at the top of the explorer, displaying the current level * and allowing access back to the parent level. */ -const PageExplorerHeader: React.FunctionComponent<PageExplorerHeaderProps> = ( - { page, depth, onClick, gotoPage, navigate }) => { +const PageExplorerHeader: React.FunctionComponent<PageExplorerHeaderProps> = ({ + page, + depth, + onClick, + gotoPage, + navigate, +}) => { const isRoot = depth === 0; const isSiteRoot = page.id === 0; @@ -68,10 +82,16 @@ const PageExplorerHeader: React.FunctionComponent<PageExplorerHeaderProps> = ( <span>{page.admin_display_title || STRINGS.PAGES}</span> </div> </Button> - {!isSiteRoot && page.meta.locale && - page.translations && - page.translations.size > 0 && - <SelectLocale locale={page.meta.locale} translations={page.translations} gotoPage={gotoPage} />} + {!isSiteRoot && + page.meta.locale && + page.translations && + page.translations.size > 0 && ( + <SelectLocale + locale={page.meta.locale} + translations={page.translations} + gotoPage={gotoPage} + /> + )} </div> ); }; diff --git a/client/src/components/PageExplorer/PageExplorerItem.scss b/client/src/components/PageExplorer/PageExplorerItem.scss index a050edc3ff..dfbd575c14 100644 --- a/client/src/components/PageExplorer/PageExplorerItem.scss +++ b/client/src/components/PageExplorer/PageExplorerItem.scss @@ -1,93 +1,93 @@ .c-page-explorer__item { - display: flex; - flex-flow: row nowrap; - border-bottom: 1px solid $c-page-explorer-bg-dark; + display: flex; + flex-flow: row nowrap; + border-bottom: 1px solid $c-page-explorer-bg-dark; } .c-page-explorer__item__link { - display: inline-flex; - align-items: center; - flex-grow: 1; - padding: 1.45em 1em; - cursor: pointer; + display: inline-flex; + align-items: center; + flex-grow: 1; + padding: 1.45em 1em; + cursor: pointer; - &:focus { - background: $c-page-explorer-bg-active; - color: $color-white; - } + &:focus { + background: $c-page-explorer-bg-active; + color: $color-white; + } - // Overrides for default link hover. - &:hover { - color: $color-white; - } + // Overrides for default link hover. + &:hover { + color: $color-white; + } - @include hover { - background: $c-page-explorer-bg-active; - } + @include hover { + background: $c-page-explorer-bg-active; + } - @include media-breakpoint-up(sm) { - padding: 1.45em 1.75em; - } + @include media-breakpoint-up(sm) { + padding: 1.45em 1.75em; + } } .c-page-explorer__item__link .icon { - width: 2em; - height: 2em; - color: $c-page-explorer-secondary; - margin-right: 0.75rem; + width: 2em; + height: 2em; + color: $c-page-explorer-secondary; + margin-right: 0.75rem; } .c-page-explorer__item__title { - margin: 0; - color: $color-white; - display: inline-block; + margin: 0; + color: $color-white; + display: inline-block; } .c-page-explorer__item__action { - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 50px; - padding: 0 0.5em; - line-height: 1; - font-size: 2em; - cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 50px; + padding: 0 0.5em; + line-height: 1; + font-size: 2em; + cursor: pointer; + color: $c-page-explorer-secondary; + border: 0; + border-left: solid 1px $c-page-explorer-bg-dark; + + &:focus { + background: $c-page-explorer-bg-active; + color: $color-white; + } + + // Overrides for default link hover. + &:hover { color: $c-page-explorer-secondary; - border: 0; - border-left: solid 1px $c-page-explorer-bg-dark; + } - &:focus { - background: $c-page-explorer-bg-active; - color: $color-white; - } + @include hover { + background: $c-page-explorer-bg-active; + color: $color-white; + } - // Overrides for default link hover. - &:hover { - color: $c-page-explorer-secondary; - } - - @include hover { - background: $c-page-explorer-bg-active; - color: $color-white; - } - - .icon::before { - margin-right: 0; - } + .icon::before { + margin-right: 0; + } } .c-page-explorer__item__action--small { - font-size: 1.2em; + font-size: 1.2em; } .icon--item-action { - width: 1em; - height: 1em; + width: 1em; + height: 1em; } .c-page-explorer__meta { - margin-left: 0.5rem; - color: $c-page-explorer-secondary; - font-size: 12px; + margin-left: 0.5rem; + color: $c-page-explorer-secondary; + font-size: 12px; } diff --git a/client/src/components/PageExplorer/PageExplorerItem.test.js b/client/src/components/PageExplorer/PageExplorerItem.test.js index 78674faa29..4c43d60d28 100644 --- a/client/src/components/PageExplorer/PageExplorerItem.test.js +++ b/client/src/components/PageExplorer/PageExplorerItem.test.js @@ -19,7 +19,7 @@ const mockProps = { }, children: { count: 0, - } + }, }, }, onClick: () => {}, diff --git a/client/src/components/PageExplorer/PageExplorerItem.tsx b/client/src/components/PageExplorer/PageExplorerItem.tsx index 5699f23006..e0a33c0941 100644 --- a/client/src/components/PageExplorer/PageExplorerItem.tsx +++ b/client/src/components/PageExplorer/PageExplorerItem.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { ADMIN_URLS, STRINGS, LOCALE_NAMES } from '../../config/wagtailConfig'; @@ -9,9 +7,7 @@ import PublicationStatus from '../../components/PublicationStatus/PublicationSta import { PageState } from './reducers/nodes'; // Hoist icons in the explorer item, as it is re-rendered many times. -const childrenIcon = ( - <Icon name="folder-inverse" className="icon--menuitem" /> -); +const childrenIcon = <Icon name="folder-inverse" className="icon--menuitem" />; interface PageExplorerItemProps { item: PageState; @@ -23,34 +19,49 @@ interface PageExplorerItemProps { * One menu item in the page explorer, with different available actions * and information depending on the metadata of the page. */ -const PageExplorerItem: React.FunctionComponent<PageExplorerItemProps> = ({ item, onClick, navigate }) => { +const PageExplorerItem: React.FunctionComponent<PageExplorerItemProps> = ({ + item, + onClick, + navigate, +}) => { const { id, admin_display_title: title, meta } = item; const hasChildren = meta.children.count > 0; const isPublished = meta.status.live && !meta.status.has_unpublished_changes; - const localeName = meta.parent?.id === 1 && meta.locale && (LOCALE_NAMES.get(meta.locale) || meta.locale); + const localeName = + meta.parent?.id === 1 && + meta.locale && + (LOCALE_NAMES.get(meta.locale) || meta.locale); return ( <div className="c-page-explorer__item"> - <Button href={`${ADMIN_URLS.PAGES}${id}/`} navigate={navigate} className="c-page-explorer__item__link"> + <Button + href={`${ADMIN_URLS.PAGES}${id}/`} + navigate={navigate} + className="c-page-explorer__item__link" + > {hasChildren ? childrenIcon : null} - <h3 className="c-page-explorer__item__title"> - {title} - </h3> + <h3 className="c-page-explorer__item__title">{title}</h3> - {(!isPublished || localeName) && + {(!isPublished || localeName) && ( <span className="c-page-explorer__meta"> - {localeName && <span className="o-pill c-status">{localeName}</span>} + {localeName && ( + <span className="o-pill c-status">{localeName}</span> + )} {!isPublished && <PublicationStatus status={meta.status} />} </span> - } + )} </Button> <Button href={`${ADMIN_URLS.PAGES}${id}/edit/`} className="c-page-explorer__item__action c-page-explorer__item__action--small" navigate={navigate} > - <Icon name="edit" title={STRINGS.EDIT_PAGE.replace('{title}', title)} className="icon--item-action" /> + <Icon + name="edit" + title={STRINGS.EDIT_PAGE.replace('{title}', title)} + className="icon--item-action" + /> </Button> {hasChildren ? ( <Button diff --git a/client/src/components/PageExplorer/PageExplorerPanel.test.js b/client/src/components/PageExplorer/PageExplorerPanel.test.js index c59bc07af7..3c8416a45e 100644 --- a/client/src/components/PageExplorer/PageExplorerPanel.test.js +++ b/client/src/components/PageExplorer/PageExplorerPanel.test.js @@ -8,8 +8,8 @@ const mockProps = { items: [], }, meta: { - parent: null - } + parent: null, + }, }, depth: 1, onClose: jest.fn(), @@ -32,43 +32,54 @@ describe('PageExplorerPanel', () => { }); it('#isFetching', () => { - expect(shallow(( - <PageExplorerPanel - {...mockProps} - page={Object.assign({ isFetching: true }, mockProps.page)} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <PageExplorerPanel + {...mockProps} + page={Object.assign({ isFetching: true }, mockProps.page)} + />, + ), + ).toMatchSnapshot(); }); it('#isError', () => { - expect(shallow(( - <PageExplorerPanel - {...mockProps} - page={Object.assign({ isError: true }, mockProps.page)} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <PageExplorerPanel + {...mockProps} + page={Object.assign({ isError: true }, mockProps.page)} + />, + ), + ).toMatchSnapshot(); }); it('no children', () => { - expect(shallow(( - <PageExplorerPanel - {...mockProps} - page={{ children: {} }} - /> - ))).toMatchSnapshot(); + expect( + shallow(<PageExplorerPanel {...mockProps} page={{ children: {} }} />), + ).toMatchSnapshot(); }); it('#items', () => { - expect(shallow(( - <PageExplorerPanel - {...mockProps} - page={{ children: { items: [1, 2] } }} - nodes={{ - 1: { id: 1, admin_display_title: 'Test', meta: { status: {}, type: 'test' } }, - 2: { id: 2, admin_display_title: 'Foo', meta: { status: {}, type: 'foo' } }, - }} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <PageExplorerPanel + {...mockProps} + page={{ children: { items: [1, 2] } }} + nodes={{ + 1: { + id: 1, + admin_display_title: 'Test', + meta: { status: {}, type: 'test' }, + }, + 2: { + id: 2, + admin_display_title: 'Foo', + meta: { status: {}, type: 'foo' }, + }, + }} + />, + ), + ).toMatchSnapshot(); }); }); @@ -78,9 +89,15 @@ describe('PageExplorerPanel', () => { }); it('calls gotoPage', () => { - shallow(( - <PageExplorerPanel {...mockProps} depth={2} page={{ children: { items: [] }, meta: { parent: { id: 1 } } }} /> - )).find('PageExplorerHeader').prop('onClick')({ + shallow( + <PageExplorerPanel + {...mockProps} + depth={2} + page={{ children: { items: [] }, meta: { parent: { id: 1 } } }} + />, + ) + .find('PageExplorerHeader') + .prop('onClick')({ preventDefault() {}, stopPropagation() {}, }); @@ -89,9 +106,15 @@ describe('PageExplorerPanel', () => { }); it('does not call gotoPage for first page', () => { - shallow(( - <PageExplorerPanel {...mockProps} depth={0} page={{ children: { items: [] }, meta: { parent: { id: 1 } } }} /> - )).find('PageExplorerHeader').prop('onClick')({ + shallow( + <PageExplorerPanel + {...mockProps} + depth={0} + page={{ children: { items: [] }, meta: { parent: { id: 1 } } }} + />, + ) + .find('PageExplorerHeader') + .prop('onClick')({ preventDefault() {}, stopPropagation() {}, }); @@ -106,14 +129,22 @@ describe('PageExplorerPanel', () => { }); it('calls gotoPage', () => { - shallow(( + shallow( <PageExplorerPanel {...mockProps} path={[1]} page={{ children: { items: [1] } }} - nodes={{ 1: { id: 1, admin_display_title: 'Test', meta: { status: {}, type: 'test' } } }} - /> - )).find('PageExplorerItem').prop('onClick')({ + nodes={{ + 1: { + id: 1, + admin_display_title: 'Test', + meta: { status: {}, type: 'test' }, + }, + }} + />, + ) + .find('PageExplorerItem') + .prop('onClick')({ preventDefault() {}, stopPropagation() {}, }); diff --git a/client/src/components/PageExplorer/PageExplorerPanel.tsx b/client/src/components/PageExplorer/PageExplorerPanel.tsx index a717fc925d..ff10c23b43 100644 --- a/client/src/components/PageExplorer/PageExplorerPanel.tsx +++ b/client/src/components/PageExplorer/PageExplorerPanel.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { STRINGS, MAX_EXPLORER_PAGES } from '../../config/wagtailConfig'; @@ -28,7 +26,10 @@ interface PageExplorerPanelState { * The main panel of the page explorer menu, with heading, * menu items, and special states. */ -class PageExplorerPanel extends React.Component<PageExplorerPanelProps, PageExplorerPanelState> { +class PageExplorerPanel extends React.Component< + PageExplorerPanelProps, + PageExplorerPanelState +> { constructor(props) { super(props); @@ -118,7 +119,12 @@ class PageExplorerPanel extends React.Component<PageExplorerPanelProps, PageExpl const { transition } = this.state; return ( - <Transition name={transition} className="c-page-explorer" component="nav" label={STRINGS.PAGE_EXPLORER}> + <Transition + name={transition} + className="c-page-explorer" + component="nav" + label={STRINGS.PAGE_EXPLORER} + > <div key={depth} className="c-transition-group"> <PageExplorerHeader depth={depth} @@ -130,7 +136,8 @@ class PageExplorerPanel extends React.Component<PageExplorerPanelProps, PageExpl {this.renderChildren()} - {page.isError || page.children.items && page.children.count > MAX_EXPLORER_PAGES ? ( + {page.isError || + (page.children.items && page.children.count > MAX_EXPLORER_PAGES) ? ( <PageCount page={page} /> ) : null} </div> diff --git a/client/src/components/PageExplorer/actions.ts b/client/src/components/PageExplorer/actions.ts index ab23f9d13a..c8651051dc 100644 --- a/client/src/components/PageExplorer/actions.ts +++ b/client/src/components/PageExplorer/actions.ts @@ -8,26 +8,41 @@ import { State, Action } from './reducers'; type ThunkActionType = ThunkAction<void, State, unknown, Action>; -const getPageSuccess = createAction('GET_PAGE_SUCCESS', (id: number, data: admin.WagtailPageAPI) => ({ id, data })); -const getPageFailure = createAction('GET_PAGE_FAILURE', (id: number, error: Error) => ({ id, error })); +const getPageSuccess = createAction( + 'GET_PAGE_SUCCESS', + (id: number, data: admin.WagtailPageAPI) => ({ id, data }), +); +const getPageFailure = createAction( + 'GET_PAGE_FAILURE', + (id: number, error: Error) => ({ id, error }), +); /** * Gets a page from the API. */ function getPage(id: number): ThunkActionType { - return (dispatch) => admin.getPage(id).then((data) => { - dispatch(getPageSuccess(id, data)); - }, (error) => { - dispatch(getPageFailure(id, error)); - }); + return (dispatch) => + admin.getPage(id).then( + (data) => { + dispatch(getPageSuccess(id, data)); + }, + (error) => { + dispatch(getPageFailure(id, error)); + }, + ); } -const getChildrenStart = createAction('GET_CHILDREN_START', (id: number) => ({ id })); +const getChildrenStart = createAction('GET_CHILDREN_START', (id: number) => ({ + id, +})); const getChildrenSuccess = createAction( 'GET_CHILDREN_SUCCESS', - (id, items: admin.WagtailPageAPI[], meta: any) => ({ id, items, meta }) + (id, items: admin.WagtailPageAPI[], meta: any) => ({ id, items, meta }), +); +const getChildrenFailure = createAction( + 'GET_CHILDREN_FAILURE', + (id: number, error: Error) => ({ id, error }), ); -const getChildrenFailure = createAction('GET_CHILDREN_FAILURE', (id: number, error: Error) => ({ id, error })); /** * Gets the children of a node from the API. @@ -36,26 +51,39 @@ function getChildren(id: number, offset = 0): ThunkActionType { return (dispatch) => { dispatch(getChildrenStart(id)); - return admin.getPageChildren(id, { - offset: offset, - }).then(({ items, meta }) => { - const nbPages = offset + items.length; - dispatch(getChildrenSuccess(id, items, meta)); + return admin + .getPageChildren(id, { + offset: offset, + }) + .then( + ({ items, meta }) => { + const nbPages = offset + items.length; + dispatch(getChildrenSuccess(id, items, meta)); - // Load more pages if necessary. Only one request is created even though - // more might be needed, thus naturally throttling the loading. - if (nbPages < meta.total_count && nbPages < MAX_EXPLORER_PAGES) { - dispatch(getChildren(id, nbPages)); - } - }, (error) => { - dispatch(getChildrenFailure(id, error)); - }); + // Load more pages if necessary. Only one request is created even though + // more might be needed, thus naturally throttling the loading. + if (nbPages < meta.total_count && nbPages < MAX_EXPLORER_PAGES) { + dispatch(getChildren(id, nbPages)); + } + }, + (error) => { + dispatch(getChildrenFailure(id, error)); + }, + ); }; } -const getTranslationsStart = createAction('GET_TRANSLATIONS_START', id => ({ id })); -const getTranslationsSuccess = createAction('GET_TRANSLATIONS_SUCCESS', (id, items) => ({ id, items })); -const getTranslationsFailure = createAction('GET_TRANSLATIONS_FAILURE', (id, error) => ({ id, error })); +const getTranslationsStart = createAction('GET_TRANSLATIONS_START', (id) => ({ + id, +})); +const getTranslationsSuccess = createAction( + 'GET_TRANSLATIONS_SUCCESS', + (id, items) => ({ id, items }), +); +const getTranslationsFailure = createAction( + 'GET_TRANSLATIONS_FAILURE', + (id, error) => ({ id, error }), +); /** * Gets the translations of a node from the API. @@ -64,15 +92,18 @@ function getTranslations(id) { return (dispatch) => { dispatch(getTranslationsStart(id)); - return admin.getAllPageTranslations(id, { onlyWithChildren: true }).then(items => { - dispatch(getTranslationsSuccess(id, items)); - }, (error) => { - dispatch(getTranslationsFailure(id, error)); - }); + return admin.getAllPageTranslations(id, { onlyWithChildren: true }).then( + (items) => { + dispatch(getTranslationsSuccess(id, items)); + }, + (error) => { + dispatch(getTranslationsFailure(id, error)); + }, + ); }; } -const openPageExplorerPrivate = createAction('OPEN_EXPLORER', id => ({ id })); +const openPageExplorerPrivate = createAction('OPEN_EXPLORER', (id) => ({ id })); export const closePageExplorer = createAction('CLOSE_EXPLORER'); export function openPageExplorer(id: number): ThunkActionType { @@ -99,7 +130,10 @@ export function openPageExplorer(id: number): ThunkActionType { }; } -const gotoPagePrivate = createAction('GOTO_PAGE', (id: number, transition: number) => ({ id, transition })); +const gotoPagePrivate = createAction( + 'GOTO_PAGE', + (id: number, transition: number) => ({ id, transition }), +); export function gotoPage(id: number, transition: number): ThunkActionType { return (dispatch, getState) => { @@ -108,7 +142,7 @@ export function gotoPage(id: number, transition: number): ThunkActionType { dispatch(gotoPagePrivate(id, transition)); - if (page && !page.isFetchingChildren && !(page.children.count > 0)) { + if (page && !page.isFetchingChildren && !(page.children.count > 0)) { dispatch(getChildren(id)); } diff --git a/client/src/components/PageExplorer/index.tsx b/client/src/components/PageExplorer/index.tsx index c15dbbe15d..205704a862 100644 --- a/client/src/components/PageExplorer/index.tsx +++ b/client/src/components/PageExplorer/index.tsx @@ -12,24 +12,26 @@ const initPageExplorerStore = () => { nodes, }); - const middleware = [ - thunkMiddleware, - ]; + const middleware = [thunkMiddleware]; // Uncomment this to use performance measurements. // if (process.env.NODE_ENV !== 'production') { // middleware.push(perfMiddleware); // } - return createStore(rootReducer, {}, compose( - applyMiddleware(...middleware), - // Expose store to Redux DevTools extension. - window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : func => func - )); + return createStore( + rootReducer, + {}, + compose( + applyMiddleware(...middleware), + // Expose store to Redux DevTools extension. + window.__REDUX_DEVTOOLS_EXTENSION__ + ? window.__REDUX_DEVTOOLS_EXTENSION__() + : (func) => func, + ), + ); }; export default Explorer; -export { - initPageExplorerStore, -}; +export { initPageExplorerStore }; diff --git a/client/src/components/PageExplorer/reducers/explorer.test.js b/client/src/components/PageExplorer/reducers/explorer.test.js index 32916f0299..9934bc40ea 100644 --- a/client/src/components/PageExplorer/reducers/explorer.test.js +++ b/client/src/components/PageExplorer/reducers/explorer.test.js @@ -7,7 +7,7 @@ describe('explorer', () => { expect(explorer).toBeDefined(); }); - it('returns the initial state if no input is provided', () => { + it('returns the initial state if no input is provided', () => { expect(explorer(undefined, {})).toEqual(initialState); }); @@ -17,15 +17,22 @@ describe('explorer', () => { }); it('CLOSE_EXPLORER', () => { - expect(explorer(initialState, { type: 'CLOSE_EXPLORER' })).toEqual(initialState); + expect(explorer(initialState, { type: 'CLOSE_EXPLORER' })).toEqual( + initialState, + ); }); it('PUSH_PAGE', () => { - expect(explorer(initialState, { type: 'PUSH_PAGE', payload: { id: 100 } })).toMatchSnapshot(); + expect( + explorer(initialState, { type: 'PUSH_PAGE', payload: { id: 100 } }), + ).toMatchSnapshot(); }); it('POP_PAGE', () => { - const state = explorer(initialState, { type: 'PUSH_PAGE', payload: { id: 100 } }); + const state = explorer(initialState, { + type: 'PUSH_PAGE', + payload: { id: 100 }, + }); const action = { type: 'POP_PAGE', payload: { id: 100 } }; expect(explorer(state, action)).toMatchSnapshot(); }); diff --git a/client/src/components/PageExplorer/reducers/explorer.ts b/client/src/components/PageExplorer/reducers/explorer.ts index 0ede156abe..93d3d9db23 100644 --- a/client/src/components/PageExplorer/reducers/explorer.ts +++ b/client/src/components/PageExplorer/reducers/explorer.ts @@ -13,7 +13,7 @@ interface OpenPageExplorerAction { type: typeof OPEN_EXPLORER; payload: { id: number; - } + }; } export const CLOSE_EXPLORER = 'CLOSE_EXPLORER'; @@ -27,35 +27,41 @@ interface GotoPageAction { payload: { id: number; transition: number; - } + }; } -export type Action = OpenPageExplorerAction | ClosePageExplorerAction |GotoPageAction; +export type Action = + | OpenPageExplorerAction + | ClosePageExplorerAction + | GotoPageAction; /** * Oversees the state of the explorer. Defines: * - Where in the page tree the explorer is at. * - Whether the explorer is open or not. */ -export default function explorer(prevState = defaultState, action: Action): State { +export default function explorer( + prevState = defaultState, + action: Action, +): State { switch (action.type) { - case OPEN_EXPLORER: - // Provide a starting page when opening the explorer. - return { - depth: 0, - currentPageId: action.payload.id, - }; + case OPEN_EXPLORER: + // Provide a starting page when opening the explorer. + return { + depth: 0, + currentPageId: action.payload.id, + }; - case CLOSE_EXPLORER: - return defaultState; + case CLOSE_EXPLORER: + return defaultState; - case GOTO_PAGE: - return { - depth: prevState.depth + action.payload.transition, - currentPageId: action.payload.id, - }; + case GOTO_PAGE: + return { + depth: prevState.depth + action.payload.transition, + currentPageId: action.payload.id, + }; - default: - return prevState; + default: + return prevState; } } diff --git a/client/src/components/PageExplorer/reducers/index.ts b/client/src/components/PageExplorer/reducers/index.ts index 239c2cc938..69dee800c1 100644 --- a/client/src/components/PageExplorer/reducers/index.ts +++ b/client/src/components/PageExplorer/reducers/index.ts @@ -1,9 +1,12 @@ -import { State as PageExplorerState, Action as PageExplorerAction } from './explorer'; +import { + State as PageExplorerState, + Action as PageExplorerAction, +} from './explorer'; import { State as NodeState, Action as NodeAction } from './nodes'; export interface State { - explorer: PageExplorerState, - nodes: NodeState, + explorer: PageExplorerState; + nodes: NodeState; } export type Action = PageExplorerAction | NodeAction; diff --git a/client/src/components/PageExplorer/reducers/nodes.test.js b/client/src/components/PageExplorer/reducers/nodes.test.js index c68774b853..23c8064592 100644 --- a/client/src/components/PageExplorer/reducers/nodes.test.js +++ b/client/src/components/PageExplorer/reducers/nodes.test.js @@ -22,7 +22,10 @@ describe('nodes', () => { }); it('GET_PAGE_FAILURE', () => { - const state = nodes(initialState, { type: 'OPEN_EXPLORER', payload: { id: 1 } }); + const state = nodes(initialState, { + type: 'OPEN_EXPLORER', + payload: { id: 1 }, + }); const action = { type: 'GET_PAGE_FAILURE', payload: { id: 1 } }; expect(nodes(state, action)).toMatchSnapshot(); }); @@ -33,16 +36,15 @@ describe('nodes', () => { }); it('GET_CHILDREN_SUCCESS', () => { - const state = nodes(initialState, { type: 'OPEN_EXPLORER', payload: { id: 1 } }); + const state = nodes(initialState, { + type: 'OPEN_EXPLORER', + payload: { id: 1 }, + }); const action = { type: 'GET_CHILDREN_SUCCESS', payload: { id: 1, - items: [ - { id: 3 }, - { id: 4 }, - { id: 5 }, - ], + items: [{ id: 3 }, { id: 4 }, { id: 5 }], meta: { total_count: 3, }, @@ -52,7 +54,10 @@ describe('nodes', () => { }); it('GET_CHILDREN_FAILURE', () => { - const state = nodes(initialState, { type: 'OPEN_EXPLORER', payload: { id: 1 } }); + const state = nodes(initialState, { + type: 'OPEN_EXPLORER', + payload: { id: 1 }, + }); const action = { type: 'GET_CHILDREN_FAILURE', payload: { id: 1 } }; expect(nodes(state, action)).toMatchSnapshot(); }); diff --git a/client/src/components/PageExplorer/reducers/nodes.ts b/client/src/components/PageExplorer/reducers/nodes.ts index 847efaf9e9..1836807d6f 100644 --- a/client/src/components/PageExplorer/reducers/nodes.ts +++ b/client/src/components/PageExplorer/reducers/nodes.ts @@ -2,8 +2,8 @@ import { WagtailPageAPI } from '../../../api/admin'; import { OPEN_EXPLORER, CLOSE_EXPLORER } from './explorer'; export interface PageState extends WagtailPageAPI { - isFetchingChildren: boolean, - isFetchingTranslations: boolean, + isFetchingChildren: boolean; + isFetchingTranslations: boolean; isError: boolean; children: { items: any[]; @@ -25,7 +25,7 @@ const defaultPageState: PageState = { status: { status: '', live: false, - has_unpublished_changes: true + has_unpublished_changes: true, }, parent: null, children: {}, @@ -36,7 +36,7 @@ interface OpenPageExplorerAction { type: typeof OPEN_EXPLORER; payload: { id: number; - } + }; } interface ClosePageExplorerAction { @@ -66,7 +66,6 @@ interface GetChildrenSuccess { payload: { id: number; meta: { - total_count: number; }; items: WagtailPageAPI[]; @@ -87,7 +86,6 @@ interface GetTranslationsSuccess { payload: { id: number; meta: { - total_count: number; }; items: WagtailPageAPI[]; @@ -118,72 +116,75 @@ interface GetTranslationsFailure { }; } -export type Action = OpenPageExplorerAction - | ClosePageExplorerAction - | GetPageSuccess - | GetChildrenStart - | GetChildrenSuccess - | GetTranslationsStart - | GetTranslationsSuccess - | GetPageFailure - | GetChildrenFailure - | GetTranslationsFailure; +export type Action = + | OpenPageExplorerAction + | ClosePageExplorerAction + | GetPageSuccess + | GetChildrenStart + | GetChildrenSuccess + | GetTranslationsStart + | GetTranslationsSuccess + | GetPageFailure + | GetChildrenFailure + | GetTranslationsFailure; /** * A single page node in the explorer. */ const node = (state = defaultPageState, action: Action): PageState => { switch (action.type) { - case GET_PAGE_SUCCESS: - return Object.assign({}, state, action.payload.data, { - isError: false, - }); + case GET_PAGE_SUCCESS: + return Object.assign({}, state, action.payload.data, { + isError: false, + }); - case GET_CHILDREN_START: - return Object.assign({}, state, { - isFetchingChildren: true, - }); + case GET_CHILDREN_START: + return Object.assign({}, state, { + isFetchingChildren: true, + }); - case GET_TRANSLATIONS_START: - return Object.assign({}, state, { - isFetchingTranslations: true, - }); + case GET_TRANSLATIONS_START: + return Object.assign({}, state, { + isFetchingTranslations: true, + }); - case GET_CHILDREN_SUCCESS: - return Object.assign({}, state, { - isFetchingChildren: false, - isError: false, - children: { - items: state.children.items.slice().concat(action.payload.items.map(item => item.id)), - count: action.payload.meta.total_count, - }, - }); + case GET_CHILDREN_SUCCESS: + return Object.assign({}, state, { + isFetchingChildren: false, + isError: false, + children: { + items: state.children.items + .slice() + .concat(action.payload.items.map((item) => item.id)), + count: action.payload.meta.total_count, + }, + }); - case GET_TRANSLATIONS_SUCCESS: - // eslint-disable-next-line no-case-declarations - const translations = new Map(); + case GET_TRANSLATIONS_SUCCESS: + // eslint-disable-next-line no-case-declarations + const translations = new Map(); - action.payload.items.forEach(item => { - translations.set(item.meta.locale, item.id); - }); + action.payload.items.forEach((item) => { + translations.set(item.meta.locale, item.id); + }); - return Object.assign({}, state, { - isFetchingTranslations: false, - isError: false, - translations, - }); + return Object.assign({}, state, { + isFetchingTranslations: false, + isError: false, + translations, + }); - case GET_PAGE_FAILURE: - case GET_CHILDREN_FAILURE: - case GET_TRANSLATIONS_FAILURE: - return Object.assign({}, state, { - isFetchingChildren: false, - isFetchingTranslations: true, - isError: true, - }); + case GET_PAGE_FAILURE: + case GET_CHILDREN_FAILURE: + case GET_TRANSLATIONS_FAILURE: + return Object.assign({}, state, { + isFetchingChildren: false, + isFetchingTranslations: true, + isError: true, + }); - default: - return state; + default: + return state; } }; @@ -198,41 +199,41 @@ const defaultState: State = {}; */ export default function nodes(state = defaultState, action: Action) { switch (action.type) { - case OPEN_EXPLORER: { - return Object.assign({}, state, { - [action.payload.id]: Object.assign({}, defaultPageState), - }); - } + case OPEN_EXPLORER: { + return Object.assign({}, state, { + [action.payload.id]: Object.assign({}, defaultPageState), + }); + } - case GET_PAGE_SUCCESS: - case GET_CHILDREN_START: - case GET_TRANSLATIONS_START: - case GET_PAGE_FAILURE: - case GET_CHILDREN_FAILURE: - case GET_TRANSLATIONS_FAILURE: - return Object.assign({}, state, { - // Delegate logic to single-node reducer. - [action.payload.id]: node(state[action.payload.id], action), - }); + case GET_PAGE_SUCCESS: + case GET_CHILDREN_START: + case GET_TRANSLATIONS_START: + case GET_PAGE_FAILURE: + case GET_CHILDREN_FAILURE: + case GET_TRANSLATIONS_FAILURE: + return Object.assign({}, state, { + // Delegate logic to single-node reducer. + [action.payload.id]: node(state[action.payload.id], action), + }); - case GET_CHILDREN_SUCCESS: - case GET_TRANSLATIONS_SUCCESS: - // eslint-disable-next-line no-case-declarations - const newState = Object.assign({}, state, { - [action.payload.id]: node(state[action.payload.id], action), - }); + case GET_CHILDREN_SUCCESS: + case GET_TRANSLATIONS_SUCCESS: + // eslint-disable-next-line no-case-declarations + const newState = Object.assign({}, state, { + [action.payload.id]: node(state[action.payload.id], action), + }); - action.payload.items.forEach((item) => { - newState[item.id] = Object.assign({}, defaultPageState, item); - }); + action.payload.items.forEach((item) => { + newState[item.id] = Object.assign({}, defaultPageState, item); + }); - return newState; + return newState; - case CLOSE_EXPLORER: { - return defaultState; - } + case CLOSE_EXPLORER: { + return defaultState; + } - default: - return state; + default: + return state; } } diff --git a/client/src/components/Portal/Portal.js b/client/src/components/Portal/Portal.js index 7d31ec2c72..dc584d745b 100644 --- a/client/src/components/Portal/Portal.js +++ b/client/src/components/Portal/Portal.js @@ -25,7 +25,8 @@ class Portal extends Component { } componentDidMount() { - const { node, onClose, closeOnClick, closeOnType, closeOnResize } = this.props; + const { node, onClose, closeOnClick, closeOnType, closeOnResize } = + this.props; node.appendChild(this.portal); diff --git a/client/src/components/Portal/Portal.test.js b/client/src/components/Portal/Portal.test.js index ed966c6f52..b195729c8a 100644 --- a/client/src/components/Portal/Portal.test.js +++ b/client/src/components/Portal/Portal.test.js @@ -53,7 +53,7 @@ describe('Portal', () => { shallow( <Portal onClose={onClose} closeOnClick> Test! - </Portal> + </Portal>, ); expect(document.addEventListener).toHaveBeenCalledWith('mouseup', func); }); @@ -63,7 +63,7 @@ describe('Portal', () => { shallow( <Portal onClose={onClose} closeOnType> Test! - </Portal> + </Portal>, ); expect(document.addEventListener).toHaveBeenCalledWith('keyup', func); }); @@ -73,7 +73,7 @@ describe('Portal', () => { shallow( <Portal onClose={onClose} closeOnResize> Test! - </Portal> + </Portal>, ); expect(window.addEventListener).toHaveBeenCalledWith('resize', func); }); @@ -95,7 +95,7 @@ describe('Portal', () => { const wrapper = mount( <Portal onClose={onClose}> <div id="test">Test</div> - </Portal> + </Portal>, ); const target = document.querySelector('#test'); diff --git a/client/src/components/PublicationStatus/PublicationStatus.scss b/client/src/components/PublicationStatus/PublicationStatus.scss index 5353e50811..d3f2fb7d20 100644 --- a/client/src/components/PublicationStatus/PublicationStatus.scss +++ b/client/src/components/PublicationStatus/PublicationStatus.scss @@ -1,6 +1,6 @@ .c-status { - background: $color-grey-1; - text-transform: uppercase; - letter-spacing: 0.03rem; - font-size: 10px; + background: $color-grey-1; + text-transform: uppercase; + letter-spacing: 0.03rem; + font-size: 10px; } diff --git a/client/src/components/PublicationStatus/PublicationStatus.test.js b/client/src/components/PublicationStatus/PublicationStatus.test.js index 4f85ed4b0c..151b6f6689 100644 --- a/client/src/components/PublicationStatus/PublicationStatus.test.js +++ b/client/src/components/PublicationStatus/PublicationStatus.test.js @@ -9,26 +9,30 @@ describe('PublicationStatus', () => { }); it('#status live', () => { - expect(shallow(( - <PublicationStatus - status={{ - status: 'live + draft', - live: true, - has_unpublished_changes: true, - }} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <PublicationStatus + status={{ + status: 'live + draft', + live: true, + has_unpublished_changes: true, + }} + />, + ), + ).toMatchSnapshot(); }); it('#status not live', () => { - expect(shallow(( - <PublicationStatus - status={{ - status: 'live + draft', - live: false, - has_unpublished_changes: true, - }} - /> - ))).toMatchSnapshot(); + expect( + shallow( + <PublicationStatus + status={{ + status: 'live + draft', + live: false, + has_unpublished_changes: true, + }} + />, + ), + ).toMatchSnapshot(); }); }); diff --git a/client/src/components/Sidebar/Sidebar.scss b/client/src/components/Sidebar/Sidebar.scss index 01a73a74b9..3bc872e91e 100644 --- a/client/src/components/Sidebar/Sidebar.scss +++ b/client/src/components/Sidebar/Sidebar.scss @@ -1,116 +1,119 @@ @mixin sidebar-toggle() { - @include transition(background-color $menu-transition-duration ease); + @include transition(background-color $menu-transition-duration ease); - position: absolute; - top: $sidebar-toggle-spacing; - left: $sidebar-toggle-spacing; - color: $color-white; - width: $sidebar-toggle-size; - height: $sidebar-toggle-size; - box-sizing: border-box; - background: transparent; - place-items: center; - padding: 0; - border-radius: 50%; - border: 1px solid transparent; + position: absolute; + top: $sidebar-toggle-spacing; + left: $sidebar-toggle-spacing; + color: $color-white; + width: $sidebar-toggle-size; + height: $sidebar-toggle-size; + box-sizing: border-box; + background: transparent; + place-items: center; + padding: 0; + border-radius: 50%; + border: 1px solid transparent; - &:hover { - background-color: #3a3a3a; - color: #ccc; - } - - svg { - width: 15px; - height: 16px; - } - - .has-messages & { - top: $sidebar-toggle-spacing + 70px; - - @include media-breakpoint-up(sm) { - top: $sidebar-toggle-spacing; - } + &:hover { + background-color: #3a3a3a; + color: #ccc; + } + + svg { + width: 15px; + height: 16px; + } + + .has-messages & { + top: $sidebar-toggle-spacing + 70px; + + @include media-breakpoint-up(sm) { + top: $sidebar-toggle-spacing; } + } } .sidebar { - position: fixed; - left: 0; - width: $menu-width; - float: left; - display: flex; - flex-direction: column; + position: fixed; + left: 0; + width: $menu-width; + float: left; + display: flex; + flex-direction: column; + height: 100%; + background: $nav-grey-3; + z-index: 300; + + @include transition( + width $menu-transition-duration ease, + left $menu-transition-duration ease + ); + + @media (forced-colors: $media-forced-colours) { + border-inline-end: 1px solid transparent; + } + + &--slim { + width: $menu-width-slim; + } + + // The sidebar can move completely off-screen in mobile mode for extra room + &--hidden { + left: -$menu-width; + } + + &__inner { height: 100%; background: $nav-grey-3; - z-index: 300; + // On medium, make it possible for the nav links to scroll. + display: flex; + flex-flow: column nowrap; + } - @include transition(width $menu-transition-duration ease, left $menu-transition-duration ease); + &__collapse-toggle { + @include sidebar-toggle; + display: grid; + } - @media (forced-colors: $media-forced-colours) { - border-inline-end: 1px solid transparent; - } + // When in mobile mode, hide the collapse-toggle and show the nav-toggle (which is defined in the .sidebar-nav-toggle class below) + &--mobile &__collapse-toggle { + display: none; + } - &--slim { - width: $menu-width-slim; - } - - // The sidebar can move completely off-screen in mobile mode for extra room - &--hidden { - left: -$menu-width; - } - - &__inner { - height: 100%; - background: $nav-grey-3; - // On medium, make it possible for the nav links to scroll. - display: flex; - flex-flow: column nowrap; - } - - &__collapse-toggle { - @include sidebar-toggle; - display: grid; - } - - // When in mobile mode, hide the collapse-toggle and show the nav-toggle (which is defined in the .sidebar-nav-toggle class below) - &--mobile &__collapse-toggle { - display: none; - } - - // This element should cover all the area beneath the collapse toggle - // It's only used to attach mouse enter/exit event handlers to control peeking - &__peek-hover-area { - margin-top: $sidebar-toggle-size; - display: grid; - grid-template-columns: 1fr; - overflow-y: auto; - overflow-x: hidden; - } + // This element should cover all the area beneath the collapse toggle + // It's only used to attach mouse enter/exit event handlers to control peeking + &__peek-hover-area { + margin-top: $sidebar-toggle-size; + display: grid; + grid-template-columns: 1fr; + overflow-y: auto; + overflow-x: hidden; + } } // This is a separate component as it needs to display in the header .sidebar-nav-toggle { - @include sidebar-toggle; - display: none; // Nav toggle is for mobile only - z-index: 305; + @include sidebar-toggle; + display: none; // Nav toggle is for mobile only + z-index: 305; + + &:hover { + background: var(--color-primary-dark); + color: #fff; + } + + &--mobile { + display: grid; + } + + &--open { + position: fixed; &:hover { - background: var(--color-primary-dark); - color: #fff; - } - - &--mobile { - display: grid; - } - - &--open { - position: fixed; - - &:hover { - background-color: #3a3a3a; - color: #ccc; - } + background-color: #3a3a3a; + color: #ccc; } + } } // stylelint-disable no-invalid-position-at-import-rule diff --git a/client/src/components/Sidebar/Sidebar.stories.tsx b/client/src/components/Sidebar/Sidebar.stories.tsx index c79b1e11be..6be5770c58 100644 --- a/client/src/components/Sidebar/Sidebar.stories.tsx +++ b/client/src/components/Sidebar/Sidebar.stories.tsx @@ -11,7 +11,10 @@ import { initFocusOutline } from '../../utils/focus'; import '../../../../wagtail/admin/static/wagtailadmin/css/sidebar.css'; -export default { title: 'Sidebar/Sidebar', parameters: { layout: 'fullscreen' } }; +export default { + title: 'Sidebar/Sidebar', + parameters: { layout: 'fullscreen' }, +}; const STRINGS: Strings = { DASHBOARD: 'Dashboard', @@ -21,11 +24,16 @@ const STRINGS: Strings = { function wagtailBrandingModule(): WagtailBrandingModuleDefinition { return new WagtailBrandingModuleDefinition('/admin/', { - mobileLogo: 'https://wagtail.org/static/wagtailadmin/images/wagtail-logo.svg', - desktopLogoBody: 'https://wagtail.org/static/wagtailadmin/images/logo-body.svg', - desktopLogoTail: 'https://wagtail.org/static/wagtailadmin/images/logo-tail.svg', - desktopLogoEyeOpen: 'https://wagtail.org/static/wagtailadmin/images/logo-eyeopen.svg', - desktopLogoEyeClosed: 'https://wagtail.org/static/wagtailadmin/images/logo-eyeclosed.svg' + mobileLogo: + 'https://wagtail.org/static/wagtailadmin/images/wagtail-logo.svg', + desktopLogoBody: + 'https://wagtail.org/static/wagtailadmin/images/logo-body.svg', + desktopLogoTail: + 'https://wagtail.org/static/wagtailadmin/images/logo-tail.svg', + desktopLogoEyeOpen: + 'https://wagtail.org/static/wagtailadmin/images/logo-eyeopen.svg', + desktopLogoEyeClosed: + 'https://wagtail.org/static/wagtailadmin/images/logo-eyeclosed.svg', }); } @@ -36,13 +44,16 @@ function searchModule(): SearchModuleDefinition { function bogStandardMenuModule(): MainMenuModuleDefinition { return new MainMenuModuleDefinition( [ - new PageExplorerMenuItemDefinition({ - name: 'explorer', - label: 'Pages', - url: '/admin/pages', - icon_name: 'folder-open-inverse', - classnames: '', - }, 1), + new PageExplorerMenuItemDefinition( + { + name: 'explorer', + label: 'Pages', + url: '/admin/pages', + icon_name: 'folder-open-inverse', + classnames: '', + }, + 1, + ), new LinkMenuItemDefinition({ name: 'images', label: 'Images', @@ -107,7 +118,7 @@ function bogStandardMenuModule(): MainMenuModuleDefinition { icon_name: 'history', classnames: '', }), - ] + ], ), new SubMenuItemDefinition( { @@ -167,7 +178,7 @@ function bogStandardMenuModule(): MainMenuModuleDefinition { icon_name: 'redirect', classnames: '', }), - ] + ], ), ], [ @@ -188,8 +199,9 @@ function bogStandardMenuModule(): MainMenuModuleDefinition { ], { name: 'Admin', - avatarUrl: 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', - } + avatarUrl: + 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', + }, ); } @@ -199,7 +211,8 @@ interface RenderSidebarStoryOptions { } function renderSidebarStory( - modules: ModuleDefinition[], { rtl = false, strings = null }: RenderSidebarStoryOptions = {} + modules: ModuleDefinition[], + { rtl = false, strings = null }: RenderSidebarStoryOptions = {}, ) { // Enable focus outlines so we can test them React.useEffect(() => { @@ -309,7 +322,7 @@ export function withNestedSubmenu() { icon_name: 'user', classnames: '', }), - ] + ], ), new SubMenuItemDefinition( { @@ -326,12 +339,12 @@ export function withNestedSubmenu() { icon_name: 'user', classnames: '', }), - ] - ) - ] - ) - ] - ) + ], + ), + ], + ), + ], + ), ); return renderSidebarStory([ @@ -341,7 +354,6 @@ export function withNestedSubmenu() { ]); } - export function withLargeSubmenu() { const menuModule = bogStandardMenuModule(); @@ -367,8 +379,8 @@ export function withLargeSubmenu() { classnames: '', footer_text: 'Footer text', }, - menuItems - ) + menuItems, + ), ); return renderSidebarStory([ @@ -379,10 +391,7 @@ export function withLargeSubmenu() { } export function withoutSearch() { - return renderSidebarStory([ - wagtailBrandingModule(), - bogStandardMenuModule(), - ]); + return renderSidebarStory([wagtailBrandingModule(), bogStandardMenuModule()]); } // Translations taken from actual translation files at the time the code was written @@ -469,7 +478,7 @@ function arabicMenuModule(): MainMenuModuleDefinition { icon_name: 'history', classnames: '', }), - ] + ], ), new SubMenuItemDefinition( { @@ -528,7 +537,7 @@ function arabicMenuModule(): MainMenuModuleDefinition { icon_name: 'redirect', classnames: '', }), - ] + ], ), ], [ @@ -549,15 +558,15 @@ function arabicMenuModule(): MainMenuModuleDefinition { ], { name: 'Admin', - avatarUrl: 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', - } + avatarUrl: + 'https://gravatar.com/avatar/e31ec811942afbf7b9ce0ac5affe426f?s=200&d=robohash&r=x', + }, ); } export function rightToLeft() { - return renderSidebarStory([ - wagtailBrandingModule(), - searchModule(), - arabicMenuModule(), - ], { rtl: true, strings: STRINGS_AR }); + return renderSidebarStory( + [wagtailBrandingModule(), searchModule(), arabicMenuModule()], + { rtl: true, strings: STRINGS_AR }, + ); } diff --git a/client/src/components/Sidebar/Sidebar.tsx b/client/src/components/Sidebar/Sidebar.tsx index 08649b8c5e..541709a1f4 100644 --- a/client/src/components/Sidebar/Sidebar.tsx +++ b/client/src/components/Sidebar/Sidebar.tsx @@ -7,10 +7,10 @@ export const SIDEBAR_TRANSITION_DURATION = 150; export interface Strings { DASHBOARD: string; - EDIT_YOUR_ACCOUNT: string, - SEARCH: string, - TOGGLE_SIDEBAR: string, - MAIN_MENU: string, + EDIT_YOUR_ACCOUNT: string; + SEARCH: string; + TOGGLE_SIDEBAR: string; + MAIN_MENU: string; } export interface ModuleRenderContext { @@ -35,8 +35,14 @@ export interface SidebarProps { onExpandCollapse?(collapsed: boolean); } -export const Sidebar: React.FunctionComponent<SidebarProps> = ( - { modules, currentPath, collapsedOnLoad, strings, navigate, onExpandCollapse }) => { +export const Sidebar: React.FunctionComponent<SidebarProps> = ({ + modules, + currentPath, + collapsedOnLoad, + strings, + navigate, + onExpandCollapse, +}) => { // 'collapsed' is a persistent state that is controlled by the arrow icon at the top // It records the user's general preference for a collapsed/uncollapsed menu // This is just a hint though, and we may still collapse the menu if the screen is too small @@ -85,7 +91,8 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ( const slim = collapsed && !peeking && !isMobile; // 'expandingOrCollapsing' is set to true whilst the the menu is transitioning between slim and expanded layouts - const [expandingOrCollapsing, setExpandingOrCollapsing] = React.useState(false); + const [expandingOrCollapsing, setExpandingOrCollapsing] = + React.useState(false); React.useEffect(() => { setExpandingOrCollapsing(true); const finishTimeout = setTimeout(() => { @@ -155,25 +162,25 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ( }, [mouseHover, focused]); // Render modules - const renderedModules = modules.map( - (module, index) => module.render({ + const renderedModules = modules.map((module, index) => + module.render({ key: index, slim, expandingOrCollapsing, currentPath, strings, - navigate - }) + navigate, + }), ); return ( <> <div className={ - 'sidebar' - + (slim ? ' sidebar--slim' : '') - + (isMobile ? ' sidebar--mobile' : '') - + ((isMobile && !visibleOnMobile) ? ' sidebar--hidden' : '') + 'sidebar' + + (slim ? ' sidebar--slim' : '') + + (isMobile ? ' sidebar--mobile' : '') + + (isMobile && !visibleOnMobile ? ' sidebar--hidden' : '') } > <div className="sidebar__inner"> @@ -183,7 +190,11 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ( aria-expanded={!slim} className="button sidebar__collapse-toggle" > - {collapsed ? <Icon name="angle-double-right" /> : <Icon name="angle-double-left" />} + {collapsed ? ( + <Icon name="angle-double-right" /> + ) : ( + <Icon name="angle-double-left" /> + )} </button> <div @@ -202,9 +213,9 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = ( aria-label={strings.TOGGLE_SIDEBAR} aria-expanded={visibleOnMobile} className={ - 'button sidebar-nav-toggle' - + (isMobile ? ' sidebar-nav-toggle--mobile' : '') - + (visibleOnMobile ? ' sidebar-nav-toggle--open' : '') + 'button sidebar-nav-toggle' + + (isMobile ? ' sidebar-nav-toggle--mobile' : '') + + (visibleOnMobile ? ' sidebar-nav-toggle--open' : '') } > {visibleOnMobile ? <Icon name="cross" /> : <Icon name="bars" />} diff --git a/client/src/components/Sidebar/SidebarPanel.scss b/client/src/components/Sidebar/SidebarPanel.scss index 995a5b180c..05d5073f06 100644 --- a/client/src/components/Sidebar/SidebarPanel.scss +++ b/client/src/components/Sidebar/SidebarPanel.scss @@ -1,53 +1,53 @@ .sidebar-panel { - // With CSS variable allows panels with different widths to animate properly - --width: #{$menu-width}; + // With CSS variable allows panels with different widths to animate properly + --width: #{$menu-width}; - visibility: hidden; - transform: translate3d(0, 0, 0); - position: fixed; - height: 100vh; - padding: 0; - top: 0; - left: 0; - z-index: 400; - display: flex; - flex-direction: column; - overflow: hidden; + visibility: hidden; + transform: translate3d(0, 0, 0); + position: fixed; + height: 100vh; + padding: 0; + top: 0; + left: 0; + z-index: 400; + display: flex; + flex-direction: column; + overflow: hidden; - @include transition(left $menu-transition-duration ease); + @include transition(left $menu-transition-duration ease); - @include media-breakpoint-up(sm) { - z-index: var(--z-index); - width: var(--width); - left: calc(#{$menu-width} - var(--width)); + @include media-breakpoint-up(sm) { + z-index: var(--z-index); + width: var(--width); + left: calc(#{$menu-width} - var(--width)); + } + + @media (forced-colors: $media-forced-colours) { + border-inline-start: 1px solid transparent; + border-inline-end: 1px solid transparent; + } + + &--visible { + visibility: visible; + box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35); + } + + @include media-breakpoint-up(sm) { + @at-root .sidebar--slim #{&} { + left: calc(#{$menu-width-slim} - var(--width)); + } + // Don't apply this to nested submenus though + @at-root .sidebar--slim .sidebar-panel #{&} { + left: 0; } - @media (forced-colors: $media-forced-colours) { - border-inline-start: 1px solid transparent; - border-inline-end: 1px solid transparent; - } - - &--visible { - visibility: visible; - box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35); - } - - @include media-breakpoint-up(sm) { - @at-root .sidebar--slim #{&} { - left: calc(#{$menu-width-slim} - var(--width)); - } - // Don't apply this to nested submenus though - @at-root .sidebar--slim .sidebar-panel #{&} { - left: 0; - } - - &--open { - left: $menu-width; - - // Don't apply this to nested submenus though - @at-root .sidebar--slim .sidebar-panel #{&} { - left: $menu-width; - } - } + &--open { + left: $menu-width; + + // Don't apply this to nested submenus though + @at-root .sidebar--slim .sidebar-panel #{&} { + left: $menu-width; + } } + } } diff --git a/client/src/components/Sidebar/SidebarPanel.tsx b/client/src/components/Sidebar/SidebarPanel.tsx index 5da8c69659..a62d1bffb1 100644 --- a/client/src/components/Sidebar/SidebarPanel.tsx +++ b/client/src/components/Sidebar/SidebarPanel.tsx @@ -1,5 +1,3 @@ - - import * as React from 'react'; export interface SidebarPanelProps { @@ -9,13 +7,17 @@ export interface SidebarPanelProps { widthPx?: number; } -export const SidebarPanel: React.FunctionComponent<SidebarPanelProps> = ( - { isVisible, isOpen, depth, widthPx, children }) => { - const className = ( - 'sidebar-panel' - + (isVisible ? ' sidebar-panel--visible' : '') - + (isOpen ? ' sidebar-panel--open' : '') - ); +export const SidebarPanel: React.FunctionComponent<SidebarPanelProps> = ({ + isVisible, + isOpen, + depth, + widthPx, + children, +}) => { + const className = + 'sidebar-panel' + + (isVisible ? ' sidebar-panel--visible' : '') + + (isOpen ? ' sidebar-panel--open' : ''); let zIndex = -depth * 2; diff --git a/client/src/components/Sidebar/index.tsx b/client/src/components/Sidebar/index.tsx index cbac09a55b..8d0106d39f 100644 --- a/client/src/components/Sidebar/index.tsx +++ b/client/src/components/Sidebar/index.tsx @@ -26,7 +26,9 @@ export function initSidebar() { const collapsedCookie: any = Cookies.get(SIDEBAR_COLLAPSED_COOKIE_NAME); // Cast to boolean - const collapsed = !((collapsedCookie === undefined || collapsedCookie === '0')); + const collapsed = !( + collapsedCookie === undefined || collapsedCookie === '0' + ); const onExpandCollapse = (_collapsed: boolean) => { if (_collapsed) { @@ -50,7 +52,7 @@ export function initSidebar() { element, () => { document.body.classList.add('ready'); - } + }, ); } } diff --git a/client/src/components/Sidebar/menu/LinkMenuItem.tsx b/client/src/components/Sidebar/menu/LinkMenuItem.tsx index 85b3459d1a..5be0c0a933 100644 --- a/client/src/components/Sidebar/menu/LinkMenuItem.tsx +++ b/client/src/components/Sidebar/menu/LinkMenuItem.tsx @@ -1,24 +1,18 @@ - - import * as React from 'react'; import Icon from '../../Icon/Icon'; import { MenuItemDefinition, MenuItemProps } from './MenuItem'; -export const LinkMenuItem: React.FunctionComponent<MenuItemProps<LinkMenuItemDefinition>> = ( - { item, path, state, dispatch, navigate }) => { +export const LinkMenuItem: React.FunctionComponent< + MenuItemProps<LinkMenuItemDefinition> +> = ({ item, path, state, dispatch, navigate }) => { const isCurrent = state.activePath === path; const isActive = state.activePath.startsWith(path); const isInSubMenu = path.split('.').length > 2; const onClick = (e: React.MouseEvent) => { // Do not capture click events with modifier keys or non-main buttons. - if ( - e.ctrlKey || - e.shiftKey || - e.metaKey || - (e.button && e.button !== 0) - ) { + if (e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button !== 0)) { return; } @@ -39,11 +33,10 @@ export const LinkMenuItem: React.FunctionComponent<MenuItemProps<LinkMenuItemDef }); }; - const className = ( - 'sidebar-menu-item' - + (isActive ? ' sidebar-menu-item--active' : '') - + (isInSubMenu ? ' sidebar-menu-item--in-sub-menu' : '') - ); + const className = + 'sidebar-menu-item' + + (isActive ? ' sidebar-menu-item--active' : '') + + (isInSubMenu ? ' sidebar-menu-item--in-sub-menu' : ''); return ( <li className={className}> @@ -53,7 +46,9 @@ export const LinkMenuItem: React.FunctionComponent<MenuItemProps<LinkMenuItemDef onClick={onClick} className={`sidebar-menu-item__link ${item.classNames}`} > - {item.iconName && <Icon name={item.iconName} className="icon--menuitem" />} + {item.iconName && ( + <Icon name={item.iconName} className="icon--menuitem" /> + )} <span className="menuitem-label">{item.label}</span> </a> </li> @@ -67,7 +62,13 @@ export class LinkMenuItemDefinition implements MenuItemDefinition { iconName: string | null; classNames?: string; - constructor({ name, label, url, icon_name: iconName = null, classnames = undefined }) { + constructor({ + name, + label, + url, + icon_name: iconName = null, + classnames = undefined, + }) { this.name = name; this.label = label; this.url = url; diff --git a/client/src/components/Sidebar/menu/MenuItem.scss b/client/src/components/Sidebar/menu/MenuItem.scss index 943d84b274..0d2aef05c0 100644 --- a/client/src/components/Sidebar/menu/MenuItem.scss +++ b/client/src/components/Sidebar/menu/MenuItem.scss @@ -1,91 +1,94 @@ // stylelint-disable declaration-no-important .sidebar-menu-item { - $root: &; - @include transition(border-color $menu-transition-duration ease); + $root: &; + @include transition(border-color $menu-transition-duration ease); + position: relative; + + &__link { + @include transition( + border-color $menu-transition-duration ease, + background-color $menu-transition-duration ease + ); position: relative; + display: block; + width: 100%; + box-sizing: border-box; + white-space: nowrap; + border-left: 3px solid transparent; - &__link { - @include transition(border-color $menu-transition-duration ease, background-color $menu-transition-duration ease); - position: relative; - display: block; - width: 100%; - box-sizing: border-box; - white-space: nowrap; - border-left: 3px solid transparent; + -webkit-font-smoothing: auto; + border: 0; + background: transparent; + text-align: left; + color: $color-menu-text; + padding: 11px 20px; + font-size: 13px; + font-weight: 400; - -webkit-font-smoothing: auto; - border: 0; - background: transparent; - text-align: left; - color: $color-menu-text; - padding: 11px 20px; - font-size: 13px; - font-weight: 400; - - // Note, font-weights lower than normal, - // and font-size smaller than 1em (80% ~= 12.8px), - // makes the strokes thinner than 1px on non-retina screens - // making the text semi-transparent - &:hover, - &:focus { - background-color: $nav-item-hover-bg; - color: $color-white; - text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - } - - &:before { - font-size: 1rem; - vertical-align: -15%; - margin-right: 0.5em; - } - - // only really used for spinners and settings menu - &:after { - font-size: 1.5em; - margin: 0; - position: absolute; - right: 0.5em; - top: 0.5em; - margin-top: 0; - } - - // Disable icon margin, this is instead applied to the left of the .menuitem-label - // This is because SVG icons and legacy icons apply this margin differently, - // we could remove this override when we remove legacy icons - .icon { - margin-right: 0 !important; - } + // Note, font-weights lower than normal, + // and font-size smaller than 1em (80% ~= 12.8px), + // makes the strokes thinner than 1px on non-retina screens + // making the text semi-transparent + &:hover, + &:focus { + background-color: $nav-item-hover-bg; + color: $color-white; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); } - &--in-sub-menu { - #{$root}__link { - // Links inside a submenu should have normal wrapping - white-space: normal; - } - - &:hover { - background-color: rgba(100, 100, 100, 0.2); - } + &:before { + font-size: 1rem; + vertical-align: -15%; + margin-right: 0.5em; } - &--active { - background: $nav-item-active-bg; - text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - - > a { - border-left-color: $color-salmon; - color: $color-white; - } + // only really used for spinners and settings menu + &:after { + font-size: 1.5em; + margin: 0; + position: absolute; + right: 0.5em; + top: 0.5em; + margin-top: 0; } + + // Disable icon margin, this is instead applied to the left of the .menuitem-label + // This is because SVG icons and legacy icons apply this margin differently, + // we could remove this override when we remove legacy icons + .icon { + margin-right: 0 !important; + } + } + + &--in-sub-menu { + #{$root}__link { + // Links inside a submenu should have normal wrapping + white-space: normal; + } + + &:hover { + background-color: rgba(100, 100, 100, 0.2); + } + } + + &--active { + background: $nav-item-active-bg; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); + + > a { + border-left-color: $color-salmon; + color: $color-white; + } + } } .menuitem-label { - @include transition(opacity $menu-transition-duration ease); - margin-left: 0.5em; + @include transition(opacity $menu-transition-duration ease); + margin-left: 0.5em; } .sidebar--slim { - .menuitem-label { - opacity: 0; - } + .menuitem-label { + opacity: 0; + } } diff --git a/client/src/components/Sidebar/menu/MenuItem.tsx b/client/src/components/Sidebar/menu/MenuItem.tsx index 466802dbf6..329355dee2 100644 --- a/client/src/components/Sidebar/menu/MenuItem.tsx +++ b/client/src/components/Sidebar/menu/MenuItem.tsx @@ -23,4 +23,3 @@ export interface MenuItemProps<T> { dispatch(action: MenuAction): void; navigate(url: string): Promise<void>; } - diff --git a/client/src/components/Sidebar/menu/PageExplorerMenuItem.tsx b/client/src/components/Sidebar/menu/PageExplorerMenuItem.tsx index 8786ae8b27..862cc00bd0 100644 --- a/client/src/components/Sidebar/menu/PageExplorerMenuItem.tsx +++ b/client/src/components/Sidebar/menu/PageExplorerMenuItem.tsx @@ -1,5 +1,3 @@ - - import * as React from 'react'; import Button from '../../Button/Button'; @@ -8,12 +6,16 @@ import { MenuItemProps } from './MenuItem'; import { LinkMenuItemDefinition } from './LinkMenuItem'; import { Provider } from 'react-redux'; import PageExplorer, { initPageExplorerStore } from '../../PageExplorer'; -import { openPageExplorer, closePageExplorer } from '../../PageExplorer/actions'; +import { + openPageExplorer, + closePageExplorer, +} from '../../PageExplorer/actions'; import { SidebarPanel } from '../SidebarPanel'; import { SIDEBAR_TRANSITION_DURATION } from '../Sidebar'; -export const PageExplorerMenuItem: React.FunctionComponent<MenuItemProps<PageExplorerMenuItemDefinition>> = ( - { path, item, state, dispatch, navigate }) => { +export const PageExplorerMenuItem: React.FunctionComponent< + MenuItemProps<PageExplorerMenuItemDefinition> +> = ({ path, item, state, dispatch, navigate }) => { const isOpen = state.navigationPath.startsWith(path); const isActive = isOpen || state.activePath.startsWith(path); const depth = path.split('.').length; @@ -62,31 +64,38 @@ export const PageExplorerMenuItem: React.FunctionComponent<MenuItemProps<PageExp } }; - const className = ( - 'sidebar-menu-item' - + (isActive ? ' sidebar-menu-item--active' : '') - + (isInSubMenu ? ' sidebar-menu-item--in-sub-menu' : '') - ); + const className = + 'sidebar-menu-item' + + (isActive ? ' sidebar-menu-item--active' : '') + + (isInSubMenu ? ' sidebar-menu-item--in-sub-menu' : ''); - const sidebarTriggerIconClassName = ( - 'sidebar-sub-menu-trigger-icon' - + (isOpen ? ' sidebar-sub-menu-trigger-icon--open' : '') - ); + const sidebarTriggerIconClassName = + 'sidebar-sub-menu-trigger-icon' + + (isOpen ? ' sidebar-sub-menu-trigger-icon--open' : ''); return ( <li className={className}> - <Button dialogTrigger={true} onClick={onClick} className="sidebar-menu-item__link"> + <Button + dialogTrigger={true} + onClick={onClick} + className="sidebar-menu-item__link" + > <Icon name="folder-open-inverse" className="icon--menuitem" /> <span className="menuitem-label">{item.label}</span> <Icon className={sidebarTriggerIconClassName} name="arrow-right" /> </Button> <div> - <SidebarPanel isVisible={isVisible} isOpen={isOpen} depth={depth} widthPx={485}> - {store.current && + <SidebarPanel + isVisible={isVisible} + isOpen={isOpen} + depth={depth} + widthPx={485} + > + {store.current && ( <Provider store={store.current}> <PageExplorer isVisible={isVisible} navigate={navigate} /> </Provider> - } + )} </SidebarPanel> </div> </li> @@ -96,7 +105,10 @@ export const PageExplorerMenuItem: React.FunctionComponent<MenuItemProps<PageExp export class PageExplorerMenuItemDefinition extends LinkMenuItemDefinition { startPageId: number; - constructor({ name, label, url, icon_name: iconName = null, classnames = undefined }, startPageId: number) { + constructor( + { name, label, url, icon_name: iconName = null, classnames = undefined }, + startPageId: number, + ) { super({ name, label, url, icon_name: iconName, classnames }); this.startPageId = startPageId; } diff --git a/client/src/components/Sidebar/menu/SubMenuItem.scss b/client/src/components/Sidebar/menu/SubMenuItem.scss index d4f6beec56..67eb6f0110 100644 --- a/client/src/components/Sidebar/menu/SubMenuItem.scss +++ b/client/src/components/Sidebar/menu/SubMenuItem.scss @@ -1,117 +1,119 @@ .sidebar-sub-menu-trigger-icon { - display: block; - width: 20px; - height: 20px; - position: absolute; - right: 15px; - top: 0; - bottom: 0; - margin: auto 0; + display: block; + width: 20px; + height: 20px; + position: absolute; + right: 15px; + top: 0; + bottom: 0; + margin: auto 0; - @include transition(transform $menu-transition-duration ease, width $menu-transition-duration ease, height $menu-transition-duration ease); + @include transition( + transform $menu-transition-duration ease, + width $menu-transition-duration ease, + height $menu-transition-duration ease + ); - &--open { - transform-origin: 50% 50%; - transform: rotate(180deg); - } + &--open { + transform-origin: 50% 50%; + transform: rotate(180deg); + } - .sidebar--slim & { - width: 16px; - height: 16px; - transform: translate3d(13px, 0, 0); - } + .sidebar--slim & { + width: 16px; + height: 16px; + transform: translate3d(13px, 0, 0); + } } - .sidebar-sub-menu-panel { - display: flex; - flex-direction: column; - background: $nav-submenu-bg; + display: flex; + flex-direction: column; + background: $nav-submenu-bg; + width: $menu-width; + height: 100vh; + + > h2, + &__list { width: $menu-width; - height: 100vh; + } - > h2, - &__list { - width: $menu-width; + > h2 { + display: block; + padding: 0.2em 0; + font-size: 1.2em; + font-weight: 500; + text-transform: none; + text-align: center; + color: $color-menu-text; + + &:before { + font-size: 4em; + display: block; + text-align: center; + margin: 0 0 0.2em; + width: 100%; + opacity: 0.15; } + } - > h2 { - display: block; - padding: 0.2em 0; - font-size: 1.2em; - font-weight: 500; - text-transform: none; - text-align: center; - color: $color-menu-text; + ul > li { + position: relative; - &:before { - font-size: 4em; - display: block; - text-align: center; - margin: 0 0 0.2em; - width: 100%; - opacity: 0.15; - } - } + @include transition(border-color $menu-transition-duration ease); + } + > ul { + flex-grow: 1; + padding: 0; + margin: 0; + overflow-y: auto; + } - ul > li { - position: relative; + > ul > li { + border: 0; + } - @include transition(border-color $menu-transition-duration ease); - } + &__footer { + margin: 0; + padding: 0.9em 1.7em; + text-align: center; + color: $color-menu-text; + } - > ul { - flex-grow: 1; - padding: 0; - margin: 0; - overflow-y: auto; - } + &--visible { + visibility: visible; + box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35); + } - > ul > li { - border: 0; - } + @at-root .sidebar--slim #{&} { + transform: translate3d($menu-width-slim - $menu-width, 0, 0); + } + // Don't apply this to nested submenus though + @at-root .sidebar--slim .sidebar-sub-menu-panel #{&} { + transform: translate3d(0, 0, 0); + } - &__footer { - margin: 0; - padding: 0.9em 1.7em; - text-align: center; - color: $color-menu-text; - } + &--open { + transform: translate3d($menu-width, 0, 0); - &--visible { - visibility: visible; - box-shadow: 2px 0 2px rgba(0, 0, 0, 0.35); - } + // If another submenu is opening, display this menu behind it + z-index: -1; @at-root .sidebar--slim #{&} { - transform: translate3d($menu-width-slim - $menu-width, 0, 0); + transform: translate3d($menu-width-slim, 0, 0); } // Don't apply this to nested submenus though @at-root .sidebar--slim .sidebar-sub-menu-panel #{&} { - transform: translate3d(0, 0, 0); - } - - &--open { - transform: translate3d($menu-width, 0, 0); - - // If another submenu is opening, display this menu behind it - z-index: -1; - - @at-root .sidebar--slim #{&} { - transform: translate3d($menu-width-slim, 0, 0); - } - // Don't apply this to nested submenus though - @at-root .sidebar--slim .sidebar-sub-menu-panel #{&} { - transform: translate3d($menu-width, 0, 0); - } + transform: translate3d($menu-width, 0, 0); } + } } .sidebar-sub-menu-item { - &--open { - > a { - text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - } + &--open { + > a { + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); } + } } diff --git a/client/src/components/Sidebar/menu/SubMenuItem.tsx b/client/src/components/Sidebar/menu/SubMenuItem.tsx index f33bc60ebf..1efbb9162a 100644 --- a/client/src/components/Sidebar/menu/SubMenuItem.tsx +++ b/client/src/components/Sidebar/menu/SubMenuItem.tsx @@ -1,5 +1,3 @@ - - import * as React from 'react'; import Icon from '../../Icon/Icon'; @@ -13,8 +11,14 @@ interface SubMenuItemProps extends MenuItemProps<SubMenuItemDefinition> { slim: boolean; } -export const SubMenuItem: React.FunctionComponent<SubMenuItemProps> = ( - { path, item, slim, state, dispatch, navigate }) => { +export const SubMenuItem: React.FunctionComponent<SubMenuItemProps> = ({ + path, + item, + slim, + state, + dispatch, + navigate, +}) => { const isOpen = state.navigationPath.startsWith(path); const isActive = isOpen || state.activePath.startsWith(path); const depth = path.split('.').length; @@ -52,16 +56,14 @@ export const SubMenuItem: React.FunctionComponent<SubMenuItemProps> = ( e.preventDefault(); }; - const className = ( - 'sidebar-menu-item sidebar-sub-menu-item' - + (isActive ? ' sidebar-menu-item--active' : '') - + (isOpen ? ' sidebar-sub-menu-item--open' : '') - ); + const className = + 'sidebar-menu-item sidebar-sub-menu-item' + + (isActive ? ' sidebar-menu-item--active' : '') + + (isOpen ? ' sidebar-sub-menu-item--open' : ''); - const sidebarTriggerIconClassName = ( - 'sidebar-sub-menu-trigger-icon' - + (isOpen ? ' sidebar-sub-menu-trigger-icon--open' : '') - ); + const sidebarTriggerIconClassName = + 'sidebar-sub-menu-trigger-icon' + + (isOpen ? ' sidebar-sub-menu-trigger-icon--open' : ''); return ( <li className={className}> @@ -71,20 +73,33 @@ export const SubMenuItem: React.FunctionComponent<SubMenuItemProps> = ( aria-haspopup="true" aria-expanded={isOpen ? 'true' : 'false'} > - {item.iconName && <Icon name={item.iconName} className="icon--menuitem" />} + {item.iconName && ( + <Icon name={item.iconName} className="icon--menuitem" /> + )} <span className="menuitem-label">{item.label}</span> <Icon className={sidebarTriggerIconClassName} name="arrow-right" /> </button> <SidebarPanel isVisible={isVisible} isOpen={isOpen} depth={depth}> <div className="sidebar-sub-menu-panel"> - <h2 id={`wagtail-sidebar-submenu${path.split('.').join('-')}-title`} className={item.classNames}> - {item.iconName && <Icon name={item.iconName} className="icon--submenu-header" />} + <h2 + id={`wagtail-sidebar-submenu${path.split('.').join('-')}-title`} + className={item.classNames} + > + {item.iconName && ( + <Icon name={item.iconName} className="icon--submenu-header" /> + )} {item.label} </h2> - <ul aria-labelledby={`wagtail-sidebar-submenu${path.split('.').join('-')}-title`}> + <ul + aria-labelledby={`wagtail-sidebar-submenu${path + .split('.') + .join('-')}-title`} + > {renderMenu(path, item.menuItems, slim, state, dispatch, navigate)} </ul> - {item.footerText && <p className="sidebar-sub-menu-panel__footer">{item.footerText}</p>} + {item.footerText && ( + <p className="sidebar-sub-menu-panel__footer">{item.footerText}</p> + )} </div> </SidebarPanel> </li> @@ -107,7 +122,7 @@ export class SubMenuItemDefinition implements MenuItemDefinition { classnames = undefined, footer_text: footerText = '', }: any, - menuItems: MenuItemDefinition[] + menuItems: MenuItemDefinition[], ) { this.name = name; this.label = label; diff --git a/client/src/components/Sidebar/modules/MainMenu.scss b/client/src/components/Sidebar/modules/MainMenu.scss index 448ce92cca..519a728ebb 100644 --- a/client/src/components/Sidebar/modules/MainMenu.scss +++ b/client/src/components/Sidebar/modules/MainMenu.scss @@ -2,173 +2,173 @@ $avatar-size: 60px; // stylelint-disable declaration-no-important .sidebar-main-menu { - overflow: auto; - overflow-x: hidden; - margin-bottom: $avatar-size; - @include transition(margin-bottom $menu-transition-duration ease); + overflow: auto; + overflow-x: hidden; + margin-bottom: $avatar-size; + @include transition(margin-bottom $menu-transition-duration ease); - &--open-footer { - margin-bottom: 127px; - } - - &__list { - margin: 0; - padding: 0; - list-style-type: none; - } - - *:focus { - @include show-focus-outline-inside; - } - - .icon--menuitem { - width: 1.25em; - height: 1.25em; - min-width: 1.25em; - margin-right: 0.5em; - vertical-align: text-top; - } - - .icon--submenu-header { - display: block; - width: 4rem; - height: 4rem; - margin: 0 auto 0.8em; - opacity: 0.15; - } - - > ul > li > a { - // Need !important to override body.ready class - transition: padding $menu-transition-duration ease !important; - - .menuitem-label { - transition: opacity $menu-transition-duration ease; - } + &--open-footer { + margin-bottom: 127px; + } + + &__list { + margin: 0; + padding: 0; + list-style-type: none; + } + + *:focus { + @include show-focus-outline-inside; + } + + .icon--menuitem { + width: 1.25em; + height: 1.25em; + min-width: 1.25em; + margin-right: 0.5em; + vertical-align: text-top; + } + + .icon--submenu-header { + display: block; + width: 4rem; + height: 4rem; + margin: 0 auto 0.8em; + opacity: 0.15; + } + + > ul > li > a { + // Need !important to override body.ready class + transition: padding $menu-transition-duration ease !important; + + .menuitem-label { + transition: opacity $menu-transition-duration ease; } + } } .sidebar-footer { - position: fixed !important; // override li styling in MenuWrapper - width: $menu-width; - bottom: 0; - background-color: $nav-footer-submenu-bg; - transition: width $menu-transition-duration ease !important; // Override body.ready + position: fixed !important; // override li styling in MenuWrapper + width: $menu-width; + bottom: 0; + background-color: $nav-footer-submenu-bg; + transition: width $menu-transition-duration ease !important; // Override body.ready - > ul, - ul > li { - margin: 0; - padding: 0; - list-style-type: none; + > ul, + ul > li { + margin: 0; + padding: 0; + list-style-type: none; + } + + ul > li { + position: relative; + + @include transition(border-color $menu-transition-duration ease); + } + + > ul { + @include transition(max-height $menu-transition-duration ease); + visibility: hidden; + + max-height: 0; + + a { + border-left: 3px solid transparent; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &:before { + font-size: 1rem; + margin-right: 0.5em; + vertical-align: -10%; + } + } + } + + &__account { + background: $nav-footer-account-bg; + color: $color-menu-text; + text-transform: uppercase; + display: flex; + align-items: center; + cursor: pointer; + position: relative; + padding: 0; + width: 100%; + appearance: none; + border: 0; + overflow: hidden; + + @include clearfix; + + &:hover, + &:focus { + background-color: rgba(100, 100, 100, 0.15); + color: $color-white; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); } - ul > li { - position: relative; + .avatar { + width: $avatar-size; + height: $avatar-size; - @include transition(border-color $menu-transition-duration ease); + &:before { + color: inherit; + border-color: inherit; + } } + $icon-size: 1.5em; + + &-icon { + height: $icon-size; + width: $icon-size; + } + + &-toggle { + @include show-focus-outline-inside(); + display: grid; + grid-template-columns: 1fr min-content; + box-sizing: border-box; + padding: 0 0.9em; + font-style: normal; + font-weight: 700; + width: $menu-width - $avatar-size; + line-height: $icon-size; + position: absolute; + left: $avatar-size; // Width of avatar + transform: translateX(0); + transition: transform $menu-transition-duration ease; + } + + &-label { + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + + @at-root .sidebar--slim #{&} { + width: $menu-width-slim; + + &__account-toggle { + transform: translateX($menu-width-slim - $menu-width); + } + } + + &--open { + width: $menu-width !important; // Override collapsed style + > ul { - @include transition(max-height $menu-transition-duration ease); - visibility: hidden; - - max-height: 0; - - a { - border-left: 3px solid transparent; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - &:before { - font-size: 1rem; - margin-right: 0.5em; - vertical-align: -10%; - } - } + max-height: $nav-footer-submenu-height; + visibility: visible; } + } - &__account { - background: $nav-footer-account-bg; - color: $color-menu-text; - text-transform: uppercase; - display: flex; - align-items: center; - cursor: pointer; - position: relative; - padding: 0; - width: 100%; - appearance: none; - border: 0; - overflow: hidden; - - @include clearfix; - - &:hover, - &:focus { - background-color: rgba(100, 100, 100, 0.15); - color: $color-white; - text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.3); - } - - .avatar { - width: $avatar-size; - height: $avatar-size; - - &:before { - color: inherit; - border-color: inherit; - } - } - - $icon-size: 1.5em; - - &-icon { - height: $icon-size; - width: $icon-size; - } - - &-toggle { - @include show-focus-outline-inside(); - display: grid; - grid-template-columns: 1fr min-content; - box-sizing: border-box; - padding: 0 0.9em; - font-style: normal; - font-weight: 700; - width: $menu-width - $avatar-size; - line-height: $icon-size; - position: absolute; - left: $avatar-size; // Width of avatar - transform: translateX(0); - transition: transform $menu-transition-duration ease; - } - - &-label { - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } - - @at-root .sidebar--slim #{&} { - width: $menu-width-slim; - - &__account-toggle { - transform: translateX($menu-width-slim - $menu-width); - } - } - - &--open { - width: $menu-width !important; // Override collapsed style - - > ul { - max-height: $nav-footer-submenu-height; - visibility: visible; - } - } - - &--open &__account-toggle { - left: $avatar-size !important; - } + &--open &__account-toggle { + left: $avatar-size !important; + } } diff --git a/client/src/components/Sidebar/modules/MainMenu.tsx b/client/src/components/Sidebar/modules/MainMenu.tsx index 271237b998..a42be6d390 100644 --- a/client/src/components/Sidebar/modules/MainMenu.tsx +++ b/client/src/components/Sidebar/modules/MainMenu.tsx @@ -12,29 +12,31 @@ export function renderMenu( slim: boolean, state: MenuState, dispatch: (action: MenuAction) => void, - navigate: (url: string) => Promise<void> + navigate: (url: string) => Promise<void>, ) { return ( <> - {items.map(item => item.render({ - path: `${path}.${item.name}`, - slim, - state, - dispatch, - navigate, - }))} + {items.map((item) => + item.render({ + path: `${path}.${item.name}`, + slim, + state, + dispatch, + navigate, + }), + )} </> ); } interface SetActivePath { - type: 'set-active-path', - path: string, + type: 'set-active-path'; + path: string; } interface SetNavigationPath { - type: 'set-navigation-path', - path: string, + type: 'set-navigation-path'; + path: string; } export type MenuAction = SetActivePath | SetNavigationPath; @@ -67,8 +69,16 @@ interface MenuProps { navigate(url: string): Promise<void>; } -export const Menu: React.FunctionComponent<MenuProps> = ( - { menuItems, accountMenuItems, user, expandingOrCollapsing, slim, currentPath, strings, navigate }) => { +export const Menu: React.FunctionComponent<MenuProps> = ({ + menuItems, + accountMenuItems, + user, + expandingOrCollapsing, + slim, + currentPath, + strings, + navigate, +}) => { // navigationPath and activePath are two dot-delimited path's referencing a menu item // They are created by concatenating the name fields of all the menu/sub-menu items leading to the relevant one. // For example, the "Users" item in the "Settings" sub-menu would have the path 'settings.users' @@ -123,7 +133,7 @@ export const Menu: React.FunctionComponent<MenuProps> = ( if (e.key === 'Escape' || e.key === 'Esc') { dispatch({ type: 'set-navigation-path', - path: '' + path: '', }); } }; @@ -135,7 +145,7 @@ export const Menu: React.FunctionComponent<MenuProps> = ( if (!isInside) { dispatch({ type: 'set-navigation-path', - path: '' + path: '', }); } }; @@ -156,7 +166,7 @@ export const Menu: React.FunctionComponent<MenuProps> = ( if (expandingOrCollapsing) { dispatch({ type: 'set-navigation-path', - path: '' + path: '', }); } }, [expandingOrCollapsing]); @@ -177,10 +187,9 @@ export const Menu: React.FunctionComponent<MenuProps> = ( } }; - const className = ( - 'sidebar-main-menu' - + (accountSettingsOpen ? ' sidebar-main-menu--open-footer' : '') - ); + const className = + 'sidebar-main-menu' + + (accountSettingsOpen ? ' sidebar-main-menu--open-footer' : ''); return ( <> @@ -189,7 +198,12 @@ export const Menu: React.FunctionComponent<MenuProps> = ( {renderMenu('', menuItems, slim, state, dispatch, navigate)} </ul> </nav> - <div className={'sidebar-footer' + (accountSettingsOpen ? ' sidebar-footer--open' : '')}> + <div + className={ + 'sidebar-footer' + + (accountSettingsOpen ? ' sidebar-footer--open' : '') + } + > <button className="sidebar-footer__account" title={strings.EDIT_YOUR_ACCOUNT} @@ -205,7 +219,7 @@ export const Menu: React.FunctionComponent<MenuProps> = ( <div className="sidebar-footer__account-label">{user.name}</div> <Icon className="sidebar-footer__account-icon" - name={(accountSettingsOpen ? 'arrow-down' : 'arrow-up')} + name={accountSettingsOpen ? 'arrow-down' : 'arrow-up'} /> </div> </button> @@ -222,14 +236,14 @@ export class MainMenuModuleDefinition implements ModuleDefinition { menuItems: MenuItemDefinition[]; accountMenuItems: MenuItemDefinition[]; user: { - name: string; - avatarUrl: string; + name: string; + avatarUrl: string; }; constructor( menuItems: MenuItemDefinition[], accountMenuItems: MenuItemDefinition[], - user: MainMenuModuleDefinition['user'] + user: MainMenuModuleDefinition['user'], ) { this.menuItems = menuItems; this.accountMenuItems = accountMenuItems; diff --git a/client/src/components/Sidebar/modules/Search.scss b/client/src/components/Sidebar/modules/Search.scss index 2add64157e..49f7d0326f 100644 --- a/client/src/components/Sidebar/modules/Search.scss +++ b/client/src/components/Sidebar/modules/Search.scss @@ -1,75 +1,77 @@ // stylelint-disable declaration-no-important .sidebar-search { - $root: &; - position: relative; - box-sizing: border-box; - padding: 0 20px; - display: flex; - align-items: center; - flex-direction: row-reverse; - height: 42px; + $root: &; + position: relative; + box-sizing: border-box; + padding: 0 20px; + display: flex; + align-items: center; + flex-direction: row-reverse; + height: 42px; + + .sidebar--slim & { + padding: 0; + } + + &__label { + @include visuallyhidden; + } + + // Beat specificity + input:not([type='submit']) { + @include show-focus-outline-inside(); + position: absolute; + left: 0; + top: 0; + // Need !important to override body.ready class + transition: background-color $menu-transition-duration ease, + opacity $menu-transition-duration ease !important; + font-size: 13px; + font-weight: 400; + background-color: transparent; + border: 0; + border-radius: 0; + color: $color-menu-text; + -webkit-font-smoothing: auto; .sidebar--slim & { - padding: 0; + opacity: 0; } - &__label { - @include visuallyhidden; + &::placeholder { + color: $color-menu-text; + } + } + + &__submit { + @include show-focus-outline-inside(); + position: absolute; + right: 0; + background-color: transparent; + border: 0; + border-radius: 0; + color: #ccc; + padding: 0; + width: 35px; + height: 35px; + transition: opacity $menu-transition-duration ease, + width $menu-transition-duration ease; + + svg { + margin-right: 15px; + transition: margin-right $menu-transition-duration ease; } - // Beat specificity - input:not([type='submit']) { - @include show-focus-outline-inside(); - position: absolute; - left: 0; - top: 0; - // Need !important to override body.ready class - transition: background-color $menu-transition-duration ease, opacity $menu-transition-duration ease !important; - font-size: 13px; - font-weight: 400; - background-color: transparent; - border: 0; - border-radius: 0; - color: $color-menu-text; - -webkit-font-smoothing: auto; + .sidebar--slim & { + width: 100%; - .sidebar--slim & { - opacity: 0; - } - - &::placeholder { - color: $color-menu-text; - } + svg { + margin-right: 0; + } } - &__submit { - @include show-focus-outline-inside(); - position: absolute; - right: 0; - background-color: transparent; - border: 0; - border-radius: 0; - color: #ccc; - padding: 0; - width: 35px; - height: 35px; - transition: opacity $menu-transition-duration ease, width $menu-transition-duration ease; - - svg { - margin-right: 15px; - transition: margin-right $menu-transition-duration ease; - } - - .sidebar--slim & { - width: 100%; - - svg { - margin-right: 0; - } - } - - &:hover { - background-color: transparent; - } + &:hover { + background-color: transparent; } + } } diff --git a/client/src/components/Sidebar/modules/Search.tsx b/client/src/components/Sidebar/modules/Search.tsx index a55065ede3..d91e3b0006 100644 --- a/client/src/components/Sidebar/modules/Search.tsx +++ b/client/src/components/Sidebar/modules/Search.tsx @@ -1,5 +1,3 @@ - - import * as React from 'react'; import Icon from '../../Icon/Icon'; @@ -13,8 +11,13 @@ interface SearchInputProps { navigate(url: string): void; } -export const SearchInput: React.FunctionComponent<SearchInputProps> = ( - { slim, expandingOrCollapsing, searchUrl, strings, navigate }) => { +export const SearchInput: React.FunctionComponent<SearchInputProps> = ({ + slim, + expandingOrCollapsing, + searchUrl, + strings, + navigate, +}) => { const isVisible = !slim || expandingOrCollapsing; const onSubmitForm = (e: React.FormEvent<HTMLFormElement>) => { @@ -22,7 +25,9 @@ export const SearchInput: React.FunctionComponent<SearchInputProps> = ( e.preventDefault(); if (isVisible) { - const inputElement = e.target.querySelector('input[name="q"]') as HTMLInputElement; + const inputElement = e.target.querySelector( + 'input[name="q"]', + ) as HTMLInputElement; navigate(searchUrl + '?q=' + encodeURIComponent(inputElement.value)); } else { navigate(searchUrl); @@ -30,17 +35,34 @@ export const SearchInput: React.FunctionComponent<SearchInputProps> = ( } }; - const className = ( - 'sidebar-search' - + (slim ? ' sidebar-search--slim' : '') - + (isVisible ? ' sidebar-search--visible' : '') - ); + const className = + 'sidebar-search' + + (slim ? ' sidebar-search--slim' : '') + + (isVisible ? ' sidebar-search--visible' : ''); return ( - <form role="search" className={className} action={searchUrl} method="get" onSubmit={onSubmitForm}> - <label className="sidebar-search__label" htmlFor="menu-search-q">{strings.SEARCH}</label> - <input className="sidebar-search__input" type="text" id="menu-search-q" name="q" placeholder={strings.SEARCH} /> - <button className="button sidebar-search__submit" type="submit" aria-label={strings.SEARCH}> + <form + role="search" + className={className} + action={searchUrl} + method="get" + onSubmit={onSubmitForm} + > + <label className="sidebar-search__label" htmlFor="menu-search-q"> + {strings.SEARCH} + </label> + <input + className="sidebar-search__input" + type="text" + id="menu-search-q" + name="q" + placeholder={strings.SEARCH} + /> + <button + className="button sidebar-search__submit" + type="submit" + aria-label={strings.SEARCH} + > <Icon className="icon--menuitem" name="search" /> </button> </form> diff --git a/client/src/components/Sidebar/modules/WagtailBranding.scss b/client/src/components/Sidebar/modules/WagtailBranding.scss index 52957f75e5..505bbd1457 100644 --- a/client/src/components/Sidebar/modules/WagtailBranding.scss +++ b/client/src/components/Sidebar/modules/WagtailBranding.scss @@ -2,143 +2,149 @@ // Wagging animation @keyframes tail-wag { - from { - transform: rotate(-3deg); - } + from { + transform: rotate(-3deg); + } - to { - transform: rotate(7deg); - } + to { + transform: rotate(7deg); + } } .sidebar-wagtail-branding { - $root: &; - position: relative; - display: block; - align-items: center; - color: #aaa; - -webkit-font-smoothing: auto; - margin: 1.8em auto 2.5em; - text-align: center; - width: 100px; - height: 100px; - transition: transform 150ms cubic-bezier(0.28, 0.15, 0, 2.1), width $menu-transition-duration ease, height $menu-transition-duration ease, padding-top $menu-transition-duration ease; - box-sizing: border-box; - overflow: hidden; + $root: &; + position: relative; + display: block; + align-items: center; + color: #aaa; + -webkit-font-smoothing: auto; + margin: 1.8em auto 2.5em; + text-align: center; + width: 100px; + height: 100px; + transition: transform 150ms cubic-bezier(0.28, 0.15, 0, 2.1), + width $menu-transition-duration ease, height $menu-transition-duration ease, + padding-top $menu-transition-duration ease; + box-sizing: border-box; + overflow: hidden; + &:hover { + color: $color-white; + transform: rotate(4deg); + } + + // Reduce overall size when in slim mode + .sidebar--slim & { + width: 60px; + transform: none; + } + + // Remove background on 404 page + .page404__bg & { + background-color: transparent; + } + + // Set wagging styles + &--wagging { &:hover { - color: $color-white; - transform: rotate(4deg); - } + transform: rotate(8deg); + transition: transform 1.2s ease; - // Reduce overall size when in slim mode - .sidebar--slim & { - width: 60px; - transform: none; - } - - // Remove background on 404 page - .page404__bg & { - background-color: transparent; - } - - // Set wagging styles - &--wagging { - &:hover { - transform: rotate(8deg); - transition: transform 1.2s ease; - - #{$root}__icon { - // stylelint-disable max-nesting-depth - &[data-part='tail'] { - animation: tail-wag 0.09s alternate; - animation-iteration-count: infinite; - } - - // TODO: Fix legacy specificity issues - &[data-part='eye--open'] { - display: none !important; - } - - &[data-part='eye--closed'] { - display: inline !important; - } - } - } - } - - // Bird wrapper - &__icon-wrapper { - margin: auto; - position: absolute; - left: 0; - top: 0; - width: 100px; - height: 100px; - background-color: #3a3a3a; - border-radius: 50%; - transition: left $menu-transition-duration ease, top $menu-transition-duration ease, width $menu-transition-duration ease, height $menu-transition-duration ease; - - .sidebar--slim & { - left: -10px; - top: 10px; - width: 80px; - height: 80px; - } - - .page404__bg & { - width: auto; - height: auto; - position: static; - } - } - - // Bird icons - &__icon { - display: block; - left: 0; - top: 0; - width: 70%; - height: 70%; - position: absolute; - margin: auto; - bottom: 0; - right: 0; - transition: left $menu-transition-duration ease, width $menu-transition-duration ease, height $menu-transition-duration ease; - - .sidebar--slim & { - left: -10px; - width: 70%; - height: 70%; + #{$root}__icon { + // stylelint-disable max-nesting-depth + &[data-part='tail'] { + animation: tail-wag 0.09s alternate; + animation-iteration-count: infinite; } // TODO: Fix legacy specificity issues &[data-part='eye--open'] { - display: inline !important; + display: none !important; } &[data-part='eye--closed'] { - display: none !important; + display: inline !important; } + } } + } + + // Bird wrapper + &__icon-wrapper { + margin: auto; + position: absolute; + left: 0; + top: 0; + width: 100px; + height: 100px; + background-color: #3a3a3a; + border-radius: 50%; + transition: left $menu-transition-duration ease, + top $menu-transition-duration ease, width $menu-transition-duration ease, + height $menu-transition-duration ease; + + .sidebar--slim & { + left: -10px; + top: 10px; + width: 80px; + height: 80px; + } + + .page404__bg & { + width: auto; + height: auto; + position: static; + } + } + + // Bird icons + &__icon { + display: block; + left: 0; + top: 0; + width: 70%; + height: 70%; + position: absolute; + margin: auto; + bottom: 0; + right: 0; + transition: left $menu-transition-duration ease, + width $menu-transition-duration ease, + height $menu-transition-duration ease; + + .sidebar--slim & { + left: -10px; + width: 70%; + height: 70%; + } + + // TODO: Fix legacy specificity issues + &[data-part='eye--open'] { + display: inline !important; + } + + &[data-part='eye--closed'] { + display: none !important; + } + } } .sidebar-custom-branding { - display: block; - align-items: center; - color: #aaa; - -webkit-font-smoothing: auto; - position: relative; - margin: 2em auto; - text-align: center; - padding: 10px 0; - transition: padding $menu-transition-duration ease; + display: block; + align-items: center; + color: #aaa; + -webkit-font-smoothing: auto; + position: relative; + margin: 2em auto; + text-align: center; + padding: 10px 0; + transition: padding $menu-transition-duration ease; - &:hover { - color: $color-white; - } + &:hover { + color: $color-white; + } - @at-root .sidebar--slim #{&} { - padding: 40px 0; - } + @at-root .sidebar--slim #{&} { + padding: 40px 0; + } } diff --git a/client/src/components/Sidebar/modules/WagtailBranding.tsx b/client/src/components/Sidebar/modules/WagtailBranding.tsx index 88ef28422c..3815acd34a 100644 --- a/client/src/components/Sidebar/modules/WagtailBranding.tsx +++ b/client/src/components/Sidebar/modules/WagtailBranding.tsx @@ -1,11 +1,9 @@ - - import * as React from 'react'; import { ModuleDefinition, Strings } from '../Sidebar'; export interface LogoImages { mobileLogo: string; - desktopLogoBody: string + desktopLogoBody: string; desktopLogoTail: string; desktopLogoEyeOpen: string; desktopLogoEyeClosed: string; @@ -29,20 +27,15 @@ const WagtailBranding: React.FunctionComponent<WagtailBrandingProps> = ({ const brandingLogo = React.useMemo( () => document.querySelector<HTMLTemplateElement>( - '[data-wagtail-sidebar-branding-logo]' + '[data-wagtail-sidebar-branding-logo]', ), - [] + [], ); const hasCustomBranding = brandingLogo && brandingLogo.innerHTML !== ''; const onClick = (e: React.MouseEvent) => { // Do not capture click events with modifier keys or non-main buttons. - if ( - e.ctrlKey || - e.shiftKey || - e.metaKey || - (e.button && e.button !== 0) - ) { + if (e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button !== 0)) { return; } @@ -59,7 +52,9 @@ const WagtailBranding: React.FunctionComponent<WagtailBrandingProps> = ({ href={homeUrl} aria-label={strings.DASHBOARD} aria-current={currentPath === homeUrl ? 'page' : undefined} - dangerouslySetInnerHTML={{ __html: brandingLogo ? brandingLogo.innerHTML : '' }} + dangerouslySetInnerHTML={{ + __html: brandingLogo ? brandingLogo.innerHTML : '', + }} /> ); } @@ -73,7 +68,7 @@ const WagtailBranding: React.FunctionComponent<WagtailBrandingProps> = ({ const onMouseMove = (e: React.MouseEvent) => { const mouseX = e.pageX; - const dir: 'r' | 'l' = (mouseX > lastMouseX.current) ? 'r' : 'l'; + const dir: 'r' | 'l' = mouseX > lastMouseX.current ? 'r' : 'l'; if (mouseX !== lastMouseX.current && dir !== lastDir.current) { dirChangeCount.current += 1; @@ -92,23 +87,43 @@ const WagtailBranding: React.FunctionComponent<WagtailBrandingProps> = ({ dirChangeCount.current = 0; }; - const desktopClassName = ( - 'sidebar-wagtail-branding' - + (isWagging ? ' sidebar-wagtail-branding--wagging' : '') - ); + const desktopClassName = + 'sidebar-wagtail-branding' + + (isWagging ? ' sidebar-wagtail-branding--wagging' : ''); return ( <a - className={desktopClassName} href={homeUrl} aria-label={strings.DASHBOARD} + className={desktopClassName} + href={homeUrl} + aria-label={strings.DASHBOARD} aria-current={currentPath === homeUrl ? 'page' : undefined} - onClick={onClick} onMouseMove={onMouseMove} onMouseLeave={onMouseLeave} + onClick={onClick} + onMouseMove={onMouseMove} + onMouseLeave={onMouseLeave} > <div className="sidebar-wagtail-branding__icon-wrapper"> - <img className="sidebar-wagtail-branding__icon" data-part="body" src={images.desktopLogoBody} alt="" /> - <img className="sidebar-wagtail-branding__icon" data-part="tail" src={images.desktopLogoTail} alt="" /> - <img className="sidebar-wagtail-branding__icon" data-part="eye--open" src={images.desktopLogoEyeOpen} alt="" /> <img - className="sidebar-wagtail-branding__icon" data-part="eye--closed" src={images.desktopLogoEyeClosed} + className="sidebar-wagtail-branding__icon" + data-part="body" + src={images.desktopLogoBody} + alt="" + /> + <img + className="sidebar-wagtail-branding__icon" + data-part="tail" + src={images.desktopLogoTail} + alt="" + /> + <img + className="sidebar-wagtail-branding__icon" + data-part="eye--open" + src={images.desktopLogoEyeOpen} + alt="" + /> + <img + className="sidebar-wagtail-branding__icon" + data-part="eye--closed" + src={images.desktopLogoEyeClosed} alt="" /> </div> @@ -126,9 +141,15 @@ export class WagtailBrandingModuleDefinition implements ModuleDefinition { } render({ strings, key, navigate, currentPath }) { - return (<WagtailBranding - key={key} homeUrl={this.homeUrl} images={this.images} - strings={strings} navigate={navigate} currentPath={currentPath} - />); + return ( + <WagtailBranding + key={key} + homeUrl={this.homeUrl} + images={this.images} + strings={strings} + navigate={navigate} + currentPath={currentPath} + /> + ); } } diff --git a/client/src/components/StreamField/StreamField.scss b/client/src/components/StreamField/StreamField.scss index fec9448d8c..497dd7bd02 100644 --- a/client/src/components/StreamField/StreamField.scss +++ b/client/src/components/StreamField/StreamField.scss @@ -1,5 +1,5 @@ @use './scss/index' with ( - $teal: $color-teal, - $error-color: $color-red, - $font-sans: $font-sans, + $teal: $color-teal, + $error-color: $color-red, + $font-sans: $font-sans ); diff --git a/client/src/components/StreamField/blocks/BaseSequenceBlock.js b/client/src/components/StreamField/blocks/BaseSequenceBlock.js index cc34d61f5f..97346d4e55 100644 --- a/client/src/components/StreamField/blocks/BaseSequenceBlock.js +++ b/client/src/components/StreamField/blocks/BaseSequenceBlock.js @@ -11,10 +11,13 @@ class ActionButton { } render(container) { - const label = this.sequenceChild.strings[this.labelIdentifier] || this.labelIdentifier; + const label = + this.sequenceChild.strings[this.labelIdentifier] || this.labelIdentifier; this.dom = $(` - <button type="button" class="c-sf-block__actions__single" title="${h(label)}"> + <button type="button" class="c-sf-block__actions__single" title="${h( + label, + )}"> <svg class="icon icon-${h(this.icon)}" aria-hidden="true"> <use href="#icon-${h(this.icon)}"></use> </svg> @@ -23,7 +26,7 @@ class ActionButton { this.dom.on('click', () => { if (this.onClick) this.onClick(); - return false; // don't propagate to header's onclick event (which collapses the block) + return false; // don't propagate to header's onclick event (which collapses the block) }); $(container).append(this.dom); @@ -98,7 +101,16 @@ class DeleteButton extends ActionButton { } export class BaseSequenceChild extends EventEmitter { - constructor(blockDef, placeholder, prefix, index, id, initialState, sequence, opts) { + constructor( + blockDef, + placeholder, + prefix, + index, + id, + initialState, + sequence, + opts, + ) { this.blockDef = blockDef; this.type = blockDef.name; this.prefix = prefix; @@ -111,22 +123,34 @@ export class BaseSequenceChild extends EventEmitter { this.strings = (opts && opts.strings) || {}; const dom = $(` - <div aria-hidden="false" ${this.id ? `data-contentpath="${h(this.id)}"` : 'data-contentpath-disabled'}> + <div aria-hidden="false" ${ + this.id + ? `data-contentpath="${h(this.id)}"` + : 'data-contentpath-disabled' + }> <input type="hidden" name="${this.prefix}-deleted" value=""> <input type="hidden" name="${this.prefix}-order" value="${index}"> - <input type="hidden" name="${this.prefix}-type" value="${h(this.type || '')}"> - <input type="hidden" name="${this.prefix}-id" value="${h(this.id || '')}"> + <input type="hidden" name="${this.prefix}-type" value="${h( + this.type || '', + )}"> + <input type="hidden" name="${this.prefix}-id" value="${h( + this.id || '', + )}"> <div> <div class="c-sf-container__block-container"> <div class="c-sf-block"> <div data-block-header class="c-sf-block__header c-sf-block__header--collapsible"> - <svg class="icon icon-${h(this.blockDef.meta.icon)} c-sf-block__header__icon" aria-hidden="true"> + <svg class="icon icon-${h( + this.blockDef.meta.icon, + )} c-sf-block__header__icon" aria-hidden="true"> <use href="#icon-${h(this.blockDef.meta.icon)}"></use> </svg> <h3 data-block-title class="c-sf-block__header__title"></h3> <div class="c-sf-block__actions" data-block-actions> - <span class="c-sf-block__type">${h(this.blockDef.meta.label)}</span> + <span class="c-sf-block__type">${h( + this.blockDef.meta.label, + )}</span> </div> </div> <div data-block-content class="c-sf-block__content" aria-hidden="false"> @@ -158,7 +182,11 @@ export class BaseSequenceChild extends EventEmitter { this.addActionButton(new DuplicateButton(this)); this.addActionButton(new DeleteButton(this)); - this.block = this.blockDef.render(blockElement, this.prefix + '-value', initialState); + this.block = this.blockDef.render( + blockElement, + this.prefix + '-value', + initialState, + ); if (this.collapsed) { this.collapse(); @@ -195,9 +223,7 @@ export class BaseSequenceChild extends EventEmitter { markDeleted({ animate = false }) { this.deletedInput.val('1'); if (animate) { - $(this.element).slideUp().dequeue() - .fadeOut() - .attr('aria-hidden', 'true'); + $(this.element).slideUp().dequeue().fadeOut().attr('aria-hidden', 'true'); } else { $(this.element).hide().attr('aria-hidden', 'true'); } @@ -254,14 +280,22 @@ export class BaseSequenceChild extends EventEmitter { const label = this.getTextLabel({ maxLength: 50 }); this.titleElement.text(label || ''); this.collapsed = true; - this.contentElement.get(0).dispatchEvent(new CustomEvent('commentAnchorVisibilityChange', { bubbles: true })); + this.contentElement + .get(0) + .dispatchEvent( + new CustomEvent('commentAnchorVisibilityChange', { bubbles: true }), + ); } expand() { this.contentElement.show().attr('aria-hidden', 'false'); this.titleElement.text(''); this.collapsed = false; - this.contentElement.get(0).dispatchEvent(new CustomEvent('commentAnchorVisibilityChange', { bubbles: true })); + this.contentElement + .get(0) + .dispatchEvent( + new CustomEvent('commentAnchorVisibilityChange', { bubbles: true }), + ); } toggleCollapsedState() { @@ -297,22 +331,29 @@ export class BaseInsertionControl { } } - export class BaseSequenceBlock { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _createChild(blockDef, placeholder, prefix, index, id, initialState, sequence, opts) { + /* eslint-disable @typescript-eslint/no-unused-vars */ + _createChild( + blockDef, + placeholder, + prefix, + index, + id, + initialState, + sequence, + opts, + ) { throw new Error('not implemented'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars _createInsertionControl(placeholder, opts) { throw new Error('not implemented'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars _getChildDataForInsertion(opts) { throw new Error('not implemented'); } + /* eslint-enable @typescript-eslint/no-unused-vars */ clear() { this.countInput.val(0); @@ -324,15 +365,13 @@ export class BaseSequenceBlock { const placeholder = document.createElement('div'); this.sequenceContainer.append(placeholder); this.inserters = [ - this._createInsertionControl( - placeholder, { - index: 0, - onRequestInsert: (newIndex, opts) => { - this._onRequestInsert(newIndex, opts); - }, - strings: this.blockDef.meta.strings, - } - ) + this._createInsertionControl(placeholder, { + index: 0, + onRequestInsert: (newIndex, opts) => { + this._onRequestInsert(newIndex, opts); + }, + strings: this.blockDef.meta.strings, + }), ]; this.blockCountChanged(); @@ -341,7 +380,9 @@ export class BaseSequenceBlock { _onRequestInsert(index, opts) { /* handler for an 'insert new block' action */ const [blockDef, initialState, id] = this._getChildDataForInsertion(opts); - const newChild = this._insert(blockDef, initialState, id || null, index, { animate: true }); + const newChild = this._insert(blockDef, initialState, id || null, index, { + animate: true, + }); // focus the newly added field if we can do so without obtrusive UI behaviour newChild.focus({ soft: true }); } @@ -378,29 +419,36 @@ export class BaseSequenceBlock { this.inserters[i].setIndex(i + 1); } - const child = this._createChild(childBlockDef, blockPlaceholder, prefix, index, id, initialState, this, { - animate, - collapsed, - strings: this.blockDef.meta.strings, - }); + const child = this._createChild( + childBlockDef, + blockPlaceholder, + prefix, + index, + id, + initialState, + this, + { + animate, + collapsed, + strings: this.blockDef.meta.strings, + }, + ); this.children.splice(index, 0, child); - const inserter = this._createInsertionControl( - inserterPlaceholder, { - index: index + 1, - onRequestInsert: (newIndex, inserterOpts) => { - this._onRequestInsert(newIndex, inserterOpts); - }, - strings: this.blockDef.meta.strings, - animate, - } - ); + const inserter = this._createInsertionControl(inserterPlaceholder, { + index: index + 1, + onRequestInsert: (newIndex, inserterOpts) => { + this._onRequestInsert(newIndex, inserterOpts); + }, + strings: this.blockDef.meta.strings, + animate, + }); this.inserters.splice(index + 1, 0, inserter); this.countInput.val(this.blockCounter); - const isFirstChild = (index === 0); - const isLastChild = (index === this.children.length - 1); + const isFirstChild = index === 0; + const isLastChild = index === this.children.length - 1; if (!isFirstChild) { child.enableMoveUp(); if (isLastChild) { @@ -517,11 +565,11 @@ export class BaseSequenceBlock { } getState() { - return this.children.map(child => child.getState()); + return this.children.map((child) => child.getState()); } getValue() { - return this.children.map(child => child.getValue()); + return this.children.map((child) => child.getValue()); } getTextLabel(opts) { @@ -536,7 +584,7 @@ export class BaseSequenceBlock { // always use the first child, truncated as necessary result = childLabel; } else { - const newResult = (result + ', ' + childLabel); + const newResult = result + ', ' + childLabel; if (maxLength && newResult.length > maxLength - 1) { // too long, so don't add this; return the current list with an ellipsis instead if (!result.endsWith('…')) result += '…'; diff --git a/client/src/components/StreamField/blocks/FieldBlock.js b/client/src/components/StreamField/blocks/FieldBlock.js index c279b5e27e..6cc7f27594 100644 --- a/client/src/components/StreamField/blocks/FieldBlock.js +++ b/client/src/components/StreamField/blocks/FieldBlock.js @@ -22,12 +22,21 @@ export class FieldBlock { this.element = dom[0]; try { - this.widget = this.blockDef.widget.render(widgetElement, prefix, prefix, initialState); + this.widget = this.blockDef.widget.render( + widgetElement, + prefix, + prefix, + initialState, + ); } catch (e) { // eslint-disable-next-line no-console console.error(e); this.setError([ - { messages: ['This widget failed to render, please check the console for details'] } + { + messages: [ + 'This widget failed to render, please check the console for details', + ], + }, ]); return; } @@ -37,7 +46,7 @@ export class FieldBlock { if (this.blockDef.meta.helpText) { const helpElement = document.createElement('p'); helpElement.classList.add('help'); - helpElement.innerHTML = this.blockDef.meta.helpText; // unescaped, as per Django conventions + helpElement.innerHTML = this.blockDef.meta.helpText; // unescaped, as per Django conventions this.element.querySelector('.field-content').appendChild(helpElement); } @@ -48,18 +57,20 @@ export class FieldBlock { const addCommentButtonElement = document.createElement('button'); addCommentButtonElement.type = 'button'; - addCommentButtonElement.setAttribute('aria-label', blockDef.meta.strings.ADD_COMMENT); + addCommentButtonElement.setAttribute( + 'aria-label', + blockDef.meta.strings.ADD_COMMENT, + ); addCommentButtonElement.setAttribute('data-comment-add', ''); addCommentButtonElement.classList.add('button'); addCommentButtonElement.classList.add('button-secondary'); addCommentButtonElement.classList.add('button-small'); addCommentButtonElement.classList.add('u-hidden'); - addCommentButtonElement.innerHTML = ( - '<svg class="icon icon-comment-add initial icon-default" aria-hidden="true" focusable="false">' - + '<use href="#icon-comment-add"></use></svg>' - + '<svg class="icon icon-comment-add initial icon-reversed" aria-hidden="true" focusable="false">' - + '<use href="#icon-comment-add-reversed"></use></svg>' - ); + addCommentButtonElement.innerHTML = + '<svg class="icon icon-comment-add initial icon-default" aria-hidden="true" focusable="false">' + + '<use href="#icon-comment-add"></use></svg>' + + '<svg class="icon icon-comment-add initial icon-reversed" aria-hidden="true" focusable="false">' + + '<use href="#icon-comment-add-reversed"></use></svg>'; fieldCommentControlElement.appendChild(addCommentButtonElement); window.comments.initAddCommentButton(addCommentButtonElement); } @@ -76,14 +87,18 @@ export class FieldBlock { } setError(errorList) { - this.element.querySelectorAll(':scope > .field-content > .error-message').forEach(element => element.remove()); + this.element + .querySelectorAll(':scope > .field-content > .error-message') + .forEach((element) => element.remove()); if (errorList) { this.element.classList.add('error'); const errorElement = document.createElement('p'); errorElement.classList.add('error-message'); - errorElement.innerHTML = errorList.map(error => `<span>${h(error.messages[0])}</span>`).join(''); + errorElement.innerHTML = errorList + .map((error) => `<span>${h(error.messages[0])}</span>`) + .join(''); this.element.querySelector('.field-content').appendChild(errorElement); } else { this.element.classList.remove('error'); @@ -120,6 +135,12 @@ export class FieldBlockDefinition { } render(placeholder, prefix, initialState, initialError) { - return new FieldBlock(this, placeholder, prefix, initialState, initialError); + return new FieldBlock( + this, + placeholder, + prefix, + initialState, + initialError, + ); } } diff --git a/client/src/components/StreamField/blocks/FieldBlock.test.js b/client/src/components/StreamField/blocks/FieldBlock.test.js index c5d9c78cd2..8f8f2c2a92 100644 --- a/client/src/components/StreamField/blocks/FieldBlock.test.js +++ b/client/src/components/StreamField/blocks/FieldBlock.test.js @@ -30,12 +30,24 @@ class DummyWidgetDefinition { const widgetName = this.widgetName; constructor(widgetName, { name, id, initialState }); - $(placeholder).replaceWith(`<p name="${name}" id="${id}">${widgetName}</p>`); + $(placeholder).replaceWith( + `<p name="${name}" id="${id}">${widgetName}</p>`, + ); return { - setState(state) { setState(widgetName, state); }, - getState() { getState(widgetName); return `state: ${widgetName} - ${name}`; }, - getValue() { getValue(widgetName); return `value: ${widgetName} - ${name}`; }, - focus() { focus(widgetName); }, + setState(state) { + setState(widgetName, state); + }, + getState() { + getState(widgetName); + return `state: ${widgetName} - ${name}`; + }, + getValue() { + getValue(widgetName); + return `value: ${widgetName} - ${name}`; + }, + focus() { + focus(widgetName); + }, idForLabel: id, }; } @@ -71,9 +83,10 @@ describe('telepath: wagtail.blocks.FieldBlock', () => { label: 'Test Field', required: true, icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-test_charblock', - helpText: 'drink <em>more</em> water' - } + classname: + 'field char_field widget-text_input fieldname-test_charblock', + helpText: 'drink <em>more</em> water', + }, ); // Render it @@ -81,7 +94,7 @@ describe('telepath: wagtail.blocks.FieldBlock', () => { boundBlock = blockDef.render( $('#placeholder'), 'the-prefix', - 'Test initial state' + 'Test initial state', ); }); @@ -157,13 +170,14 @@ describe('telepath: wagtail.blocks.FieldBlock with comments enabled', () => { label: 'Test Field', required: true, icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-test_charblock', + classname: + 'field char_field widget-text_input fieldname-test_charblock', helpText: 'drink <em>more</em> water', showAddCommentButton: true, strings: { - ADD_COMMENT: 'Add Comment' - } - } + ADD_COMMENT: 'Add Comment', + }, + }, ); // Render it @@ -171,7 +185,7 @@ describe('telepath: wagtail.blocks.FieldBlock with comments enabled', () => { boundBlock = blockDef.render( $('#placeholder'), 'the-prefix', - 'Test initial state' + 'Test initial state', ); }); @@ -199,9 +213,10 @@ describe('telepath: wagtail.blocks.FieldBlock catches widget render errors', () label: 'Test Field', required: true, icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-test_charblock', - helpText: 'drink <em>more</em> water' - } + classname: + 'field char_field widget-text_input fieldname-test_charblock', + helpText: 'drink <em>more</em> water', + }, ); // Render it @@ -209,7 +224,7 @@ describe('telepath: wagtail.blocks.FieldBlock catches widget render errors', () boundBlock = blockDef.render( $('#placeholder'), 'the-prefix', - 'Test initial state' + 'Test initial state', ); }); diff --git a/client/src/components/StreamField/blocks/ListBlock.js b/client/src/components/StreamField/blocks/ListBlock.js index 7205916a63..d4d81b01a4 100644 --- a/client/src/components/StreamField/blocks/ListBlock.js +++ b/client/src/components/StreamField/blocks/ListBlock.js @@ -2,7 +2,11 @@ import { v4 as uuidv4 } from 'uuid'; -import { BaseSequenceBlock, BaseSequenceChild, BaseInsertionControl } from './BaseSequenceBlock'; +import { + BaseSequenceBlock, + BaseSequenceChild, + BaseInsertionControl, +} from './BaseSequenceBlock'; import { escapeHtml as h } from '../../../utils/text'; /* global $ */ @@ -42,7 +46,9 @@ class InsertPosition extends BaseInsertionControl { const animate = opts && opts.animate; const button = $(` - <button type="button" title="${h(opts.strings.ADD)}" data-streamfield-list-add + <button type="button" title="${h( + opts.strings.ADD, + )}" data-streamfield-list-add class="c-sf-add-button c-sf-add-button--visible"> <i aria-hidden="true">+</i> </button> @@ -78,7 +84,9 @@ export class ListBlock extends BaseSequenceBlock { const dom = $(` <div class="c-sf-container ${h(this.blockDef.meta.classname || '')}"> - <input type="hidden" name="${h(prefix)}-count" data-streamfield-list-count value="0"> + <input type="hidden" name="${h( + prefix, + )}-count" data-streamfield-list-count value="0"> <div data-streamfield-list-container></div> </div> @@ -104,7 +112,7 @@ export class ListBlock extends BaseSequenceBlock { this.container = dom; this.setState(initialState || []); if (this.blockDef.meta.collapsed) { - this.children.forEach(block => { + this.children.forEach((block) => { block.collapse(); }); } @@ -133,8 +141,26 @@ export class ListBlock extends BaseSequenceBlock { return [blockDef, initialState]; } - _createChild(blockDef, placeholder, prefix, index, id, initialState, sequence, opts) { - return new ListChild(blockDef, placeholder, prefix, index, id, initialState, sequence, opts); + _createChild( + blockDef, + placeholder, + prefix, + index, + id, + initialState, + sequence, + opts, + ) { + return new ListChild( + blockDef, + placeholder, + prefix, + index, + id, + initialState, + sequence, + opts, + ); } _createInsertionControl(placeholder, opts) { @@ -171,7 +197,13 @@ export class ListBlock extends BaseSequenceBlock { } insert(value, index, opts) { - return this._insert(this.blockDef.childBlockDef, value, opts?.id, index, opts); + return this._insert( + this.blockDef.childBlockDef, + value, + opts?.id, + index, + opts, + ); } duplicateBlock(index, opts) { @@ -192,11 +224,13 @@ export class ListBlock extends BaseSequenceBlock { // Non block errors const container = this.container[0]; - container.querySelectorAll(':scope > .help-block.help-critical').forEach(element => element.remove()); + container + .querySelectorAll(':scope > .help-block.help-critical') + .forEach((element) => element.remove()); if (error.nonBlockErrors.length > 0) { // Add a help block for each error raised - error.nonBlockErrors.forEach(nonBlockError => { + error.nonBlockErrors.forEach((nonBlockError) => { const errorElement = document.createElement('p'); errorElement.classList.add('help-block'); errorElement.classList.add('help-critical'); diff --git a/client/src/components/StreamField/blocks/ListBlock.test.js b/client/src/components/StreamField/blocks/ListBlock.test.js index 5f6c00e3f9..8b1d863d7d 100644 --- a/client/src/components/StreamField/blocks/ListBlock.test.js +++ b/client/src/components/StreamField/blocks/ListBlock.test.js @@ -26,12 +26,24 @@ class DummyWidgetDefinition { const widgetName = this.widgetName; constructor(widgetName, { name, id, initialState }); - $(placeholder).replaceWith(`<p name="${name}" id="${id}">${widgetName}</p>`); + $(placeholder).replaceWith( + `<p name="${name}" id="${id}">${widgetName}</p>`, + ); return { - setState(state) { setState(widgetName, state); }, - getState() { getState(widgetName); return `state: ${widgetName} - ${name}`; }, - getValue() { getValue(widgetName); return `value: ${widgetName} - ${name}`; }, - focus() { focus(widgetName); }, + setState(state) { + setState(widgetName, state); + }, + getState() { + getState(widgetName); + return `state: ${widgetName} - ${name}`; + }, + getValue() { + getValue(widgetName); + return `value: ${widgetName} - ${name}`; + }, + focus() { + focus(widgetName); + }, idForLabel: id, }; } @@ -43,7 +55,6 @@ class ValidationError { } } - /* ListBlock should not call setError on its children with a null value; FieldBlock handles this gracefully, so define a custom one that doesn't */ @@ -51,7 +62,9 @@ gracefully, so define a custom one that doesn't class ParanoidFieldBlock extends FieldBlock { setError(errorList) { if (!errorList) { - throw new Error('ParanoidFieldBlock.setError was passed a null errorList'); + throw new Error( + 'ParanoidFieldBlock.setError was passed a null errorList', + ); } return super.setError(errorList); } @@ -59,7 +72,13 @@ class ParanoidFieldBlock extends FieldBlock { class ParanoidFieldBlockDefinition extends FieldBlockDefinition { render(placeholder, prefix, initialState, initialError) { - return new ParanoidFieldBlock(this, placeholder, prefix, initialState, initialError); + return new ParanoidFieldBlock( + this, + placeholder, + prefix, + initialState, + initialError, + ); } } @@ -84,8 +103,9 @@ describe('telepath: wagtail.blocks.ListBlock', () => { label: '', required: true, icon: 'pilcrow', - classname: 'field char_field widget-admin_auto_height_text_input fieldname-' - } + classname: + 'field char_field widget-admin_auto_height_text_input fieldname-', + }, ), null, { @@ -101,7 +121,7 @@ describe('telepath: wagtail.blocks.ListBlock', () => { DUPLICATE: 'Duplicate', ADD: 'Add', }, - } + }, ); // Render it @@ -139,7 +159,7 @@ describe('telepath: wagtail.blocks.ListBlock', () => { expect(getValue.mock.calls.length).toBe(2); expect(value).toEqual([ 'value: The widget - the-prefix-0-value', - 'value: The widget - the-prefix-1-value' + 'value: The widget - the-prefix-1-value', ]); }); @@ -147,15 +167,27 @@ describe('telepath: wagtail.blocks.ListBlock', () => { const state = boundBlock.getState(); expect(getState.mock.calls.length).toBe(2); expect(state).toEqual([ - { value: 'state: The widget - the-prefix-0-value', id: '11111111-1111-1111-1111-111111111111' }, - { value: 'state: The widget - the-prefix-1-value', id: '22222222-2222-2222-2222-222222222222' }, + { + value: 'state: The widget - the-prefix-0-value', + id: '11111111-1111-1111-1111-111111111111', + }, + { + value: 'state: The widget - the-prefix-1-value', + id: '22222222-2222-2222-2222-222222222222', + }, ]); }); test('setState() creates new widgets', () => { boundBlock.setState([ - { value: 'Changed first value', id: '11111111-1111-1111-1111-111111111111' }, - { value: 'Changed second value', id: '22222222-2222-2222-2222-222222222222' }, + { + value: 'Changed first value', + id: '11111111-1111-1111-1111-111111111111', + }, + { + value: 'Changed second value', + id: '22222222-2222-2222-2222-222222222222', + }, { value: 'Third value', id: '33333333-3333-3333-3333-333333333333' }, ]); @@ -187,9 +219,18 @@ describe('telepath: wagtail.blocks.ListBlock', () => { const state = boundBlock.getState(); expect(getState.mock.calls.length).toBe(3); expect(state).toEqual([ - { value: 'state: The widget - the-prefix-0-value', id: '11111111-1111-1111-1111-111111111111' }, - { value: 'state: The widget - the-prefix-1-value', id: '22222222-2222-2222-2222-222222222222' }, - { value: 'state: The widget - the-prefix-2-value', id: '33333333-3333-3333-3333-333333333333' }, + { + value: 'state: The widget - the-prefix-0-value', + id: '11111111-1111-1111-1111-111111111111', + }, + { + value: 'state: The widget - the-prefix-1-value', + id: '22222222-2222-2222-2222-222222222222', + }, + { + value: 'state: The widget - the-prefix-2-value', + id: '33333333-3333-3333-3333-333333333333', + }, ]); }); @@ -232,11 +273,8 @@ describe('telepath: wagtail.blocks.ListBlock', () => { test('setError passes error messages to children', () => { boundBlock.setError([ new ListBlockValidationError( - [ - null, - [new ValidationError(['Not as good as the first one'])], - ], - [] + [null, [new ValidationError(['Not as good as the first one'])]], + [], ), ]); expect(document.body.innerHTML).toMatchSnapshot(); @@ -246,9 +284,7 @@ describe('telepath: wagtail.blocks.ListBlock', () => { boundBlock.setError([ new ListBlockValidationError( [null, null], - [ - new ValidationError(['At least three blocks are required']), - ] + [new ValidationError(['At least three blocks are required'])], ), ]); expect(document.body.innerHTML).toMatchSnapshot(); @@ -266,8 +302,9 @@ describe('telepath: wagtail.blocks.ListBlock with maxNum set', () => { label: '', required: true, icon: 'pilcrow', - classname: 'field char_field widget-admin_auto_height_text_input fieldname-' - } + classname: + 'field char_field widget-admin_auto_height_text_input fieldname-', + }, ), null, { @@ -284,25 +321,41 @@ describe('telepath: wagtail.blocks.ListBlock with maxNum set', () => { DUPLICATE: 'Duplicate', ADD: 'Add', }, - } + }, ); const assertCanAddBlock = () => { // Test duplicate button // querySelector always returns the first element it sees so this only checks the first block - expect(document.querySelector('button[title="Duplicate"]').getAttribute('disabled')).toBe(null); + expect( + document + .querySelector('button[title="Duplicate"]') + .getAttribute('disabled'), + ).toBe(null); // Test menu - expect(document.querySelector('button[data-streamfield-list-add]').getAttribute('disabled')).toBe(null); + expect( + document + .querySelector('button[data-streamfield-list-add]') + .getAttribute('disabled'), + ).toBe(null); }; const assertCannotAddBlock = () => { // Test duplicate button // querySelector always returns the first element it sees so this only checks the first block - expect(document.querySelector('button[title="Duplicate"]').getAttribute('disabled')).toEqual('disabled'); + expect( + document + .querySelector('button[title="Duplicate"]') + .getAttribute('disabled'), + ).toEqual('disabled'); // Test menu - expect(document.querySelector('button[data-streamfield-list-add]').getAttribute('disabled')).toEqual('disabled'); + expect( + document + .querySelector('button[data-streamfield-list-add]') + .getAttribute('disabled'), + ).toEqual('disabled'); }; test('test can add block when under limit', () => { diff --git a/client/src/components/StreamField/blocks/StaticBlock.test.js b/client/src/components/StreamField/blocks/StaticBlock.test.js index e66a14eab5..29aa444be4 100644 --- a/client/src/components/StreamField/blocks/StaticBlock.test.js +++ b/client/src/components/StreamField/blocks/StaticBlock.test.js @@ -14,14 +14,11 @@ describe('telepath: wagtail.blocks.StaticBlock', () => { beforeEach(() => { // Define a test block - const blockDef = new StaticBlockDefinition( - 'test_field', - { - text: 'The admin text', - icon: 'icon', - label: 'The label', - } - ); + const blockDef = new StaticBlockDefinition('test_field', { + text: 'The admin text', + icon: 'icon', + label: 'The label', + }); // Render it document.body.innerHTML = '<div id="placeholder"></div>'; @@ -40,14 +37,11 @@ describe('telepath: wagtail.blocks.StaticBlock HTML escaping', () => { window.somethingBad = jest.fn(); // Define a test block - const blockDef = new StaticBlockDefinition( - 'test_field', - { - text: 'The admin text <script>somethingBad();</script>', - icon: 'icon', - label: 'The label', - } - ); + const blockDef = new StaticBlockDefinition('test_field', { + text: 'The admin text <script>somethingBad();</script>', + icon: 'icon', + label: 'The label', + }); // Render it document.body.innerHTML = '<div id="placeholder"></div>'; @@ -58,7 +52,7 @@ describe('telepath: wagtail.blocks.StaticBlock HTML escaping', () => { expect(document.body.innerHTML).toMatchSnapshot(); }); - test('javascript can\'t execute', () => { + test("javascript can't execute", () => { expect(window.somethingBad.mock.calls.length).toBe(0); }); }); @@ -70,14 +64,11 @@ describe('telepath: wagtail.blocks.StaticBlock allows safe HTML', () => { window.somethingBad = jest.fn(); // Define a test block - const blockDef = new StaticBlockDefinition( - 'test_field', - { - html: 'The admin text <script>somethingBad();</script>', - icon: 'icon', - label: 'The label', - } - ); + const blockDef = new StaticBlockDefinition('test_field', { + html: 'The admin text <script>somethingBad();</script>', + icon: 'icon', + label: 'The label', + }); // Render it document.body.innerHTML = '<div id="placeholder"></div>'; diff --git a/client/src/components/StreamField/blocks/StreamBlock.js b/client/src/components/StreamField/blocks/StreamBlock.js index b499465921..0f90b00394 100644 --- a/client/src/components/StreamField/blocks/StreamBlock.js +++ b/client/src/components/StreamField/blocks/StreamBlock.js @@ -2,7 +2,11 @@ import { v4 as uuidv4 } from 'uuid'; -import { BaseSequenceBlock, BaseSequenceChild, BaseInsertionControl } from './BaseSequenceBlock'; +import { + BaseSequenceBlock, + BaseSequenceChild, + BaseInsertionControl, +} from './BaseSequenceBlock'; import { escapeHtml as h } from '../../../utils/text'; /* global $ */ @@ -44,7 +48,9 @@ class StreamBlockMenu extends BaseInsertionControl { const dom = $(` <div> - <button data-streamblock-menu-open type="button" title="${h(opts.strings.ADD)}" + <button data-streamblock-menu-open type="button" title="${h( + opts.strings.ADD, + )}" class="c-sf-add-button c-sf-add-button--visible"> <i aria-hidden="true">+</i> </button> @@ -79,15 +85,21 @@ class StreamBlockMenu extends BaseInsertionControl { this.groupedChildBlockDefs.forEach(([group, blockDefs]) => { if (group) { - const heading = $('<h4 class="c-sf-add-panel__group-title"></h4>').text(group); + const heading = $('<h4 class="c-sf-add-panel__group-title"></h4>').text( + group, + ); this.innerContainer.append(heading); } const grid = $('<div class="c-sf-add-panel__grid"></div>'); this.innerContainer.append(grid); - blockDefs.forEach(blockDef => { + blockDefs.forEach((blockDef) => { const button = $(` - <button type="button" class="c-sf-button action-add-block-${h(blockDef.name)}"> - <svg class="icon icon-${h(blockDef.meta.icon)} c-sf-button__icon" aria-hidden="true"> + <button type="button" class="c-sf-button action-add-block-${h( + blockDef.name, + )}"> + <svg class="icon icon-${h( + blockDef.meta.icon, + )} c-sf-button__icon" aria-hidden="true"> <use href="#icon-${h(blockDef.meta.icon)}"></use> </svg> ${h(blockDef.meta.label)} @@ -104,8 +116,11 @@ class StreamBlockMenu extends BaseInsertionControl { }); // Disable buttons for any disabled block types - this.disabledBlockTypes.forEach(blockType => { - $(`button.action-add-block-${h(blockType)}`, this.innerContainer).attr('disabled', 'true'); + this.disabledBlockTypes.forEach((blockType) => { + $(`button.action-add-block-${h(blockType)}`, this.innerContainer).attr( + 'disabled', + 'true', + ); }); } @@ -127,8 +142,11 @@ class StreamBlockMenu extends BaseInsertionControl { // Disable/enable individual block type buttons $('button', this.innerContainer).removeAttr('disabled'); - disabledBlockTypes.forEach(blockType => { - $(`button.action-add-block-${h(blockType)}`, this.innerContainer).attr('disabled', 'true'); + disabledBlockTypes.forEach((blockType) => { + $(`button.action-add-block-${h(blockType)}`, this.innerContainer).attr( + 'disabled', + 'true', + ); }); } @@ -174,7 +192,9 @@ export class StreamBlock extends BaseSequenceBlock { const dom = $(` <div class="c-sf-container ${h(this.blockDef.meta.classname || '')}"> - <input type="hidden" name="${h(prefix)}-count" data-streamfield-stream-count value="0"> + <input type="hidden" name="${h( + prefix, + )}-count" data-streamfield-stream-count value="0"> <div data-streamfield-stream-container></div> </div> `); @@ -210,7 +230,7 @@ export class StreamBlock extends BaseSequenceBlock { this.sequenceContainer = dom.find('[data-streamfield-stream-container]'); this.setState(initialState || []); if (this.blockDef.meta.collapsed) { - this.children.forEach(block => { + this.children.forEach((block) => { block.collapse(); }); } @@ -230,20 +250,24 @@ export class StreamBlock extends BaseSequenceBlock { super.blockCountChanged(); this.canAddBlock = true; - if (typeof this.blockDef.meta.maxNum === 'number' && this.children.length >= this.blockDef.meta.maxNum) { + if ( + typeof this.blockDef.meta.maxNum === 'number' && + this.children.length >= this.blockDef.meta.maxNum + ) { this.canAddBlock = false; } // If we can add blocks, check if there are any block types that have count limits this.disabledBlockTypes = new Set(); if (this.canAddBlock) { - for (const blockType in this.blockDef.meta.blockCounts) { if (this.blockDef.meta.blockCounts.hasOwnProperty(blockType)) { const counts = this.blockDef.meta.blockCounts[blockType]; if (typeof counts.max_num === 'number') { - const currentBlockCount = this.children.filter(child => child.type === blockType).length; + const currentBlockCount = this.children.filter( + (child) => child.type === blockType, + ).length; if (currentBlockCount >= counts.max_num) { this.disabledBlockTypes.add(blockType); @@ -254,7 +278,8 @@ export class StreamBlock extends BaseSequenceBlock { } for (let i = 0; i < this.children.length; i++) { - const canDuplicate = this.canAddBlock && !this.disabledBlockTypes.has(this.children[i].type); + const canDuplicate = + this.canAddBlock && !this.disabledBlockTypes.has(this.children[i].type); if (canDuplicate) { this.children[i].enableDuplication(); @@ -263,12 +288,33 @@ export class StreamBlock extends BaseSequenceBlock { } } for (let i = 0; i < this.inserters.length; i++) { - this.inserters[i].setNewBlockRestrictions(this.canAddBlock, this.disabledBlockTypes); + this.inserters[i].setNewBlockRestrictions( + this.canAddBlock, + this.disabledBlockTypes, + ); } } - _createChild(blockDef, placeholder, prefix, index, id, initialState, sequence, opts) { - return new StreamChild(blockDef, placeholder, prefix, index, id, initialState, sequence, opts); + _createChild( + blockDef, + placeholder, + prefix, + index, + id, + initialState, + sequence, + opts, + ) { + return new StreamChild( + blockDef, + placeholder, + prefix, + index, + id, + initialState, + sequence, + opts, + ); } _createInsertionControl(placeholder, opts) { @@ -318,11 +364,13 @@ export class StreamBlock extends BaseSequenceBlock { // Non block errors const container = this.container[0]; - container.querySelectorAll(':scope > .help-block.help-critical').forEach(element => element.remove()); + container + .querySelectorAll(':scope > .help-block.help-critical') + .forEach((element) => element.remove()); if (error.nonBlockErrors.length > 0) { // Add a help block for each error raised - error.nonBlockErrors.forEach(nonBlockError => { + error.nonBlockErrors.forEach((nonBlockError) => { const errorElement = document.createElement('p'); errorElement.classList.add('help-block'); errorElement.classList.add('help-critical'); @@ -332,7 +380,7 @@ export class StreamBlock extends BaseSequenceBlock { } // Block errors - + for (const blockIndex in error.blockErrors) { if (error.blockErrors.hasOwnProperty(blockIndex)) { this.children[blockIndex].setError(error.blockErrors[blockIndex]); @@ -349,7 +397,7 @@ export class StreamBlockDefinition { this.childBlockDefsByName = {}; // eslint-disable-next-line @typescript-eslint/no-unused-vars this.groupedChildBlockDefs.forEach(([group, blockDefs]) => { - blockDefs.forEach(blockDef => { + blockDefs.forEach((blockDef) => { this.childBlockDefsByName[blockDef.name] = blockDef; }); }); @@ -357,6 +405,12 @@ export class StreamBlockDefinition { } render(placeholder, prefix, initialState, initialError) { - return new StreamBlock(this, placeholder, prefix, initialState, initialError); + return new StreamBlock( + this, + placeholder, + prefix, + initialState, + initialError, + ); } } diff --git a/client/src/components/StreamField/blocks/StreamBlock.test.js b/client/src/components/StreamField/blocks/StreamBlock.test.js index dfeaabc473..f6a6bcf296 100644 --- a/client/src/components/StreamField/blocks/StreamBlock.test.js +++ b/client/src/components/StreamField/blocks/StreamBlock.test.js @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { FieldBlockDefinition } from './FieldBlock'; -import { StreamBlockDefinition, StreamBlockValidationError } from './StreamBlock'; +import { + StreamBlockDefinition, + StreamBlockValidationError, +} from './StreamBlock'; import $ from 'jquery'; window.$ = $; @@ -26,12 +29,24 @@ class DummyWidgetDefinition { const widgetName = this.widgetName; constructor(widgetName, { name, id, initialState }); - $(placeholder).replaceWith(`<p name="${name}" id="${id}">${widgetName}</p>`); + $(placeholder).replaceWith( + `<p name="${name}" id="${id}">${widgetName}</p>`, + ); return { - setState(state) { setState(widgetName, state); }, - getState() { getState(widgetName); return `state: ${widgetName} - ${name}`; }, - getValue() { getValue(widgetName); return `value: ${widgetName} - ${name}`; }, - focus() { focus(widgetName); }, + setState(state) { + setState(widgetName, state); + }, + getState() { + getState(widgetName); + return `state: ${widgetName} - ${name}`; + }, + getValue() { + getValue(widgetName); + return `value: ${widgetName} - ${name}`; + }, + focus() { + focus(widgetName); + }, idForLabel: id, }; } @@ -58,28 +73,33 @@ describe('telepath: wagtail.blocks.StreamBlock', () => { const blockDef = new StreamBlockDefinition( '', [ - ['', [ - new FieldBlockDefinition( - 'test_block_a', - new DummyWidgetDefinition('Block A widget'), - { - label: 'Test Block A', - required: true, - icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-test_charblock' - } - ), - new FieldBlockDefinition( - 'test_block_b', - new DummyWidgetDefinition('Block B widget'), - { - label: 'Test Block B', - required: true, - icon: 'pilcrow', - classname: 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock' - } - ), - ]] + [ + '', + [ + new FieldBlockDefinition( + 'test_block_a', + new DummyWidgetDefinition('Block A widget'), + { + label: 'Test Block A', + required: true, + icon: 'placeholder', + classname: + 'field char_field widget-text_input fieldname-test_charblock', + }, + ), + new FieldBlockDefinition( + 'test_block_b', + new DummyWidgetDefinition('Block B widget'), + { + label: 'Test Block B', + required: true, + icon: 'pilcrow', + classname: + 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock', + }, + ), + ], + ], ], { test_block_a: 'Block A options', @@ -102,7 +122,7 @@ describe('telepath: wagtail.blocks.StreamBlock', () => { DUPLICATE: 'Duplicate', ADD: 'Add', }, - } + }, ); // Render it @@ -111,12 +131,12 @@ describe('telepath: wagtail.blocks.StreamBlock', () => { { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, ]); }); @@ -155,12 +175,12 @@ describe('telepath: wagtail.blocks.StreamBlock', () => { { id: '1', type: 'test_block_a', - value: 'value: Block A widget - the-prefix-0-value' + value: 'value: Block A widget - the-prefix-0-value', }, { id: '2', type: 'test_block_b', - value: 'value: Block B widget - the-prefix-1-value' + value: 'value: Block B widget - the-prefix-1-value', }, ]); }); @@ -172,12 +192,12 @@ describe('telepath: wagtail.blocks.StreamBlock', () => { { id: '1', type: 'test_block_a', - value: 'state: Block A widget - the-prefix-0-value' + value: 'state: Block A widget - the-prefix-0-value', }, { id: '2', type: 'test_block_b', - value: 'state: Block B widget - the-prefix-1-value' + value: 'state: Block B widget - the-prefix-1-value', }, ]); }); @@ -187,17 +207,17 @@ describe('telepath: wagtail.blocks.StreamBlock', () => { { id: '1', type: 'test_block_a', - value: 'Changed first value' + value: 'Changed first value', }, { id: '3', type: 'test_block_b', - value: 'Third value' + value: 'Third value', }, { id: '2', type: 'test_block_b', - value: 'Changed second value' + value: 'Changed second value', }, ]); @@ -232,17 +252,17 @@ describe('telepath: wagtail.blocks.StreamBlock', () => { { id: '1', type: 'test_block_a', - value: 'state: Block A widget - the-prefix-0-value' + value: 'state: Block A widget - the-prefix-0-value', }, { id: '3', type: 'test_block_b', - value: 'state: Block B widget - the-prefix-1-value' + value: 'state: Block B widget - the-prefix-1-value', }, { id: '2', type: 'test_block_b', - value: 'state: Block B widget - the-prefix-2-value' + value: 'state: Block B widget - the-prefix-2-value', }, ]); }); @@ -289,7 +309,8 @@ describe('telepath: wagtail.blocks.StreamBlock', () => { { /* block error */ 1: [new ValidationError(['Not as good as the first one'])], - }), + }, + ), ]); expect(document.body.innerHTML).toMatchSnapshot(); }); @@ -310,28 +331,33 @@ describe('telepath: wagtail.blocks.StreamBlock with labels that need escaping', const blockDef = new StreamBlockDefinition( '', [ - ['', [ - new FieldBlockDefinition( - 'test_block_a', - new DummyWidgetDefinition('Block A widget'), - { - label: 'Test Block <A>', - required: true, - icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-test_charblock' - } - ), - new FieldBlockDefinition( - 'test_block_b', - new DummyWidgetDefinition('Block B widget'), - { - label: 'Test Block <B>', - required: true, - icon: 'pilcrow', - classname: 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock' - } - ), - ]] + [ + '', + [ + new FieldBlockDefinition( + 'test_block_a', + new DummyWidgetDefinition('Block A widget'), + { + label: 'Test Block <A>', + required: true, + icon: 'placeholder', + classname: + 'field char_field widget-text_input fieldname-test_charblock', + }, + ), + new FieldBlockDefinition( + 'test_block_b', + new DummyWidgetDefinition('Block B widget'), + { + label: 'Test Block <B>', + required: true, + icon: 'pilcrow', + classname: + 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock', + }, + ), + ], + ], ], { test_block_a: 'Block A options', @@ -354,7 +380,7 @@ describe('telepath: wagtail.blocks.StreamBlock with labels that need escaping', DUPLICATE: 'Duplicate', ADD: 'Add', }, - } + }, ); // Render it @@ -363,12 +389,12 @@ describe('telepath: wagtail.blocks.StreamBlock with labels that need escaping', { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, ]); }); @@ -384,28 +410,33 @@ describe('telepath: wagtail.blocks.StreamBlock with maxNum set', () => { const blockDef = new StreamBlockDefinition( '', [ - ['', [ - new FieldBlockDefinition( - 'test_block_a', - new DummyWidgetDefinition('Block A widget'), - { - label: 'Test Block <A>', - required: true, - icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-test_charblock' - } - ), - new FieldBlockDefinition( - 'test_block_b', - new DummyWidgetDefinition('Block B widget'), - { - label: 'Test Block <B>', - required: true, - icon: 'pilcrow', - classname: 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock' - } - ), - ]] + [ + '', + [ + new FieldBlockDefinition( + 'test_block_a', + new DummyWidgetDefinition('Block A widget'), + { + label: 'Test Block <A>', + required: true, + icon: 'placeholder', + classname: + 'field char_field widget-text_input fieldname-test_charblock', + }, + ), + new FieldBlockDefinition( + 'test_block_b', + new DummyWidgetDefinition('Block B widget'), + { + label: 'Test Block <B>', + required: true, + icon: 'pilcrow', + classname: + 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock', + }, + ), + ], + ], ], { test_block_a: 'Block A options', @@ -428,25 +459,41 @@ describe('telepath: wagtail.blocks.StreamBlock with maxNum set', () => { DUPLICATE: 'Duplicate', ADD: 'Add', }, - } + }, ); const assertCanAddBlock = () => { // Test duplicate button // querySelector always returns the first element it sees so this only checks the first block - expect(document.querySelector('button[title="Duplicate"]').getAttribute('disabled')).toBe(null); + expect( + document + .querySelector('button[title="Duplicate"]') + .getAttribute('disabled'), + ).toBe(null); // Test menu - expect(document.querySelector('button[data-streamblock-menu-open]').getAttribute('disabled')).toBe(null); + expect( + document + .querySelector('button[data-streamblock-menu-open]') + .getAttribute('disabled'), + ).toBe(null); }; const assertCannotAddBlock = () => { // Test duplicate button // querySelector always returns the first element it sees so this only checks the first block - expect(document.querySelector('button[title="Duplicate"]').getAttribute('disabled')).toEqual('disabled'); + expect( + document + .querySelector('button[title="Duplicate"]') + .getAttribute('disabled'), + ).toEqual('disabled'); // Test menu - expect(document.querySelector('button[data-streamblock-menu-open]').getAttribute('disabled')).toEqual('disabled'); + expect( + document + .querySelector('button[data-streamblock-menu-open]') + .getAttribute('disabled'), + ).toEqual('disabled'); }; test('test can add block when under limit', () => { @@ -455,12 +502,12 @@ describe('telepath: wagtail.blocks.StreamBlock with maxNum set', () => { { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, ]); boundBlock.inserters[0].open(); @@ -474,17 +521,17 @@ describe('telepath: wagtail.blocks.StreamBlock with maxNum set', () => { { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, { id: '3', type: 'test_block_b', - value: 'Third value' + value: 'Third value', }, ]); boundBlock.inserters[0].open(); @@ -498,23 +545,26 @@ describe('telepath: wagtail.blocks.StreamBlock with maxNum set', () => { { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, ]); boundBlock.inserters[0].open(); assertCanAddBlock(); - boundBlock.insert({ - id: '3', - type: 'test_block_b', - value: 'Third value' - }, 2); + boundBlock.insert( + { + id: '3', + type: 'test_block_b', + value: 'Third value', + }, + 2, + ); assertCannotAddBlock(); }); @@ -525,17 +575,17 @@ describe('telepath: wagtail.blocks.StreamBlock with maxNum set', () => { { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, { id: '3', type: 'test_block_b', - value: 'Third value' + value: 'Third value', }, ]); boundBlock.inserters[0].open(); @@ -553,28 +603,33 @@ describe('telepath: wagtail.blocks.StreamBlock with blockCounts.max_num set', () const blockDef = new StreamBlockDefinition( '', [ - ['', [ - new FieldBlockDefinition( - 'test_block_a', - new DummyWidgetDefinition('Block A widget'), - { - label: 'Test Block <A>', - required: true, - icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-test_charblock' - } - ), - new FieldBlockDefinition( - 'test_block_b', - new DummyWidgetDefinition('Block B widget'), - { - label: 'Test Block <B>', - required: true, - icon: 'pilcrow', - classname: 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock' - } - ), - ]] + [ + '', + [ + new FieldBlockDefinition( + 'test_block_a', + new DummyWidgetDefinition('Block A widget'), + { + label: 'Test Block <A>', + required: true, + icon: 'placeholder', + classname: + 'field char_field widget-text_input fieldname-test_charblock', + }, + ), + new FieldBlockDefinition( + 'test_block_b', + new DummyWidgetDefinition('Block B widget'), + { + label: 'Test Block <B>', + required: true, + icon: 'pilcrow', + classname: + 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock', + }, + ), + ], + ], ], { test_block_a: 'Block A options', @@ -591,8 +646,8 @@ describe('telepath: wagtail.blocks.StreamBlock with blockCounts.max_num set', () minNum: null, blockCounts: { test_block_a: { - max_num: 2 - } + max_num: 2, + }, }, strings: { MOVE_UP: 'Move up', @@ -601,25 +656,41 @@ describe('telepath: wagtail.blocks.StreamBlock with blockCounts.max_num set', () DUPLICATE: 'Duplicate', ADD: 'Add', }, - } + }, ); const assertCanAddBlock = () => { // Test duplicate button // querySelector always returns the first element it sees so this only checks the first block - expect(document.querySelector('button[title="Duplicate"]').getAttribute('disabled')).toBe(null); + expect( + document + .querySelector('button[title="Duplicate"]') + .getAttribute('disabled'), + ).toBe(null); // Test menu item - expect(document.querySelector('button.action-add-block-test_block_a').getAttribute('disabled')).toBe(null); + expect( + document + .querySelector('button.action-add-block-test_block_a') + .getAttribute('disabled'), + ).toBe(null); }; const assertCannotAddBlock = () => { // Test duplicate button // querySelector always returns the first element it sees so this only checks the first block - expect(document.querySelector('button[title="Duplicate"]').getAttribute('disabled')).toEqual('disabled'); + expect( + document + .querySelector('button[title="Duplicate"]') + .getAttribute('disabled'), + ).toEqual('disabled'); // Test menu item - expect(document.querySelector('button.action-add-block-test_block_a').getAttribute('disabled')).toEqual('disabled'); + expect( + document + .querySelector('button.action-add-block-test_block_a') + .getAttribute('disabled'), + ).toEqual('disabled'); }; test('single instance allows creation of new block and duplication', () => { @@ -628,12 +699,12 @@ describe('telepath: wagtail.blocks.StreamBlock with blockCounts.max_num set', () { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, ]); boundBlock.inserters[0].open(); @@ -647,17 +718,17 @@ describe('telepath: wagtail.blocks.StreamBlock with blockCounts.max_num set', () { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, { id: '3', type: 'test_block_a', - value: 'Third value' + value: 'Third value', }, ]); boundBlock.inserters[0].open(); @@ -671,23 +742,26 @@ describe('telepath: wagtail.blocks.StreamBlock with blockCounts.max_num set', () { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, ]); boundBlock.inserters[0].open(); assertCanAddBlock(); - boundBlock.insert({ - id: '3', - type: 'test_block_a', - value: 'Third value' - }, 2); + boundBlock.insert( + { + id: '3', + type: 'test_block_a', + value: 'Third value', + }, + 2, + ); assertCannotAddBlock(); }); @@ -698,17 +772,17 @@ describe('telepath: wagtail.blocks.StreamBlock with blockCounts.max_num set', () { id: '1', type: 'test_block_a', - value: 'First value' + value: 'First value', }, { id: '2', type: 'test_block_b', - value: 'Second value' + value: 'Second value', }, { id: '3', type: 'test_block_a', - value: 'Third value' + value: 'Third value', }, ]); boundBlock.inserters[0].open(); diff --git a/client/src/components/StreamField/blocks/StructBlock.js b/client/src/components/StreamField/blocks/StructBlock.js index aba6731d68..30e585926a 100644 --- a/client/src/components/StreamField/blocks/StructBlock.js +++ b/client/src/components/StreamField/blocks/StructBlock.js @@ -20,13 +20,15 @@ export class StructBlock { const html = blockDef.meta.formTemplate.replace(/__PREFIX__/g, prefix); const dom = $(html); $(placeholder).replaceWith(dom); - this.blockDef.childBlockDefs.forEach(childBlockDef => { - const childBlockElement = dom.find('[data-structblock-child="' + childBlockDef.name + '"]').get(0); + this.blockDef.childBlockDefs.forEach((childBlockDef) => { + const childBlockElement = dom + .find('[data-structblock-child="' + childBlockDef.name + '"]') + .get(0); const childBlock = childBlockDef.render( childBlockElement, prefix + '-' + childBlockDef.name, state[childBlockDef.name], - initialError?.blockErrors[childBlockDef.name] + initialError?.blockErrors[childBlockDef.name], ); this.childBlocks[childBlockDef.name] = childBlock; }); @@ -49,21 +51,25 @@ export class StructBlock { `); } - this.blockDef.childBlockDefs.forEach(childBlockDef => { + this.blockDef.childBlockDefs.forEach((childBlockDef) => { const childDom = $(` - <div class="field ${childBlockDef.meta.required ? 'required' : ''}" data-contentpath="${childBlockDef.name}"> + <div class="field ${ + childBlockDef.meta.required ? 'required' : '' + }" data-contentpath="${childBlockDef.name}"> <label class="field__label">${h(childBlockDef.meta.label)}</label> <div data-streamfield-block></div> </div> `); dom.append(childDom); - const childBlockElement = childDom.find('[data-streamfield-block]').get(0); + const childBlockElement = childDom + .find('[data-streamfield-block]') + .get(0); const labelElement = childDom.find('label').get(0); const childBlock = childBlockDef.render( childBlockElement, prefix + '-' + childBlockDef.name, state[childBlockDef.name], - initialError?.blockErrors[childBlockDef.name] + initialError?.blockErrors[childBlockDef.name], ); this.childBlocks[childBlockDef.name] = childBlock; @@ -87,7 +93,6 @@ export class StructBlock { } const error = errorList[0]; - for (const blockName in error.blockErrors) { if (error.blockErrors.hasOwnProperty(blockName)) { this.childBlocks[blockName].setError(error.blockErrors[blockName]); @@ -117,17 +122,20 @@ export class StructBlock { if (this.blockDef.meta.labelFormat) { /* use labelFormat - regexp replace any field references like '{first_name}' with the text label of that sub-block */ - return this.blockDef.meta.labelFormat.replace(/\{(\w+)\}/g, (tag, blockName) => { - const block = this.childBlocks[blockName]; - if (block.getTextLabel) { - /* to be strictly correct, we should be adjusting opts.maxLength to account for the overheads + return this.blockDef.meta.labelFormat.replace( + /\{(\w+)\}/g, + (tag, blockName) => { + const block = this.childBlocks[blockName]; + if (block.getTextLabel) { + /* to be strictly correct, we should be adjusting opts.maxLength to account for the overheads in the format string, and dividing the remainder across all the placeholders in the string, rather than just passing opts on to the child. But that would get complicated, and this is better than nothing... */ - return block.getTextLabel(opts); - } - return ''; - }); + return block.getTextLabel(opts); + } + return ''; + }, + ); } /* if no labelFormat specified, just try each child block in turn until we find one that provides a label */ @@ -158,6 +166,12 @@ export class StructBlockDefinition { } render(placeholder, prefix, initialState, initialError) { - return new StructBlock(this, placeholder, prefix, initialState, initialError); + return new StructBlock( + this, + placeholder, + prefix, + initialState, + initialError, + ); } } diff --git a/client/src/components/StreamField/blocks/StructBlock.test.js b/client/src/components/StreamField/blocks/StructBlock.test.js index 884555f1f0..908aaa530d 100644 --- a/client/src/components/StreamField/blocks/StructBlock.test.js +++ b/client/src/components/StreamField/blocks/StructBlock.test.js @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { FieldBlockDefinition } from './FieldBlock'; -import { StructBlockDefinition, StructBlockValidationError } from './StructBlock'; +import { + StructBlockDefinition, + StructBlockValidationError, +} from './StructBlock'; import $ from 'jquery'; window.$ = $; @@ -26,13 +29,27 @@ class DummyWidgetDefinition { const widgetName = this.widgetName; constructor(widgetName, { name, id, initialState }); - $(placeholder).replaceWith(`<p name="${name}" id="${id}">${widgetName}</p>`); + $(placeholder).replaceWith( + `<p name="${name}" id="${id}">${widgetName}</p>`, + ); return { - setState(state) { setState(widgetName, state); }, - getState() { getState(widgetName); return `state: ${widgetName} - ${name}`; }, - getValue() { getValue(widgetName); return `value: ${widgetName} - ${name}`; }, - getTextLabel() { return `label: ${name}`; }, - focus() { focus(widgetName); }, + setState(state) { + setState(widgetName, state); + }, + getState() { + getState(widgetName); + return `state: ${widgetName} - ${name}`; + }, + getValue() { + getValue(widgetName); + return `value: ${widgetName} - ${name}`; + }, + getTextLabel() { + return `label: ${name}`; + }, + focus() { + focus(widgetName); + }, idForLabel: id, }; } @@ -66,8 +83,9 @@ describe('telepath: wagtail.blocks.StructBlock', () => { label: 'Heading text', required: true, icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-heading_text' - } + classname: + 'field char_field widget-text_input fieldname-heading_text', + }, ), new FieldBlockDefinition( 'size', @@ -76,8 +94,8 @@ describe('telepath: wagtail.blocks.StructBlock', () => { label: 'Size', required: false, icon: 'placeholder', - classname: 'field choice_field widget-select fieldname-size' - } + classname: 'field choice_field widget-select fieldname-size', + }, ), ], { @@ -87,19 +105,15 @@ describe('telepath: wagtail.blocks.StructBlock', () => { classname: 'struct-block', helpText: 'use <strong>lots</strong> of these', helpIcon: '<div class="icon-help">?</div>', - } + }, ); // Render it document.body.innerHTML = '<div id="placeholder"></div>'; - boundBlock = blockDef.render( - $('#placeholder'), - 'the-prefix', - { - heading_text: 'Test heading text', - size: '123' - } - ); + boundBlock = blockDef.render($('#placeholder'), 'the-prefix', { + heading_text: 'Test heading text', + size: '123', + }); }); test('it renders correctly', () => { @@ -129,7 +143,7 @@ describe('telepath: wagtail.blocks.StructBlock', () => { expect(getValue.mock.calls.length).toBe(2); expect(value).toEqual({ heading_text: 'value: Heading widget - the-prefix-heading_text', - size: 'value: Size widget - the-prefix-size' + size: 'value: Size widget - the-prefix-size', }); }); @@ -138,14 +152,14 @@ describe('telepath: wagtail.blocks.StructBlock', () => { expect(getState.mock.calls.length).toBe(2); expect(state).toEqual({ heading_text: 'state: Heading widget - the-prefix-heading_text', - size: 'state: Size widget - the-prefix-size' + size: 'state: Size widget - the-prefix-size', }); }); test('setState() calls setState() on all widgets', () => { boundBlock.setState({ heading_text: 'Changed heading text', - size: '456' + size: '456', }); expect(setState.mock.calls.length).toBe(2); expect(setState.mock.calls[0][0]).toBe('Heading widget'); @@ -196,8 +210,9 @@ describe('telepath: wagtail.blocks.StructBlock with formTemplate', () => { label: 'Heading text', required: true, icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-heading_text' - } + classname: + 'field char_field widget-text_input fieldname-heading_text', + }, ), new FieldBlockDefinition( 'size', @@ -206,8 +221,8 @@ describe('telepath: wagtail.blocks.StructBlock with formTemplate', () => { label: 'Size', required: false, icon: 'placeholder', - classname: 'field choice_field widget-select fieldname-size' - } + classname: 'field choice_field widget-select fieldname-size', + }, ), ], { @@ -221,19 +236,15 @@ describe('telepath: wagtail.blocks.StructBlock with formTemplate', () => { <div data-structblock-child="size"></div> </div>`, labelFormat: '{heading_text} - {size}', - } + }, ); // Render it document.body.innerHTML = '<div id="placeholder"></div>'; - boundBlock = blockDef.render( - $('#placeholder'), - 'the-prefix', - { - heading_text: 'Test heading text', - size: '123' - } - ); + boundBlock = blockDef.render($('#placeholder'), 'the-prefix', { + heading_text: 'Test heading text', + size: '123', + }); }); test('it renders correctly', () => { @@ -263,7 +274,7 @@ describe('telepath: wagtail.blocks.StructBlock with formTemplate', () => { expect(getValue.mock.calls.length).toBe(2); expect(value).toEqual({ heading_text: 'value: Heading widget - the-prefix-heading_text', - size: 'value: Size widget - the-prefix-size' + size: 'value: Size widget - the-prefix-size', }); }); @@ -272,14 +283,14 @@ describe('telepath: wagtail.blocks.StructBlock with formTemplate', () => { expect(getState.mock.calls.length).toBe(2); expect(state).toEqual({ heading_text: 'state: Heading widget - the-prefix-heading_text', - size: 'state: Size widget - the-prefix-size' + size: 'state: Size widget - the-prefix-size', }); }); test('setState() calls setState() on all widgets', () => { boundBlock.setState({ heading_text: 'Changed heading text', - size: '456' + size: '456', }); expect(setState.mock.calls.length).toBe(2); expect(setState.mock.calls[0][0]).toBe('Heading widget'); @@ -296,7 +307,7 @@ describe('telepath: wagtail.blocks.StructBlock with formTemplate', () => { test('getTextLabel() returns text label according to labelFormat', () => { expect(boundBlock.getTextLabel()).toBe( - 'label: the-prefix-heading_text - label: the-prefix-size' + 'label: the-prefix-heading_text - label: the-prefix-size', ); }); }); diff --git a/client/src/components/StreamField/scss/_variables.scss b/client/src/components/StreamField/scss/_variables.scss index 7f5fe3192b..64fd438a09 100644 --- a/client/src/components/StreamField/scss/_variables.scss +++ b/client/src/components/StreamField/scss/_variables.scss @@ -1,4 +1,4 @@ -@use "sass:math"; +@use 'sass:math'; $grid-gutter-width: 30px !default; $header-padding-horizontal: 4px !default; @@ -33,4 +33,5 @@ $screen-xs-max: 799px !default; $screen-sm-min: 800px !default; $screen-l-min: 1075px !default; $add-panel-gutter: 8px !default; -$font-sans: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji" !default; +$font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, + sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji' !default; diff --git a/client/src/components/StreamField/scss/components/c-sf-add-button.scss b/client/src/components/StreamField/scss/components/c-sf-add-button.scss index 0532e97932..1dbf00b902 100644 --- a/client/src/components/StreamField/scss/components/c-sf-add-button.scss +++ b/client/src/components/StreamField/scss/components/c-sf-add-button.scss @@ -1,44 +1,44 @@ .c-sf-add-button { - width: 100%; - height: $add-button-size; - appearance: none; - border: 0 none; - color: $teal; - font-weight: bold; - background: none; - padding: 0; - cursor: pointer; - outline: none; - backface-visibility: hidden; - overflow: hidden; // Makes the rotated i box not clickable. - user-select: none; - opacity: 0; + width: 100%; + height: $add-button-size; + appearance: none; + border: 0 none; + color: $teal; + font-weight: bold; + background: none; + padding: 0; + cursor: pointer; + outline: none; + backface-visibility: hidden; + overflow: hidden; // Makes the rotated i box not clickable. + user-select: none; + opacity: 0; + pointer-events: none; + transition: opacity 100ms ease-in-out; + + &:hover { + opacity: 1; + } + + i { + display: block; + transition: transform $add-transition-duration $bounce-transition-timing; + font-style: normal; + font-size: $add-button-font-size; + line-height: $add-button-size; + } + + &--visible { + opacity: 0.8; + pointer-events: unset; + } + + &--close i { + transform: rotate(45deg); + } + + &[disabled] { + opacity: 0.2; pointer-events: none; - transition: opacity 100ms ease-in-out; - - &:hover { - opacity: 1; - } - - i { - display: block; - transition: transform $add-transition-duration $bounce-transition-timing; - font-style: normal; - font-size: $add-button-font-size; - line-height: $add-button-size; - } - - &--visible { - opacity: 0.8; - pointer-events: unset; - } - - &--close i { - transform: rotate(45deg); - } - - &[disabled] { - opacity: 0.2; - pointer-events: none; - } + } } diff --git a/client/src/components/StreamField/scss/components/c-sf-add-panel.scss b/client/src/components/StreamField/scss/components/c-sf-add-panel.scss index 075dc6218f..c9342f0d50 100644 --- a/client/src/components/StreamField/scss/components/c-sf-add-panel.scss +++ b/client/src/components/StreamField/scss/components/c-sf-add-panel.scss @@ -1,33 +1,30 @@ -@use "sass:math"; +@use 'sass:math'; .c-sf-add-panel { - position: relative; - padding: $grid-gutter-width * 0.25 - 0 - $grid-gutter-width; - border-radius: $border-radius; - user-select: none; + position: relative; + padding: $grid-gutter-width * 0.25 0 $grid-gutter-width; + border-radius: $border-radius; + user-select: none; - @media (min-width: $screen-l-min) { - padding: $grid-gutter-width * 0.25 - $grid-gutter-width * 2 - $add-button-size - math.div($grid-gutter-width, 2); - } - - &__group-title { - margin: 5px 0; - font-weight: 600; - } - - &__grid { - display: flex; - flex-flow: row wrap; - margin-left: -$add-panel-gutter; - margin-right: -$add-panel-gutter; - margin-bottom: math.div($grid-gutter-width, 2); - - &:last-child { - margin-bottom: 0; - } + @media (min-width: $screen-l-min) { + padding: $grid-gutter-width * 0.25 $grid-gutter-width * 2 $add-button-size - + math.div($grid-gutter-width, 2); + } + + &__group-title { + margin: 5px 0; + font-weight: 600; + } + + &__grid { + display: flex; + flex-flow: row wrap; + margin-left: -$add-panel-gutter; + margin-right: -$add-panel-gutter; + margin-bottom: math.div($grid-gutter-width, 2); + + &:last-child { + margin-bottom: 0; } + } } diff --git a/client/src/components/StreamField/scss/components/c-sf-block.scss b/client/src/components/StreamField/scss/components/c-sf-block.scss index 993eba1f34..8b38babe59 100644 --- a/client/src/components/StreamField/scss/components/c-sf-block.scss +++ b/client/src/components/StreamField/scss/components/c-sf-block.scss @@ -9,180 +9,182 @@ // However, the new classes adequately sanitise streamfield only CSS so am leaving this for // now to avoid blocking the release of the new Streamfield. -@jonnyscholes .c-sf-block { - flex: 1 1 auto; - margin: $block-margin-vertical $block-margin-horizontal; - border: 1px solid $block-border-color; - border-radius: $border-radius; - background: #fff; - transition: border-color $hover-transition-duration ease-in-out; - transition-property: border-color, box-shadow; + flex: 1 1 auto; + margin: $block-margin-vertical $block-margin-horizontal; + border: 1px solid $block-border-color; + border-radius: $border-radius; + background: #fff; + transition: border-color $hover-transition-duration ease-in-out; + transition-property: border-color, box-shadow; - &__header { - display: flex; - justify-content: space-between; - align-items: center; - padding: $header-padding-vertical $header-padding-horizontal; - user-select: none; - transition: background-color $hover-transition-duration ease-in-out; - cursor: default; - border-top-left-radius: $border-radius; - border-top-right-radius: $border-radius; - min-height: 30px; - background: $header-background; + &__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: $header-padding-vertical $header-padding-horizontal; + user-select: none; + transition: background-color $hover-transition-duration ease-in-out; + cursor: default; + border-top-left-radius: $border-radius; + border-top-right-radius: $border-radius; + min-height: 30px; + background: $header-background; - @media (min-width: $screen-sm-min) { - padding-left: $content-padding-horizontal - $header-gutter; - } - - &--collapsible { - cursor: pointer; - } - - &--sortable { - cursor: grab; - } - - &__title, - &__icon { - color: $header-text-color; - } - - &__title { - display: inline-block; - flex: 1 10 auto; - margin: 0; - font-size: 12px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - - &__icon { - width: 20px; - height: 20px; - margin: 0 $header-gutter; - transition: color $hover-transition-duration ease-in-out; - } + @media (min-width: $screen-sm-min) { + padding-left: $content-padding-horizontal - $header-gutter; } - &__content { - &-inner { - padding: $content-padding-vertical $content-padding-horizontal; - } + &--collapsible { + cursor: pointer; } - &__actions { - flex: 0 1 auto; - display: flex; - align-items: center; - white-space: nowrap; - overflow-x: hidden; - - &__single { - appearance: none; - border: 0 none; - background: none; - cursor: pointer; - color: $header-text-color; - opacity: 1; - transition: opacity $hover-transition-duration ease-in-out, color $hover-transition-duration ease-in-out, background-color $hover-transition-duration ease-in-out; - border-radius: 50%; - width: 30px; - height: 30px; - line-height: 1; - font-size: $action-font-size; - text-align: center; - padding: 0; - - &:not(:last-of-type) { - margin-right: 3px; - } - - &:focus, - &:hover { - color: #333; - background-color: rgba(0, 0, 0, 0.05); - } - - svg.icon { - width: 1em; - height: 1em; - vertical-align: middle; - } - - &[disabled] { - opacity: 0.2; - pointer-events: none; - } - } + &--sortable { + cursor: grab; } - &__type { - margin: 0 $header-gutter; - text-align: right; - font-size: 12px; - color: $header-text-color; - user-select: none; - vertical-align: 2px; - overflow-x: hidden; - text-overflow: ellipsis; + &__title, + &__icon { + color: $header-text-color; } - &.c-sf-block--error { - border-color: $error-border-color; + &__title { + display: inline-block; + flex: 1 10 auto; + margin: 0; + font-size: 12px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } - > .c-sf-block__header { - background: $error-background-color; - } + &__icon { + width: 20px; + height: 20px; + margin: 0 $header-gutter; + transition: color $hover-transition-duration ease-in-out; + } + } - &:hover, - &:focus { - border-color: $error-border-color-focus; + &__content { + &-inner { + padding: $content-padding-vertical $content-padding-horizontal; + } + } - > .c-sf-block__header { - background: $error-background-color; - } - } + &__actions { + flex: 0 1 auto; + display: flex; + align-items: center; + white-space: nowrap; + overflow-x: hidden; - // Duplicated because of - // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16651302/ - &:focus-within { - border-color: $error-border-color-focus; + &__single { + appearance: none; + border: 0 none; + background: none; + cursor: pointer; + color: $header-text-color; + opacity: 1; + transition: opacity $hover-transition-duration ease-in-out, + color $hover-transition-duration ease-in-out, + background-color $hover-transition-duration ease-in-out; + border-radius: 50%; + width: 30px; + height: 30px; + line-height: 1; + font-size: $action-font-size; + text-align: center; + padding: 0; - > .c-sf-block__header { - background: $error-background-color; - } - } + &:not(:last-of-type) { + margin-right: 3px; + } + + &:focus, + &:hover { + color: #333; + background-color: rgba(0, 0, 0, 0.05); + } + + svg.icon { + width: 1em; + height: 1em; + vertical-align: middle; + } + + &[disabled] { + opacity: 0.2; + pointer-events: none; + } + } + } + + &__type { + margin: 0 $header-gutter; + text-align: right; + font-size: 12px; + color: $header-text-color; + user-select: none; + vertical-align: 2px; + overflow-x: hidden; + text-overflow: ellipsis; + } + + &.c-sf-block--error { + border-color: $error-border-color; + + > .c-sf-block__header { + background: $error-background-color; } &:hover, &:focus { - border-color: $block-border-color-focus; - box-shadow: 3px 2px 3px -1px rgba(0, 0, 0, 0.1); + border-color: $error-border-color-focus; - > .c-sf-block__header { - background: $block-hover-background; - - .c-sf-block__header__title, - .c-sf-block__actions__single { - color: $header-text-color-focus; - } - } + > .c-sf-block__header { + background: $error-background-color; + } } // Duplicated because of // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16651302/ &:focus-within { - border-color: $block-border-color-focus; - box-shadow: 3px 2px 3px -1px rgba(0, 0, 0, 0.1); + border-color: $error-border-color-focus; - > .c-sf-block__header { - background: $block-hover-background; - - .c-sf-block__header__title, - .c-sf-block__actions__single { - color: $header-text-color-focus; - } - } + > .c-sf-block__header { + background: $error-background-color; + } } + } + + &:hover, + &:focus { + border-color: $block-border-color-focus; + box-shadow: 3px 2px 3px -1px rgba(0, 0, 0, 0.1); + + > .c-sf-block__header { + background: $block-hover-background; + + .c-sf-block__header__title, + .c-sf-block__actions__single { + color: $header-text-color-focus; + } + } + } + + // Duplicated because of + // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16651302/ + &:focus-within { + border-color: $block-border-color-focus; + box-shadow: 3px 2px 3px -1px rgba(0, 0, 0, 0.1); + + > .c-sf-block__header { + background: $block-hover-background; + + .c-sf-block__header__title, + .c-sf-block__actions__single { + color: $header-text-color-focus; + } + } + } } diff --git a/client/src/components/StreamField/scss/components/c-sf-button.scss b/client/src/components/StreamField/scss/components/c-sf-button.scss index 942e5d7e2b..e79c10f237 100644 --- a/client/src/components/StreamField/scss/components/c-sf-button.scss +++ b/client/src/components/StreamField/scss/components/c-sf-button.scss @@ -1,37 +1,38 @@ .c-sf-button { - flex: 1 1 200px; - margin: $add-panel-gutter; - appearance: none; - color: #333; - line-height: 1.833; - font-weight: 600; - font-size: 12px; - font-family: $font-sans; - text-align: left; - background: #eee; - padding: $type-button-padding-vertical $type-button-padding-horizontal; - border: 1px solid #e6e6e6; - border-radius: $border-radius; - outline: none; - cursor: pointer; - overflow: hidden; - transition: background-color $hover-transition-duration ease-in-out, color $hover-transition-duration ease-in-out; + flex: 1 1 200px; + margin: $add-panel-gutter; + appearance: none; + color: #333; + line-height: 1.833; + font-weight: 600; + font-size: 12px; + font-family: $font-sans; + text-align: left; + background: #eee; + padding: $type-button-padding-vertical $type-button-padding-horizontal; + border: 1px solid #e6e6e6; + border-radius: $border-radius; + outline: none; + cursor: pointer; + overflow: hidden; + transition: background-color $hover-transition-duration ease-in-out, + color $hover-transition-duration ease-in-out; - &:hover, - &:focus { - color: #fff; - background-color: $teal; - } + &:hover, + &:focus { + color: #fff; + background-color: $teal; + } - &[disabled] { - opacity: 0.2; - pointer-events: none; - } + &[disabled] { + opacity: 0.2; + pointer-events: none; + } - &__icon { - width: 16px; - height: 16px; - margin-right: 0.5em; - vertical-align: text-top; - } + &__icon { + width: 16px; + height: 16px; + margin-right: 0.5em; + vertical-align: text-top; + } } diff --git a/client/src/components/StreamField/scss/components/c-sf-container.scss b/client/src/components/StreamField/scss/components/c-sf-container.scss index 3e00a4672f..c78d981e70 100644 --- a/client/src/components/StreamField/scss/components/c-sf-container.scss +++ b/client/src/components/StreamField/scss/components/c-sf-container.scss @@ -1,102 +1,102 @@ -@use "sass:math"; +@use 'sass:math'; .c-sf-container { + position: relative; + display: flex; + flex-flow: column nowrap; + padding: $children-container-padding 0; + + // TODO: #CSSoverhaul the bse icon style here - the margin - should be reconsidered when + // re-building the icon component as part of the CSS These styles come from global + // label styles in css/mixins/_general.scss -@jonnyscholes + .c-sf-button__icon { + .icon::before { + margin: unset; + } + } + + &__block-container { position: relative; display: flex; flex-flow: column nowrap; - padding: $children-container-padding 0; + } - // TODO: #CSSoverhaul the bse icon style here - the margin - should be reconsidered when - // re-building the icon component as part of the CSS These styles come from global - // label styles in css/mixins/_general.scss -@jonnyscholes - .c-sf-button__icon { - .icon::before { - margin: unset; - } + &--add-in-gutter { + @media (min-width: $screen-sm-min) { + padding-left: $add-button-size; + + .c-sf-add-button { + width: $add-button-size; + height: 0; + transform: translate(-100%, math.div(-$add-button-size, 2)); + overflow: visible; + } + } + } + + // TODO: Remove these references to classes that are styled by core Wagtail CSS. The best + // opportunity for this is probably as part of Wagtails general CSS overhaul. -@jonnyscholes + .field { + + .field { + padding-top: math.div($grid-gutter-width, 2); } - &__block-container { - position: relative; - display: flex; - flex-flow: column nowrap; + // TODO: #CSSoverhaul global label styles need to be removed. These styles come from global + // label styles in css/_grid.scss -@jonnyscholes + label { + float: unset; // LEGIT + width: unset; // LEGIT + max-width: unset; // LEGIT + padding: 0; // LEGIT + + &::after { + content: ''; // LEGIT + } + + input[type='radio'] { + margin-bottom: 1.1em; + vertical-align: middle; + + /* stylelint-disable-next-line max-nesting-depth */ + &::before { + top: unset; + } + } } - &--add-in-gutter { - @media (min-width: $screen-sm-min) { - padding-left: $add-button-size; - - .c-sf-add-button { - width: $add-button-size; - height: 0; - transform: translate(-100%, math.div(-$add-button-size, 2)); - overflow: visible; - } - } + &__label { + display: block; + font-weight: bold; + margin-bottom: $grid-gutter-width * 0.25; } - // TODO: Remove these references to classes that are styled by core Wagtail CSS. The best - // opportunity for this is probably as part of Wagtails general CSS overhaul. -@jonnyscholes - .field { - + .field { - padding-top: math.div($grid-gutter-width, 2); - } - - // TODO: #CSSoverhaul global label styles need to be removed. These styles come from global - // label styles in css/_grid.scss -@jonnyscholes - label { - float: unset; // LEGIT - width: unset; // LEGIT - max-width: unset; // LEGIT - padding: 0; // LEGIT - - &::after { - content: ''; // LEGIT - } - - input[type='radio'] { - margin-bottom: 1.1em; - vertical-align: middle; - - /* stylelint-disable-next-line max-nesting-depth */ - &::before { - top: unset; - } - } - } - - &__label { - display: block; - font-weight: bold; - margin-bottom: $grid-gutter-width * 0.25; - } - - &.required > label::after { - content: '*'; - color: #cd3238; - font-weight: 700; - display: inline-block; - margin-left: 0.5em; - line-height: 1em; - font-size: 13px; - } + &.required > label::after { + content: '*'; + color: #cd3238; + font-weight: 700; + display: inline-block; + margin-left: 0.5em; + line-height: 1em; + font-size: 13px; } + } - // TODO: #CSSoverhaul This should be fixed as part of Wagtail CSS overhaul. The default - // `.field`/`.field-componet` (or whatever they become) should be full width by default. - // -@jonnyscholes - .field-content { - float: unset; - display: block; - width: unset; + // TODO: #CSSoverhaul This should be fixed as part of Wagtail CSS overhaul. The default + // `.field`/`.field-componet` (or whatever they become) should be full width by default. + // -@jonnyscholes + .field-content { + float: unset; + display: block; + width: unset; - textarea { - max-width: 100%; - } + textarea { + max-width: 100%; } + } - // TODO: #CSSoverhaul This should be fixed as part of Wagtail CSS overhaul. Whatever we do with - // `.field`/`.field-content` should take into account help text -@jonnyscholes - .help { - margin: 8px 0; - } + // TODO: #CSSoverhaul This should be fixed as part of Wagtail CSS overhaul. Whatever we do with + // `.field`/`.field-content` should take into account help text -@jonnyscholes + .help { + margin: 8px 0; + } } diff --git a/client/src/components/Transition/Transition.scss b/client/src/components/Transition/Transition.scss index 9dc05f466e..c94bccb4c6 100644 --- a/client/src/components/Transition/Transition.scss +++ b/client/src/components/Transition/Transition.scss @@ -5,54 +5,58 @@ $c-transition-duration: 200ms; .c-transition-group { - position: absolute; - width: 100%; - top: 0; + position: absolute; + width: 100%; + top: 0; } .c-transition-push-enter { - transform: translateX(100%); - transition: transform $c-transition-duration ease, opacity $c-transition-duration linear; - opacity: 0; + transform: translateX(100%); + transition: transform $c-transition-duration ease, + opacity $c-transition-duration linear; + opacity: 0; } .c-transition-push-enter-active { - transform: translateX(0); - opacity: 1; + transform: translateX(0); + opacity: 1; } .c-transition-push-leave { - transform: translateX(0); - transition: transform $c-transition-duration ease, opacity $c-transition-duration linear; - opacity: 1; + transform: translateX(0); + transition: transform $c-transition-duration ease, + opacity $c-transition-duration linear; + opacity: 1; } .c-transition-push-leave-active { - transform: translateX(-100%); - opacity: 0; + transform: translateX(-100%); + opacity: 0; } // ============================================================================= // Pop transition // ============================================================================= .c-transition-pop-enter { - transform: translateX(-100%); - transition: transform $c-transition-duration ease, opacity $c-transition-duration linear; - opacity: 0; + transform: translateX(-100%); + transition: transform $c-transition-duration ease, + opacity $c-transition-duration linear; + opacity: 0; } .c-transition-pop-enter-active { - transform: translateX(0); - opacity: 1; + transform: translateX(0); + opacity: 1; } .c-transition-pop-leave { - transform: translateX(0); - transition: transform $c-transition-duration ease, opacity $c-transition-duration linear; - opacity: 1; + transform: translateX(0); + transition: transform $c-transition-duration ease, + opacity $c-transition-duration linear; + opacity: 1; } .c-transition-pop-leave-active { - transform: translateX(100%); - opacity: 0; + transform: translateX(100%); + opacity: 0; } diff --git a/client/src/components/Transition/Transition.test.js b/client/src/components/Transition/Transition.test.js index ca433c39ec..64d4f8e783 100644 --- a/client/src/components/Transition/Transition.test.js +++ b/client/src/components/Transition/Transition.test.js @@ -13,6 +13,8 @@ describe('Transition', () => { }); it('label', () => { - expect(shallow(<Transition name={PUSH} label="Page explorer" />)).toMatchSnapshot(); + expect( + shallow(<Transition name={PUSH} label="Page explorer" />), + ).toMatchSnapshot(); }); }); diff --git a/client/src/components/UpgradeNotification/index.js b/client/src/components/UpgradeNotification/index.js index b3d3ecf933..710cdbe745 100644 --- a/client/src/components/UpgradeNotification/index.js +++ b/client/src/components/UpgradeNotification/index.js @@ -8,30 +8,41 @@ const initUpgradeNotification = () => { } /* - * Expected JSON payload: - * { - * "version" : "1.2.3", // Version number. Can only contain numbers and decimal point. - * "url" : "https://wagtail.org" // Absolute URL to page/file containing release notes or actual package. It's up to you. - * } - */ + * Expected JSON payload: + * { + * "version" : "1.2.3", // Version number. Can only contain numbers and decimal point. + * "url" : "https://wagtail.org" // Absolute URL to page/file containing release notes or actual package. It's up to you. + * } + */ const releasesUrl = 'https://releases.wagtail.org/latest.txt'; const currentVersion = container.dataset.wagtailVersion; - fetch(releasesUrl).then(response => { - if (response.status !== 200) { - // eslint-disable-next-line no-console - console.log(`Unexpected response from ${releasesUrl}. Status: ${response.status}`); - return false; - } - return response.json(); - }).then(data => { - if (data && data.version && versionOutOfDate(data.version, currentVersion)) { - container.querySelector('[data-upgrade-version]').innerText = data.version; - container.querySelector('[data-upgrade-link]').setAttribute('href', data.url); - container.style.display = ''; - } - }) - .catch(err => { + fetch(releasesUrl) + .then((response) => { + if (response.status !== 200) { + // eslint-disable-next-line no-console + console.log( + `Unexpected response from ${releasesUrl}. Status: ${response.status}`, + ); + return false; + } + return response.json(); + }) + .then((data) => { + if ( + data && + data.version && + versionOutOfDate(data.version, currentVersion) + ) { + container.querySelector('[data-upgrade-version]').innerText = + data.version; + container + .querySelector('[data-upgrade-link]') + .setAttribute('href', data.url); + container.style.display = ''; + } + }) + .catch((err) => { // eslint-disable-next-line no-console console.log(`Error fetching ${releasesUrl}. Error: ${err}`); }); diff --git a/client/src/custom.d.ts b/client/src/custom.d.ts index ab66aca089..5b9f56546c 100644 --- a/client/src/custom.d.ts +++ b/client/src/custom.d.ts @@ -1,32 +1,32 @@ export {}; declare global { - interface Window { - __REDUX_DEVTOOLS_EXTENSION__: any; - telepath: any; - } + interface Window { + __REDUX_DEVTOOLS_EXTENSION__: any; + telepath: any; + } - // Wagtail globals + // Wagtail globals - interface WagtailConfig { - ADMIN_API: { - PAGES: string; - DOCUMENTS: string; - IMAGES: string; - EXTRA_CHILDREN_PARAMETERS: string; - }; + interface WagtailConfig { + ADMIN_API: { + PAGES: string; + DOCUMENTS: string; + IMAGES: string; + EXTRA_CHILDREN_PARAMETERS: string; + }; - ADMIN_URLS: { - PAGES: string; - }; + ADMIN_URLS: { + PAGES: string; + }; - I18N_ENABLED: boolean; - LOCALES: { - code: string; - - display_name: string; - }[]; - STRINGS: any; - } - const wagtailConfig: WagtailConfig; + I18N_ENABLED: boolean; + LOCALES: { + code: string; + + display_name: string; + }[]; + STRINGS: any; + } + const wagtailConfig: WagtailConfig; } diff --git a/client/src/entrypoints/admin/bulk-actions.js b/client/src/entrypoints/admin/bulk-actions.js index 6d4491924e..36c23911f2 100644 --- a/client/src/entrypoints/admin/bulk-actions.js +++ b/client/src/entrypoints/admin/bulk-actions.js @@ -1,26 +1,29 @@ /* global wagtailConfig */ const BULK_ACTION_PAGE_CHECKBOX_INPUT = '[data-bulk-action-checkbox]'; -const BULK_ACTION_SELECT_ALL_CHECKBOX = '[data-bulk-action-select-all-checkbox]'; +const BULK_ACTION_SELECT_ALL_CHECKBOX = + '[data-bulk-action-select-all-checkbox]'; const BULK_ACTIONS_CHECKBOX_PARENT = '[data-bulk-action-parent-id]'; const BULK_ACTION_FOOTER = '[data-bulk-action-footer]'; const BULK_ACTION_NUM_OBJECTS = '[data-bulk-action-num-objects]'; -const BULK_ACTION_NUM_OBJECTS_IN_LISTING = '[data-bulk-action-num-objects-in-listing]'; +const BULK_ACTION_NUM_OBJECTS_IN_LISTING = + '[data-bulk-action-num-objects-in-listing]'; const checkedState = { checkedObjects: new Set(), numObjects: 0, selectAllInListing: false, - shouldShowAllInListingText: true + shouldShowAllInListingText: true, }; - /** * Utility function to get the appropriate string for display in action bar */ function getStringForListing(key) { if (wagtailConfig.STRINGS.BULK_ACTIONS[wagtailConfig.BULK_ACTION_ITEM_TYPE]) { - return wagtailConfig.STRINGS.BULK_ACTIONS[wagtailConfig.BULK_ACTION_ITEM_TYPE][key]; + return wagtailConfig.STRINGS.BULK_ACTIONS[ + wagtailConfig.BULK_ACTION_ITEM_TYPE + ][key]; } return wagtailConfig.STRINGS.BULK_ACTIONS.ITEM[key]; } @@ -29,11 +32,11 @@ function getStringForListing(key) { * Event listener for the `Select All` checkbox */ function onSelectAllChange(e) { - document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach(el => { + document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach((el) => { el.checked = e.target.checked; // eslint-disable-line no-param-reassign }); const changeEvent = new Event('change'); - document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT).forEach(el => { + document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT).forEach((el) => { if (el.checked !== e.target.checked) { // eslint-disable-next-line no-param-reassign el.checked = e.target.checked; @@ -51,7 +54,6 @@ function onSelectAllChange(e) { } } - /** * Event listener for individual checkbox */ @@ -62,7 +64,7 @@ function onSelectIndividualCheckbox(e) { checkedState.checkedObjects.add(Number(e.target.dataset.objectId)); } else { /* unchecks `Select all` checkbox as soon as one page is unchecked */ - document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach(el => { + document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach((el) => { el.checked = false; // eslint-disable-line no-param-reassign }); checkedState.checkedObjects.delete(Number(e.target.dataset.objectId)); @@ -73,10 +75,12 @@ function onSelectIndividualCheckbox(e) { if (numCheckedObjects === 0) { /* when all checkboxes are unchecked */ document.querySelector(BULK_ACTION_FOOTER).classList.add('hidden'); - document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT).forEach(el => el.classList.remove('show')); + document + .querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT) + .forEach((el) => el.classList.remove('show')); } else if (numCheckedObjects === 1 && prevLength === 0) { /* when 1 checkbox is checked for the first time */ - document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT).forEach(el => { + document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT).forEach((el) => { el.classList.add('show'); }); document.querySelector(BULK_ACTION_FOOTER).classList.remove('hidden'); @@ -84,14 +88,18 @@ function onSelectIndividualCheckbox(e) { if (numCheckedObjects === checkedState.numObjects) { /* when all checkboxes in the page are checked */ - document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach(el => { + document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach((el) => { el.checked = true; // eslint-disable-line no-param-reassign }); if (checkedState.shouldShowAllInListingText) { - document.querySelector(BULK_ACTION_NUM_OBJECTS_IN_LISTING).classList.remove('u-hidden'); + document + .querySelector(BULK_ACTION_NUM_OBJECTS_IN_LISTING) + .classList.remove('u-hidden'); } } else if (checkedState.shouldShowAllInListingText) { - document.querySelector(BULK_ACTION_NUM_OBJECTS_IN_LISTING).classList.add('u-hidden'); + document + .querySelector(BULK_ACTION_NUM_OBJECTS_IN_LISTING) + .classList.add('u-hidden'); } if (numCheckedObjects > 0) { @@ -101,12 +109,19 @@ function onSelectIndividualCheckbox(e) { numObjectsSelected = getStringForListing('SINGULAR'); } else { if (numCheckedObjects === checkedState.numObjects) { - numObjectsSelected = getStringForListing('ALL').replace('{0}', numCheckedObjects); + numObjectsSelected = getStringForListing('ALL').replace( + '{0}', + numCheckedObjects, + ); } else { - numObjectsSelected = getStringForListing('PLURAL').replace('{0}', numCheckedObjects); + numObjectsSelected = getStringForListing('PLURAL').replace( + '{0}', + numCheckedObjects, + ); } } - document.querySelector(BULK_ACTION_NUM_OBJECTS).textContent = numObjectsSelected; + document.querySelector(BULK_ACTION_NUM_OBJECTS).textContent = + numObjectsSelected; } } @@ -116,9 +131,12 @@ function onSelectIndividualCheckbox(e) { function onClickSelectAllInListing(e) { e.preventDefault(); checkedState.selectAllInListing = true; - document.querySelector(BULK_ACTION_NUM_OBJECTS). - textContent = `${getStringForListing('ALL_IN_LISTING')}.`; - document.querySelector(BULK_ACTION_NUM_OBJECTS_IN_LISTING).classList.add('u-hidden'); + document.querySelector( + BULK_ACTION_NUM_OBJECTS, + ).textContent = `${getStringForListing('ALL_IN_LISTING')}.`; + document + .querySelector(BULK_ACTION_NUM_OBJECTS_IN_LISTING) + .classList.add('u-hidden'); } /** @@ -136,64 +154,63 @@ function onClickActionButton(e) { urlParams.append('childOf', parentPageId); } } else { - checkedState.checkedObjects.forEach(objectId => { + checkedState.checkedObjects.forEach((objectId) => { urlParams.append('id', objectId); }); } window.location.href = `${url}&${urlParams.toString()}`; } - /** * Adds all event listeners */ function addBulkActionListeners() { const changeEvent = new Event('change'); - document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT) - .forEach(el => { - checkedState.numObjects++; - el.addEventListener('change', onSelectIndividualCheckbox); - }); - document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach(el => { + document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT).forEach((el) => { + checkedState.numObjects++; + el.addEventListener('change', onSelectIndividualCheckbox); + }); + document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach((el) => { el.addEventListener('change', onSelectAllChange); }); - document.querySelectorAll(`${BULK_ACTION_FOOTER} .bulk-action-btn`).forEach( - elem => elem.addEventListener('click', onClickActionButton) + document + .querySelectorAll(`${BULK_ACTION_FOOTER} .bulk-action-btn`) + .forEach((elem) => elem.addEventListener('click', onClickActionButton)); + const selectAllInListingText = document.querySelector( + BULK_ACTION_NUM_OBJECTS_IN_LISTING, ); - const selectAllInListingText = document.querySelector(BULK_ACTION_NUM_OBJECTS_IN_LISTING); - if (selectAllInListingText) selectAllInListingText.addEventListener('click', onClickSelectAllInListing); + if (selectAllInListingText) + selectAllInListingText.addEventListener('click', onClickSelectAllInListing); else checkedState.shouldShowAllInListingText = false; - document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT) - .forEach(el => { - if (el.checked) { - el.dispatchEvent(changeEvent); - } - }); + document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT).forEach((el) => { + if (el.checked) { + el.dispatchEvent(changeEvent); + } + }); } function rebindBulkActionsEventListeners() { // when deselecting all checkbox, simply hide the footer for smooth transition - document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach(el => { + document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach((el) => { el.checked = false; // eslint-disable-line no-param-reassign }); document.querySelector(BULK_ACTION_FOOTER).classList.add('hidden'); - document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach(el => { + document.querySelectorAll(BULK_ACTION_SELECT_ALL_CHECKBOX).forEach((el) => { // remove already attached event listener first el.removeEventListener('change', onSelectAllChange); el.addEventListener('change', onSelectAllChange); }); checkedState.checkedObjects.clear(); checkedState.numObjects = 0; - document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT) - .forEach(el => { - checkedState.numObjects++; - el.addEventListener('change', onSelectIndividualCheckbox); - }); + document.querySelectorAll(BULK_ACTION_PAGE_CHECKBOX_INPUT).forEach((el) => { + checkedState.numObjects++; + el.addEventListener('change', onSelectIndividualCheckbox); + }); } document.addEventListener('DOMContentLoaded', addBulkActionListeners); if (window.headerSearch) { - document.querySelector(window.headerSearch.termInput).addEventListener( - 'search-success', rebindBulkActionsEventListeners - ); + document + .querySelector(window.headerSearch.termInput) + .addEventListener('search-success', rebindBulkActionsEventListeners); } diff --git a/client/src/entrypoints/admin/collapsible.js b/client/src/entrypoints/admin/collapsible.js index bf07982e79..627c288986 100644 --- a/client/src/entrypoints/admin/collapsible.js +++ b/client/src/entrypoints/admin/collapsible.js @@ -9,9 +9,12 @@ function initCollapsibleBlocks() { $content .get(0) .dispatchEvent( - new CustomEvent('commentAnchorVisibilityChange', { bubbles: true }) + new CustomEvent('commentAnchorVisibilityChange', { bubbles: true }), ); - if ($target.hasClass('collapsed') && $target.find('.error-message').length === 0) { + if ( + $target.hasClass('collapsed') && + $target.find('.error-message').length === 0 + ) { $content.hide({ complete: onAnimationComplete, }); diff --git a/client/src/entrypoints/admin/comments.js b/client/src/entrypoints/admin/comments.js index 3a261afd8a..a9abc0e7d6 100644 --- a/client/src/entrypoints/admin/comments.js +++ b/client/src/entrypoints/admin/comments.js @@ -10,9 +10,9 @@ window.comments = (() => { const commentApp = initCommentApp(); /** - * Returns true if the provided keyboard event is using the 'add/focus comment' keyboard - * shortcut - */ + * Returns true if the provided keyboard event is using the 'add/focus comment' keyboard + * shortcut + */ function isCommentShortcut(e) { return (e.ctrlKey || e.metaKey) && e.altKey && e.keyCode === KEYCODE_M; } @@ -40,20 +40,20 @@ window.comments = (() => { */ class BasicFieldLevelAnnotation { /** - * Create a field-level annotation - * @param {Element} fieldNode - an element to provide the comment position - * @param {Element} node - the button to focus/pin the comment - */ + * Create a field-level annotation + * @param {Element} fieldNode - an element to provide the comment position + * @param {Element} node - the button to focus/pin the comment + */ constructor(fieldNode, node) { this.node = node; this.fieldNode = fieldNode; this.unsubscribe = null; } /** - * Subscribes the annotation to update when the state of a particular comment changes, - * and to focus that comment when clicked - * @param {number} localId - the localId of the comment to subscribe to - */ + * Subscribes the annotation to update when the state of a particular comment changes, + * and to focus that comment when clicked + * @param {number} localId - the localId of the comment to subscribe to + */ subscribeToUpdates(localId) { const { selectFocused, selectEnabled } = commentApp.selectors; const selectComment = commentApp.utils.selectCommentFactory(localId); @@ -73,7 +73,7 @@ window.comments = (() => { if (!comment) { this.onDelete(); } - const nowFocused = (selectFocused(state) === localId); + const nowFocused = selectFocused(state) === localId; if (nowFocused !== focused) { if (focused) { this.onUnfocus(); @@ -90,8 +90,7 @@ window.comments = (() => { } shown = selectEnabled(state); } - } - ); + }); this.setOnClickHandler(localId); } onDelete() { @@ -112,7 +111,7 @@ window.comments = (() => { onUnfocus() { this.node.classList.add('button-secondary'); this.node.ariaLabel = STRINGS.FOCUS_COMMENT; - + // TODO: ensure comment is focused accessibly when this is clicked, // and that screenreader users can return to the annotation point when desired } @@ -125,12 +124,17 @@ window.comments = (() => { setOnClickHandler(localId) { this.node.addEventListener('click', () => { commentApp.store.dispatch( - commentApp.actions.setFocusedComment(localId, { updatePinnedComment: true, forceFocus: true }) + commentApp.actions.setFocusedComment(localId, { + updatePinnedComment: true, + forceFocus: true, + }), ); }); } getTab() { - return this.fieldNode.closest('section[data-tab]')?.getAttribute('data-tab'); + return this.fieldNode + .closest('section[data-tab]') + ?.getAttribute('data-tab'); } getAnchorNode() { return this.fieldNode; @@ -138,11 +142,7 @@ window.comments = (() => { } class FieldLevelCommentWidget { - constructor({ - fieldNode, - commentAdditionNode, - annotationTemplateNode, - }) { + constructor({ fieldNode, commentAdditionNode, annotationTemplateNode }) { this.fieldNode = fieldNode; this.contentpath = getContentPath(fieldNode); this.commentAdditionNode = commentAdditionNode; @@ -153,34 +153,34 @@ window.comments = (() => { const { selectEnabled } = commentApp.selectors; const initialState = commentApp.store.getState(); let currentlyEnabled = selectEnabled(initialState); - const selectCommentsForContentPath = commentApp.utils.selectCommentsForContentPathFactory( - this.contentpath - ); + const selectCommentsForContentPath = + commentApp.utils.selectCommentsForContentPathFactory(this.contentpath); let currentComments = selectCommentsForContentPath(initialState); this.updateVisibility(currentComments.length === 0 && currentlyEnabled); const unsubscribeWidget = commentApp.store.subscribe(() => { const state = commentApp.store.getState(); const newComments = selectCommentsForContentPath(state); const newEnabled = selectEnabled(state); - const commentsChanged = (currentComments !== newComments); - const enabledChanged = (currentlyEnabled !== newEnabled); + const commentsChanged = currentComments !== newComments; + const enabledChanged = currentlyEnabled !== newEnabled; if (commentsChanged) { // Add annotations for any new comments currentComments = newComments; - currentComments.filter((comment) => comment.annotation === null).forEach((comment) => { - const annotation = this.getAnnotationForComment(comment); - commentApp.updateAnnotation( - annotation, - comment.localId - ); - annotation.subscribeToUpdates(comment.localId); - }); + currentComments + .filter((comment) => comment.annotation === null) + .forEach((comment) => { + const annotation = this.getAnnotationForComment(comment); + commentApp.updateAnnotation(annotation, comment.localId); + annotation.subscribeToUpdates(comment.localId); + }); } if (enabledChanged || commentsChanged) { // If comments have been enabled or disabled, or the comments have changed // check whether to show the widget (if comments are enabled and there are no existing comments) currentlyEnabled = newEnabled; - this.updateVisibility(currentComments.length === 0 && currentlyEnabled); + this.updateVisibility( + currentComments.length === 0 && currentlyEnabled, + ); } }); initialState.comments.comments.forEach((comment) => { @@ -206,15 +206,15 @@ window.comments = (() => { addComment(); } else { commentApp.store.dispatch( - commentApp.actions.setFocusedComment( - currentComments[0].localId, - { updatePinnedComment: true, forceFocus: true } - ) + commentApp.actions.setFocusedComment(currentComments[0].localId, { + updatePinnedComment: true, + forceFocus: true, + }), ); } } }); - + return unsubscribeWidget; // TODO: listen for widget deletion and use this } updateVisibility(newShown) { @@ -233,8 +233,15 @@ window.comments = (() => { const annotationNode = this.annotationTemplateNode.cloneNode(true); annotationNode.id = ''; annotationNode.classList.remove('u-hidden'); - this.commentAdditionNode.insertAdjacentElement('afterend', annotationNode); - return new BasicFieldLevelAnnotation(this.fieldNode, annotationNode, commentApp); + this.commentAdditionNode.insertAdjacentElement( + 'afterend', + annotationNode, + ); + return new BasicFieldLevelAnnotation( + this.fieldNode, + annotationNode, + commentApp, + ); } } @@ -254,14 +261,23 @@ window.comments = (() => { const commentsOutputElement = document.getElementById('comments-output'); const dataElement = document.getElementById('comments-data'); if (!commentsElement || !commentsOutputElement || !dataElement) { - throw new Error('Comments app failed to initialise. Missing HTML element'); + throw new Error( + 'Comments app failed to initialise. Missing HTML element', + ); } const data = JSON.parse(dataElement.textContent); commentApp.renderApp( - commentsElement, commentsOutputElement, data.user, data.comments, new Map(Object.entries(data.authors)), STRINGS + commentsElement, + commentsOutputElement, + data.user, + data.comments, + new Map(Object.entries(data.authors)), + STRINGS, ); - Array.from(formElement.querySelectorAll('[data-component="add-comment-button"]')).forEach(initAddCommentButton); + Array.from( + formElement.querySelectorAll('[data-component="add-comment-button"]'), + ).forEach(initAddCommentButton); // Attach the commenting app to the tab navigation, if it exists const tabNavElement = formElement.querySelector('[data-tab-nav]'); @@ -274,10 +290,16 @@ window.comments = (() => { // Comments toggle const commentToggleWrapper = formElement.querySelector('.comments-toggle'); - const commentToggle = formElement.querySelector('.comments-toggle input[type=checkbox]'); + const commentToggle = formElement.querySelector( + '.comments-toggle input[type=checkbox]', + ); const tabContentElement = formElement.querySelector('.tab-content'); - const commentNotificationsToggleButton = formElement.querySelector('.comment-notifications-toggle-button'); - const commentNotificationsDropdown = formElement.querySelector('.comment-notifications-dropdown'); + const commentNotificationsToggleButton = formElement.querySelector( + '.comment-notifications-toggle-button', + ); + const commentNotificationsDropdown = formElement.querySelector( + '.comment-notifications-dropdown', + ); const updateCommentVisibility = (visible) => { // Show/hide comments @@ -287,19 +309,31 @@ window.comments = (() => { if (visible) { tabContentElement.classList.add('tab-content--comments-enabled'); commentToggleWrapper.classList.add('comments-toggle--active'); - commentNotificationsToggleButton.classList.add('comment-notifications-toggle-button--active'); + commentNotificationsToggleButton.classList.add( + 'comment-notifications-toggle-button--active', + ); } else { tabContentElement.classList.remove('tab-content--comments-enabled'); commentToggleWrapper.classList.remove('comments-toggle--active'); - commentNotificationsToggleButton.classList.remove('comment-notifications-toggle-button--active'); - commentNotificationsDropdown.classList.remove('comment-notifications-dropdown--active'); - commentNotificationsToggleButton.classList.remove('comment-notifications-toggle-button--icon-toggle'); + commentNotificationsToggleButton.classList.remove( + 'comment-notifications-toggle-button--active', + ); + commentNotificationsDropdown.classList.remove( + 'comment-notifications-dropdown--active', + ); + commentNotificationsToggleButton.classList.remove( + 'comment-notifications-toggle-button--icon-toggle', + ); } }; commentNotificationsToggleButton.addEventListener('click', () => { - commentNotificationsDropdown.classList.toggle('comment-notifications-dropdown--active'); - commentNotificationsToggleButton.classList.toggle('comment-notifications-toggle-button--icon-toggle'); + commentNotificationsDropdown.classList.toggle( + 'comment-notifications-dropdown--active', + ); + commentNotificationsToggleButton.classList.toggle( + 'comment-notifications-toggle-button--icon-toggle', + ); }); commentToggle.addEventListener('change', (e) => { @@ -310,7 +344,9 @@ window.comments = (() => { // Keep number of comments up to date with comment app const commentCounter = formElement.querySelector('.comments-toggle__count'); const updateCommentCount = () => { - const commentCount = commentApp.selectors.selectCommentCount(commentApp.store.getState()); + const commentCount = commentApp.selectors.selectCommentCount( + commentApp.store.getState(), + ); if (commentCount > 0) { commentCounter.innerText = commentCount.toString(); diff --git a/client/src/entrypoints/admin/core.js b/client/src/entrypoints/admin/core.js index 98f3fa47e7..8ea54ec8d6 100644 --- a/client/src/entrypoints/admin/core.js +++ b/client/src/entrypoints/admin/core.js @@ -2,7 +2,9 @@ import $ from 'jquery'; /* generic function for adding a message to message area through JS alone */ function addMessage(status, text) { - $('.messages').addClass('new').empty() + $('.messages') + .addClass('new') + .empty() .append('<ul><li class="' + status + '">' + text + '</li></ul>'); const addMsgTimeout = setTimeout(() => { $('.messages').addClass('appear'); @@ -17,7 +19,7 @@ function escapeHtml(text) { '<': '<', '>': '>', '"': '"', - '\'': ''' + "'": ''', }; return text.replace(/[&<>"']/g, (char) => map[char]); @@ -25,18 +27,21 @@ function escapeHtml(text) { window.escapeHtml = escapeHtml; function initTagField(id, autocompleteUrl, options) { - const finalOptions = Object.assign({ - autocomplete: { source: autocompleteUrl }, - preprocessTag(val) { - // Double quote a tag if it contains a space - // and if it isn't already quoted. - if (val && val[0] !== '"' && val.indexOf(' ') > -1) { - return '"' + val + '"'; - } + const finalOptions = Object.assign( + { + autocomplete: { source: autocompleteUrl }, + preprocessTag(val) { + // Double quote a tag if it contains a space + // and if it isn't already quoted. + if (val && val[0] !== '"' && val.indexOf(' ') > -1) { + return '"' + val + '"'; + } - return val; + return val; + }, }, - }, options); + options, + ); $('#' + id).tagit(finalOptions); } @@ -59,7 +64,7 @@ window.initTagField = initTagField; * should include comments * - callback - A function to be run when the dirty status of the form, or the comments * system (if using) changes, taking formDirty, commentsDirty as arguments -*/ + */ function enableDirtyFormCheck(formSelector, options) { const $form = $(formSelector); @@ -85,17 +90,24 @@ function enableDirtyFormCheck(formSelector, options) { let updateIsCommentsDirtyTimeout = -1; if (commentApp) { - isCommentsDirty = commentApp.selectors.selectIsDirty(commentApp.store.getState()); + isCommentsDirty = commentApp.selectors.selectIsDirty( + commentApp.store.getState(), + ); commentApp.store.subscribe(() => { // Update on a timeout to match the timings for responding to page form changes clearTimeout(updateIsCommentsDirtyTimeout); - updateIsCommentsDirtyTimeout = setTimeout(() => { - const newIsCommentsDirty = commentApp.selectors.selectIsDirty(commentApp.store.getState()); - if (newIsCommentsDirty !== isCommentsDirty) { - isCommentsDirty = newIsCommentsDirty; - updateCallback(isDirty, isCommentsDirty); - } - }, isCommentsDirty ? 3000 : 300); + updateIsCommentsDirtyTimeout = setTimeout( + () => { + const newIsCommentsDirty = commentApp.selectors.selectIsDirty( + commentApp.store.getState(), + ); + if (newIsCommentsDirty !== isCommentsDirty) { + isCommentsDirty = newIsCommentsDirty; + updateCallback(isDirty, isCommentsDirty); + } + }, + isCommentsDirty ? 3000 : 300, + ); }); } @@ -111,7 +123,9 @@ function enableDirtyFormCheck(formSelector, options) { } const formData = new FormData($form[0]); - const keys = Array.from(formData.keys()).filter((key) => !key.startsWith('comments-')); + const keys = Array.from(formData.keys()).filter( + (key) => !key.startsWith('comments-'), + ); if (keys.length !== initialData.size) { return true; } @@ -122,7 +136,10 @@ function enableDirtyFormCheck(formSelector, options) { if (newValue === oldValue) { return false; } else if (Array.isArray(newValue) && Array.isArray(oldValue)) { - return newValue.length !== oldValue.length || newValue.some((value, index) => value !== oldValue[index]); + return ( + newValue.length !== oldValue.length || + newValue.some((value, index) => value !== oldValue[index]) + ); } return false; }); @@ -143,8 +160,8 @@ function enableDirtyFormCheck(formSelector, options) { const initialFormData = new FormData($form[0]); initialData = new Map(); Array.from(initialFormData.keys()) - .filter(key => !key.startsWith('comments-')) - .forEach(key => initialData.set(key, initialFormData.getAll(key))); + .filter((key) => !key.startsWith('comments-')) + .forEach((key) => initialData.set(key, initialFormData.getAll(key))); const updateDirtyCheck = () => { clearTimeout(updateIsDirtyTimeout); @@ -159,7 +176,10 @@ function enableDirtyFormCheck(formSelector, options) { const validInputNodeInList = (nodeList) => { for (const node of nodeList) { - if (node.nodeType === node.ELEMENT_NODE && ['INPUT', 'TEXTAREA', 'SELECT'].includes(node.tagName)) { + if ( + node.nodeType === node.ELEMENT_NODE && + ['INPUT', 'TEXTAREA', 'SELECT'].includes(node.tagName) + ) { return true; } } @@ -168,7 +188,10 @@ function enableDirtyFormCheck(formSelector, options) { const observer = new MutationObserver((mutationList) => { for (const mutation of mutationList) { - if (validInputNodeInList(mutation.addedNodes) || validInputNodeInList(mutation.removedNodes)) { + if ( + validInputNodeInList(mutation.addedNodes) || + validInputNodeInList(mutation.removedNodes) + ) { updateDirtyCheck(); return; } @@ -177,7 +200,7 @@ function enableDirtyFormCheck(formSelector, options) { observer.observe($form[0], { childList: true, attributes: false, - subtree: true + subtree: true, }); }, 1000 * 10); } @@ -186,9 +209,7 @@ function enableDirtyFormCheck(formSelector, options) { window.addEventListener('beforeunload', (event) => { clearTimeout(updateIsDirtyTimeout); updateIsDirty(); - const displayConfirmation = ( - !formSubmitted && (isDirty || isCommentsDirty) - ); + const displayConfirmation = !formSubmitted && (isDirty || isCommentsDirty); if (displayConfirmation) { // eslint-disable-next-line no-param-reassign @@ -314,13 +335,17 @@ $(() => { tabNavElem.dataset.currentTab = tabButtonElem.dataset.tab; // Trigger switch event - tabNavElem.dispatchEvent(new CustomEvent('switch', { detail: { tab: tabButtonElem.dataset.tab } })); + tabNavElem.dispatchEvent( + new CustomEvent('switch', { detail: { tab: tabButtonElem.dataset.tab } }), + ); }; if (window.location.hash) { /* look for a tab matching the URL hash and activate it if found */ const cleanedHash = window.location.hash.replace(/[^\w\-#]/g, ''); - const tab = document.querySelector('a[href="' + cleanedHash + '"][data-tab]'); + const tab = document.querySelector( + 'a[href="' + cleanedHash + '"][data-tab]', + ); if (tab) showTab(tab); } @@ -363,13 +388,13 @@ $(() => { }); /* Dropzones */ - // eslint-disable-next-line func-names - $('.drop-zone').on('dragover', function () { - $(this).addClass('hovered'); - // eslint-disable-next-line func-names - }).on('dragleave dragend drop', function () { - $(this).removeClass('hovered'); - }); + $('.drop-zone') + .on('dragover', function onDragOver() { + $(this).addClass('hovered'); + }) + .on('dragleave dragend drop', function onDragLeave() { + $(this).removeClass('hovered'); + }); /* Header search behaviour */ if (window.headerSearch) { @@ -415,14 +440,16 @@ $(() => { complete() { window.wagtail.ui.initDropDowns(); $inputContainer.removeClass(workingClasses); - } + }, }); } }; // eslint-disable-next-line func-names const getURLParam = function (name) { - const results = new RegExp('[\\?&]' + name + '=([^]*)').exec(window.location.search); + const results = new RegExp('[\\?&]' + name + '=([^]*)').exec( + window.location.search, + ); if (results) { return results[1]; } @@ -440,7 +467,10 @@ $(() => { // eslint-disable-next-line func-names window.cancelSpinner = function () { - $self.prop('disabled', '').removeData(dataName).removeClass('button-longrunning-active'); + $self + .prop('disabled', '') + .removeData(dataName) + .removeClass('button-longrunning-active'); if ($self.data('clicked-text')) { $replacementElem.text($self.data('original-text')); @@ -450,7 +480,12 @@ $(() => { // If client-side validation is active on this form, and is going to block submission of the // form, don't activate the spinner const form = $self.closest('form').get(0); - if (form && form.checkValidity && !form.noValidate && (!form.checkValidity())) { + if ( + form && + form.checkValidity && + !form.noValidate && + !form.checkValidity() + ) { return; } @@ -461,12 +496,15 @@ $(() => { if (!$self.data(dataName)) { // Button re-enables after a timeout to prevent button becoming // permanently un-usable - $self.data(dataName, setTimeout(() => { - clearTimeout($self.data(dataName)); + $self.data( + dataName, + setTimeout(() => { + clearTimeout($self.data(dataName)); - // eslint-disable-next-line no-undef - cancelSpinner(); - }, reEnableAfter * 1000)); + // eslint-disable-next-line no-undef + cancelSpinner(); + }, reEnableAfter * 1000), + ); if ($self.data('clicked-text') && $replacementElem.length) { // Save current button text @@ -511,10 +549,9 @@ const ARIA = 'aria-hidden'; const keys = { ESC: 27, ENTER: 13, - SPACE: 32 + SPACE: 32, }; - /** * Singleton controller and registry for DropDown components. * @@ -556,10 +593,9 @@ const DropDownController = { }); return needle; - } + }, }; - /** * DropDown component * @@ -572,7 +608,9 @@ function DropDown(el, registry) { if (!el || !registry) { if ('error' in console) { // eslint-disable-next-line no-console - console.error('A dropdown was created without an element or the DropDownController.\nMake sure to pass both to your component.'); + console.error( + 'A dropdown was created without an element or the DropDownController.\nMake sure to pass both to your component.', + ); return; } } @@ -581,7 +619,7 @@ function DropDown(el, registry) { this.$parent = $(el).parents(LISTING_TITLE_SELECTOR); this.state = { - isOpen: false + isOpen: false, }; this.registry = registry; @@ -654,7 +692,7 @@ DropDown.prototype = { if (!$(relTarget).parents().is(el)) { this.closeDropDown(); } - } + }, }; function initDropDown() { @@ -694,9 +732,13 @@ function initButtonSelects() { e.preventDefault(); inputElement.value = buttonElement.value; - qsa(element, '.button-select__option--selected').forEach((selectedButtonElement) => { - selectedButtonElement.classList.remove('button-select__option--selected'); - }); + qsa(element, '.button-select__option--selected').forEach( + (selectedButtonElement) => { + selectedButtonElement.classList.remove( + 'button-select__option--selected', + ); + }, + ); buttonElement.classList.add('button-select__option--selected'); }); diff --git a/client/src/entrypoints/admin/date-time-chooser.js b/client/src/entrypoints/admin/date-time-chooser.js index c49bf5f094..b6213c5c33 100644 --- a/client/src/entrypoints/admin/date-time-chooser.js +++ b/client/src/entrypoints/admin/date-time-chooser.js @@ -11,9 +11,11 @@ $.datetimepicker.setLocale('wagtail_custom_locale'); // Compare two date objects. Ignore minutes and seconds. function dateEqual(x, y) { - return x.getDate() === y.getDate() && - x.getMonth() === y.getMonth() && - x.getYear() === y.getYear(); + return ( + x.getDate() === y.getDate() && + x.getMonth() === y.getMonth() && + x.getYear() === y.getYear() + ); } window.dateEqual = dateEqual; @@ -24,79 +26,111 @@ Keep the normal behaviour if the home button is clicked. function hideCurrent(current, input) { const selected = new Date(input[0].value); if (!dateEqual(selected, current)) { - $(this).find('.xdsoft_datepicker .xdsoft_current:not(.xdsoft_today)').removeClass('xdsoft_current'); + $(this) + .find('.xdsoft_datepicker .xdsoft_current:not(.xdsoft_today)') + .removeClass('xdsoft_current'); } } window.hideCurrent = hideCurrent; function initDateChooser(id, opts) { if (window.dateTimePickerTranslations) { - $('#' + id).datetimepicker($.extend({ - closeOnDateSelect: true, - timepicker: false, - scrollInput: false, - format: 'Y-m-d', - onGenerate: hideCurrent, - onChangeDateTime(_, $el) { - $el.get(0).dispatchEvent(new Event('change')); - } - }, opts || {})); + $('#' + id).datetimepicker( + $.extend( + { + closeOnDateSelect: true, + timepicker: false, + scrollInput: false, + format: 'Y-m-d', + onGenerate: hideCurrent, + onChangeDateTime(_, $el) { + $el.get(0).dispatchEvent(new Event('change')); + }, + }, + opts || {}, + ), + ); } else { - $('#' + id).datetimepicker($.extend({ - timepicker: false, - scrollInput: false, - format: 'Y-m-d', - onGenerate: hideCurrent, - onChangeDateTime(_, $el) { - $el.get(0).dispatchEvent(new Event('change')); - } - }, opts || {})); + $('#' + id).datetimepicker( + $.extend( + { + timepicker: false, + scrollInput: false, + format: 'Y-m-d', + onGenerate: hideCurrent, + onChangeDateTime(_, $el) { + $el.get(0).dispatchEvent(new Event('change')); + }, + }, + opts || {}, + ), + ); } } window.initDateChooser = initDateChooser; function initTimeChooser(id, opts) { if (window.dateTimePickerTranslations) { - $('#' + id).datetimepicker($.extend({ - closeOnDateSelect: true, - datepicker: false, - scrollInput: false, - format: 'H:i', - onChangeDateTime(_, $el) { - $el.get(0).dispatchEvent(new Event('change')); - } - }, opts || {})); + $('#' + id).datetimepicker( + $.extend( + { + closeOnDateSelect: true, + datepicker: false, + scrollInput: false, + format: 'H:i', + onChangeDateTime(_, $el) { + $el.get(0).dispatchEvent(new Event('change')); + }, + }, + opts || {}, + ), + ); } else { - $('#' + id).datetimepicker($.extend({ - datepicker: false, - format: 'H:i', - onChangeDateTime(_, $el) { - $el.get(0).dispatchEvent(new Event('change')); - } - }, opts || {})); + $('#' + id).datetimepicker( + $.extend( + { + datepicker: false, + format: 'H:i', + onChangeDateTime(_, $el) { + $el.get(0).dispatchEvent(new Event('change')); + }, + }, + opts || {}, + ), + ); } } window.initTimeChooser = initTimeChooser; function initDateTimeChooser(id, opts) { if (window.dateTimePickerTranslations) { - $('#' + id).datetimepicker($.extend({ - closeOnDateSelect: true, - format: 'Y-m-d H:i', - scrollInput: false, - onGenerate: hideCurrent, - onChangeDateTime(_, $el) { - $el.get(0).dispatchEvent(new Event('change')); - } - }, opts || {})); + $('#' + id).datetimepicker( + $.extend( + { + closeOnDateSelect: true, + format: 'Y-m-d H:i', + scrollInput: false, + onGenerate: hideCurrent, + onChangeDateTime(_, $el) { + $el.get(0).dispatchEvent(new Event('change')); + }, + }, + opts || {}, + ), + ); } else { - $('#' + id).datetimepicker($.extend({ - format: 'Y-m-d H:i', - onGenerate: hideCurrent, - onChangeDateTime(_, $el) { - $el.get(0).dispatchEvent(new Event('change')); - } - }, opts || {})); + $('#' + id).datetimepicker( + $.extend( + { + format: 'Y-m-d H:i', + onGenerate: hideCurrent, + onChangeDateTime(_, $el) { + $el.get(0).dispatchEvent(new Event('change')); + }, + }, + opts || {}, + ), + ); } } window.initDateTimeChooser = initDateTimeChooser; diff --git a/client/src/entrypoints/admin/draftail.test.js b/client/src/entrypoints/admin/draftail.test.js index f655933bc5..b263b316cc 100644 --- a/client/src/entrypoints/admin/draftail.test.js +++ b/client/src/entrypoints/admin/draftail.test.js @@ -10,7 +10,12 @@ describe('draftail', () => { }); it('has defaults registered', () => { - expect(Object.keys(window.draftail.registerPlugin({}))) - .toEqual(['DOCUMENT', 'LINK', 'IMAGE', 'EMBED', 'undefined']); + expect(Object.keys(window.draftail.registerPlugin({}))).toEqual([ + 'DOCUMENT', + 'LINK', + 'IMAGE', + 'EMBED', + 'undefined', + ]); }); }); diff --git a/client/src/entrypoints/admin/expanding-formset.js b/client/src/entrypoints/admin/expanding-formset.js index e68cc1be64..a04d2468ef 100644 --- a/client/src/entrypoints/admin/expanding-formset.js +++ b/client/src/entrypoints/admin/expanding-formset.js @@ -12,7 +12,9 @@ function buildExpandingFormset(prefix, opts = {}) { } } - let emptyFormTemplate = document.getElementById(prefix + '-EMPTY_FORM_TEMPLATE'); + let emptyFormTemplate = document.getElementById( + prefix + '-EMPTY_FORM_TEMPLATE', + ); if (emptyFormTemplate.innerText) { emptyFormTemplate = emptyFormTemplate.innerText; } else if (emptyFormTemplate.textContent) { diff --git a/client/src/entrypoints/admin/expanding-formset.test.js b/client/src/entrypoints/admin/expanding-formset.test.js index 426e374148..39df1c054d 100644 --- a/client/src/entrypoints/admin/expanding-formset.test.js +++ b/client/src/entrypoints/admin/expanding-formset.test.js @@ -9,20 +9,23 @@ describe('buildExpandingFormset', () => { expect(window.buildExpandingFormset).toBeDefined(); }); - it('should add an expanded item if the add button is not disabled', () => { const prefix = 'id_form_fields'; document.body.innerHTML = ` <div class="object" id="content"> <input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS"> <ul id="${prefix}-FORMS"> - ${[0, 1].map(id => ` + ${[0, 1].map( + (id) => ` <li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled> <input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label"> - <input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id"> + <input type="hidden" name="form_fields-${id}-id" value="${ + id + 1 + }" id="id_form_fields-${id}-id"> <input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE"> </li> - `)} + `, + )} </ul> <button class="button" id="${prefix}-ADD" type="button"> Add form fields @@ -40,7 +43,9 @@ describe('buildExpandingFormset', () => { const onInit = jest.fn(); expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2'); - expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2); + expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength( + 2, + ); expect(onAdd).not.toHaveBeenCalled(); expect(onInit).not.toHaveBeenCalled(); @@ -54,21 +59,30 @@ describe('buildExpandingFormset', () => { expect(onInit).toHaveBeenNthCalledWith(2, 1); // click the 'add' button - document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click')); + document + .getElementById(`${prefix}-ADD`) + .dispatchEvent(new MouseEvent('click')); // check that template was generated and additional onInit / onAdd called expect(onAdd).toHaveBeenCalledWith(2); // zero indexed expect(onInit).toHaveBeenCalledTimes(3); expect(onInit).toHaveBeenLastCalledWith(2); expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('3'); - expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(3); - + expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength( + 3, + ); // check template was created into a new form item or malformed - expect(document.getElementById('inline_child_form_fields-__prefix__')).toBeNull(); - const newFormHtml = document.getElementById(`inline_child_form_fields-${2}`); + expect( + document.getElementById('inline_child_form_fields-__prefix__'), + ).toBeNull(); + const newFormHtml = document.getElementById( + `inline_child_form_fields-${2}`, + ); expect(newFormHtml.querySelectorAll('[id*="__prefix__"]')).toHaveLength(0); - expect(newFormHtml.querySelectorAll(`[id*="form_fields-${2}"]`)).toHaveLength(3); + expect( + newFormHtml.querySelectorAll(`[id*="form_fields-${2}"]`), + ).toHaveLength(3); expect(newFormHtml).toMatchSnapshot(); }); @@ -79,13 +93,17 @@ describe('buildExpandingFormset', () => { <div class="object" id="content"> <input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS"> <ul id="${prefix}-FORMS"> - ${[0, 1].map(id => ` + ${[0, 1].map( + (id) => ` <li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled> <input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label"> - <input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id"> + <input type="hidden" name="form_fields-${id}-id" value="${ + id + 1 + }" id="id_form_fields-${id}-id"> <input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE"> </li> - `)} + `, + )} </ul> <button class="button disabled" id="${prefix}-ADD" type="button"> Add form fields (DISABLED) @@ -103,7 +121,9 @@ describe('buildExpandingFormset', () => { const onInit = jest.fn(); expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2'); - expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2); + expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength( + 2, + ); expect(onAdd).not.toHaveBeenCalled(); expect(onInit).not.toHaveBeenCalled(); @@ -116,16 +136,22 @@ describe('buildExpandingFormset', () => { expect(onInit).toHaveBeenNthCalledWith(2, 1); // click the 'add' button - document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click')); + document + .getElementById(`${prefix}-ADD`) + .dispatchEvent(new MouseEvent('click')); // check that no template was generated and additional onInit / onAdd not called expect(onAdd).not.toHaveBeenCalled(); expect(onInit).toHaveBeenCalledTimes(2); expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2'); - expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2); + expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength( + 2, + ); // check template was not created into a new form item or malformed - expect(document.getElementById('inline_child_form_fields-__prefix__')).toBeNull(); + expect( + document.getElementById('inline_child_form_fields-__prefix__'), + ).toBeNull(); }); it('should replace the __prefix__ correctly for nested formset templates', () => { @@ -148,18 +174,21 @@ describe('buildExpandingFormset', () => { <-/script> `; - document.body.innerHTML = ` <div class="object" id="content"> <input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS"> <ul id="${prefix}-FORMS"> - ${[0, 1].map(id => ` + ${[0, 1].map( + (id) => ` <li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled> <input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label"> - <input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id"> + <input type="hidden" name="form_fields-${id}-id" value="${ + id + 1 + }" id="id_form_fields-${id}-id"> <input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE"> </li> - `)} + `, + )} </ul> <button class="button" id="${prefix}-ADD" type="button"> Add Venue @@ -174,12 +203,13 @@ describe('buildExpandingFormset', () => { </script> </div>`; - const onAdd = jest.fn(); const onInit = jest.fn(); expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2'); - expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2); + expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength( + 2, + ); expect(onAdd).not.toHaveBeenCalled(); expect(onInit).not.toHaveBeenCalled(); @@ -193,21 +223,29 @@ describe('buildExpandingFormset', () => { expect(onInit).toHaveBeenNthCalledWith(2, 1); // click the 'add' button - document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click')); + document + .getElementById(`${prefix}-ADD`) + .dispatchEvent(new MouseEvent('click')); // check that template was generated and additional onInit / onAdd called expect(onAdd).toHaveBeenCalledWith(2); // zero indexed expect(onInit).toHaveBeenCalledTimes(3); expect(onInit).toHaveBeenLastCalledWith(2); expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('3'); - expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(3); + expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength( + 3, + ); // check the nested template was created with the correct prefixes - const newTemplate = document.getElementById(`${prefix}-2-events-EMPTY_FORM_TEMPLATE`); + const newTemplate = document.getElementById( + `${prefix}-2-events-EMPTY_FORM_TEMPLATE`, + ); expect(newTemplate).toBeTruthy(); - expect(newTemplate.textContent).toContain('id="id_venues-2-events-__prefix__-DELETE-button"'); expect(newTemplate.textContent).toContain( - '<input type="text" name="venues-2-events-__prefix__-name" id="id_venues-2-events-__prefix__-name">' + 'id="id_venues-2-events-__prefix__-DELETE-button"', + ); + expect(newTemplate.textContent).toContain( + '<input type="text" name="venues-2-events-__prefix__-name" id="id_venues-2-events-__prefix__-name">', ); }); }); diff --git a/client/src/entrypoints/admin/filtered-select.js b/client/src/entrypoints/admin/filtered-select.js index 9f22210d65..3ab49a3a35 100644 --- a/client/src/entrypoints/admin/filtered-select.js +++ b/client/src/entrypoints/admin/filtered-select.js @@ -46,7 +46,7 @@ $(() => { optionData.push({ value: this.value, label: this.label, - filterValue: filterValue + filterValue: filterValue, }); }); @@ -62,7 +62,10 @@ $(() => { } else { filteredValues = []; for (let i = 0; i < optionData.length; i++) { - if (optionData[i].value === '' || optionData[i].filterValue.indexOf(chosenFilter) !== -1) { + if ( + optionData[i].value === '' || + optionData[i].filterValue.indexOf(chosenFilter) !== -1 + ) { filteredValues.push(optionData[i]); } } diff --git a/client/src/entrypoints/admin/hallo-bootstrap.js b/client/src/entrypoints/admin/hallo-bootstrap.js index 93fa451b08..c1f264f861 100644 --- a/client/src/entrypoints/admin/hallo-bootstrap.js +++ b/client/src/entrypoints/admin/hallo-bootstrap.js @@ -2,7 +2,9 @@ import $ from 'jquery'; function makeHalloRichTextEditable(id, plugins) { const input = $('#' + id); - const editor = $('<div class="halloeditor" data-hallo-editor></div>').html(input.val()); + const editor = $('<div class="halloeditor" data-hallo-editor></div>').html( + input.val(), + ); editor.insertBefore(input); input.hide(); @@ -12,10 +14,12 @@ function makeHalloRichTextEditable(id, plugins) { (we don't remove the span entirely as that messes with the cursor position, and spans will be removed anyway by our whitelisting) */ - // eslint-disable-next-line func-names - $('span[style]', editor).filter(function () { - return this.attributes.length === 1; - }).removeAttr('style'); + $('span[style]', editor) + // eslint-disable-next-line func-names + .filter(function () { + return this.attributes.length === 1; + }) + .removeAttr('style'); removeStylingPending = false; } @@ -29,27 +33,30 @@ function makeHalloRichTextEditable(id, plugins) { const closestObj = input.closest('.object'); - editor.hallo({ - toolbar: 'halloToolbarFixed', - toolbarCssClass: (closestObj.hasClass('full')) ? 'full' : '', - /* use the passed-in plugins arg */ - plugins: plugins - }).on('hallomodified', (event, data) => { - input.val(data.content); - if (!removeStylingPending) { - setTimeout(removeStyling, 100); - removeStylingPending = true; - } - }).on('paste drop', () => { - setTimeout(() => { - removeStyling(); - setModified(); - }, 1); - /* Animate the fields open when you click into them. */ - }) + editor + .hallo({ + toolbar: 'halloToolbarFixed', + toolbarCssClass: closestObj.hasClass('full') ? 'full' : '', + /* use the passed-in plugins arg */ + plugins: plugins, + }) + .on('hallomodified', (event, data) => { + input.val(data.content); + if (!removeStylingPending) { + setTimeout(removeStyling, 100); + removeStylingPending = true; + } + }) + .on('paste drop', () => { + setTimeout(() => { + removeStyling(); + setModified(); + }, 1); + /* Animate the fields open when you click into them. */ + }) .on('halloactivated', (event) => { $(event.target).addClass('expanded', 200, () => { - /* Hallo's toolbar will reposition itself on the scroll event. + /* Hallo's toolbar will reposition itself on the scroll event. This is useful since animating the fields can cause it to be positioned badly initially. */ $(window).trigger('scroll'); @@ -74,13 +81,15 @@ function setupLinkTooltips(elem) { }, trigger: 'hover', placement: 'bottom', - selector: 'a' + selector: 'a', }); } window.setupLinkTooltips = setupLinkTooltips; function insertRichTextDeleteControl(elem) { - const anchor = $('<a class="icon icon-cross text-replace halloembed__delete">Delete</a>'); + const anchor = $( + '<a class="icon icon-cross text-replace halloembed__delete">Delete</a>', + ); $(elem).addClass('halloembed').prepend(anchor); anchor.on('click', () => { const widget = $(elem).parent('[data-hallo-editor]').data('IKS-hallo'); diff --git a/client/src/entrypoints/admin/hallo-plugins/hallo-hr.js b/client/src/entrypoints/admin/hallo-plugins/hallo-hr.js index e2f6ddb79d..05740e784d 100644 --- a/client/src/entrypoints/admin/hallo-plugins/hallo-hr.js +++ b/client/src/entrypoints/admin/hallo-plugins/hallo-hr.js @@ -5,7 +5,7 @@ $.widget('IKS.hallohr', { editable: null, toolbar: null, uuid: '', - buttonCssClass: null + buttonCssClass: null, }, populateToolbar(toolbar) { const buttonset = $('<span class="' + this.widgetName + '"></span>'); @@ -16,10 +16,10 @@ $.widget('IKS.hallohr', { label: 'Horizontal rule', command: 'insertHorizontalRule', icon: 'icon-horizontalrule', - cssClass: this.options.buttonCssClass + cssClass: this.options.buttonCssClass, }); buttonset.append(buttonElement); buttonset.hallobuttonset(); return toolbar.append(buttonset); - } + }, }); diff --git a/client/src/entrypoints/admin/hallo-plugins/hallo-requireparagraphs.js b/client/src/entrypoints/admin/hallo-plugins/hallo-requireparagraphs.js index 5230b97d35..3a38c12005 100644 --- a/client/src/entrypoints/admin/hallo-plugins/hallo-requireparagraphs.js +++ b/client/src/entrypoints/admin/hallo-plugins/hallo-requireparagraphs.js @@ -19,13 +19,16 @@ $.widget('IKS.hallorequireparagraphs', { 'h3', 'h4', 'h5', - 'h6' - ] + 'h6', + ], }, cleanupContentClone(el) { // if the editable element contains no block-level elements wrap the contents in a <P> - if (el.html().length && !$(this.options.blockElements.toString(), el).length) { + if ( + el.html().length && + !$(this.options.blockElements.toString(), el).length + ) { this.options.editable.execute('formatBlock', 'p'); } - } + }, }); diff --git a/client/src/entrypoints/admin/hallo-plugins/hallo-wagtaillink.js b/client/src/entrypoints/admin/hallo-plugins/hallo-wagtaillink.js index 291cbddf68..85e5f73284 100644 --- a/client/src/entrypoints/admin/hallo-plugins/hallo-wagtaillink.js +++ b/client/src/entrypoints/admin/hallo-plugins/hallo-wagtaillink.js @@ -3,14 +3,15 @@ import $ from 'jquery'; $.widget('IKS.hallowagtaillink', { options: { uuid: '', - editable: null + editable: null, }, populateToolbar(toolbar) { // eslint-disable-next-line @typescript-eslint/no-this-alias const widget = this; // eslint-disable-next-line func-names const getEnclosingLink = function () { - const node = widget.options.editable.getSelection().commonAncestorContainer; + const node = + widget.options.editable.getSelection().commonAncestorContainer; return $(node).parents('a').get(0); }; @@ -25,7 +26,7 @@ $.widget('IKS.hallowagtaillink', { command: null, queryState() { return addButton.hallobutton('checked', !!getEnclosingLink()); - } + }, }); addButton.on('click', () => { let href; @@ -65,7 +66,8 @@ $.widget('IKS.hallowagtaillink', { url = window.chooserUrls.anchorLinkChooser; href = href.replace('#', ''); urlParams.link_url = href; - } else if (!linkType) { /* external link */ + } else if (!linkType) { + /* external link */ url = window.chooserUrls.externalLinkChooser; urlParams.link_url = href; } @@ -99,7 +101,8 @@ $.widget('IKS.hallowagtaillink', { // eslint-disable-next-line func-names $('a[href]', anchor).each(function () { const parent = this.parentNode; - while (this.firstChild) parent.insertBefore(this.firstChild, this); + while (this.firstChild) + parent.insertBefore(this.firstChild, this); parent.removeChild(this); }); @@ -123,14 +126,17 @@ $.widget('IKS.hallowagtaillink', { anchor.removeAttribute('data-linktype'); } - if (pageData.prefer_this_title_as_link_text || !linkHasExistingContent) { + if ( + pageData.prefer_this_title_as_link_text || + !linkHasExistingContent + ) { // overwrite existing link content with the returned 'title' text anchor.innerText = pageData.title; } return widget.options.editable.element.trigger('change'); - } - } + }, + }, }); }); buttonSet.append(addButton); @@ -147,7 +153,7 @@ $.widget('IKS.hallowagtaillink', { return cancelButton.hallobutton('enable'); } return cancelButton.hallobutton('disable'); - } + }, }); cancelButton.on('click', () => { var enclosingLink; @@ -173,5 +179,5 @@ $.widget('IKS.hallowagtaillink', { buttonSet.hallobuttonset(); toolbar.append(buttonSet); - } + }, }); diff --git a/client/src/entrypoints/admin/lock-unlock-action.js b/client/src/entrypoints/admin/lock-unlock-action.js index d5f803bee8..f5bba0858b 100644 --- a/client/src/entrypoints/admin/lock-unlock-action.js +++ b/client/src/entrypoints/admin/lock-unlock-action.js @@ -3,33 +3,37 @@ function LockUnlockAction(csrfToken, next) { const actionElements = document.querySelectorAll('[data-locking-action]'); [...actionElements].forEach((buttonElement) => { - buttonElement.addEventListener('click', (e) => { - // Stop the button from submitting the form - e.preventDefault(); - e.stopPropagation(); + buttonElement.addEventListener( + 'click', + (e) => { + // Stop the button from submitting the form + e.preventDefault(); + e.stopPropagation(); - const formElement = document.createElement('form'); + const formElement = document.createElement('form'); - formElement.action = buttonElement.dataset.lockingAction; - formElement.method = 'POST'; + formElement.action = buttonElement.dataset.lockingAction; + formElement.method = 'POST'; - const csrftokenElement = document.createElement('input'); - csrftokenElement.type = 'hidden'; - csrftokenElement.name = 'csrfmiddlewaretoken'; - csrftokenElement.value = csrfToken; - formElement.appendChild(csrftokenElement); + const csrftokenElement = document.createElement('input'); + csrftokenElement.type = 'hidden'; + csrftokenElement.name = 'csrfmiddlewaretoken'; + csrftokenElement.value = csrfToken; + formElement.appendChild(csrftokenElement); - if (typeof next !== 'undefined') { - const nextElement = document.createElement('input'); - nextElement.type = 'hidden'; - nextElement.name = 'next'; - nextElement.value = next; - formElement.appendChild(nextElement); - } + if (typeof next !== 'undefined') { + const nextElement = document.createElement('input'); + nextElement.type = 'hidden'; + nextElement.name = 'next'; + nextElement.value = next; + formElement.appendChild(nextElement); + } - document.body.appendChild(formElement); - formElement.submit(); - }, { capture: true }); + document.body.appendChild(formElement); + formElement.submit(); + }, + { capture: true }, + ); }); } window.LockUnlockAction = LockUnlockAction; diff --git a/client/src/entrypoints/admin/modal-workflow.js b/client/src/entrypoints/admin/modal-workflow.js index e23c19797e..158f07543d 100644 --- a/client/src/entrypoints/admin/modal-workflow.js +++ b/client/src/entrypoints/admin/modal-workflow.js @@ -30,8 +30,14 @@ function ModalWorkflow(opts) { self.triggerElement.setAttribute('disabled', true); // set default contents of container - const iconClose = '<svg class="icon icon-cross" aria-hidden="true" focusable="false"><use href="#icon-cross"></use></svg>'; - const container = $('<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">\n <div class="modal-dialog">\n <div class="modal-content">\n <button type="button" class="button close button--icon text-replace" data-dismiss="modal">' + iconClose + wagtailConfig.STRINGS.CLOSE + '</button>\n <div class="modal-body"></div>\n </div><!-- /.modal-content -->\n </div><!-- /.modal-dialog -->\n</div>'); + const iconClose = + '<svg class="icon icon-cross" aria-hidden="true" focusable="false"><use href="#icon-cross"></use></svg>'; + const container = $( + '<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">\n <div class="modal-dialog">\n <div class="modal-content">\n <button type="button" class="button close button--icon text-replace" data-dismiss="modal">' + + iconClose + + wagtailConfig.STRINGS.CLOSE + + '</button>\n <div class="modal-body"></div>\n </div><!-- /.modal-content -->\n </div><!-- /.modal-dialog -->\n</div>', + ); // add container to body and hide it, so content can be added to it before display $('body').append(container); @@ -89,7 +95,7 @@ function ModalWorkflow(opts) { /* If response contains a 'step' identifier, and that identifier is found in the onload dict, call that onload handler */ - if (opts.onload && response.step && (response.step in opts.onload)) { + if (opts.onload && response.step && response.step in opts.onload) { opts.onload[response.step](self, response); } }; diff --git a/client/src/entrypoints/admin/modal-workflow.test.js b/client/src/entrypoints/admin/modal-workflow.test.js index db184ef330..41d6655b78 100644 --- a/client/src/entrypoints/admin/modal-workflow.test.js +++ b/client/src/entrypoints/admin/modal-workflow.test.js @@ -6,7 +6,9 @@ import '../../../../wagtail/admin/static_src/wagtailadmin/js/vendor/bootstrap-mo import './modal-workflow'; $.get = jest.fn().mockImplementation((url, data, cb) => { - cb(JSON.stringify({ html: `<div id="url">${url}</div>`, data, step: 'start' })); + cb( + JSON.stringify({ html: `<div id="url">${url}</div>`, data, step: 'start' }), + ); return { fail: jest.fn() }; }); @@ -123,7 +125,11 @@ describe('modal-workflow', () => { expect(modalWorkflow).toBeInstanceOf(Object); // important: see mock implementation above, returning a response with injected data to validate behaviour - const response = { data: urlParams, html: '<div id="url">path/to/endpoint</div>', step: 'start' }; + const response = { + data: urlParams, + html: '<div id="url">path/to/endpoint</div>', + step: 'start', + }; expect(onload.start).toHaveBeenCalledWith(modalWorkflow, response); }); }); diff --git a/client/src/entrypoints/admin/page-chooser-modal.js b/client/src/entrypoints/admin/page-chooser-modal.js index 60f6138c44..ad973cb2f5 100644 --- a/client/src/entrypoints/admin/page-chooser-modal.js +++ b/client/src/entrypoints/admin/page-chooser-modal.js @@ -49,7 +49,7 @@ const PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = { data: { // eslint-disable-next-line id-length q: query, - results_only: true + results_only: true, }, success(data) { request = null; @@ -59,7 +59,7 @@ const PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = { }, error() { request = null; - } + }, }); } else { /* search box is empty - restore original page browser HTML */ diff --git a/client/src/entrypoints/admin/page-chooser.js b/client/src/entrypoints/admin/page-chooser.js index ae52befba5..a3149daa78 100644 --- a/client/src/entrypoints/admin/page-chooser.js +++ b/client/src/entrypoints/admin/page-chooser.js @@ -21,7 +21,7 @@ function createPageChooser(id, openAtParentId, options) { id: input.val(), parentId: openAtParentId, adminTitle: pageTitle.text(), - editUrl: editLink.attr('href') + editUrl: editLink.attr('href'), }; } @@ -81,8 +81,8 @@ function createPageChooser(id, openAtParentId, options) { responses: { pageChosen: (result) => { chooser.setState(result); - } - } + }, + }, }); }, diff --git a/client/src/entrypoints/admin/page-editor.js b/client/src/entrypoints/admin/page-editor.js index 74a233ce1e..0ad9555728 100644 --- a/client/src/entrypoints/admin/page-editor.js +++ b/client/src/entrypoints/admin/page-editor.js @@ -4,14 +4,16 @@ import { cleanForSlug } from '../../utils/cleanForSlug'; window.halloPlugins = {}; // eslint-disable-next-line @typescript-eslint/no-unused-vars -function registerHalloPlugin(name, opts) { // lgtm[js/unused-local-variable] +function registerHalloPlugin(name, opts) { + // lgtm[js/unused-local-variable] /* Obsolete - used on Wagtail <1.12 to register plugins for the hallo.js editor. Defined here so that third-party plugins can continue to call it to provide Wagtail <1.12 compatibility, without throwing an error on later versions. */ } window.registerHalloPlugin = registerHalloPlugin; -function InlinePanel(opts) { // lgtm[js/unused-local-variable] +function InlinePanel(opts) { + // lgtm[js/unused-local-variable] const self = {}; // eslint-disable-next-line func-names @@ -34,17 +36,21 @@ function InlinePanel(opts) { // lgtm[js/unused-local-variable] $('#' + deleteInputId + '-button').on('click', () => { /* set 'deleted' form field to true */ $('#' + deleteInputId).val('1'); - $('#' + childId).addClass('deleted').slideUp(() => { - self.updateMoveButtonDisabledStates(); - self.updateAddButtonState(); - self.setHasContent(); - }); + $('#' + childId) + .addClass('deleted') + .slideUp(() => { + self.updateMoveButtonDisabledStates(); + self.updateAddButtonState(); + self.setHasContent(); + }); }); if (opts.canOrder) { $('#' + prefix + '-move-up').on('click', () => { const currentChild = $('#' + childId); - const currentChildOrderElem = currentChild.children('input[name$="-ORDER"]'); + const currentChildOrderElem = currentChild.children( + 'input[name$="-ORDER"]', + ); const currentChildOrder = currentChildOrderElem.val(); /* find the previous visible 'inline_child' li before this one */ @@ -65,7 +71,9 @@ function InlinePanel(opts) { // lgtm[js/unused-local-variable] $('#' + prefix + '-move-down').on('click', () => { const currentChild = $('#' + childId); - const currentChildOrderElem = currentChild.children('input[name$="-ORDER"]'); + const currentChildOrderElem = currentChild.children( + 'input[name$="-ORDER"]', + ); const currentChildOrder = currentChildOrderElem.val(); /* find the next visible 'inline_child' li after this one */ @@ -89,13 +97,17 @@ function InlinePanel(opts) { // lgtm[js/unused-local-variable] message so that it doesn't count towards the number of errors on the tab at the top of the page. */ if ($('#' + deleteInputId).val() === '1') { - $('#' + childId).addClass('deleted').hide(0, () => { - self.updateMoveButtonDisabledStates(); - self.updateAddButtonState(); - self.setHasContent(); - }); + $('#' + childId) + .addClass('deleted') + .hide(0, () => { + self.updateMoveButtonDisabledStates(); + self.updateAddButtonState(); + self.setHasContent(); + }); - $('#' + childId).find('.error-message').remove(); + $('#' + childId) + .find('.error-message') + .remove(); } }; @@ -120,7 +132,9 @@ function InlinePanel(opts) { // lgtm[js/unused-local-variable] // eslint-disable-next-line func-names self.updateAddButtonState = function () { if (opts.maxForms) { - const forms = $('> [data-inline-panel-child]', self.formsUl).not('.deleted'); + const forms = $('> [data-inline-panel-child]', self.formsUl).not( + '.deleted', + ); const addButton = $('#' + opts.formsetPrefix + '-ADD'); if (forms.length >= opts.maxForms) { @@ -141,31 +155,43 @@ function InlinePanel(opts) { // lgtm[js/unused-local-variable] // to prevent the containercollapsing while its children go absolute parent.addClass('moving').css('height', parent.height()); - // eslint-disable-next-line func-names - children.each(function () { - $(this).css('top', $(this).position().top); - }).addClass('moving'); + children + .each(function moveChildTop() { + $(this).css('top', $(this).position().top); + }) + .addClass('moving'); // animate swapping around - item1.animate({ - top: item2.position().top - }, 200, () => { - parent.removeClass('moving').removeAttr('style'); - children.removeClass('moving').removeAttr('style'); - }); + item1.animate( + { + top: item2.position().top, + }, + 200, + () => { + parent.removeClass('moving').removeAttr('style'); + children.removeClass('moving').removeAttr('style'); + }, + ); - item2.animate({ - top: item1.position().top - }, 200, () => { - parent.removeClass('moving').removeAttr('style'); - children.removeClass('moving').removeAttr('style'); - }); + item2.animate( + { + top: item1.position().top, + }, + 200, + () => { + parent.removeClass('moving').removeAttr('style'); + children.removeClass('moving').removeAttr('style'); + }, + ); }; // eslint-disable-next-line no-undef buildExpandingFormset(opts.formsetPrefix, { onAdd(formCount) { - const newChildPrefix = opts.emptyChildFormPrefix.replace(/__prefix__/g, formCount); + const newChildPrefix = opts.emptyChildFormPrefix.replace( + /__prefix__/g, + formCount, + ); self.initChildControls(newChildPrefix); if (opts.canOrder) { /* NB form hidden inputs use 0-based index and only increment formCount *after* this function is run. @@ -178,7 +204,7 @@ function InlinePanel(opts) { // lgtm[js/unused-local-variable] self.updateAddButtonState(); if (opts.onAdd) opts.onAdd(); - } + }, }); return self; @@ -195,7 +221,7 @@ function initSlugAutoPopulate() { /* slug should only follow the title field if its value matched the title's value at the time of focus */ const currentSlug = $('#id_slug').val(); const slugifiedTitle = cleanForSlug(this.value, true); - slugFollowsTitle = (currentSlug === slugifiedTitle); + slugFollowsTitle = currentSlug === slugifiedTitle; }); // eslint-disable-next-line func-names @@ -229,13 +255,16 @@ function initErrorDetection() { errorSections[parentSection.attr('id')] = 0; } - errorSections[parentSection.attr('id')] = errorSections[parentSection.attr('id')] + 1; + errorSections[parentSection.attr('id')] = + errorSections[parentSection.attr('id')] + 1; }); // now identify them on each tab // eslint-disable-next-line guard-for-in for (const index in errorSections) { - $('[data-tab-nav] a[href="#' + index + '"]').addClass('errors').attr('data-count', errorSections[index]); + $('[data-tab-nav] a[href="#' + index + '"]') + .addClass('errors') + .attr('data-count', errorSections[index]); } } window.initErrorDetection = initErrorDetection; @@ -285,7 +314,7 @@ $(() => { method: 'POST', data: new FormData($form[0]), processData: false, - contentType: false + contentType: false, }); } @@ -296,12 +325,14 @@ $(() => { // and deleted (DOMSubtreeModified event), and we need to delay // setPreviewData when typing to avoid useless extra AJAX requests // (so we postpone setPreviewData when keyup occurs). - + // TODO: Replace DOMSubtreeModified with a MutationObserver. - $form.on('change keyup DOMSubtreeModified', () => { - clearTimeout(autoUpdatePreviewDataTimeout); - autoUpdatePreviewDataTimeout = setTimeout(setPreviewData, 1000); - }).trigger('change'); + $form + .on('change keyup DOMSubtreeModified', () => { + clearTimeout(autoUpdatePreviewDataTimeout); + autoUpdatePreviewDataTimeout = setTimeout(setPreviewData, 1000); + }) + .trigger('change'); } }); @@ -315,22 +346,24 @@ $(() => { const previewWindow = window.open('', thisPreviewUrl); previewWindow.focus(); - setPreviewData().done((data) => { - if (data.is_valid) { - previewWindow.document.location = thisPreviewUrl; - } else { + setPreviewData() + .done((data) => { + if (data.is_valid) { + previewWindow.document.location = thisPreviewUrl; + } else { + window.focus(); + previewWindow.close(); + + // TODO: Stop sending the form, as it removes file data. + $form.trigger('submit'); + } + }) + .fail(() => { + // eslint-disable-next-line no-alert + alert('Error while sending preview data.'); window.focus(); previewWindow.close(); - - // TODO: Stop sending the form, as it removes file data. - $form.trigger('submit'); - } - }).fail(() => { - // eslint-disable-next-line no-alert - alert('Error while sending preview data.'); - window.focus(); - previewWindow.close(); - }) + }) .always(() => { $icon.addClass('icon-view').removeClass('icon-spinner'); }); @@ -346,7 +379,7 @@ window.updateFooterSaveWarning = (formDirty, commentsDirty) => { all: formDirty && commentsDirty, any: anyDirty, comments: commentsDirty && !formDirty, - edits: formDirty && !commentsDirty + edits: formDirty && !commentsDirty, }; let hiding = false; diff --git a/client/src/entrypoints/admin/privacy-switch.js b/client/src/entrypoints/admin/privacy-switch.js index 27d067b25a..2b4709bc6a 100644 --- a/client/src/entrypoints/admin/privacy-switch.js +++ b/client/src/entrypoints/admin/privacy-switch.js @@ -15,8 +15,14 @@ $(() => { return false; }); - const restrictionTypePasswordField = $("input[name='restriction_type'][value='password']", modal.body); - const restrictionTypeGroupsField = $("input[name='restriction_type'][value='groups']", modal.body); + const restrictionTypePasswordField = $( + "input[name='restriction_type'][value='password']", + modal.body, + ); + const restrictionTypeGroupsField = $( + "input[name='restriction_type'][value='groups']", + modal.body, + ); const passwordField = $('.password-field', modal.body); const groupsFields = $('#groups-fields', modal.body); @@ -34,24 +40,31 @@ $(() => { } refreshFormFields(); - $("input[name='restriction_type']", modal.body).on('change', refreshFormFields); + $("input[name='restriction_type']", modal.body).on( + 'change', + refreshFormFields, + ); }, set_privacy_done(modal, jsonData) { modal.respond('setPermission', jsonData.is_public); modal.close(); - } + }, }, responses: { setPermission(isPublic) { if (isPublic) { $('.privacy-indicator').removeClass('private').addClass('public'); - $('.privacy-indicator-tag').addClass('u-hidden').attr('aria-hidden', 'true'); + $('.privacy-indicator-tag') + .addClass('u-hidden') + .attr('aria-hidden', 'true'); } else { $('.privacy-indicator').removeClass('public').addClass('private'); - $('.privacy-indicator-tag').removeClass('u-hidden').attr('aria-hidden', 'false'); + $('.privacy-indicator-tag') + .removeClass('u-hidden') + .attr('aria-hidden', 'false'); } - } - } + }, + }, }); return false; }); diff --git a/client/src/entrypoints/admin/sidebar-legacy.test.js b/client/src/entrypoints/admin/sidebar-legacy.test.js index 725c4f191b..ed6c2914ab 100644 --- a/client/src/entrypoints/admin/sidebar-legacy.test.js +++ b/client/src/entrypoints/admin/sidebar-legacy.test.js @@ -19,7 +19,8 @@ describe('sidebar-legacy', () => { }); it('init with DOM', () => { - document.body.innerHTML = '<div data-explorer-menu></div><div data-explorer-start-page></div>'; + document.body.innerHTML = + '<div data-explorer-menu></div><div data-explorer-start-page></div>'; listener(); expect(Explorer.initExplorer).toHaveBeenCalled(); }); diff --git a/client/src/entrypoints/admin/sidebar.js b/client/src/entrypoints/admin/sidebar.js index d2b3587f5a..66fdb1b5f6 100644 --- a/client/src/entrypoints/admin/sidebar.js +++ b/client/src/entrypoints/admin/sidebar.js @@ -7,13 +7,28 @@ import { WagtailBrandingModuleDefinition } from '../../components/Sidebar/module import { SearchModuleDefinition } from '../../components/Sidebar/modules/Search'; import { MainMenuModuleDefinition } from '../../components/Sidebar/modules/MainMenu'; -window.telepath.register('wagtail.sidebar.LinkMenuItem', LinkMenuItemDefinition); +window.telepath.register( + 'wagtail.sidebar.LinkMenuItem', + LinkMenuItemDefinition, +); window.telepath.register('wagtail.sidebar.SubMenuItem', SubMenuItemDefinition); -window.telepath.register('wagtail.sidebar.PageExplorerMenuItem', PageExplorerMenuItemDefinition); +window.telepath.register( + 'wagtail.sidebar.PageExplorerMenuItem', + PageExplorerMenuItemDefinition, +); -window.telepath.register('wagtail.sidebar.WagtailBrandingModule', WagtailBrandingModuleDefinition); -window.telepath.register('wagtail.sidebar.SearchModule', SearchModuleDefinition); -window.telepath.register('wagtail.sidebar.MainMenuModule', MainMenuModuleDefinition); +window.telepath.register( + 'wagtail.sidebar.WagtailBrandingModule', + WagtailBrandingModuleDefinition, +); +window.telepath.register( + 'wagtail.sidebar.SearchModule', + SearchModuleDefinition, +); +window.telepath.register( + 'wagtail.sidebar.MainMenuModule', + MainMenuModuleDefinition, +); document.addEventListener('DOMContentLoaded', () => { initSidebar(); diff --git a/client/src/entrypoints/admin/task-chooser-modal.js b/client/src/entrypoints/admin/task-chooser-modal.js index 29987ab5bb..3f212e9887 100644 --- a/client/src/entrypoints/admin/task-chooser-modal.js +++ b/client/src/entrypoints/admin/task-chooser-modal.js @@ -1,8 +1,10 @@ import $ from 'jquery'; const ajaxifyTaskCreateTab = (modal, jsonData) => { - // eslint-disable-next-line func-names - $('#new a.task-type-choice, #new a.choose-different-task-type', modal.body).on('click', function () { + $( + '#new a.task-type-choice, #new a.choose-different-task-type', + modal.body, + ).on('click', function onClickNew() { modal.loadUrl(this.href); return false; }); @@ -20,11 +22,21 @@ const ajaxifyTaskCreateTab = (modal, jsonData) => { dataType: 'text', success: modal.loadResponseText, error(response, textStatus, errorThrown) { - const message = jsonData.error_message + '<br />' + errorThrown + ' - ' + response.status; + const message = + jsonData.error_message + + '<br />' + + errorThrown + + ' - ' + + response.status; $('#new', modal.body).append( '<div class="help-block help-critical">' + - '<strong>' + jsonData.error_label + ': </strong>' + message + '</div>'); - } + '<strong>' + + jsonData.error_label + + ': </strong>' + + message + + '</div>', + ); + }, }); return false; @@ -114,6 +126,6 @@ const TASK_CHOOSER_MODAL_ONLOAD_HANDLERS = { reshow_create_tab(modal, jsonData) { $('#new', modal.body).html(jsonData.htmlFragment); ajaxifyTaskCreateTab(modal, jsonData); - } + }, }; window.TASK_CHOOSER_MODAL_ONLOAD_HANDLERS = TASK_CHOOSER_MODAL_ONLOAD_HANDLERS; diff --git a/client/src/entrypoints/admin/task-chooser.js b/client/src/entrypoints/admin/task-chooser.js index d19228425a..fafefe5e07 100644 --- a/client/src/entrypoints/admin/task-chooser.js +++ b/client/src/entrypoints/admin/task-chooser.js @@ -18,8 +18,8 @@ function createTaskChooser(id) { taskName.text(data.name); chooserElement.removeClass('blank'); editAction.attr('href', data.edit_url); - } - } + }, + }, }); }); } diff --git a/client/src/entrypoints/admin/telepath/blocks.js b/client/src/entrypoints/admin/telepath/blocks.js index b4c2af6549..26bb5d5377 100644 --- a/client/src/entrypoints/admin/telepath/blocks.js +++ b/client/src/entrypoints/admin/telepath/blocks.js @@ -1,13 +1,25 @@ -import { FieldBlock, FieldBlockDefinition } from '../../../components/StreamField/blocks/FieldBlock'; -import { StaticBlock, StaticBlockDefinition } from '../../../components/StreamField/blocks/StaticBlock'; import { - StructBlock, StructBlockDefinition, StructBlockValidationError + FieldBlock, + FieldBlockDefinition, +} from '../../../components/StreamField/blocks/FieldBlock'; +import { + StaticBlock, + StaticBlockDefinition, +} from '../../../components/StreamField/blocks/StaticBlock'; +import { + StructBlock, + StructBlockDefinition, + StructBlockValidationError, } from '../../../components/StreamField/blocks/StructBlock'; import { - ListBlock, ListBlockDefinition, ListBlockValidationError + ListBlock, + ListBlockDefinition, + ListBlockValidationError, } from '../../../components/StreamField/blocks/ListBlock'; import { - StreamBlock, StreamBlockDefinition, StreamBlockValidationError + StreamBlock, + StreamBlockDefinition, + StreamBlockValidationError, } from '../../../components/StreamField/blocks/StreamBlock'; const wagtailStreamField = window.wagtailStreamField || {}; @@ -59,10 +71,19 @@ window.initBlockWidget = initBlockWidget; window.telepath.register('wagtail.blocks.FieldBlock', FieldBlockDefinition); window.telepath.register('wagtail.blocks.StaticBlock', StaticBlockDefinition); window.telepath.register('wagtail.blocks.StructBlock', StructBlockDefinition); -window.telepath.register('wagtail.blocks.StructBlockValidationError', StructBlockValidationError); +window.telepath.register( + 'wagtail.blocks.StructBlockValidationError', + StructBlockValidationError, +); window.telepath.register('wagtail.blocks.ListBlock', ListBlockDefinition); -window.telepath.register('wagtail.blocks.ListBlockValidationError', ListBlockValidationError); +window.telepath.register( + 'wagtail.blocks.ListBlockValidationError', + ListBlockValidationError, +); window.telepath.register('wagtail.blocks.StreamBlock', StreamBlockDefinition); -window.telepath.register('wagtail.blocks.StreamBlockValidationError', StreamBlockValidationError); +window.telepath.register( + 'wagtail.blocks.StreamBlockValidationError', + StreamBlockValidationError, +); window.wagtailStreamField = wagtailStreamField; diff --git a/client/src/entrypoints/admin/telepath/widgets.js b/client/src/entrypoints/admin/telepath/widgets.js index 1cdb731126..ac6ffaaffc 100644 --- a/client/src/entrypoints/admin/telepath/widgets.js +++ b/client/src/entrypoints/admin/telepath/widgets.js @@ -1,272 +1,273 @@ - - /* global $ */ class BoundWidget { - constructor(element, name, idForLabel, initialState) { - var selector = ':input[name="' + name + '"]'; - this.input = element.find(selector).addBack(selector); // find, including element itself - this.idForLabel = idForLabel; - this.setState(initialState); - } - getValue() { - return this.input.val(); - } - getState() { - return this.input.val(); - } - setState(state) { - this.input.val(state); - } - getTextLabel(opts) { - const val = this.getValue(); - if (typeof val !== 'string') return null; - const maxLength = opts && opts.maxLength; - if (maxLength && val.length > maxLength) { - return val.substring(0, maxLength - 1) + '…'; - } - return val; - } - focus() { - this.input.focus(); + constructor(element, name, idForLabel, initialState) { + var selector = ':input[name="' + name + '"]'; + this.input = element.find(selector).addBack(selector); // find, including element itself + this.idForLabel = idForLabel; + this.setState(initialState); + } + getValue() { + return this.input.val(); + } + getState() { + return this.input.val(); + } + setState(state) { + this.input.val(state); + } + getTextLabel(opts) { + const val = this.getValue(); + if (typeof val !== 'string') return null; + const maxLength = opts && opts.maxLength; + if (maxLength && val.length > maxLength) { + return val.substring(0, maxLength - 1) + '…'; } + return val; + } + focus() { + this.input.focus(); + } } class Widget { - constructor(html, idPattern) { - this.html = html; - this.idPattern = idPattern; - } + constructor(html, idPattern) { + this.html = html; + this.idPattern = idPattern; + } - boundWidgetClass = BoundWidget; + boundWidgetClass = BoundWidget; - render(placeholder, name, id, initialState) { - var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id); - var idForLabel = this.idPattern.replace(/__ID__/g, id); - var dom = $(html); - $(placeholder).replaceWith(dom); - // eslint-disable-next-line new-cap - return new this.boundWidgetClass(dom, name, idForLabel, initialState); - } + render(placeholder, name, id, initialState) { + var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id); + var idForLabel = this.idPattern.replace(/__ID__/g, id); + var dom = $(html); + $(placeholder).replaceWith(dom); + // eslint-disable-next-line new-cap + return new this.boundWidgetClass(dom, name, idForLabel, initialState); + } } window.telepath.register('wagtail.widgets.Widget', Widget); - class BoundCheckboxInput extends BoundWidget { - getValue() { - return this.input.is(':checked'); - } - getState() { - return this.input.is(':checked'); - } - setState(state) { - // if false, set attribute value to null to remove it - this.input.attr('checked', state || null); - } + getValue() { + return this.input.is(':checked'); + } + getState() { + return this.input.is(':checked'); + } + setState(state) { + // if false, set attribute value to null to remove it + this.input.attr('checked', state || null); + } } - class CheckboxInput extends Widget { - boundWidgetClass = BoundCheckboxInput; + boundWidgetClass = BoundCheckboxInput; } window.telepath.register('wagtail.widgets.CheckboxInput', CheckboxInput); - class BoundRadioSelect { - constructor(element, name, idForLabel, initialState) { - this.element = element; - this.name = name; - this.idForLabel = idForLabel; - this.selector = 'input[name="' + name + '"]:checked'; - this.setState(initialState); - } - getValue() { - return this.element.find(this.selector).val(); - } - getState() { - return this.element.find(this.selector).val(); - } - setState(state) { - this.element.find('input[name="' + this.name + '"]').val([state]); - } - focus() { - this.element.find('input[name="' + this.name + '"]').focus(); - } + constructor(element, name, idForLabel, initialState) { + this.element = element; + this.name = name; + this.idForLabel = idForLabel; + this.selector = 'input[name="' + name + '"]:checked'; + this.setState(initialState); + } + getValue() { + return this.element.find(this.selector).val(); + } + getState() { + return this.element.find(this.selector).val(); + } + setState(state) { + this.element.find('input[name="' + this.name + '"]').val([state]); + } + focus() { + this.element.find('input[name="' + this.name + '"]').focus(); + } } class RadioSelect extends Widget { - boundWidgetClass = BoundRadioSelect; + boundWidgetClass = BoundRadioSelect; } window.telepath.register('wagtail.widgets.RadioSelect', RadioSelect); - class BoundSelect extends BoundWidget { - getTextLabel() { - return this.input.find(':selected').text(); - } + getTextLabel() { + return this.input.find(':selected').text(); + } } class Select extends Widget { - boundWidgetClass = BoundSelect; + boundWidgetClass = BoundSelect; } window.telepath.register('wagtail.widgets.Select', Select); - class PageChooser { - constructor(html, idPattern, config) { - this.html = html; - this.idPattern = idPattern; - this.config = config; - } + constructor(html, idPattern, config) { + this.html = html; + this.idPattern = idPattern; + this.config = config; + } - render(placeholder, name, id, initialState) { - var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id); - var dom = $(html); - $(placeholder).replaceWith(dom); - /* the chooser object returned by createPageChooser also serves as the JS widget representation */ - // eslint-disable-next-line no-undef - const chooser = createPageChooser(id, null, this.config); - chooser.setState(initialState); - return chooser; - } + render(placeholder, name, id, initialState) { + var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id); + var dom = $(html); + $(placeholder).replaceWith(dom); + /* the chooser object returned by createPageChooser also serves as the JS widget representation */ + // eslint-disable-next-line no-undef + const chooser = createPageChooser(id, null, this.config); + chooser.setState(initialState); + return chooser; + } } window.telepath.register('wagtail.widgets.PageChooser', PageChooser); - class AdminAutoHeightTextInput extends Widget { - render(placeholder, name, id, initialState) { - const boundWidget = super.render(placeholder, name, id, initialState); - window.autosize($('#' + id)); - return boundWidget; - } + render(placeholder, name, id, initialState) { + const boundWidget = super.render(placeholder, name, id, initialState); + window.autosize($('#' + id)); + return boundWidget; + } } -window.telepath.register('wagtail.widgets.AdminAutoHeightTextInput', AdminAutoHeightTextInput); - +window.telepath.register( + 'wagtail.widgets.AdminAutoHeightTextInput', + AdminAutoHeightTextInput, +); class DraftailRichTextArea { - constructor(options) { - this.options = options; - } + constructor(options) { + this.options = options; + } - render(container, name, id, initialState) { - const options = this.options; - const input = document.createElement('input'); - input.type = 'hidden'; - input.id = id; - input.name = name; - input.value = initialState; - container.appendChild(input); - // eslint-disable-next-line no-undef - draftail.initEditor('#' + id, options, document.currentScript); + render(container, name, id, initialState) { + const options = this.options; + const input = document.createElement('input'); + input.type = 'hidden'; + input.id = id; + input.name = name; + input.value = initialState; + container.appendChild(input); + // eslint-disable-next-line no-undef + draftail.initEditor('#' + id, options, document.currentScript); - return { - getValue() { - return input.value; - }, - getState() { - return input.value; - }, - setState() { - throw new Error('DraftailRichTextArea.setState is not implemented'); - }, - getTextLabel(opts) { - const maxLength = opts && opts.maxLength; - if (!input.value) return ''; - const value = JSON.parse(input.value); - if (!value || !value.blocks) return ''; + return { + getValue() { + return input.value; + }, + getState() { + return input.value; + }, + setState() { + throw new Error('DraftailRichTextArea.setState is not implemented'); + }, + getTextLabel(opts) { + const maxLength = opts && opts.maxLength; + if (!input.value) return ''; + const value = JSON.parse(input.value); + if (!value || !value.blocks) return ''; - let result = ''; - for (const block of value.blocks) { - if (block.text) { - result += (result ? ' ' + block.text : block.text); - if (maxLength && result.length > maxLength) { - return result.substring(0, maxLength - 1) + '…'; - } - } - } - return result; - }, - focus: () => { - setTimeout(() => { - input.draftailEditor.focus(); - }, 50); - }, - }; - } + let result = ''; + for (const block of value.blocks) { + if (block.text) { + result += result ? ' ' + block.text : block.text; + if (maxLength && result.length > maxLength) { + return result.substring(0, maxLength - 1) + '…'; + } + } + } + return result; + }, + focus: () => { + setTimeout(() => { + input.draftailEditor.focus(); + }, 50); + }, + }; + } } -window.telepath.register('wagtail.widgets.DraftailRichTextArea', DraftailRichTextArea); - +window.telepath.register( + 'wagtail.widgets.DraftailRichTextArea', + DraftailRichTextArea, +); class BoundHalloRichTextArea extends BoundWidget { - setState(state) { - this.input.val(state); - this.input.siblings('[data-hallo-editor]').html(state); - } - focus() { - /* not implemented (leave blank so we don't try to focus the hidden field) */ - } + setState(state) { + this.input.val(state); + this.input.siblings('[data-hallo-editor]').html(state); + } + focus() { + /* not implemented (leave blank so we don't try to focus the hidden field) */ + } } class HalloRichTextArea extends Widget { - boundWidgetClass = BoundHalloRichTextArea; + boundWidgetClass = BoundHalloRichTextArea; } -window.telepath.register('wagtail.widgets.HalloRichTextArea', HalloRichTextArea); - +window.telepath.register( + 'wagtail.widgets.HalloRichTextArea', + HalloRichTextArea, +); class BaseDateTimeWidget extends Widget { - constructor(options) { - this.options = options; - } + constructor(options) { + this.options = options; + } - render(placeholder, name, id, initialState) { - const element = document.createElement('input'); - element.type = 'text'; - element.name = name; - element.id = id; - placeholder.replaceWith(element); + render(placeholder, name, id, initialState) { + const element = document.createElement('input'); + element.type = 'text'; + element.name = name; + element.id = id; + placeholder.replaceWith(element); - this.initChooserFn(id, this.options); + this.initChooserFn(id, this.options); - const widget = { - getValue() { - return element.value; - }, - getState() { - return element.value; - }, - setState(state) { - element.value = state; - }, - focus(opts) { - // focusing opens the date picker, so don't do this if it's a 'soft' focus - if (opts && opts.soft) return; - element.focus(); - }, - idForLabel: id, - }; - widget.setState(initialState); - return widget; - } + const widget = { + getValue() { + return element.value; + }, + getState() { + return element.value; + }, + setState(state) { + element.value = state; + }, + focus(opts) { + // focusing opens the date picker, so don't do this if it's a 'soft' focus + if (opts && opts.soft) return; + element.focus(); + }, + idForLabel: id, + }; + widget.setState(initialState); + return widget; + } } class AdminDateInput extends BaseDateTimeWidget { - initChooserFn = window.initDateChooser; + initChooserFn = window.initDateChooser; } window.telepath.register('wagtail.widgets.AdminDateInput', AdminDateInput); class AdminTimeInput extends BaseDateTimeWidget { - initChooserFn = window.initTimeChooser; + initChooserFn = window.initTimeChooser; } window.telepath.register('wagtail.widgets.AdminTimeInput', AdminTimeInput); class AdminDateTimeInput extends BaseDateTimeWidget { - initChooserFn = window.initDateTimeChooser; + initChooserFn = window.initDateTimeChooser; } -window.telepath.register('wagtail.widgets.AdminDateTimeInput', AdminDateTimeInput); +window.telepath.register( + 'wagtail.widgets.AdminDateTimeInput', + AdminDateTimeInput, +); class ValidationError { - constructor(messages) { - this.messages = messages; - } + constructor(messages) { + this.messages = messages; + } } window.telepath.register('wagtail.errors.ValidationError', ValidationError); diff --git a/client/src/entrypoints/admin/telepath/widgets.test.js b/client/src/entrypoints/admin/telepath/widgets.test.js index 426e860b8d..6d79e484bb 100644 --- a/client/src/entrypoints/admin/telepath/widgets.test.js +++ b/client/src/entrypoints/admin/telepath/widgets.test.js @@ -22,14 +22,21 @@ describe('telepath: wagtail.widgets.Widget', () => { _type: 'wagtail.widgets.Widget', _args: [ '<input type="text" name="__NAME__" maxlength="255" id="__ID__">', - '__ID__' - ] + '__ID__', + ], }); - boundWidget = widgetDef.render($('#placeholder'), 'the-name', 'the-id', 'The Value'); + boundWidget = widgetDef.render( + $('#placeholder'), + 'the-name', + 'the-id', + 'The Value', + ); }); test('it renders correctly', () => { - expect(document.body.innerHTML).toBe('<input type="text" name="the-name" maxlength="255" id="the-id">'); + expect(document.body.innerHTML).toBe( + '<input type="text" name="the-name" maxlength="255" id="the-id">', + ); expect(document.querySelector('input').value).toBe('The Value'); }); @@ -73,10 +80,15 @@ describe('telepath: wagtail.widgets.RadioSelect', () => { <input type="radio" name="__NAME__" value="coffee" id="__ID___1"> Coffee</label> </li> </ul>`, - '__ID___0' - ] + '__ID___0', + ], }); - boundWidget = widgetDef.render($('#placeholder'), 'the-name', 'the-id', 'tea'); + boundWidget = widgetDef.render( + $('#placeholder'), + 'the-name', + 'the-id', + 'tea', + ); }); test('it renders correctly', () => { @@ -103,7 +115,9 @@ describe('telepath: wagtail.widgets.RadioSelect', () => { boundWidget.focus(); // Note: This widget always focuses the last element - expect(document.activeElement).toBe(document.querySelector('input[value="coffee"]')); + expect(document.activeElement).toBe( + document.querySelector('input[value="coffee"]'), + ); }); }); @@ -117,12 +131,14 @@ describe('telepath: wagtail.widgets.CheckboxInput', () => { // Unpack and render a radio select widget const widgetDef = window.telepath.unpack({ _type: 'wagtail.widgets.CheckboxInput', - _args: [ - '<input type="checkbox" name="__NAME__" id="__ID__">', - '__ID__' - ] + _args: ['<input type="checkbox" name="__NAME__" id="__ID__">', '__ID__'], }); - boundWidget = widgetDef.render($('#placeholder'), 'sugar', 'id-sugar', true); + boundWidget = widgetDef.render( + $('#placeholder'), + 'sugar', + 'id-sugar', + true, + ); }); test('it renders correctly', () => { @@ -148,7 +164,9 @@ describe('telepath: wagtail.widgets.CheckboxInput', () => { test('focus() focuses the checkbox', () => { boundWidget.focus(); - expect(document.activeElement).toBe(document.querySelector('input[id="id-sugar"]')); + expect(document.activeElement).toBe( + document.querySelector('input[id="id-sugar"]'), + ); }); }); @@ -167,10 +185,15 @@ describe('telepath: wagtail.widgets.Select', () => { <option value="1">Option 1</option> <option value="2">Option 2</option> </select>`, - '__ID__' - ] + '__ID__', + ], }); - boundWidget = widgetDef.render($('#placeholder'), 'the-name', 'the-id', '1'); + boundWidget = widgetDef.render( + $('#placeholder'), + 'the-name', + 'the-id', + '1', + ); }); test('it renders correctly', () => { @@ -225,15 +248,15 @@ describe('telepath: wagtail.widgets.PageChooser', () => { { model_names: ['wagtailcore.page'], can_choose_root: false, - user_perms: null - } - ] + user_perms: null, + }, + ], }); boundWidget = widgetDef.render($('#placeholder'), 'the-name', 'the-id', { id: 60, parentId: 1, adminTitle: 'Welcome to the Wagtail Bakery!', - editUrl: '/admin/pages/60/edit/' + editUrl: '/admin/pages/60/edit/', }); }); @@ -251,7 +274,7 @@ describe('telepath: wagtail.widgets.PageChooser', () => { id: 60, parentId: 1, adminTitle: 'Welcome to the Wagtail Bakery!', - editUrl: '/admin/pages/60/edit/' + editUrl: '/admin/pages/60/edit/', }); }); @@ -260,7 +283,7 @@ describe('telepath: wagtail.widgets.PageChooser', () => { id: 34, parentId: 3, adminTitle: 'Anadama', - editUrl: '/admin/pages/34/edit/' + editUrl: '/admin/pages/34/edit/', }); expect(document.body.innerHTML).toMatchSnapshot(); expect(document.querySelector('input').value).toBe('34'); @@ -276,7 +299,9 @@ describe('telepath: wagtail.widgets.PageChooser', () => { boundWidget.focus(); // Note: This widget always focuses the unchosen button, even if it has a value - expect(document.activeElement).toBe(document.querySelector('.unchosen button')); + expect(document.activeElement).toBe( + document.querySelector('.unchosen button'), + ); }); }); @@ -294,20 +319,29 @@ describe('telepath: wagtail.widgets.AdminAutoHeightTextInput', () => { _type: 'wagtail.widgets.AdminAutoHeightTextInput', _args: [ '<textarea name="__NAME__" cols="40" rows="1" id="__ID__"></textarea>', - '__ID__' - ] + '__ID__', + ], }); - boundWidget = widgetDef.render($('#placeholder'), 'the-name', 'the-id', 'The Value'); + boundWidget = widgetDef.render( + $('#placeholder'), + 'the-name', + 'the-id', + 'The Value', + ); }); test('it renders correctly', () => { - expect(document.body.innerHTML).toBe('<textarea name="the-name" cols="40" rows="1" id="the-id"></textarea>'); + expect(document.body.innerHTML).toBe( + '<textarea name="the-name" cols="40" rows="1" id="the-id"></textarea>', + ); expect(document.querySelector('textarea').value).toBe('The Value'); }); test('window.autosize was called', () => { expect(window.autosize.mock.calls.length).toBe(1); - expect(window.autosize.mock.calls[0][0].get(0)).toBe(document.querySelector('textarea')); + expect(window.autosize.mock.calls[0][0].get(0)).toBe( + document.querySelector('textarea'), + ); }); test('getValue() returns the current value', () => { @@ -333,23 +367,28 @@ describe('telepath: wagtail.widgets.DraftailRichTextArea', () => { let boundWidget; const TEST_VALUE = JSON.stringify({ - blocks: [{ - key: 't30wm', - type: 'unstyled', - depth: 0, - text: 'Test Bold Italic', - inlineStyleRanges: [{ - offset: 5, - length: 4, - style: 'BOLD' - }, { - offset: 10, - length: 6, - style: 'ITALIC' - }], - entityRanges: [] - }], - entityMap: {} + blocks: [ + { + key: 't30wm', + type: 'unstyled', + depth: 0, + text: 'Test Bold Italic', + inlineStyleRanges: [ + { + offset: 5, + length: 4, + style: 'BOLD', + }, + { + offset: 10, + length: 6, + style: 'ITALIC', + }, + ], + entityRanges: [], + }, + ], + entityMap: {}, }); beforeEach(() => { @@ -359,52 +398,67 @@ describe('telepath: wagtail.widgets.DraftailRichTextArea', () => { // Unpack and render a Draftail input const widgetDef = window.telepath.unpack({ _type: 'wagtail.widgets.DraftailRichTextArea', - _args: [{ - entityTypes: [{ - _dict: { - type: 'LINK', - icon: 'link', - description: 'Link', - attributes: ['url', 'id', 'parentId'], - whitelist: { - href: '^(http:|https:|undefined$)' - } - }, - }, { - _dict: { - type: 'IMAGE', - icon: 'image', - description: 'Image', - attributes: ['id', 'src', 'alt', 'format'], - whitelist: { - id: true - } - }, - }], - enableHorizontalRule: true, - inlineStyles: [{ - _dict: { - type: 'BOLD', - icon: 'bold', - description: 'Bold' - }, - }, { - _dict: { - type: 'ITALIC', - icon: 'italic', - description: 'Italic' - }, - }], - blockTypes: [{ - _dict: { - label: 'H2', - type: 'header-two', - description: 'Heading 2' - }, - }] - }] + _args: [ + { + entityTypes: [ + { + _dict: { + type: 'LINK', + icon: 'link', + description: 'Link', + attributes: ['url', 'id', 'parentId'], + whitelist: { + href: '^(http:|https:|undefined$)', + }, + }, + }, + { + _dict: { + type: 'IMAGE', + icon: 'image', + description: 'Image', + attributes: ['id', 'src', 'alt', 'format'], + whitelist: { + id: true, + }, + }, + }, + ], + enableHorizontalRule: true, + inlineStyles: [ + { + _dict: { + type: 'BOLD', + icon: 'bold', + description: 'Bold', + }, + }, + { + _dict: { + type: 'ITALIC', + icon: 'italic', + description: 'Italic', + }, + }, + ], + blockTypes: [ + { + _dict: { + label: 'H2', + type: 'header-two', + description: 'Heading 2', + }, + }, + ], + }, + ], }); - boundWidget = widgetDef.render(document.getElementById('placeholder'), 'the-name', 'the-id', TEST_VALUE); + boundWidget = widgetDef.render( + document.getElementById('placeholder'), + 'the-name', + 'the-id', + TEST_VALUE, + ); }); test('it renders correctly', () => { @@ -422,15 +476,17 @@ describe('telepath: wagtail.widgets.DraftailRichTextArea', () => { test('setState() changes the current state', () => { const NEW_VALUE = JSON.stringify({ - blocks: [{ - key: 't30wm', - type: 'unstyled', - depth: 0, - text: 'New value', - inlineStyleRanges: [], - entityRanges: [] - }], - entityMap: {} + blocks: [ + { + key: 't30wm', + type: 'unstyled', + depth: 0, + text: 'New value', + inlineStyleRanges: [], + entityRanges: [], + }, + ], + entityMap: {}, }); expect(() => { @@ -443,7 +499,9 @@ describe('telepath: wagtail.widgets.DraftailRichTextArea', () => { jest.useFakeTimers(); boundWidget.focus(); jest.runAllTimers(); - expect(document.activeElement).toBe(document.querySelector('.public-DraftEditor-content')); + expect(document.activeElement).toBe( + document.querySelector('.public-DraftEditor-content'), + ); }); }); @@ -459,16 +517,25 @@ describe('telepath: wagtail.widgets.DateInput', () => { // Render const widgetDef = window.telepath.unpack({ _type: 'wagtail.widgets.AdminDateInput', - _args: [{ - dayOfWeekStart: 0, - format: 'Y-m-d' - }] + _args: [ + { + dayOfWeekStart: 0, + format: 'Y-m-d', + }, + ], }); - boundWidget = widgetDef.render($('#placeholder'), 'the-name', 'the-id', '2021-01-19'); + boundWidget = widgetDef.render( + $('#placeholder'), + 'the-name', + 'the-id', + '2021-01-19', + ); }); test('it renders correctly', () => { - expect(document.body.innerHTML).toBe('<input type="text" name="the-name" id="the-id">'); + expect(document.body.innerHTML).toBe( + '<input type="text" name="the-name" id="the-id">', + ); expect(document.querySelector('input').value).toBe('2021-01-19'); }); @@ -477,7 +544,7 @@ describe('telepath: wagtail.widgets.DateInput', () => { expect(window.initDateChooser.mock.calls[0][0]).toBe('the-id'); expect(window.initDateChooser.mock.calls[0][1]).toEqual({ dayOfWeekStart: 0, - format: 'Y-m-d' + format: 'Y-m-d', }); }); @@ -512,16 +579,25 @@ describe('telepath: wagtail.widgets.TimeInput', () => { // Render const widgetDef = window.telepath.unpack({ _type: 'wagtail.widgets.AdminTimeInput', - _args: [{ - format: 'H:i', - formatTime: 'H:i' - }] + _args: [ + { + format: 'H:i', + formatTime: 'H:i', + }, + ], }); - boundWidget = widgetDef.render($('#placeholder'), 'the-name', 'the-id', '11:59'); + boundWidget = widgetDef.render( + $('#placeholder'), + 'the-name', + 'the-id', + '11:59', + ); }); test('it renders correctly', () => { - expect(document.body.innerHTML).toBe('<input type="text" name="the-name" id="the-id">'); + expect(document.body.innerHTML).toBe( + '<input type="text" name="the-name" id="the-id">', + ); expect(document.querySelector('input').value).toBe('11:59'); }); @@ -530,7 +606,7 @@ describe('telepath: wagtail.widgets.TimeInput', () => { expect(window.initTimeChooser.mock.calls[0][0]).toBe('the-id'); expect(window.initTimeChooser.mock.calls[0][1]).toEqual({ format: 'H:i', - formatTime: 'H:i' + formatTime: 'H:i', }); }); @@ -565,17 +641,26 @@ describe('telepath: wagtail.widgets.DateTimeInput', () => { // Render const widgetDef = window.telepath.unpack({ _type: 'wagtail.widgets.AdminDateTimeInput', - _args: [{ - dayOfWeekStart: 0, - format: 'Y-m-d H:i', - formatTime: 'H:i' - }] + _args: [ + { + dayOfWeekStart: 0, + format: 'Y-m-d H:i', + formatTime: 'H:i', + }, + ], }); - boundWidget = widgetDef.render($('#placeholder'), 'the-name', 'the-id', '2021-01-19 11:59'); + boundWidget = widgetDef.render( + $('#placeholder'), + 'the-name', + 'the-id', + '2021-01-19 11:59', + ); }); test('it renders correctly', () => { - expect(document.body.innerHTML).toBe('<input type="text" name="the-name" id="the-id">'); + expect(document.body.innerHTML).toBe( + '<input type="text" name="the-name" id="the-id">', + ); expect(document.querySelector('input').value).toBe('2021-01-19 11:59'); }); @@ -585,7 +670,7 @@ describe('telepath: wagtail.widgets.DateTimeInput', () => { expect(window.initDateTimeChooser.mock.calls[0][1]).toEqual({ dayOfWeekStart: 0, format: 'Y-m-d H:i', - formatTime: 'H:i' + formatTime: 'H:i', }); }); diff --git a/client/src/entrypoints/admin/userbar.js b/client/src/entrypoints/admin/userbar.js index 82f5ebd540..b0bc96109f 100644 --- a/client/src/entrypoints/admin/userbar.js +++ b/client/src/entrypoints/admin/userbar.js @@ -32,7 +32,6 @@ document.addEventListener('DOMContentLoaded', () => { // eslint-disable-next-line @typescript-eslint/no-use-before-define list.addEventListener('focusout', handleFocusChange); - // eslint-disable-next-line @typescript-eslint/no-use-before-define resetItemsTabIndex(); // On initialisation, all menu items should be disabled for roving tab index @@ -85,12 +84,15 @@ document.addEventListener('DOMContentLoaded', () => { } function isFocusOnItems() { - return document.activeElement && !!document.activeElement.closest('.wagtail-userbar-items'); + return ( + document.activeElement && + !!document.activeElement.closest('.wagtail-userbar-items') + ); } /** Reset all focusable menu items to `tabIndex = -1` */ function resetItemsTabIndex() { - listItems.forEach(listItem => { + listItems.forEach((listItem) => { // eslint-disable-next-line no-param-reassign listItem.firstElementChild.tabIndex = -1; }); @@ -129,7 +131,8 @@ document.addEventListener('DOMContentLoaded', () => { if (element.firstElementChild === document.activeElement) { if (idx + 1 < listItems.length) { focusElement(listItems[idx + 1].firstElementChild); - } else { // Loop around + } else { + // Loop around setFocusToFirstItem(); } } @@ -169,24 +172,24 @@ document.addEventListener('DOMContentLoaded', () => { // List items are in focus, move focus if needed if (isFocusOnItems()) { switch (event.key) { - case 'ArrowDown': - event.preventDefault(); - setFocusToNextItem(); - return false; - case 'ArrowUp': - event.preventDefault(); - setFocusToPreviousItem(); - return false; - case 'Home': - event.preventDefault(); - setFocusToFirstItem(); - return false; - case 'End': - event.preventDefault(); - setFocusToLastItem(); - return false; - default: - break; + case 'ArrowDown': + event.preventDefault(); + setFocusToNextItem(); + return false; + case 'ArrowUp': + event.preventDefault(); + setFocusToPreviousItem(); + return false; + case 'Home': + event.preventDefault(); + setFocusToFirstItem(); + return false; + case 'End': + event.preventDefault(); + setFocusToLastItem(); + return false; + default: + break; } } } @@ -195,7 +198,11 @@ document.addEventListener('DOMContentLoaded', () => { function handleFocusChange(event) { // Is the focus is still in the menu? If so, don't to anything - if (event.relatedTarget == null || (event.relatedTarget && event.relatedTarget.closest('.wagtail-userbar-items'))) { + if ( + event.relatedTarget == null || + (event.relatedTarget && + event.relatedTarget.closest('.wagtail-userbar-items')) + ) { return; } // List items not in focus - the menu should close @@ -209,26 +216,29 @@ document.addEventListener('DOMContentLoaded', () => { */ function handleTriggerKeyDown(event) { // Check if the userbar is focused (but not open yet) and should be opened by keyboard input - if (trigger === document.activeElement && trigger.getAttribute('aria-expanded') === 'false') { + if ( + trigger === document.activeElement && + trigger.getAttribute('aria-expanded') === 'false' + ) { switch (event.key) { - case 'ArrowUp': - event.preventDefault(); - showUserbar(false); + case 'ArrowUp': + event.preventDefault(); + showUserbar(false); - // Workaround for focus bug - // Needs extra delay to account for the userbar open animation. Otherwise won't focus properly. - setTimeout(() => setFocusToLastItem(), 300); - break; - case 'ArrowDown': - event.preventDefault(); - showUserbar(false); + // Workaround for focus bug + // Needs extra delay to account for the userbar open animation. Otherwise won't focus properly. + setTimeout(() => setFocusToLastItem(), 300); + break; + case 'ArrowDown': + event.preventDefault(); + showUserbar(false); - // Workaround for focus bug - // Needs extra delay to account for the userbar open animation. Otherwise won't focus properly. - setTimeout(() => setFocusToFirstItem(), 300); - break; - default: - break; + // Workaround for focus bug + // Needs extra delay to account for the userbar open animation. Otherwise won't focus properly. + setTimeout(() => setFocusToFirstItem(), 300); + break; + default: + break; } } } diff --git a/client/src/entrypoints/admin/workflow-action.js b/client/src/entrypoints/admin/workflow-action.js index cf29592b6d..fa7668214d 100644 --- a/client/src/entrypoints/admin/workflow-action.js +++ b/client/src/entrypoints/admin/workflow-action.js @@ -12,87 +12,112 @@ window._addHiddenInput = addHiddenInput; /* When a workflow action button is clicked, either show a modal or make a POST request to the workflow action view */ function ActivateWorkflowActionsForDashboard(csrfToken) { - const workflowActionElements = document.querySelectorAll('[data-workflow-action-url]'); + const workflowActionElements = document.querySelectorAll( + '[data-workflow-action-url]', + ); [...workflowActionElements].forEach((buttonElement) => { - buttonElement.addEventListener('click', (e) => { - // Stop the button from submitting the form - e.preventDefault(); - e.stopPropagation(); - - if ('launchModal' in buttonElement.dataset) { - // eslint-disable-next-line no-undef - ModalWorkflow({ - url: buttonElement.dataset.workflowActionUrl, - onload: { - action(modal) { - const nextElement = document.createElement('input'); - nextElement.type = 'hidden'; - nextElement.name = 'next'; - nextElement.value = window.location; - $('form', modal.body).append(nextElement); - modal.ajaxifyForm($('form', modal.body)); - }, - success(modal, jsonData) { - window.location.href = jsonData.redirect; - } - }, - }); - } else { - // if not opening a modal, submit a POST request to the action url - const formElement = document.createElement('form'); - - formElement.action = buttonElement.dataset.workflowActionUrl; - formElement.method = 'POST'; - - addHiddenInput(formElement, 'csrfmiddlewaretoken', csrfToken); - addHiddenInput(formElement, 'next', window.location); - - document.body.appendChild(formElement); - formElement.submit(); - } - }, { capture: true }); - }); -} -window.ActivateWorkflowActionsForDashboard = ActivateWorkflowActionsForDashboard; - -function ActivateWorkflowActionsForEditView(formSelector) { - const form = $(formSelector).get(0); - - const workflowActionElements = document.querySelectorAll('[data-workflow-action-name]'); - [...workflowActionElements].forEach((buttonElement) => { - buttonElement.addEventListener('click', (e) => { - if ('workflowActionModalUrl' in buttonElement.dataset) { - // This action requires opening a modal to collect additional data. + buttonElement.addEventListener( + 'click', + (e) => { // Stop the button from submitting the form e.preventDefault(); e.stopPropagation(); - // open the modal at the given URL - // eslint-disable-next-line no-undef - ModalWorkflow({ - url: buttonElement.dataset.workflowActionModalUrl, - onload: { - action(modal) { - modal.ajaxifyForm($('form', modal.body)); + if ('launchModal' in buttonElement.dataset) { + // eslint-disable-next-line no-undef + ModalWorkflow({ + url: buttonElement.dataset.workflowActionUrl, + onload: { + action(modal) { + const nextElement = document.createElement('input'); + nextElement.type = 'hidden'; + nextElement.name = 'next'; + nextElement.value = window.location; + $('form', modal.body).append(nextElement); + modal.ajaxifyForm($('form', modal.body)); + }, + success(modal, jsonData) { + window.location.href = jsonData.redirect; + }, }, - success(modal, jsonData) { - // a success response includes the additional data to submit with the edit form - addHiddenInput(form, 'action-workflow-action', 'true'); - addHiddenInput(form, 'workflow-action-name', buttonElement.dataset.workflowActionName); - addHiddenInput(form, 'workflow-action-extra-data', JSON.stringify(jsonData.cleaned_data)); - // note: need to submit via jQuery (as opposed to form.submit()) so that the onsubmit handler - // that disables the dirty-form prompt doesn't get bypassed - $(form).submit(); - } - }, - }); - } else { - // no modal, so let the form submission to the edit view proceed, with additional - // hidden inputs to tell it to perform our action - addHiddenInput(form, 'action-workflow-action', 'true'); - addHiddenInput(form, 'workflow-action-name', buttonElement.dataset.workflowActionName); - } - }, { capture: true }); + }); + } else { + // if not opening a modal, submit a POST request to the action url + const formElement = document.createElement('form'); + + formElement.action = buttonElement.dataset.workflowActionUrl; + formElement.method = 'POST'; + + addHiddenInput(formElement, 'csrfmiddlewaretoken', csrfToken); + addHiddenInput(formElement, 'next', window.location); + + document.body.appendChild(formElement); + formElement.submit(); + } + }, + { capture: true }, + ); + }); +} +window.ActivateWorkflowActionsForDashboard = + ActivateWorkflowActionsForDashboard; + +function ActivateWorkflowActionsForEditView(formSelector) { + const form = $(formSelector).get(0); + + const workflowActionElements = document.querySelectorAll( + '[data-workflow-action-name]', + ); + [...workflowActionElements].forEach((buttonElement) => { + buttonElement.addEventListener( + 'click', + (e) => { + if ('workflowActionModalUrl' in buttonElement.dataset) { + // This action requires opening a modal to collect additional data. + // Stop the button from submitting the form + e.preventDefault(); + e.stopPropagation(); + + // open the modal at the given URL + // eslint-disable-next-line no-undef + ModalWorkflow({ + url: buttonElement.dataset.workflowActionModalUrl, + onload: { + action(modal) { + modal.ajaxifyForm($('form', modal.body)); + }, + success(modal, jsonData) { + // a success response includes the additional data to submit with the edit form + addHiddenInput(form, 'action-workflow-action', 'true'); + addHiddenInput( + form, + 'workflow-action-name', + buttonElement.dataset.workflowActionName, + ); + addHiddenInput( + form, + 'workflow-action-extra-data', + JSON.stringify(jsonData.cleaned_data), + ); + // note: need to submit via jQuery (as opposed to form.submit()) so that the onsubmit handler + // that disables the dirty-form prompt doesn't get bypassed + $(form).submit(); + }, + }, + }); + } else { + // no modal, so let the form submission to the edit view proceed, with additional + // hidden inputs to tell it to perform our action + addHiddenInput(form, 'action-workflow-action', 'true'); + addHiddenInput( + form, + 'workflow-action-name', + buttonElement.dataset.workflowActionName, + ); + } + }, + { capture: true }, + ); }); } window.ActivateWorkflowActionsForEditView = ActivateWorkflowActionsForEditView; diff --git a/client/src/entrypoints/contrib/table_block/table.js b/client/src/entrypoints/contrib/table_block/table.js index 397de92fee..a535fb1c6f 100644 --- a/client/src/entrypoints/contrib/table_block/table.js +++ b/client/src/entrypoints/contrib/table_block/table.js @@ -23,7 +23,10 @@ function initTable(id, tableOptions) { }; const getHeight = function () { const tableParent = $('#' + id).parent(); - return tableParent.find('.htCore').height() + (tableParent.find('.input').height() * 2); + return ( + tableParent.find('.htCore').height() + + tableParent.find('.input').height() * 2 + ); }; const resizeTargets = ['.input > .handsontable', '.wtHider', '.wtHolder']; const resizeHeight = function (height) { @@ -38,7 +41,9 @@ function initTable(id, tableOptions) { }); const parentDiv = $('.widget-table_input').parent(); parentDiv.find('.field-content').width(width); - parentDiv.find('.fieldname-table .field-content .field-content').width('80%'); + parentDiv + .find('.fieldname-table .field-content .field-content') + .width('80%'); } try { @@ -49,7 +54,10 @@ function initTable(id, tableOptions) { if (dataForForm !== null) { if (dataForForm.hasOwnProperty('first_row_is_table_header')) { - tableHeaderCheckbox.prop('checked', dataForForm.first_row_is_table_header); + tableHeaderCheckbox.prop( + 'checked', + dataForForm.first_row_is_table_header, + ); } if (dataForForm.hasOwnProperty('first_col_is_header')) { colHeaderCheckbox.prop('checked', dataForForm.first_col_is_header); @@ -59,12 +67,15 @@ function initTable(id, tableOptions) { } } - if (!tableOptions.hasOwnProperty('width') || !tableOptions.hasOwnProperty('height')) { + if ( + !tableOptions.hasOwnProperty('width') || + !tableOptions.hasOwnProperty('height') + ) { // Size to parent .sequence-member-inner width if width is not given in tableOptions $(window).on('resize', () => { hot.updateSettings({ width: getWidth(), - height: getHeight() + height: getHeight(), }); resizeWidth('100%'); }); @@ -78,7 +89,7 @@ function initTable(id, tableOptions) { cellsClassnames.push({ row: meta[i].row, col: meta[i].col, - className: meta[i].className + className: meta[i].className, }); } } @@ -86,18 +97,20 @@ function initTable(id, tableOptions) { }; const persist = function () { - hiddenStreamInput.val(JSON.stringify({ - data: hot.getData(), - cell: getCellsClassnames(), - first_row_is_table_header: tableHeaderCheckbox.prop('checked'), - first_col_is_header: colHeaderCheckbox.prop('checked'), - table_caption: tableCaption.val() - })); + hiddenStreamInput.val( + JSON.stringify({ + data: hot.getData(), + cell: getCellsClassnames(), + first_row_is_table_header: tableHeaderCheckbox.prop('checked'), + first_col_is_header: colHeaderCheckbox.prop('checked'), + table_caption: tableCaption.val(), + }), + ); }; const cellEvent = function (change, source) { if (source === 'loadData') { - return; // don't save this change + return; // don't save this change } persist(); @@ -209,7 +222,7 @@ class TableInput { <input type="text" id="${id}-handsontable-col-caption" name="handsontable-col-caption" /> </div> <p class="help"> - ${this.strings['A heading that identifies the overall topic of the table, and is useful for screen reader users'] } + ${this.strings['A heading that identifies the overall topic of the table, and is useful for screen reader users']} </p> </div> </div> diff --git a/client/src/entrypoints/contrib/table_block/table.test.js b/client/src/entrypoints/contrib/table_block/table.test.js index 9a7a8a9880..075dfed394 100644 --- a/client/src/entrypoints/contrib/table_block/table.test.js +++ b/client/src/entrypoints/contrib/table_block/table.test.js @@ -21,37 +21,39 @@ const TEST_OPTIONS = { 'remove_col', '---------', 'undo', - 'redo' + 'redo', ], editor: 'text', stretchH: 'all', height: 108, renderer: 'text', autoColumnSize: false, - language: 'en-us' + language: 'en-us', }; const TEST_STRINGS = { 'Row header': 'Row header', 'Display the first row as a header.': 'Display the first row as a header.', 'Column header': 'Column header', - 'Display the first column as a header.': 'Display the first column as a header.', + 'Display the first column as a header.': + 'Display the first column as a header.', 'Table caption': 'Table caption', - - 'A heading that identifies the overall topic of the table, and is useful for screen reader users': 'A heading that identifies the overall topic of the table, and is useful for screen reader users', - 'Table': 'Table' + + 'A heading that identifies the overall topic of the table, and is useful for screen reader users': + 'A heading that identifies the overall topic of the table, and is useful for screen reader users', + 'Table': 'Table', }; const TEST_VALUE = { data: [ ['Test', 'Heading'], ['Foo', '123'], - ['Bar', '456'] + ['Bar', '456'], ], cell: [], first_row_is_table_header: true, first_col_is_header: false, - table_caption: '' + table_caption: '', }; // Note: Tests both TableInput and initTable together as this is the only supported way to use them @@ -72,14 +74,16 @@ describe('telepath: wagtail.widgets.TableInput', () => { // Unpack and render a simple text block widget const widgetDef = window.telepath.unpack({ _type: 'wagtail.widgets.TableInput', - _args: [testOptions, testStrings] + _args: [testOptions, testStrings], }); return widgetDef.render($('#placeholder'), 'the-name', 'the-id', testValue); }; beforeEach(() => { handsontableConstructorMock = jest.fn(); - window.Handsontable = (...args) => { handsontableConstructorMock(...args); }; + window.Handsontable = (...args) => { + handsontableConstructorMock(...args); + }; window.Handsontable.prototype.render = jest.fn(); // Reset options, strings, and value for each test @@ -91,18 +95,28 @@ describe('telepath: wagtail.widgets.TableInput', () => { test('it renders correctly', () => { render(); expect(document.body.innerHTML).toMatchSnapshot(); - expect(document.querySelector('input[name="the-name"]').value).toEqual(JSON.stringify(testValue)); + expect(document.querySelector('input[name="the-name"]').value).toEqual( + JSON.stringify(testValue), + ); }); test('Handsontable constructor is called', () => { render(); expect(handsontableConstructorMock.mock.calls.length).toBe(1); - expect(handsontableConstructorMock.mock.calls[0][0]).toBe(document.getElementById('the-id-handsontable-container')); - expect(handsontableConstructorMock.mock.calls[0][1].autoColumnSize).toBe(false); + expect(handsontableConstructorMock.mock.calls[0][0]).toBe( + document.getElementById('the-id-handsontable-container'), + ); + expect(handsontableConstructorMock.mock.calls[0][1].autoColumnSize).toBe( + false, + ); expect(handsontableConstructorMock.mock.calls[0][1].cell).toEqual([]); expect(handsontableConstructorMock.mock.calls[0][1].colHeaders).toBe(false); - expect(handsontableConstructorMock.mock.calls[0][1].contextMenu).toEqual(testOptions.contextMenu); - expect(handsontableConstructorMock.mock.calls[0][1].data).toEqual(testValue.data); + expect(handsontableConstructorMock.mock.calls[0][1].contextMenu).toEqual( + testOptions.contextMenu, + ); + expect(handsontableConstructorMock.mock.calls[0][1].data).toEqual( + testValue.data, + ); expect(handsontableConstructorMock.mock.calls[0][1].editor).toBe('text'); expect(handsontableConstructorMock.mock.calls[0][1].height).toBe(108); expect(handsontableConstructorMock.mock.calls[0][1].language).toBe('en-us'); @@ -123,13 +137,16 @@ describe('telepath: wagtail.widgets.TableInput', () => { test('translation', () => { testStrings = { 'Row header': 'En-tête de ligne', - 'Display the first row as a header.': 'Affichez la première ligne sous forme d\'en-tête.', + 'Display the first row as a header.': + "Affichez la première ligne sous forme d'en-tête.", 'Column header': 'En-tête de colonne', - 'Display the first column as a header.': 'Affichez la première colonne sous forme d\'en-tête.', + 'Display the first column as a header.': + "Affichez la première colonne sous forme d'en-tête.", 'Table caption': 'Légende du tableau', - - 'A heading that identifies the overall topic of the table, and is useful for screen reader users': 'Un en-tête qui identifie le sujet général du tableau et qui est utile pour les utilisateurs de lecteurs d\'écran', - 'Table': 'Tableau' + + 'A heading that identifies the overall topic of the table, and is useful for screen reader users': + "Un en-tête qui identifie le sujet général du tableau et qui est utile pour les utilisateurs de lecteurs d'écran", + 'Table': 'Tableau', }; render(); expect(document.body.innerHTML).toMatchSnapshot(); @@ -149,6 +166,8 @@ describe('telepath: wagtail.widgets.TableInput', () => { const boundWidget = render(); testValue.data.push(['Baz', '789']); boundWidget.setState(testValue); - expect(document.querySelector('input[name="the-name"]').value).toEqual(JSON.stringify(testValue)); + expect(document.querySelector('input[name="the-name"]').value).toEqual( + JSON.stringify(testValue), + ); }); }); diff --git a/client/src/entrypoints/contrib/typed_table_block/typed_table_block.js b/client/src/entrypoints/contrib/typed_table_block/typed_table_block.js index 7f4c999a09..019f1b0226 100644 --- a/client/src/entrypoints/contrib/typed_table_block/typed_table_block.js +++ b/client/src/entrypoints/contrib/typed_table_block/typed_table_block.js @@ -2,7 +2,6 @@ import { escapeHtml as h } from '../../../utils/text'; - export class TypedTableBlock { constructor(blockDef, placeholder, prefix, initialState, initialError) { this.blockDef = blockDef; @@ -34,15 +33,19 @@ export class TypedTableBlock { this.rowCountIncludingDeleted = 0; this.prefix = prefix; this.childBlockDefsByName = {}; - this.blockDef.childBlockDefs.forEach(childBlockDef => { + this.blockDef.childBlockDefs.forEach((childBlockDef) => { this.childBlockDefsByName[childBlockDef.name] = childBlockDef; }); const strings = this.blockDef.meta.strings; const dom = $(` <div class="typed-table-block ${h(this.blockDef.meta.classname || '')}"> - <input type="hidden" name="${h(prefix)}-column-count" data-column-count value="0"> - <input type="hidden" name="${h(prefix)}-row-count" data-row-count value="0"> + <input type="hidden" name="${h( + prefix, + )}-column-count" data-column-count value="0"> + <input type="hidden" name="${h( + prefix, + )}-row-count" data-row-count value="0"> <div data-deleted-fields></div> <div class="typed-table-block__wrapper"> <table> @@ -63,7 +66,9 @@ export class TypedTableBlock { <td class="control-cell"> <button type="button" class="button button-small button-secondary button--icon text-replace prepend-row" - aria-label="${h(strings.ADD_ROW)}" title="${h(strings.ADD_ROW)}" data-add-row> + aria-label="${h(strings.ADD_ROW)}" title="${h( + strings.ADD_ROW, + )}" data-add-row> <svg class="icon icon-plus icon" aria-hidden="true" focusable="false"> <use href="#icon-plus"></use> </svg> @@ -109,9 +114,9 @@ export class TypedTableBlock { this.addColumnCallback = null; this.addColumnMenu = $('<ul class="add-column-menu"></ul>'); - this.blockDef.childBlockDefs.forEach(childBlockDef => { + this.blockDef.childBlockDefs.forEach((childBlockDef) => { const columnTypeButton = $( - '<button type="button" class="button button-small"></button>' + '<button type="button" class="button button-small"></button>', ).text(childBlockDef.meta.label); columnTypeButton.on('click', () => { if (this.addColumnCallback) this.addColumnCallback(childBlockDef); @@ -120,11 +125,13 @@ export class TypedTableBlock { const li = $('<li></li>').append(columnTypeButton); this.addColumnMenu.append(li); }); - this.addColumnMenuBaseElement = null; // the element the add-column menu is attached to + this.addColumnMenuBaseElement = null; // the element the add-column menu is attached to this.appendColumnButton.on('click', () => { this.toggleAddColumnMenu(this.appendColumnButton, (chosenBlockDef) => { - this.insertColumn(this.columns.length, chosenBlockDef, { addInitialRow: true }); + this.insertColumn(this.columns.length, chosenBlockDef, { + addInitialRow: true, + }); }); }); @@ -169,8 +176,12 @@ export class TypedTableBlock { const headerRow = this.thead.children[0]; // delete all header cells except for the control columns - headerRow.replaceChildren(headerRow.firstElementChild, headerRow.lastElementChild); - this.appendColumnButton.text(this.blockDef.meta.strings.ADD_COLUMN) + headerRow.replaceChildren( + headerRow.firstElementChild, + headerRow.lastElementChild, + ); + this.appendColumnButton + .text(this.blockDef.meta.strings.ADD_COLUMN) .removeClass('button--icon text-replace white') .removeAttr('aria-label') .removeAttr('title'); @@ -214,7 +225,8 @@ export class TypedTableBlock { newHeaderCell.appendChild(column.positionInput); column.deletedInput = document.createElement('input'); column.deletedInput.type = 'hidden'; - column.deletedInput.name = this.prefix + '-column-' + column.id + '-deleted'; + column.deletedInput.name = + this.prefix + '-column-' + column.id + '-deleted'; column.deletedInput.value = ''; this.deletedFieldsContainer.appendChild(column.deletedInput); @@ -227,12 +239,15 @@ export class TypedTableBlock { $(newHeaderCell).append(prependColumnButton); prependColumnButton.on('click', () => { this.toggleAddColumnMenu(prependColumnButton, (chosenBlockDef) => { - this.insertColumn(column.position, chosenBlockDef, { addInitialRow: true }); + this.insertColumn(column.position, chosenBlockDef, { + addInitialRow: true, + }); }); }); column.headingInput = document.createElement('input'); - column.headingInput.name = this.prefix + '-column-' + column.id + '-heading'; + column.headingInput.name = + this.prefix + '-column-' + column.id + '-heading'; column.headingInput.className = 'column-heading'; column.headingInput.placeholder = this.blockDef.meta.strings.COLUMN_HEADING; newHeaderCell.appendChild(column.headingInput); @@ -249,7 +264,8 @@ export class TypedTableBlock { }); // add new cell to each body row - const initialCellState = this.blockDef.childBlockDefaultStates[blockDef.name]; + const initialCellState = + this.blockDef.childBlockDefaultStates[blockDef.name]; Array.from(this.tbody.children).forEach((tr, rowIndex) => { const row = this.rows[rowIndex]; const cells = tr.children; @@ -258,23 +274,23 @@ export class TypedTableBlock { // has an extra final cell to contain the 'delete row' button. // The +1 accounts for the 'control' column on the left side, holding the 'insert row' buttons. tr.insertBefore(newCellElement, cells[index + 1]); - const newCellBlock = this.initCell(newCellElement, column, row, initialCellState); + const newCellBlock = this.initCell( + newCellElement, + column, + row, + initialCellState, + ); row.blocks.splice(index, 0, newCellBlock); }); /* after first column is added, enable adding rows */ this.addRowButton.show(); - this.appendColumnButton.html( - '<svg class="icon icon-plus icon" aria-hidden="true" focusable="false"><use href="#icon-plus"></use></svg>' - ) - .addClass( - 'button--icon text-replace white' + this.appendColumnButton + .html( + '<svg class="icon icon-plus icon" aria-hidden="true" focusable="false"><use href="#icon-plus"></use></svg>', ) - .attr( - 'aria-label', this.blockDef.meta.strings.ADD_COLUMN - ) - .attr( - 'title', this.blockDef.meta.strings.ADD_COLUMN - ); + .addClass('button--icon text-replace white') + .attr('aria-label', this.blockDef.meta.strings.ADD_COLUMN) + .attr('title', this.blockDef.meta.strings.ADD_COLUMN); if (opts && opts.addInitialRow && this.tbody.children.length === 0) { /* add an initial row */ @@ -346,7 +362,8 @@ export class TypedTableBlock { initialState = initialStates[i]; } else { // use block's default state - initialState = this.blockDef.childBlockDefaultStates[column.blockDef.name]; + initialState = + this.blockDef.childBlockDefaultStates[column.blockDef.name]; } const newCell = document.createElement('td'); rowElement.appendChild(newCell); @@ -441,24 +458,26 @@ export class TypedTableBlock { getState() { const state = { - columns: this.columns.map(column => ( - { type: column.blockDef.name, heading: column.headingInput.value } - )), - rows: this.rows.map(row => ( - { values: row.blocks.map(block => block.getState()) } - )), + columns: this.columns.map((column) => ({ + type: column.blockDef.name, + heading: column.headingInput.value, + })), + rows: this.rows.map((row) => ({ + values: row.blocks.map((block) => block.getState()), + })), }; return state; } getValue() { const value = { - columns: this.columns.map(column => ( - { type: column.blockDef.name, heading: column.headingInput.value } - )), - rows: this.rows.map(row => ( - { values: row.blocks.map(block => block.getValue()) } - )), + columns: this.columns.map((column) => ({ + type: column.blockDef.name, + heading: column.headingInput.value, + })), + rows: this.rows.map((row) => ({ + values: row.blocks.map((block) => block.getValue()), + })), }; return value; } @@ -477,7 +496,7 @@ export class TypedTableBlock { // always use the first child, truncated as necessary result = childLabel; } else { - const newResult = (result + ', ' + childLabel); + const newResult = result + ', ' + childLabel; if (maxLength && newResult.length > maxLength - 1) { // too long, so don't add this; return the current list with an ellipsis instead if (!result.endsWith('…')) result += '…'; @@ -512,10 +531,19 @@ export class TypedTableBlockDefinition { } render(placeholder, prefix, initialState, initialError) { - return new TypedTableBlock(this, placeholder, prefix, initialState, initialError); + return new TypedTableBlock( + this, + placeholder, + prefix, + initialState, + initialError, + ); } } -window.telepath.register('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', TypedTableBlockDefinition); +window.telepath.register( + 'wagtail.contrib.typed_table_block.blocks.TypedTableBlock', + TypedTableBlockDefinition, +); export class TypedTableBlockValidationError { constructor(cellErrors) { @@ -523,5 +551,6 @@ export class TypedTableBlockValidationError { } } window.telepath.register( - 'wagtail.contrib.typed_table_block.TypedTableBlockValidationError', TypedTableBlockValidationError + 'wagtail.contrib.typed_table_block.TypedTableBlockValidationError', + TypedTableBlockValidationError, ); diff --git a/client/src/entrypoints/contrib/typed_table_block/typed_table_block.test.js b/client/src/entrypoints/contrib/typed_table_block/typed_table_block.test.js index 545ad84395..f337c44172 100644 --- a/client/src/entrypoints/contrib/typed_table_block/typed_table_block.test.js +++ b/client/src/entrypoints/contrib/typed_table_block/typed_table_block.test.js @@ -27,18 +27,29 @@ class DummyWidgetDefinition { const widgetName = this.widgetName; constructor(widgetName, { name, id, initialState }); - $(placeholder).replaceWith(`<p name="${name}" id="${id}">${widgetName}</p>`); + $(placeholder).replaceWith( + `<p name="${name}" id="${id}">${widgetName}</p>`, + ); return { - setState(state) { setState(widgetName, state); }, - getState() { getState(widgetName); return `state: ${widgetName} - ${name}`; }, - getValue() { getValue(widgetName); return `value: ${widgetName} - ${name}`; }, - focus() { focus(widgetName); }, + setState(state) { + setState(widgetName, state); + }, + getState() { + getState(widgetName); + return `state: ${widgetName} - ${name}`; + }, + getValue() { + getValue(widgetName); + return `value: ${widgetName} - ${name}`; + }, + focus() { + focus(widgetName); + }, idForLabel: id, }; } } - describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { let blockDef; let boundBlock; @@ -61,8 +72,9 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { label: 'Test Block A', required: true, icon: 'placeholder', - classname: 'field char_field widget-text_input fieldname-test_charblock' - } + classname: + 'field char_field widget-text_input fieldname-test_charblock', + }, ); childBlockB = new FieldBlockDefinition( 'test_block_b', @@ -71,8 +83,9 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { label: 'Test Block B', required: true, icon: 'pilcrow', - classname: 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock' - } + classname: + 'field char_field widget-admin_auto_height_text_input fieldname-test_textblock', + }, ); blockDef = new TypedTableBlockDefinition( @@ -98,21 +111,22 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { INSERT_ROW: 'Insert row', DELETE_ROW: 'Delete row', }, - } + }, ); // Render it document.body.innerHTML = '<div id="placeholder"></div>'; - boundBlock = blockDef.render(document.getElementById('placeholder'), 'mytable', { - columns: [ - { type: 'test_block_a', heading: 'Item' }, - { type: 'test_block_b', heading: 'Quantity' }, - ], - rows: [ - { values: ['Cheese', 3] }, - { values: ['Peas', 5] }, - ] - }); + boundBlock = blockDef.render( + document.getElementById('placeholder'), + 'mytable', + { + columns: [ + { type: 'test_block_a', heading: 'Item' }, + { type: 'test_block_b', heading: 'Quantity' }, + ], + rows: [{ values: ['Cheese', 3] }, { values: ['Peas', 5] }], + }, + ); }); test('it renders correctly', () => { @@ -125,14 +139,18 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { boundBlock.clear(); expect(boundBlock.columns.length).toBe(0); expect(boundBlock.rows.length).toBe(0); - expect(document.getElementsByName('mytable-column-count')[0].value).toBe('0'); + expect(document.getElementsByName('mytable-column-count')[0].value).toBe( + '0', + ); expect(document.getElementsByName('mytable-row-count')[0].value).toBe('0'); }); test('supports inserting columns', () => { boundBlock.insertColumn(1, childBlockA); expect(boundBlock.columns.length).toBe(3); - expect(document.getElementsByName('mytable-column-count')[0].value).toBe('3'); + expect(document.getElementsByName('mytable-column-count')[0].value).toBe( + '3', + ); }); test('supports deleting columns', () => { @@ -141,14 +159,18 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { // column count field still counts deleted columns (as it's used by the server-side code // to find out the maximum column ID to look for) - expect(document.getElementsByName('mytable-column-count')[0].value).toBe('2'); + expect(document.getElementsByName('mytable-column-count')[0].value).toBe( + '2', + ); }); test('counts deleted columns in column-count hidden field', () => { boundBlock.deleteColumn(0); boundBlock.insertColumn(1, childBlockA); expect(boundBlock.columns.length).toBe(2); - expect(document.getElementsByName('mytable-column-count')[0].value).toBe('3'); + expect(document.getElementsByName('mytable-column-count')[0].value).toBe( + '3', + ); }); test('supports inserting rows', () => { diff --git a/client/src/entrypoints/documents/document-chooser-telepath.js b/client/src/entrypoints/documents/document-chooser-telepath.js index 1d4542d239..4f495d8ea0 100644 --- a/client/src/entrypoints/documents/document-chooser-telepath.js +++ b/client/src/entrypoints/documents/document-chooser-telepath.js @@ -15,4 +15,7 @@ class DocumentChooser { return chooser; } } -window.telepath.register('wagtail.documents.widgets.DocumentChooser', DocumentChooser); +window.telepath.register( + 'wagtail.documents.widgets.DocumentChooser', + DocumentChooser, +); diff --git a/client/src/entrypoints/documents/document-chooser.js b/client/src/entrypoints/documents/document-chooser.js index ebb52944e5..9d939d2bdd 100644 --- a/client/src/entrypoints/documents/document-chooser.js +++ b/client/src/entrypoints/documents/document-chooser.js @@ -62,8 +62,8 @@ function createDocumentChooser(id) { responses: { documentChosen: (result) => { chooser.setState(result); - } - } + }, + }, }); }, diff --git a/client/src/entrypoints/images/image-chooser.js b/client/src/entrypoints/images/image-chooser.js index ecf43adb2c..71e4608d24 100644 --- a/client/src/entrypoints/images/image-chooser.js +++ b/client/src/entrypoints/images/image-chooser.js @@ -26,7 +26,7 @@ function createImageChooser(id) { url: previewImage.attr('src'), width: previewImage.attr('width'), height: previewImage.attr('height'), - } + }, }; } @@ -42,7 +42,7 @@ function createImageChooser(id) { width: newState.preview.width, height: newState.preview.height, alt: newState.title, - title: newState.title + title: newState.title, }); chooserElement.removeClass('blank'); editLink.attr('href', newState.edit_link); @@ -81,7 +81,7 @@ function createImageChooser(id) { clear: () => { chooser.setState(null); - } + }, }; /* hook up chooser API to the buttons */ diff --git a/client/src/entrypoints/snippets/snippet-chooser-telepath.js b/client/src/entrypoints/snippets/snippet-chooser-telepath.js index fadb7e111a..a09115dc34 100644 --- a/client/src/entrypoints/snippets/snippet-chooser-telepath.js +++ b/client/src/entrypoints/snippets/snippet-chooser-telepath.js @@ -15,4 +15,7 @@ class SnippetChooser { return chooser; } } -window.telepath.register('wagtail.snippets.widgets.SnippetChooser', SnippetChooser); +window.telepath.register( + 'wagtail.snippets.widgets.SnippetChooser', + SnippetChooser, +); diff --git a/client/src/entrypoints/snippets/snippet-chooser.js b/client/src/entrypoints/snippets/snippet-chooser.js index 33ebfc4e11..7680f28f9b 100644 --- a/client/src/entrypoints/snippets/snippet-chooser.js +++ b/client/src/entrypoints/snippets/snippet-chooser.js @@ -80,7 +80,6 @@ function createSnippetChooser(id) { clear: () => { chooser.setState(null); }, - }; $('.action-choose', chooserElement).on('click', () => { diff --git a/client/src/includes/initIE11Warning.js b/client/src/includes/initIE11Warning.js index 879c3c741c..8739f62cd0 100644 --- a/client/src/includes/initIE11Warning.js +++ b/client/src/includes/initIE11Warning.js @@ -8,9 +8,11 @@ const initIE11Warning = () => { return; } - const warnings = [].slice.call(document.querySelectorAll('[data-ie11-warning]')); + const warnings = [].slice.call( + document.querySelectorAll('[data-ie11-warning]'), + ); - warnings.forEach(warning => { + warnings.forEach((warning) => { // eslint-disable-next-line no-param-reassign warning.hidden = false; }); diff --git a/client/src/includes/initSubmenus.js b/client/src/includes/initSubmenus.js index 482432a915..6f67402b32 100644 --- a/client/src/includes/initSubmenus.js +++ b/client/src/includes/initSubmenus.js @@ -10,21 +10,21 @@ const initSubmenus = () => { } const subMenuTriggers = document.querySelectorAll( - '[data-nav-primary-submenu-trigger]' + '[data-nav-primary-submenu-trigger]', ); const activeClass = 'submenu-active'; - [...subMenuTriggers].forEach(subMenuTrigger => { - subMenuTrigger.addEventListener('click', clickEvent => { + [...subMenuTriggers].forEach((subMenuTrigger) => { + subMenuTrigger.addEventListener('click', (clickEvent) => { const submenuContainer = subMenuTrigger.parentNode; primaryNavContainer.classList.remove(activeClass); - [...subMenuTriggers].forEach(sm => sm.classList.remove(activeClass)); + [...subMenuTriggers].forEach((sm) => sm.classList.remove(activeClass)); primaryNavContainer.classList.toggle(activeClass); submenuContainer.classList.toggle(activeClass); - document.addEventListener('mousedown', e => { + document.addEventListener('mousedown', (e) => { if ( !submenuContainer.contains(e.target) && subMenuTrigger !== e.target @@ -34,7 +34,7 @@ const initSubmenus = () => { } }); - document.addEventListener('keydown', e => { + document.addEventListener('keydown', (e) => { // IE11 uses "Esc" instead of "Escape" if (e.key === 'Escape' || e.key === 'Esc') { primaryNavContainer.classList.remove(activeClass); diff --git a/client/src/utils/actions.ts b/client/src/utils/actions.ts index 6bcac7a5e8..a9130285bd 100644 --- a/client/src/utils/actions.ts +++ b/client/src/utils/actions.ts @@ -15,7 +15,7 @@ interface Action<N, P, M> { export function createAction<N extends string, T extends any[], P, M>( type: N, actionCreator: (...args: T) => P = identity, - metaCreator?: (...args: T) => M + metaCreator?: (...args: T) => M, ): (...args: T) => Action<N, P, M> { return (...args) => { const action: Action<N, P, M> = { diff --git a/client/src/utils/cleanForSlug.js b/client/src/utils/cleanForSlug.js index df1e7ed34c..38f65614e5 100644 --- a/client/src/utils/cleanForSlug.js +++ b/client/src/utils/cleanForSlug.js @@ -14,7 +14,13 @@ export function cleanForSlug(val, useURLify) { // just do the "replace" if (window.unicodeSlugsEnabled) { - return val.replace(/\s/g, '-').replace(/[&/\\#,+()$~%.'":`@^!*?<>{}]/g, '').toLowerCase(); + return val + .replace(/\s/g, '-') + .replace(/[&/\\#,+()$~%.'":`@^!*?<>{}]/g, '') + .toLowerCase(); } - return val.replace(/\s/g, '-').replace(/[^A-Za-z0-9\-_]/g, '').toLowerCase(); + return val + .replace(/\s/g, '-') + .replace(/[^A-Za-z0-9\-_]/g, '') + .toLowerCase(); } diff --git a/client/src/utils/cleanForSlug.test.js b/client/src/utils/cleanForSlug.test.js index d582e44559..d464303d95 100644 --- a/client/src/utils/cleanForSlug.test.js +++ b/client/src/utils/cleanForSlug.test.js @@ -1,5 +1,6 @@ // eslint-disable-next-line no-unused-expressions -require('../../../wagtail/admin/static_src/wagtailadmin/js/vendor/urlify').default; +require('../../../wagtail/admin/static_src/wagtailadmin/js/vendor/urlify') + .default; import { cleanForSlug } from './cleanForSlug'; describe('page-editor tests', () => { @@ -12,9 +13,13 @@ describe('page-editor tests', () => { /* true triggers to use django's urlify */ expect(cleanForSlug('Before', true)).toBe('before'); expect(cleanForSlug('The', true)).toBe('the'); - expect(cleanForSlug('Before the sun rises', true)).toBe('before-the-sun-rises'); + expect(cleanForSlug('Before the sun rises', true)).toBe( + 'before-the-sun-rises', + ); expect(cleanForSlug('ON', true)).toBe('on'); - expect(cleanForSlug('ON this day in november', true)).toBe('on-this-day-in-november'); + expect(cleanForSlug('ON this day in november', true)).toBe( + 'on-this-day-in-november', + ); expect(cleanForSlug('This & That', true)).toBe('this-that'); }); @@ -22,9 +27,13 @@ describe('page-editor tests', () => { /* false triggers ignores django's urlify */ expect(cleanForSlug('Before', false)).toBe('before'); expect(cleanForSlug('The', false)).toBe('the'); - expect(cleanForSlug('Before the sun rises', false)).toBe('before-the-sun-rises'); + expect(cleanForSlug('Before the sun rises', false)).toBe( + 'before-the-sun-rises', + ); expect(cleanForSlug('ON', false)).toBe('on'); - expect(cleanForSlug('ON this day in november', false)).toBe('on-this-day-in-november'); + expect(cleanForSlug('ON this day in november', false)).toBe( + 'on-this-day-in-november', + ); expect(cleanForSlug('This & That', false)).toBe('this--that'); }); }); diff --git a/client/src/utils/focus.js b/client/src/utils/focus.js index 08cb13d2a5..53992d8619 100644 --- a/client/src/utils/focus.js +++ b/client/src/utils/focus.js @@ -20,7 +20,7 @@ export const initFocusOutline = () => { window.addEventListener('mousedown', removeFocusOutline); window.addEventListener('touchstart', removeFocusOutline); - window.addEventListener('keydown', e => { + window.addEventListener('keydown', (e) => { const isTabKey = e.keyCode === 9; if (isTabKey) { diff --git a/client/src/utils/focus.test.js b/client/src/utils/focus.test.js index ff0739a7b4..df8e106a0e 100644 --- a/client/src/utils/focus.test.js +++ b/client/src/utils/focus.test.js @@ -14,8 +14,8 @@ describe('initFocusOutline', () => { initFocusOutline(); window.dispatchEvent( Object.assign(new Event('keydown'), { - keyCode: 9 - }) + keyCode: 9, + }), ); expect(document.body.className).toBe('focus-outline-on'); }); @@ -29,8 +29,8 @@ describe('initFocusOutline', () => { it('removes styles when using a mouse', () => { window.dispatchEvent( Object.assign(new Event('keydown'), { - keyCode: 9 - }) + keyCode: 9, + }), ); window.dispatchEvent(new Event('mousedown')); expect(document.body.className).toBe('focus-outline-off'); @@ -39,8 +39,8 @@ describe('initFocusOutline', () => { it('removes styles when using a touch screen', () => { window.dispatchEvent( Object.assign(new Event('keydown'), { - keyCode: 9 - }) + keyCode: 9, + }), ); window.dispatchEvent(new Event('touchstart')); expect(document.body.className).toBe('focus-outline-off'); diff --git a/client/src/utils/performance.js b/client/src/utils/performance.js index 4ac8deb577..11c942553e 100644 --- a/client/src/utils/performance.js +++ b/client/src/utils/performance.js @@ -28,6 +28,4 @@ if (process.env.NODE_ENV !== 'production') { }; } -export { - perfMiddleware, -}; +export { perfMiddleware }; diff --git a/client/src/utils/version.js b/client/src/utils/version.js index 64aedc22eb..3e9e075077 100644 --- a/client/src/utils/version.js +++ b/client/src/utils/version.js @@ -17,7 +17,4 @@ function versionOutOfDate(latestVersion, currentVersion) { return compareVersion(latestVersion, currentVersion) > 0; } -export { - compareVersion, - versionOutOfDate, -}; +export { compareVersion, versionOutOfDate }; diff --git a/client/tests/integration/.eslintrc.js b/client/tests/integration/.eslintrc.js index 57c0bfedcb..3ac3678939 100644 --- a/client/tests/integration/.eslintrc.js +++ b/client/tests/integration/.eslintrc.js @@ -12,10 +12,10 @@ module.exports = { env: { jest: true, browser: true, - node: true + node: true, }, globals: { page: 'readonly', - TEST_ORIGIN: 'readonly' + TEST_ORIGIN: 'readonly', }, }; diff --git a/client/tests/integration/PuppeteerEnvironment.js b/client/tests/integration/PuppeteerEnvironment.js index 91ac2f1c0c..a9bdbff151 100644 --- a/client/tests/integration/PuppeteerEnvironment.js +++ b/client/tests/integration/PuppeteerEnvironment.js @@ -19,7 +19,8 @@ class PuppeteerEnvironment extends NodeEnvironment { throw new Error('wsEndpoint not found'); } - this.global.TEST_ORIGIN = process.env.TEST_ORIGIN ?? 'http://localhost:8000'; + this.global.TEST_ORIGIN = + process.env.TEST_ORIGIN ?? 'http://localhost:8000'; // connect to puppeteer this.global.browser = await puppeteer.connect({ diff --git a/client/tests/integration/editbird.test.js b/client/tests/integration/editbird.test.js index 83de94d807..800b521b15 100644 --- a/client/tests/integration/editbird.test.js +++ b/client/tests/integration/editbird.test.js @@ -7,10 +7,12 @@ describe.skip('Editbird', () => { const trigger = await page.$('[aria-controls="wagtail-userbar-items"]'); await Promise.all([ trigger.click(), - page.waitForSelector('[aria-labelledby="wagtail-userbar-trigger"]', { visible: true }), + page.waitForSelector('[aria-labelledby="wagtail-userbar-trigger"]', { + visible: true, + }), ]); await expect(page).toPassAxeTests({ - exclude: '[role="menuitem"]' + exclude: '[role="menuitem"]', }); }); }); diff --git a/client/tests/integration/editor.test.js b/client/tests/integration/editor.test.js index 19379f3397..5f40beff9e 100644 --- a/client/tests/integration/editor.test.js +++ b/client/tests/integration/editor.test.js @@ -2,9 +2,7 @@ describe('Editor', () => { const globalEditorExcludes = '.skiplink, .sidebar__collapse-toggle, #wagtail-sidebar, li[aria-controls^="tab-"]'; beforeAll(async () => { - await page.goto( - `${TEST_ORIGIN}/admin/pages/add/demosite/standardpage/2/` - ); + await page.goto(`${TEST_ORIGIN}/admin/pages/add/demosite/standardpage/2/`); }); it('has the right heading', async () => { diff --git a/client/tests/integration/groups.test.js b/client/tests/integration/groups.test.js index ece98d9566..e31e4c4dcc 100644 --- a/client/tests/integration/groups.test.js +++ b/client/tests/integration/groups.test.js @@ -9,7 +9,7 @@ describe('Groups', () => { it('axe', async () => { await expect(page).toPassAxeTests({ - exclude: '.skiplink, .sidebar__collapse-toggle, #wagtail-sidebar' + exclude: '.skiplink, .sidebar__collapse-toggle, #wagtail-sidebar', }); }); }); diff --git a/client/tests/integration/jest.config.js b/client/tests/integration/jest.config.js index b91082b996..15581ca9d7 100644 --- a/client/tests/integration/jest.config.js +++ b/client/tests/integration/jest.config.js @@ -2,8 +2,5 @@ module.exports = { globalSetup: './setup.js', globalTeardown: './teardown.js', testEnvironment: './PuppeteerEnvironment.js', - setupFilesAfterEnv: [ - 'expect-puppeteer', - '@wordpress/jest-puppeteer-axe' - ] + setupFilesAfterEnv: ['expect-puppeteer', '@wordpress/jest-puppeteer-axe'], }; diff --git a/client/tests/integration/listing.test.js b/client/tests/integration/listing.test.js index d2a03a9910..c45d218d9d 100644 --- a/client/tests/integration/listing.test.js +++ b/client/tests/integration/listing.test.js @@ -4,12 +4,15 @@ describe('Listing', () => { }); it('has the right heading', async () => { - expect(await page.title()).toContain('Wagtail - Exploring Welcome to your new Wagtail site!'); + expect(await page.title()).toContain( + 'Wagtail - Exploring Welcome to your new Wagtail site!', + ); }); it('axe', async () => { await expect(page).toPassAxeTests({ - exclude: '.skiplink, .sidebar__collapse-toggle, #wagtail-sidebar, a[href$="dummy-button"]' + exclude: + '.skiplink, .sidebar__collapse-toggle, #wagtail-sidebar, a[href$="dummy-button"]', }); }); }); diff --git a/client/tests/integration/users.test.js b/client/tests/integration/users.test.js index df101bedbb..920474b6b0 100644 --- a/client/tests/integration/users.test.js +++ b/client/tests/integration/users.test.js @@ -7,7 +7,7 @@ describe('Users', () => { const toggle = await page.$('[aria-label="Select all"]'); await toggle.click(); await expect(page).toPassAxeTests({ - exclude: '.skiplink, .sidebar__collapse-toggle, #wagtail-sidebar' + exclude: '.skiplink, .sidebar__collapse-toggle, #wagtail-sidebar', }); }); }); diff --git a/client/tests/mock-fetch.js b/client/tests/mock-fetch.js index c7ab2853d0..95e6aee32d 100644 --- a/client/tests/mock-fetch.js +++ b/client/tests/mock-fetch.js @@ -4,26 +4,32 @@ global.Headers = jest.fn(); // Helper to mock a success response. fetch.mockResponseSuccess = (body) => { - fetch.mockImplementationOnce(() => Promise.resolve({ - json: () => Promise.resolve(JSON.parse(body)), - status: 200, - statusText: 'OK', - })); + fetch.mockImplementationOnce(() => + Promise.resolve({ + json: () => Promise.resolve(JSON.parse(body)), + status: 200, + statusText: 'OK', + }), + ); }; // Helper to mock a failure response. fetch.mockResponseFailure = () => { - fetch.mockImplementationOnce(() => Promise.resolve({ - status: 500, - statusText: 'Internal Error', - })); + fetch.mockImplementationOnce(() => + Promise.resolve({ + status: 500, + statusText: 'Internal Error', + }), + ); }; fetch.mockResponseCrash = () => { - fetch.mockImplementationOnce(() => Promise.reject({ - status: 500, - statusText: 'Internal Error', - })); + fetch.mockImplementationOnce(() => + Promise.reject({ + status: 500, + statusText: 'Internal Error', + }), + ); }; // Helper to mock a timeout response. diff --git a/client/tests/stubs.js b/client/tests/stubs.js index 7d90a1ffda..c0d28e52fa 100644 --- a/client/tests/stubs.js +++ b/client/tests/stubs.js @@ -40,12 +40,13 @@ global.wagtailConfig = { RELOAD_EDITOR: 'Reload saved content', SHOW_LATEST_CONTENT: 'Show latest content', SHOW_ERROR: 'Show error', - EDITOR_CRASH: 'The editor just crashed. Content has been reset to the last saved version.', + EDITOR_CRASH: + 'The editor just crashed. Content has been reset to the last saved version.', BROKEN_LINK: 'Broken link', MISSING_DOCUMENT: 'Missing document', CLOSE: 'Close', - EDIT_PAGE: 'Edit \'{title}\'', - VIEW_CHILD_PAGES_OF_PAGE: 'View child pages of \'{title}\'', + EDIT_PAGE: "Edit '{title}'", + VIEW_CHILD_PAGES_OF_PAGE: "View child pages of '{title}'", PAGE_EXPLORER: 'Page explorer', SAVE: 'Save', SAVING: 'Saving...', @@ -72,14 +73,14 @@ global.wagtailConfig = { LOCALES: [ { code: 'en', - display_name: 'English' + display_name: 'English', }, { code: 'fr', - display_nam: 'French' - } + display_nam: 'French', + }, ], - ACTIVE_LOCALE: 'en' + ACTIVE_LOCALE: 'en', }; global.wagtailVersion = '1.6a1'; diff --git a/client/webpack.config.js b/client/webpack.config.js index e50bff4d14..b5f69e89cb 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -10,7 +10,7 @@ const getOutputPath = (app, folder, filename) => { 'contrib/typed_table_block': 'typed_table_block', 'contrib/styleguide': 'wagtailstyleguide', 'contrib/modeladmin': 'wagtailmodeladmin', - } + }; const appLabel = exceptions[app] || `wagtail${app}`; @@ -59,31 +59,18 @@ module.exports = function exports(env, argv) { 'wagtailadmin', 'workflow-action', 'workflow-status', - 'bulk-actions' - ], - 'images': [ - 'image-chooser', - 'image-chooser-telepath', - ], - 'documents': [ - 'document-chooser', - 'document-chooser-telepath', - ], - 'snippets': [ - 'snippet-chooser', - 'snippet-chooser-telepath', - ], - 'contrib/table_block': [ - 'table', - ], - 'contrib/typed_table_block': [ - 'typed_table_block', + 'bulk-actions', ], + 'images': ['image-chooser', 'image-chooser-telepath'], + 'documents': ['document-chooser', 'document-chooser-telepath'], + 'snippets': ['snippet-chooser', 'snippet-chooser-telepath'], + 'contrib/table_block': ['table'], + 'contrib/typed_table_block': ['typed_table_block'], }; const entry = {}; for (const [appName, moduleNames] of Object.entries(entrypoints)) { - moduleNames.forEach(moduleName => { + moduleNames.forEach((moduleName) => { entry[moduleName] = { import: [`./client/src/entrypoints/${appName}/${moduleName}.js`], filename: getOutputPath(appName, 'js', moduleName) + '.js', @@ -100,31 +87,226 @@ module.exports = function exports(env, argv) { } const sassEntry = {}; - sassEntry[getOutputPath('admin', 'css', 'core')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'core.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/404')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', '404.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/account')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', 'account.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/compare-revisions')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', 'compare-revisions.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/home')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', 'home.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/login')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', 'login.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/page-editor')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', 'page-editor.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/report')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', 'report.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/workflow-edit')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', 'workflow-edit.scss'); - sassEntry[getOutputPath('admin', 'css', 'layouts/workflow-progress')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'layouts', 'workflow-progress.scss'); + sassEntry[getOutputPath('admin', 'css', 'core')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'core.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/404')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + '404.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/account')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + 'account.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/compare-revisions')] = + path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + 'compare-revisions.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/home')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + 'home.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/login')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + 'login.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/page-editor')] = + path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + 'page-editor.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/report')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + 'report.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/workflow-edit')] = + path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + 'workflow-edit.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'layouts/workflow-progress')] = + path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'layouts', + 'workflow-progress.scss', + ); // sassEntry[getOutputPath('admin', 'css', 'normalize')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'css', 'normalize.css'); - sassEntry[getOutputPath('admin', 'css', 'panels/draftail')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'panels', 'draftail.scss'); - sassEntry[getOutputPath('admin', 'css', 'panels/hallo')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'panels', 'hallo.scss'); - sassEntry[getOutputPath('admin', 'css', 'panels/streamfield')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'panels', 'streamfield.scss'); - sassEntry[getOutputPath('admin', 'css', 'sidebar')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'sidebar.scss'); - sassEntry[getOutputPath('admin', 'css', 'userbar')] = path.resolve('wagtail', 'admin', 'static_src', 'wagtailadmin', 'scss', 'userbar.scss'); - sassEntry[getOutputPath('documents', 'css', 'add-multiple')] = path.resolve('wagtail', 'documents', 'static_src', 'wagtaildocs', 'scss', 'add-multiple.scss'); - sassEntry[getOutputPath('images', 'css', 'add-multiple')] = path.resolve('wagtail', 'images', 'static_src', 'wagtailimages', 'scss', 'add-multiple.scss'); - sassEntry[getOutputPath('images', 'css', 'focal-point-chooser')] = path.resolve('wagtail', 'images', 'static_src', 'wagtailimages', 'scss', 'focal-point-chooser.scss'); - sassEntry[getOutputPath('users', 'css', 'groups_edit')] = path.resolve('wagtail', 'users', 'static_src', 'wagtailusers', 'scss', 'groups_edit.scss'); - sassEntry[getOutputPath('contrib/styleguide', 'css', 'styleguide')] = path.resolve('wagtail', 'contrib', 'styleguide', 'static_src', 'wagtailstyleguide', 'scss', 'styleguide.scss'); - sassEntry[getOutputPath('contrib/modeladmin', 'css', 'index')] = path.resolve('wagtail', 'contrib', 'modeladmin', 'static_src', 'wagtailmodeladmin', 'scss', 'index.scss'); - sassEntry[getOutputPath('contrib/modeladmin', 'css', 'breadcrumbs_page')] = path.resolve('wagtail', 'contrib', 'modeladmin', 'static_src', 'wagtailmodeladmin', 'scss', 'breadcrumbs_page.scss'); - sassEntry[getOutputPath('contrib/modeladmin', 'css', 'choose_parent_page')] = path.resolve('wagtail', 'contrib', 'modeladmin', 'static_src', 'wagtailmodeladmin', 'scss', 'choose_parent_page.scss'); - sassEntry[getOutputPath('contrib/typed_table_block', 'css', 'typed_table_block')] = path.resolve('wagtail', 'contrib', 'typed_table_block', 'static_src', 'typed_table_block', 'scss', 'typed_table_block.scss'); + sassEntry[getOutputPath('admin', 'css', 'panels/draftail')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'panels', + 'draftail.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'panels/hallo')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'panels', + 'hallo.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'panels/streamfield')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'panels', + 'streamfield.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'sidebar')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'sidebar.scss', + ); + sassEntry[getOutputPath('admin', 'css', 'userbar')] = path.resolve( + 'wagtail', + 'admin', + 'static_src', + 'wagtailadmin', + 'scss', + 'userbar.scss', + ); + sassEntry[getOutputPath('documents', 'css', 'add-multiple')] = path.resolve( + 'wagtail', + 'documents', + 'static_src', + 'wagtaildocs', + 'scss', + 'add-multiple.scss', + ); + sassEntry[getOutputPath('images', 'css', 'add-multiple')] = path.resolve( + 'wagtail', + 'images', + 'static_src', + 'wagtailimages', + 'scss', + 'add-multiple.scss', + ); + sassEntry[getOutputPath('images', 'css', 'focal-point-chooser')] = + path.resolve( + 'wagtail', + 'images', + 'static_src', + 'wagtailimages', + 'scss', + 'focal-point-chooser.scss', + ); + sassEntry[getOutputPath('users', 'css', 'groups_edit')] = path.resolve( + 'wagtail', + 'users', + 'static_src', + 'wagtailusers', + 'scss', + 'groups_edit.scss', + ); + sassEntry[getOutputPath('contrib/styleguide', 'css', 'styleguide')] = + path.resolve( + 'wagtail', + 'contrib', + 'styleguide', + 'static_src', + 'wagtailstyleguide', + 'scss', + 'styleguide.scss', + ); + sassEntry[getOutputPath('contrib/modeladmin', 'css', 'index')] = path.resolve( + 'wagtail', + 'contrib', + 'modeladmin', + 'static_src', + 'wagtailmodeladmin', + 'scss', + 'index.scss', + ); + sassEntry[getOutputPath('contrib/modeladmin', 'css', 'breadcrumbs_page')] = + path.resolve( + 'wagtail', + 'contrib', + 'modeladmin', + 'static_src', + 'wagtailmodeladmin', + 'scss', + 'breadcrumbs_page.scss', + ); + sassEntry[getOutputPath('contrib/modeladmin', 'css', 'choose_parent_page')] = + path.resolve( + 'wagtail', + 'contrib', + 'modeladmin', + 'static_src', + 'wagtailmodeladmin', + 'scss', + 'choose_parent_page.scss', + ); + sassEntry[ + getOutputPath('contrib/typed_table_block', 'css', 'typed_table_block') + ] = path.resolve( + 'wagtail', + 'contrib', + 'typed_table_block', + 'static_src', + 'typed_table_block', + 'scss', + 'typed_table_block.scss', + ); return { entry: { @@ -156,15 +338,51 @@ module.exports = function exports(env, argv) { }), new CopyPlugin({ patterns: [ - { from: 'wagtail/admin/static_src/', to: 'wagtail/admin/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, - { from: 'wagtail/documents/static_src/', to: 'wagtail/documents/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, - { from: 'wagtail/embeds/static_src/', to: 'wagtail/embeds/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, - { from: 'wagtail/images/static_src/', to: 'wagtail/images/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, - { from: 'wagtail/search/static_src/', to: 'wagtail/search/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, - { from: 'wagtail/snippets/static_src/', to: 'wagtail/snippets/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, - { from: 'wagtail/users/static_src/', to: 'wagtail/users/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, - { from: 'wagtail/contrib/settings/static_src/', to: 'wagtail/contrib/settings/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, - { from: 'wagtail/contrib/modeladmin/static_src/', to: 'wagtail/contrib/modeladmin/static/', globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] } }, + { + from: 'wagtail/admin/static_src/', + to: 'wagtail/admin/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, + { + from: 'wagtail/documents/static_src/', + to: 'wagtail/documents/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, + { + from: 'wagtail/embeds/static_src/', + to: 'wagtail/embeds/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, + { + from: 'wagtail/images/static_src/', + to: 'wagtail/images/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, + { + from: 'wagtail/search/static_src/', + to: 'wagtail/search/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, + { + from: 'wagtail/snippets/static_src/', + to: 'wagtail/snippets/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, + { + from: 'wagtail/users/static_src/', + to: 'wagtail/users/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, + { + from: 'wagtail/contrib/settings/static_src/', + to: 'wagtail/contrib/settings/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, + { + from: 'wagtail/contrib/modeladmin/static_src/', + to: 'wagtail/contrib/modeladmin/static/', + globOptions: { ignore: ['**/{app,scss}/**', '*.{css,txt}'] }, + }, ], }), ], @@ -197,35 +415,34 @@ module.exports = function exports(env, argv) { loader: 'postcss-loader', options: { postcssOptions: { - plugins: [ - "autoprefixer", - "cssnano", - ] - } + plugins: ['autoprefixer', 'cssnano'], + }, }, }, - 'sass-loader' + 'sass-loader', ], }, - ].concat(Object.keys(exposedDependencies).map((name) => { - const globalName = exposedDependencies[name]; + ].concat( + Object.keys(exposedDependencies).map((name) => { + const globalName = exposedDependencies[name]; - // Create expose-loader configs for each Wagtail dependency. - return { - test: require.resolve(name), - use: [ - { - loader: 'expose-loader', - options: { - exposes: { - globalName, - override: true - } + // Create expose-loader configs for each Wagtail dependency. + return { + test: require.resolve(name), + use: [ + { + loader: 'expose-loader', + options: { + exposes: { + globalName, + override: true, + }, + }, }, - }, - ], - }; - })) + ], + }; + }), + ), }, optimization: { @@ -253,7 +470,7 @@ module.exports = function exports(env, argv) { // Disable performance hints – currently there are much more valuable // optimizations for us to do outside of Webpack performance: { - hints: false + hints: false, }, stats: { diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css index f3ff66ed24..041feafcbb 100644 --- a/docs/_static/css/custom.css +++ b/docs/_static/css/custom.css @@ -1,92 +1,95 @@ body.wy-body-for-nav { - font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Arial, sans-serif; - line-height: 1.5em; - color: #333; /*$color-grey-1*/ - background-color: #e6e6e6; /*$color-grey-4*/ + font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, + Arial, sans-serif; + line-height: 1.5em; + color: #333; /*$color-grey-1*/ + background-color: #e6e6e6; /*$color-grey-4*/ } nav.wy-nav-side { - background-color: #333; /*color-grey-1*/ + background-color: #333; /*color-grey-1*/ } .body--autocomplete-open .wy-nav-side { - position: absolute; - overflow: visible; + position: absolute; + overflow: visible; } .body--autocomplete-open .wy-side-scroll { - overflow: visible; + overflow: visible; } div.wy-side-nav-search { - background-color: #007d7e; /*color-teal*/ + background-color: #007d7e; /*color-teal*/ } .wy-nav-top { - background-color: #007d7e; /*color-teal*/ + background-color: #007d7e; /*color-teal*/ } -.wy-side-nav-search>a:hover, .wy-side-nav-search .wy-dropdown>a:hover { - background: None; /*background for logo*/ +.wy-side-nav-search > a:hover, +.wy-side-nav-search .wy-dropdown > a:hover { + background: None; /*background for logo*/ } -.wy-side-nav-search>div.version { - color: white; +.wy-side-nav-search > div.version { + color: white; } -.wy-side-nav-search input[type=text] { - border-color: #d9d9d9; /*color-grey-3*/ +.wy-side-nav-search input[type='text'] { + border-color: #d9d9d9; /*color-grey-3*/ } .logo { - transition: all 0.25s cubic-bezier(0.28, 0.15, 0, 2.1); + transition: all 0.25s cubic-bezier(0.28, 0.15, 0, 2.1); } .logo:hover { - transform: rotate(4deg); + transform: rotate(4deg); } details { - margin-bottom: 1em; + margin-bottom: 1em; } /* Wagtail Space */ a.wagtailspace { - background: #00676a; - color: white; - padding: 15px; - border-radius: 6px; - margin-bottom: 1.25em; - display: flex; - justify-content: space-between; - align-items: flex-start; + background: #00676a; + color: white; + padding: 15px; + border-radius: 6px; + margin-bottom: 1.25em; + display: flex; + justify-content: space-between; + align-items: flex-start; } .wagtailspace svg { - margin-right: 1em; - width: 56px; + margin-right: 1em; + width: 56px; } .wagtailspace div { - text-align: left; - flex-grow: 2; + text-align: left; + flex-grow: 2; } .wagtailspace strong { - font-size: 1.25em; + font-size: 1.25em; } .wagtailspace .close { - margin-left: 1em; - margin-bottom: 1em; - cursor: pointer; - font-weight: bold; - font-size: 1.25em; + margin-left: 1em; + margin-bottom: 1em; + cursor: pointer; + font-weight: bold; + font-size: 1.25em; } /* Bounce the UFO on hover */ @-webkit-keyframes bounce { - 0%, 100% { + 0%, + 100% { -webkit-transform: translateY(0); } 50% { @@ -95,7 +98,8 @@ a.wagtailspace { } @keyframes bounce { - 0%, 100% { + 0%, + 100% { transform: translateY(0); } 50% { @@ -104,8 +108,8 @@ a.wagtailspace { } a.wagtailspace:hover svg { - -webkit-animation-duration: .5s; - animation-duration: .5s; + -webkit-animation-duration: 0.5s; + animation-duration: 0.5s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-timing-function: linear; diff --git a/docs/_static/css/docsearch.overrides.css b/docs/_static/css/docsearch.overrides.css index b2b4c396a8..2f0c4ded78 100644 --- a/docs/_static/css/docsearch.overrides.css +++ b/docs/_static/css/docsearch.overrides.css @@ -1,17 +1,17 @@ .search .algolia-docsearch-suggestion--highlight, .algolia-autocomplete .algolia-docsearch-suggestion--highlight { - color: #2980B9; - background: #F1C40F; + color: #2980b9; + background: #f1c40f; } .algolia-autocomplete { - width: 100%; + width: 100%; } #search-results a, a.algolia-docsearch-suggestion { - border-bottom: 0; + border-bottom: 0; } -#search-results h2 { - display: none; +#search-results h2 { + display: none; } diff --git a/docs/_static/js/banner.js b/docs/_static/js/banner.js index cdd10476fc..3f9da613c2 100644 --- a/docs/_static/js/banner.js +++ b/docs/_static/js/banner.js @@ -1,9 +1,12 @@ function setCookie(name, value, days) { const date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - document.cookie = encodeURIComponent(name) + '=' + - encodeURIComponent(value) + - '; expires=' + date.toGMTString(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + document.cookie = + encodeURIComponent(name) + + '=' + + encodeURIComponent(value) + + '; expires=' + + date.toGMTString(); } function hasCookie(name, value) { @@ -13,16 +16,18 @@ function hasCookie(name, value) { $(() => { const $wagtailspace = $( '<a href="https://www.wagtail.space/2019/" class="wagtailspace" title="https://www.wagtail.space/2019/">' + - ' <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 190 132" width="36" height="36"><defs><path id="a" d="M89.58 26.597C81.876 11.063 65.8.375 47.213.375S12.552 11.063 4.85 26.597C1.746 32.857 0 39.902 0 47.354c0 6.875 1.484 13.404 4.15 19.288 12.926 1.555 27.562 2.432 43.064 2.432 15.503 0 30.138-.877 43.064-2.432 2.667-5.884 4.152-12.413 4.152-19.288 0-7.452-1.747-14.497-4.85-20.757"/></defs><g fill="none" fill-rule="evenodd"><g><g><path fill="#DFDFDF" d="M49.467 127.165h4.465V86.51h-4.465"/><path fill="#DFDFDF" d="M55.836 127.153c0 2.677-2.18 4.847-4.87 4.847s-4.87-2.17-4.87-4.847c0-2.676 2.18-4.846 4.87-4.846s4.87 2.17 4.87 4.846M134.91 127.165h4.466V86.51h-4.465"/><path fill="#DFDFDF" d="M142.404 127.153c0 2.677-2.18 4.847-4.87 4.847s-4.87-2.17-4.87-4.847c0-2.676 2.18-4.846 4.87-4.846s4.87 2.17 4.87 4.846"/><path fill="#D23C2B" d="M94.975 86.13L0 70.85s2.768 30.56 94.975 30.56c92.208 0 94.976-30.56 94.976-30.56L94.976 86.13"/><path fill="#DE4D33" d="M189.95 69.77c0 12.38-42.52 22.415-94.975 22.415C42.523 92.185 0 82.15 0 69.77 0 57.394 42.523 47.36 94.975 47.36c52.454 0 94.974 10.035 94.974 22.413"/><path fill="#DE4D33" d="M189.95 69.77c0 12.38-42.52 22.415-94.975 22.415C42.523 92.185 0 82.15 0 69.77 0 57.394 42.523 47.36 94.975 47.36c52.454 0 94.974 10.035 94.974 22.413"/><g transform="translate(48.343)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#9FD6E4" xlink:href="#a"/><g mask="url(#b)"><g transform="translate(-25.858 10.068)"><path fill="#8DBEC9" d="M66.328 58.76h.987c6.65-.016 13.134-.195 19.39-.518-.003-.033-1.522-14.294-11.057-29.093-2.158 2.536-5.382 4.146-8.983 4.146-6.5 0-11.77-5.243-11.77-11.712 0-3.97 1.986-7.476 5.022-9.593-6.19-4.746-13.78-8.84-23.1-11.615C33 3.422 29.67 7.045 26.96 11.115c-1.033 1.55-1.98 3.16-2.826 4.828l-.015.03c-.01.017-.017.033-.025.05l-.036.07-.006.01-.09.18c-3.104 6.26-4.852 13.306-4.852 20.76 0 .757.02 1.51.054 2.26.288 6.045 1.725 11.79 4.098 17.026 12.926 1.554 27.562 2.43 43.064 2.43" opacity=".293"/><path fill="#FFF" fill-rule="nonzero" d="M85.573 11.186v5.07s-10.397-3.38-17.124 2.755c-4.894 4.447-5.302 9.427-3.06 16.095 22.018 0 25.483 10.76 25.483 10.76l-2.242-12.36 6.932-7.38c0-8.714-8.256-14.494-9.99-14.94z" opacity=".821"/><ellipse cx="86.568" cy="23.492" fill="#000" fill-rule="nonzero" opacity=".821" rx="2.249" ry="2.237"/><path fill="#000" fill-rule="nonzero" d="M90.537 45.566s-3.35-14.862-25.274-10.833c-2.232-6.715-1.826-11.64 3.045-16.204 6.598-6.18 16.95-2.777 16.95-2.777V10.65c-3.653-1.432-7.104-1.7-10.96-1.7-14.11 0-21.925 9.31-25.274 15.577L9.338 88.897l11.165-1.88L0 121.932l14.31-2.238L25.274 92.21c31.06 0 70.846-9.85 65.264-46.644zM104.556 33.56l-8.79-7.83-6.95 7.83" opacity=".821"/><path fill="#FFF" fill-rule="nonzero" d="M30.355 83.898s1.006-.177 2.818-.53c1.812-.356 4.328-.887 7.347-1.596 1.51-.355 3.12-.798 4.83-1.33 1.712-.53 3.523-1.063 5.234-1.77 1.812-.622 3.624-1.42 5.435-2.305 1.81-.886 3.522-1.86 5.132-3.013.402-.266.805-.53 1.208-.886l1.207-.886c.705-.62 1.51-1.24 2.215-1.95.704-.62 1.308-1.328 1.912-2.037l.906-1.063.402-.532.403-.532c.2-.354.503-.708.704-1.063.2-.354.402-.71.704-1.063.1-.177.2-.355.302-.532.1-.177.2-.354.302-.53.2-.356.402-.71.503-1.065l.907-2.126c.2-.71.503-1.42.704-2.04.202-.62.303-1.328.504-1.86.1-.62.202-1.152.302-1.772.1-.532.2-1.063.2-1.506.102-.443.102-.886.203-1.33.1-1.594.1-2.48.1-2.48l1.61.088s-.1.975-.202 2.57c-.1.443-.1.886-.2 1.33-.102.53-.102 1.062-.303 1.594-.1.53-.302 1.152-.402 1.772-.202.62-.403 1.24-.604 1.95-.203.708-.504 1.328-.806 2.126-.302.71-.604 1.417-1.007 2.215-.2.354-.402.71-.604 1.063-.1.177-.2.355-.302.532l-.3.53c-.202.356-.504.71-.705 1.153-.302.355-.503.71-.805 1.064-.1.176-.302.353-.403.53l-.402.532-.906 1.063c-.706.71-1.31 1.418-2.115 2.038-.704.71-1.51 1.24-2.314 1.95l-1.207.885c-.403.266-.805.532-1.31.798-1.71 1.063-3.52 2.038-5.333 2.924-1.81.797-3.723 1.595-5.535 2.215-1.812.62-3.623 1.152-5.334 1.595-1.71.443-3.32.886-4.93 1.152-3.02.62-5.637 1.152-7.448 1.417-1.61.53-2.617.708-2.617.708z" opacity=".821"/></g></g></g><path fill="#9FD6E4" d="M75.325 68.613h.002-.002"/><path fill="#C7462F" d="M95.425 69.054h-.174.175c15.502 0 30.138-.877 43.064-2.432 2.372-5.235 3.81-10.982 4.097-17.027-.288 6.045-1.725 11.792-4.098 17.027-12.927 1.555-27.563 2.432-43.065 2.432m-.23 0h-.114.114m-.198 0h-.085.086m-.18 0h-.07.07m-.192 0h-.034.034m-.178 0h-.008.008"/><path fill="#1D2533" d="M137.33 26.445l-.09-.18.09.18m-.095-.19l-.036-.072c.01.023.024.047.035.072m-.06-.12l-.015-.03.016.03"/><path fill="#FFFFFE" d="M31.976 65.48c0 1.978-3.635 3.58-8.118 3.58-4.484 0-8.118-1.602-8.118-3.58 0-1.977 3.634-3.58 8.118-3.58 4.483 0 8.118 1.603 8.118 3.58M170.26 65.48c0 1.978-3.634 3.58-8.118 3.58s-8.118-1.602-8.118-3.58c0-1.977 3.634-3.58 8.118-3.58s8.118 1.603 8.118 3.58M58.957 76.666c0 1.977-3.634 3.58-8.117 3.58-4.483 0-8.118-1.603-8.118-3.58 0-1.976 3.635-3.58 8.118-3.58 4.483 0 8.117 1.604 8.117 3.58M145.525 76.666c0 1.977-3.634 3.58-8.117 3.58-4.484 0-8.118-1.603-8.118-3.58 0-1.976 3.634-3.58 8.118-3.58 4.483 0 8.117 1.604 8.117 3.58M103.93 80.022c0 1.977-3.636 3.58-8.12 3.58-4.483 0-8.118-1.603-8.118-3.58 0-1.976 3.635-3.58 8.12-3.58 4.482 0 8.117 1.604 8.117 3.58"/></g></g></g></svg>' + - ' <div>' + - ' <strong>Wagtail Space</strong><br> 13 – 15 March 2019 Arnhem, The Netherlands' + - ' </div>' + - ' <span class="close" title="Hide banner">×</span>' + - '</a>' + ' <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 190 132" width="36" height="36"><defs><path id="a" d="M89.58 26.597C81.876 11.063 65.8.375 47.213.375S12.552 11.063 4.85 26.597C1.746 32.857 0 39.902 0 47.354c0 6.875 1.484 13.404 4.15 19.288 12.926 1.555 27.562 2.432 43.064 2.432 15.503 0 30.138-.877 43.064-2.432 2.667-5.884 4.152-12.413 4.152-19.288 0-7.452-1.747-14.497-4.85-20.757"/></defs><g fill="none" fill-rule="evenodd"><g><g><path fill="#DFDFDF" d="M49.467 127.165h4.465V86.51h-4.465"/><path fill="#DFDFDF" d="M55.836 127.153c0 2.677-2.18 4.847-4.87 4.847s-4.87-2.17-4.87-4.847c0-2.676 2.18-4.846 4.87-4.846s4.87 2.17 4.87 4.846M134.91 127.165h4.466V86.51h-4.465"/><path fill="#DFDFDF" d="M142.404 127.153c0 2.677-2.18 4.847-4.87 4.847s-4.87-2.17-4.87-4.847c0-2.676 2.18-4.846 4.87-4.846s4.87 2.17 4.87 4.846"/><path fill="#D23C2B" d="M94.975 86.13L0 70.85s2.768 30.56 94.975 30.56c92.208 0 94.976-30.56 94.976-30.56L94.976 86.13"/><path fill="#DE4D33" d="M189.95 69.77c0 12.38-42.52 22.415-94.975 22.415C42.523 92.185 0 82.15 0 69.77 0 57.394 42.523 47.36 94.975 47.36c52.454 0 94.974 10.035 94.974 22.413"/><path fill="#DE4D33" d="M189.95 69.77c0 12.38-42.52 22.415-94.975 22.415C42.523 92.185 0 82.15 0 69.77 0 57.394 42.523 47.36 94.975 47.36c52.454 0 94.974 10.035 94.974 22.413"/><g transform="translate(48.343)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#9FD6E4" xlink:href="#a"/><g mask="url(#b)"><g transform="translate(-25.858 10.068)"><path fill="#8DBEC9" d="M66.328 58.76h.987c6.65-.016 13.134-.195 19.39-.518-.003-.033-1.522-14.294-11.057-29.093-2.158 2.536-5.382 4.146-8.983 4.146-6.5 0-11.77-5.243-11.77-11.712 0-3.97 1.986-7.476 5.022-9.593-6.19-4.746-13.78-8.84-23.1-11.615C33 3.422 29.67 7.045 26.96 11.115c-1.033 1.55-1.98 3.16-2.826 4.828l-.015.03c-.01.017-.017.033-.025.05l-.036.07-.006.01-.09.18c-3.104 6.26-4.852 13.306-4.852 20.76 0 .757.02 1.51.054 2.26.288 6.045 1.725 11.79 4.098 17.026 12.926 1.554 27.562 2.43 43.064 2.43" opacity=".293"/><path fill="#FFF" fill-rule="nonzero" d="M85.573 11.186v5.07s-10.397-3.38-17.124 2.755c-4.894 4.447-5.302 9.427-3.06 16.095 22.018 0 25.483 10.76 25.483 10.76l-2.242-12.36 6.932-7.38c0-8.714-8.256-14.494-9.99-14.94z" opacity=".821"/><ellipse cx="86.568" cy="23.492" fill="#000" fill-rule="nonzero" opacity=".821" rx="2.249" ry="2.237"/><path fill="#000" fill-rule="nonzero" d="M90.537 45.566s-3.35-14.862-25.274-10.833c-2.232-6.715-1.826-11.64 3.045-16.204 6.598-6.18 16.95-2.777 16.95-2.777V10.65c-3.653-1.432-7.104-1.7-10.96-1.7-14.11 0-21.925 9.31-25.274 15.577L9.338 88.897l11.165-1.88L0 121.932l14.31-2.238L25.274 92.21c31.06 0 70.846-9.85 65.264-46.644zM104.556 33.56l-8.79-7.83-6.95 7.83" opacity=".821"/><path fill="#FFF" fill-rule="nonzero" d="M30.355 83.898s1.006-.177 2.818-.53c1.812-.356 4.328-.887 7.347-1.596 1.51-.355 3.12-.798 4.83-1.33 1.712-.53 3.523-1.063 5.234-1.77 1.812-.622 3.624-1.42 5.435-2.305 1.81-.886 3.522-1.86 5.132-3.013.402-.266.805-.53 1.208-.886l1.207-.886c.705-.62 1.51-1.24 2.215-1.95.704-.62 1.308-1.328 1.912-2.037l.906-1.063.402-.532.403-.532c.2-.354.503-.708.704-1.063.2-.354.402-.71.704-1.063.1-.177.2-.355.302-.532.1-.177.2-.354.302-.53.2-.356.402-.71.503-1.065l.907-2.126c.2-.71.503-1.42.704-2.04.202-.62.303-1.328.504-1.86.1-.62.202-1.152.302-1.772.1-.532.2-1.063.2-1.506.102-.443.102-.886.203-1.33.1-1.594.1-2.48.1-2.48l1.61.088s-.1.975-.202 2.57c-.1.443-.1.886-.2 1.33-.102.53-.102 1.062-.303 1.594-.1.53-.302 1.152-.402 1.772-.202.62-.403 1.24-.604 1.95-.203.708-.504 1.328-.806 2.126-.302.71-.604 1.417-1.007 2.215-.2.354-.402.71-.604 1.063-.1.177-.2.355-.302.532l-.3.53c-.202.356-.504.71-.705 1.153-.302.355-.503.71-.805 1.064-.1.176-.302.353-.403.53l-.402.532-.906 1.063c-.706.71-1.31 1.418-2.115 2.038-.704.71-1.51 1.24-2.314 1.95l-1.207.885c-.403.266-.805.532-1.31.798-1.71 1.063-3.52 2.038-5.333 2.924-1.81.797-3.723 1.595-5.535 2.215-1.812.62-3.623 1.152-5.334 1.595-1.71.443-3.32.886-4.93 1.152-3.02.62-5.637 1.152-7.448 1.417-1.61.53-2.617.708-2.617.708z" opacity=".821"/></g></g></g><path fill="#9FD6E4" d="M75.325 68.613h.002-.002"/><path fill="#C7462F" d="M95.425 69.054h-.174.175c15.502 0 30.138-.877 43.064-2.432 2.372-5.235 3.81-10.982 4.097-17.027-.288 6.045-1.725 11.792-4.098 17.027-12.927 1.555-27.563 2.432-43.065 2.432m-.23 0h-.114.114m-.198 0h-.085.086m-.18 0h-.07.07m-.192 0h-.034.034m-.178 0h-.008.008"/><path fill="#1D2533" d="M137.33 26.445l-.09-.18.09.18m-.095-.19l-.036-.072c.01.023.024.047.035.072m-.06-.12l-.015-.03.016.03"/><path fill="#FFFFFE" d="M31.976 65.48c0 1.978-3.635 3.58-8.118 3.58-4.484 0-8.118-1.602-8.118-3.58 0-1.977 3.634-3.58 8.118-3.58 4.483 0 8.118 1.603 8.118 3.58M170.26 65.48c0 1.978-3.634 3.58-8.118 3.58s-8.118-1.602-8.118-3.58c0-1.977 3.634-3.58 8.118-3.58s8.118 1.603 8.118 3.58M58.957 76.666c0 1.977-3.634 3.58-8.117 3.58-4.483 0-8.118-1.603-8.118-3.58 0-1.976 3.635-3.58 8.118-3.58 4.483 0 8.117 1.604 8.117 3.58M145.525 76.666c0 1.977-3.634 3.58-8.117 3.58-4.484 0-8.118-1.603-8.118-3.58 0-1.976 3.634-3.58 8.118-3.58 4.483 0 8.117 1.604 8.117 3.58M103.93 80.022c0 1.977-3.636 3.58-8.12 3.58-4.483 0-8.118-1.603-8.118-3.58 0-1.976 3.635-3.58 8.12-3.58 4.482 0 8.117 1.604 8.117 3.58"/></g></g></g></svg>' + + ' <div>' + + ' <strong>Wagtail Space</strong><br> 13 – 15 March 2019 Arnhem, The Netherlands' + + ' </div>' + + ' <span class="close" title="Hide banner">×</span>' + + '</a>', ); - if (!hasCookie('wagtailspaceClosed', 'true') - && new Date() < new Date(2019, 2, 15)) { + if ( + !hasCookie('wagtailspaceClosed', 'true') && + new Date() < new Date(2019, 2, 15) + ) { $('.wy-nav-content').prepend($wagtailspace); $wagtailspace.find('.close').click((e) => { e.preventDefault(); diff --git a/tsconfig.json b/tsconfig.json index 37a6a6fd29..f7dc8ead5d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,14 @@ { - "compilerOptions": { - "jsx": "react", - "lib": ["es2015", "dom"], - "noImplicitAny": false, // TODO: Enable once all existing code is typed - "noUnusedLocals": true, - "noUnusedParameters": true, - "strictNullChecks": true, - "esModuleInterop": true, - "allowJs": true, - "downlevelIteration": true - }, - "files": [ - "client/src/index.ts", - "client/src/custom.d.ts" - ] + "compilerOptions": { + "jsx": "react", + "lib": ["es2015", "dom"], + "noImplicitAny": false, // TODO: Enable once all existing code is typed + "noUnusedLocals": true, + "noUnusedParameters": true, + "strictNullChecks": true, + "esModuleInterop": true, + "allowJs": true, + "downlevelIteration": true + }, + "files": ["client/src/index.ts", "client/src/custom.d.ts"] } diff --git a/wagtail/admin/static_src/wagtailadmin/css/normalize.css b/wagtail/admin/static_src/wagtailadmin/css/normalize.css index 8d945b3ec9..ddd29bdb87 100644 --- a/wagtail/admin/static_src/wagtailadmin/css/normalize.css +++ b/wagtail/admin/static_src/wagtailadmin/css/normalize.css @@ -20,7 +20,7 @@ main, nav, section, summary { - display: block; + display: block; } /** @@ -30,9 +30,9 @@ summary { audio, canvas, video { - display: inline-block; - *display: inline; - *zoom: 1; + display: inline-block; + *display: inline; + *zoom: 1; } /** @@ -41,8 +41,8 @@ video { */ audio:not([controls]) { - display: none; - height: 0; + display: none; + height: 0; } /** @@ -51,7 +51,7 @@ audio:not([controls]) { */ [hidden] { - display: none; + display: none; } /* ========================================================================== @@ -70,11 +70,11 @@ audio:not([controls]) { */ html { - background: #fff; /* 1 */ - color: #000; /* 2 */ - font-size: 100%; /* 3 */ - -webkit-text-size-adjust: 100%; /* 4 */ - -ms-text-size-adjust: 100%; /* 4 */ + background: #fff; /* 1 */ + color: #000; /* 2 */ + font-size: 100%; /* 3 */ + -webkit-text-size-adjust: 100%; /* 4 */ + -ms-text-size-adjust: 100%; /* 4 */ } /** @@ -87,7 +87,7 @@ button, input, select, textarea { - font-family: sans-serif; + font-family: sans-serif; } /** @@ -95,7 +95,7 @@ textarea { */ body { - margin: 0; + margin: 0; } /* ========================================================================== @@ -107,7 +107,7 @@ body { */ a:focus { - outline: thin dotted; + outline: thin dotted; } /** @@ -116,7 +116,7 @@ a:focus { a:active, a:hover { - outline: 0; + outline: 0; } /* ========================================================================== @@ -130,33 +130,33 @@ a:hover { */ h1 { - font-size: 2em; - margin: 0.67em 0; + font-size: 2em; + margin: 0.67em 0; } h2 { - font-size: 1.5em; - margin: 0.83em 0; + font-size: 1.5em; + margin: 0.83em 0; } h3 { - font-size: 1.17em; - margin: 1em 0; + font-size: 1.17em; + margin: 1em 0; } h4 { - font-size: 1em; - margin: 1.33em 0; + font-size: 1em; + margin: 1.33em 0; } h5 { - font-size: 0.83em; - margin: 1.67em 0; + font-size: 0.83em; + margin: 1.67em 0; } h6 { - font-size: 0.67em; - margin: 2.33em 0; + font-size: 0.67em; + margin: 2.33em 0; } /** @@ -164,7 +164,7 @@ h6 { */ abbr[title] { - border-bottom: 1px dotted; + border-bottom: 1px dotted; } /** @@ -173,11 +173,11 @@ abbr[title] { b, strong { - font-weight: bold; + font-weight: bold; } blockquote { - margin: 1em 40px; + margin: 1em 40px; } /** @@ -185,7 +185,7 @@ blockquote { */ dfn { - font-style: italic; + font-style: italic; } /** @@ -194,9 +194,9 @@ dfn { */ hr { - -moz-box-sizing: content-box; - box-sizing: content-box; - height: 0; + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; } /** @@ -204,8 +204,8 @@ hr { */ mark { - background: #ff0; - color: #000; + background: #ff0; + color: #000; } /** @@ -214,7 +214,7 @@ mark { p, pre { - margin: 1em 0; + margin: 1em 0; } /** @@ -225,9 +225,9 @@ code, kbd, pre, samp { - font-family: monospace, serif; - _font-family: 'courier new', monospace; - font-size: 1em; + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; } /** @@ -235,9 +235,9 @@ samp { */ pre { - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; } /** @@ -245,7 +245,7 @@ pre { */ q { - quotes: none; + quotes: none; } /** @@ -254,8 +254,8 @@ q { q:before, q:after { - content: ''; - content: none; + content: ''; + content: none; } /** @@ -263,7 +263,7 @@ q:after { */ small { - font-size: 80%; + font-size: 80%; } /** @@ -272,18 +272,18 @@ small { sub, sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } sup { - top: -0.5em; + top: -0.5em; } sub { - bottom: -0.25em; + bottom: -0.25em; } /* ========================================================================== @@ -298,11 +298,11 @@ dl, menu, ol, ul { - margin: 1em 0; + margin: 1em 0; } dd { - margin: 0 0 0 40px; + margin: 0 0 0 40px; } /** @@ -312,7 +312,7 @@ dd { menu, ol, ul { - padding: 0 0 0 40px; + padding: 0 0 0 40px; } /** @@ -321,8 +321,8 @@ ul { nav ul, nav ol { - list-style: none; - list-style-image: none; + list-style: none; + list-style-image: none; } /* ========================================================================== @@ -335,8 +335,8 @@ nav ol { */ img { - border: 0; /* 1 */ - -ms-interpolation-mode: bicubic; /* 2 */ + border: 0; /* 1 */ + -ms-interpolation-mode: bicubic; /* 2 */ } /** @@ -344,7 +344,7 @@ img { */ svg:not(:root) { - overflow: hidden; + overflow: hidden; } /* ========================================================================== @@ -356,7 +356,7 @@ svg:not(:root) { */ figure { - margin: 0; + margin: 0; } /* ========================================================================== @@ -368,7 +368,7 @@ figure { */ form { - margin: 0; + margin: 0; } /** @@ -376,9 +376,9 @@ form { */ fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; } /** @@ -388,10 +388,10 @@ fieldset { */ legend { - border: 0; /* 1 */ - padding: 0; - white-space: normal; /* 2 */ - *margin-left: -7px; /* 3 */ + border: 0; /* 1 */ + padding: 0; + white-space: normal; /* 2 */ + *margin-left: -7px; /* 3 */ } /** @@ -405,10 +405,10 @@ button, input, select, textarea { - font-size: 100%; /* 1 */ - margin: 0; /* 2 */ - vertical-align: baseline; /* 3 */ - *vertical-align: middle; /* 3 */ + font-size: 100%; /* 1 */ + margin: 0; /* 2 */ + vertical-align: baseline; /* 3 */ + *vertical-align: middle; /* 3 */ } /** @@ -418,7 +418,7 @@ textarea { button, input { - line-height: normal; + line-height: normal; } /** @@ -430,7 +430,7 @@ input { button, select { - text-transform: none; + text-transform: none; } /** @@ -447,9 +447,9 @@ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ - *overflow: visible; /* 4 */ + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ + *overflow: visible; /* 4 */ } /** @@ -459,12 +459,12 @@ input[type="submit"] { * Known issue: excess padding remains in IE 6. */ -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ - *height: 13px; /* 3 */ - *width: 13px; /* 3 */ +input[type='checkbox'], +input[type='radio'] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ + *height: 13px; /* 3 */ + *width: 13px; /* 3 */ } /** @@ -473,11 +473,11 @@ input[type="radio"] { * (include `-moz` to future-proof). */ -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; /* 2 */ - box-sizing: content-box; +input[type='search'] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; } /** @@ -485,9 +485,9 @@ input[type="search"] { * on OS X. */ -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; +input[type='search']::-webkit-search-cancel-button, +input[type='search']::-webkit-search-decoration { + -webkit-appearance: none; } /** @@ -496,8 +496,8 @@ input[type="search"]::-webkit-search-decoration { button::-moz-focus-inner, input::-moz-focus-inner { - border: 0; - padding: 0; + border: 0; + padding: 0; } /** @@ -506,8 +506,8 @@ input::-moz-focus-inner { */ textarea { - overflow: auto; /* 1 */ - vertical-align: top; /* 2 */ + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ } /* ========================================================================== @@ -519,6 +519,6 @@ textarea { */ table { - border-collapse: collapse; - border-spacing: 0; + border-collapse: collapse; + border-spacing: 0; } diff --git a/wagtail/admin/static_src/wagtailadmin/fonts/wagtail-icomoon.json b/wagtail/admin/static_src/wagtailadmin/fonts/wagtail-icomoon.json index 1b8016ff64..e77e52f2ae 100644 --- a/wagtail/admin/static_src/wagtailadmin/fonts/wagtail-icomoon.json +++ b/wagtail/admin/static_src/wagtailadmin/fonts/wagtail-icomoon.json @@ -6,19 +6,13 @@ "paths": [ "M327.030 64c-13.576 0-25.455 3.392-33.939 13.576-8.485 8.485-13.576 18.667-13.576 32.243v106.909h-108.606c-13.575 0-25.454 3.392-33.939 13.576-8.484 8.484-13.576 18.666-13.576 32.242v651.636c0 13.576 5.088 23.757 13.576 32.243 8.485 10.182 20.364 13.576 33.939 13.576h526.061c13.576 0 25.455-3.392 33.939-13.576 8.485-8.485 13.576-18.667 13.576-32.243v-106.909h108.606c13.575 0 25.454-3.392 33.939-13.576 8.484-8.484 13.576-18.666 13.576-32.242v-434.424c0-13.576-3.392-27.152-10.182-42.424-6.788-15.272-13.576-28.849-23.758-37.333l-151.030-151.030c-8.485-8.485-20.364-16.97-35.636-23.757-16.97-6.788-30.545-8.485-44.122-8.485zM342.303 125.091h247.757v201.939c0 13.576 5.088 23.757 13.575 32.243 8.486 10.182 20.364 13.576 32.243 13.576h201.94v373.333h-495.515zM652.849 128.483c8.485 3.392 15.272 6.788 18.666 11.879l152.728 151.030c3.392 3.392 6.788 10.181 10.182 20.363h-181.576zM186.182 277.816h93.333v483.636c0 13.576 5.088 23.757 13.576 32.242 8.484 10.182 20.364 13.576 33.939 13.576h354.666v91.636h-495.515z" ], - "attrs": [ - {} - ], + "attrs": [{}], "isMulticolor": false, "isMulticolor2": false, "grid": 14, - "tags": [ - "duplicate" - ] + "tags": ["duplicate"] }, - "attrs": [ - {} - ], + "attrs": [{}], "properties": { "order": 37, "id": 78, @@ -38,9 +32,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "text-height" - ], + "tags": ["text-height"], "defaultCode": 57414, "grid": 14 }, @@ -65,10 +57,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "chain-broken", - "unlink" - ], + "tags": ["chain-broken", "unlink"], "defaultCode": 57415, "grid": 14 }, @@ -93,9 +82,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "table" - ], + "tags": ["table"], "defaultCode": 57416, "grid": 14 }, @@ -127,9 +114,7 @@ ], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "logout" - ], + "tags": ["logout"], "defaultCode": 57417, "grid": 14 }, @@ -161,9 +146,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "strikethrough" - ], + "tags": ["strikethrough"], "defaultCode": 57418, "grid": 14 }, @@ -188,9 +171,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "superscript" - ], + "tags": ["superscript"], "defaultCode": 57419, "grid": 14 }, @@ -215,9 +196,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "subscript" - ], + "tags": ["subscript"], "defaultCode": 57420, "grid": 14 }, @@ -252,10 +231,7 @@ } ] }, - "tags": [ - "quotes-left", - "ldquo" - ], + "tags": ["quotes-left", "ldquo"], "defaultCode": 57344, "grid": 16 }, @@ -309,12 +285,7 @@ } ] }, - "tags": [ - "embed", - "code", - "html", - "xml" - ], + "tags": ["embed", "code", "html", "xml"], "defaultCode": 57345, "grid": 16 }, @@ -359,10 +330,7 @@ } ] }, - "tags": [ - "pilcrow", - "wysiwyg" - ], + "tags": ["pilcrow", "wysiwyg"], "defaultCode": 57346, "grid": 16 }, @@ -401,11 +369,7 @@ } ] }, - "tags": [ - "marquee", - "square", - "dashed" - ], + "tags": ["marquee", "square", "dashed"], "defaultCode": 57347, "grid": 16 }, @@ -434,9 +398,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "user" - ], + "tags": ["user"], "defaultCode": 57348, "grid": 16 }, @@ -461,9 +423,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "eye" - ], + "tags": ["eye"], "defaultCode": 57349, "grid": 16 }, @@ -488,9 +448,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "eye-slash" - ], + "tags": ["eye-slash"], "defaultCode": 57350, "grid": 16 }, @@ -516,9 +474,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "globe" - ], + "tags": ["globe"], "defaultCode": 57351, "grid": 16 }, @@ -543,11 +499,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "clock", - "time", - "schedule" - ], + "tags": ["clock", "time", "schedule"], "defaultCode": 57352, "grid": 16 }, @@ -573,9 +525,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "lock39 copy" - ], + "tags": ["lock39 copy"], "defaultCode": 57353, "grid": 16 }, @@ -601,9 +551,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "lock39-open" - ], + "tags": ["lock39-open"], "defaultCode": 57354, "grid": 16 }, @@ -628,9 +576,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "form" - ], + "tags": ["form"], "defaultCode": 57355, "grid": 16 }, @@ -727,9 +673,7 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": [ - "" - ], + "tags": [""], "defaultCode": 57359, "grid": 16 }, diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/404.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/404.scss index ffdb9b4646..56b4e87507 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/404.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/404.scss @@ -2,79 +2,80 @@ @import '../../../../../../client/scss/tools'; .page404__bg { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: $color-teal-darker; - font-family: $font-sans; - color: $color-white; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: $color-teal-darker; + font-family: $font-sans; + color: $color-white; } .page404__wrapper { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - display: flex; - justify-content: center; - align-items: center; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + justify-content: center; + align-items: center; } .wagtail-logo-container__desktop { - float: left; - width: 400px; - height: 500px; + float: left; + width: 400px; + height: 500px; } .page404__text-container { - float: right; - width: 600px; - height: 500px; - text-align: center; + float: right; + width: 600px; + height: 500px; + text-align: center; } .page404__header { - font-size: 6.8em; - margin-bottom: 0.2em; - color: inherit; + font-size: 6.8em; + margin-bottom: 0.2em; + color: inherit; } .page404__text { - font-size: 2.25em; - line-height: 1.25em; - color: inherit; + font-size: 2.25em; + line-height: 1.25em; + color: inherit; } -a.button.page404__button { // more specific to override standard button styles - font-size: 1.5em; - line-height: 2em; - height: 2.5em; - padding: 0 0.5em; - background-color: $color-teal-darker; - border: 4px solid $color-teal; - color: inherit; +a.button.page404__button { + // more specific to override standard button styles + font-size: 1.5em; + line-height: 2em; + height: 2.5em; + padding: 0 0.5em; + background-color: $color-teal-darker; + border: 4px solid $color-teal; + color: inherit; - &:hover { - background-color: $color-teal; - } + &:hover { + background-color: $color-teal; + } } // SMALL DESKTOP CHANGES: @include media-breakpoint-down(sm) { - .wagtail-logo-container__desktop { - display: none; - } + .wagtail-logo-container__desktop { + display: none; + } } // MOBILE CHANGES: @include media-breakpoint-down(xs) { - .page404__text-container { - width: 400px; - } + .page404__text-container { + width: 400px; + } - .page404__header { - font-size: 5em; - } + .page404__header { + font-size: 5em; + } } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/account.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/account.scss index 35cbfb083a..161d7fd3e6 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/account.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/account.scss @@ -1,38 +1,38 @@ @import '../../../../../../client/scss/settings/variables'; .top-padding { - padding-top: $object-title-height + 12px; + padding-top: $object-title-height + 12px; } .avatar-panel { + margin-top: 20px; + + &__image { + float: left; + width: 16%; + margin-bottom: 20px; + } + + &__form { + float: left; + + label { + display: block; + padding: 0; + width: unset; + float: unset; + margin-bottom: 10px; + } + } + + &__revert-to-gravatar { margin-top: 20px; + margin-bottom: 20px; + } - &__image { - float: left; - width: 16%; - margin-bottom: 20px; - } - - &__form { - float: left; - - label { - display: block; - padding: 0; - width: unset; - float: unset; - margin-bottom: 10px; - } - } - - &__revert-to-gravatar { - margin-top: 20px; - margin-bottom: 20px; - } - - &::after { - content: ' '; - display: table; - clear: both; - } + &::after { + content: ' '; + display: table; + clear: both; + } } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/compare-revisions.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/compare-revisions.scss index 67a54ad2bf..4c303d06bf 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/compare-revisions.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/compare-revisions.scss @@ -7,63 +7,63 @@ $color-deletion-dark: #f8cbcb; $color-deletion-light: #ffebeb; .comparison { - &__child-object { - border-top: 1px dashed $color-grey-4; - padding: 1em; + &__child-object { + border-top: 1px dashed $color-grey-4; + padding: 1em; - dd { - margin-left: 40px; - } - - &:first-child { - border-top: 0; - } - - &.addition { - background-color: $color-addition-light; - } - - &.deletion { - background-color: $color-deletion-light; - } + dd { + margin-left: 40px; } - &__list { - margin-top: 0; - margin-bottom: -1em; + &:first-child { + border-top: 0; } - span.addition { - background-color: $color-addition-dark; + &.addition { + background-color: $color-addition-light; } - span.deletion { - background-color: $color-deletion-dark; + &.deletion { + background-color: $color-deletion-light; + } + } + + &__list { + margin-top: 0; + margin-bottom: -1em; + } + + span.addition { + background-color: $color-addition-dark; + } + + span.deletion { + background-color: $color-deletion-dark; + } + + .preview-image { + display: inline-block; + + &.addition, + &.deletion { + padding: 5px; + margin-right: 5px; + border-style: solid; + border-width: 1px; } - .preview-image { - display: inline-block; - - &.addition, - &.deletion { - padding: 5px; - margin-right: 5px; - border-style: solid; - border-width: 1px; - } - - &.addition { - background-color: $color-addition-light; - border-color: $color-addition-dark; - } - - &.deletion { - background-color: $color-deletion-light; - border-color: $color-deletion-dark; - } - - img { - display: block; - } + &.addition { + background-color: $color-addition-light; + border-color: $color-addition-dark; } + + &.deletion { + background-color: $color-deletion-light; + border-color: $color-deletion-dark; + } + + img { + display: block; + } + } } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/home.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/home.scss index 22869ff1c7..13ba8b03a8 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/home.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/home.scss @@ -2,174 +2,174 @@ @import '../../../../../../client/scss/tools'; h1 { - font-weight: 300; - font-size: 2.2em; + font-weight: 300; + font-size: 2.2em; } header { - .col1 { - width: 50px; - margin-right: 1em; - padding: 0; + .col1 { + width: 50px; + margin-right: 1em; + padding: 0; - // make way for the nav-menu button on mobile - @include media-breakpoint-down(xs) { - position: relative; - left: $mobile-nav-indent; - } + // make way for the nav-menu button on mobile + @include media-breakpoint-down(xs) { + position: relative; + left: $mobile-nav-indent; } + } - h1, - .user-name { - color: $color-white; - } + h1, + .user-name { + color: $color-white; + } - .user-name { - text-transform: none; - font-size: 1.3em; - font-weight: 600; - } + .user-name { + text-transform: none; + font-size: 1.3em; + font-weight: 600; + } } .panel { - margin-bottom: 4em; + margin-bottom: 4em; } .summary { + @include clearfix(); + margin-bottom: 2em; + padding-top: 2em; + + ul.stats { @include clearfix(); - margin-bottom: 2em; - padding-top: 2em; + @include unlist(); + width: 100%; + margin-left: -$padding; - ul.stats { - @include clearfix(); - @include unlist(); - width: 100%; - margin-left: -$padding; + li { + @include column(4); + margin-bottom: 2em; - li { - @include column(4); - margin-bottom: 2em; - - @include media-breakpoint-down(sm) { - width: 50%; - } - } - - li.icon::before, - li svg.icon { - opacity: 0.2; - font-size: 6em; - float: left; - - @include media-breakpoint-down(md) { - font-size: 5em; - } - - @include media-breakpoint-down(sm) { - font-size: 4.5em; - } - - @include media-breakpoint-down(xs) { - font-size: 4em; - } - - // Media for Windows High Contrast Mode - @media (forced-colors: $media-forced-colours) { - color: $system-color-link-text; - opacity: 1; - } - } - - li svg.icon { - width: 1em; - height: 1em; - margin-right: 0.5em; - } - - a { - position: relative; - display: block; - width: 100%; - height: 100%; - } - - span { - font-family: $font-sans; - display: block; - font-size: 4em; - line-height: 1em; - font-weight: 300; - - @include media-breakpoint-down(md) { - font-size: 3em; - } - - @include media-breakpoint-down(sm) { - font-size: 2.5em; - } - - @include media-breakpoint-down(xs) { - font-size: 2em; - } - } + @include media-breakpoint-down(sm) { + width: 50%; + } } - .search-bar { - @include clearfix(); - border-top: 1px solid $color-grey-4; - padding: 1em 0 0; + li.icon::before, + li svg.icon { + opacity: 0.2; + font-size: 6em; + float: left; - h2 { - display: inline-block; - margin-right: 1em; - } + @include media-breakpoint-down(md) { + font-size: 5em; + } - .fields { - display: inline-block; - clear: none; - } + @include media-breakpoint-down(sm) { + font-size: 4.5em; + } - .field { - display: inline-block; - } + @include media-breakpoint-down(xs) { + font-size: 4em; + } - label { - display: inline-block; - } - - input { - background-color: $color-white; - } + // Media for Windows High Contrast Mode + @media (forced-colors: $media-forced-colours) { + color: $system-color-link-text; + opacity: 1; + } } + + li svg.icon { + width: 1em; + height: 1em; + margin-right: 0.5em; + } + + a { + position: relative; + display: block; + width: 100%; + height: 100%; + } + + span { + font-family: $font-sans; + display: block; + font-size: 4em; + line-height: 1em; + font-weight: 300; + + @include media-breakpoint-down(md) { + font-size: 3em; + } + + @include media-breakpoint-down(sm) { + font-size: 2.5em; + } + + @include media-breakpoint-down(xs) { + font-size: 2em; + } + } + } + + .search-bar { + @include clearfix(); + border-top: 1px solid $color-grey-4; + padding: 1em 0 0; + + h2 { + display: inline-block; + margin-right: 1em; + } + + .fields { + display: inline-block; + clear: none; + } + + .field { + display: inline-block; + } + + label { + display: inline-block; + } + + input { + background-color: $color-white; + } + } } .object.collapsible .object-layout .title-wrapper::before { - display: none; + display: none; } .object-layout { - // enforce flex display at all screen sizes - display: flex; - flex-flow: row-reverse wrap; + // enforce flex display at all screen sizes + display: flex; + flex-flow: row-reverse wrap; - .listing--push-top { - margin-top: 3em; + .listing--push-top { + margin-top: 3em; - thead { - display: table-row-group; - margin-bottom: 0; - } + thead { + display: table-row-group; + margin-bottom: 0; } + } - .listing { - margin-bottom: 0; - } + .listing { + margin-bottom: 0; + } - .listing tbody { - border-bottom: 0; - } + .listing tbody { + border-bottom: 0; + } } .task .icon { - margin-left: -1.75em; // pull out the icon so it aligns with no-icon text + margin-left: -1.75em; // pull out the icon so it aligns with no-icon text } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/login.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/login.scss index 360d9f4e60..b3aae71623 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/login.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/login.scss @@ -7,207 +7,207 @@ $desktop-nice-padding: 100px; html, body, .content-wrapper { - background-color: $color-grey-1; - color: $color-grey-3; + background-color: $color-grey-1; + color: $color-grey-3; } html { - height: 100%; + height: 100%; } body { - margin-left: 0; - height: 100%; + margin-left: 0; + height: 100%; } .content-wrapper { - border: 0; + border: 0; } .wrapper { - padding-left: $mobile-nice-padding; - padding-right: $mobile-nice-padding; - margin-left: 0; - max-width: none; + padding-left: $mobile-nice-padding; + padding-right: $mobile-nice-padding; + margin-left: 0; + max-width: none; } h1 { - font-weight: 300; - font-size: 2em; - line-height: 1em; - color: $color-white; - text-transform: none; - white-space: nowrap; + font-weight: 300; + font-size: 2em; + line-height: 1em; + color: $color-white; + text-transform: none; + white-space: nowrap; } form { - width: 100%; + width: 100%; - ul { - @include unlist(); - } + ul { + @include unlist(); + } } label { - width: auto; - color: $color-white; + width: auto; + color: $color-white; } .button, a.button { - line-height: 1.2em; - font-size: 1.5em; - padding: 1.1em 2.4em; - height: 3.5em; + line-height: 1.2em; + font-size: 1.5em; + padding: 1.1em 2.4em; + height: 3.5em; } -input[type=checkbox]:before { - background-color: #333; - color: #555; - border: 1px solid #555; +input[type='checkbox']:before { + background-color: #333; + color: #555; + border: 1px solid #555; } .fields-wrapper { - position: relative; + position: relative; } .fields { - max-width: none; + max-width: none; } .fields li { - padding: 1em 0; + padding: 1em 0; - &.full { - position: relative; - padding: 0; + &.full { + position: relative; + padding: 0; - label { - @include visuallyhidden; - } - - input { - border-top: 1px dashed $color-input-border; - } + label { + @include visuallyhidden; } - &:first-child input { - border: 0; + input { + border-top: 1px dashed $color-input-border; } + } + + &:first-child input { + border: 0; + } } .fields .checkbox { - padding-top: 2em; - padding-bottom: 2em; + padding-top: 2em; + padding-bottom: 2em; } .field { - padding: 0; + padding: 0; } .iconfield .input:before { - display: none; + display: none; } // Special full-width, one-off fields i.e a single text or textarea input .full input { - border-radius: 0; - font-weight: 300; - border: 0; - padding: 1.5em $mobile-nice-padding 1.5em $mobile-nice-padding; - font-size: 1.6em; - line-height: 1.6em; + border-radius: 0; + font-weight: 300; + border: 0; + padding: 1.5em $mobile-nice-padding 1.5em $mobile-nice-padding; + font-size: 1.6em; + line-height: 1.6em; } // Forgotten password link .help { - font-size: 1.3em; - margin-top: 0; - padding: 10px 0; + font-size: 1.3em; + margin-top: 0; + padding: 10px 0; + + @include media-breakpoint-up(sm) { + position: absolute; + top: 139px; + right: 5%; + padding: 0; + } + + &__link { + color: $color-white; @include media-breakpoint-up(sm) { - position: absolute; - top: 139px; - right: 5%; - padding: 0; - } - - &__link { - color: $color-white; - - @include media-breakpoint-up(sm) { - color: $color-link; - } + color: $color-link; } + } } .messages { - margin: 1em 0; - z-index: 5; + margin: 1em 0; + z-index: 5; - ul { - margin: 0; + ul { + margin: 0; - &:before { - display: none; - } - - li { - border-radius: 3px; - } + &:before { + display: none; } + + li { + border-radius: 3px; + } + } } @include media-breakpoint-up(sm) { - .content-wrapper { - float: none; - height: auto; - width: 100%; + .content-wrapper { + float: none; + height: auto; + width: 100%; + display: inline-block; + vertical-align: middle; + } + + // centres login form vertically + .wrapper { + position: relative; + height: 100%; + padding: 0 $desktop-nice-padding; + + &:before { + content: ''; + width: 0; + display: inline-block; + height: 100%; + vertical-align: middle; + margin-left: -0.4em; + } + } + + h1 { + font-size: 4em; + } + + .full { + margin: 0 (-$desktop-nice-padding); + + .iconfield { + .input:before { display: inline-block; - vertical-align: middle; - } - - // centres login form vertically - .wrapper { - position: relative; - height: 100%; - padding: 0 $desktop-nice-padding; - - &:before { - content: ''; - width: 0; - display: inline-block; - height: 100%; - vertical-align: middle; - margin-left: -0.4em; - } - } - - h1 { - font-size: 4em; - } - - .full { - margin: 0 (-$desktop-nice-padding); - - .iconfield { - .input:before { - display: inline-block; - position: absolute; - color: $color-grey-4; - border: 2px solid $color-grey-4; - border-radius: 100%; - width: 1em; - padding: 0.3em; - left: $desktop-nice-padding; - margin-top: -1.1rem; - top: 50%; - font-size: 1.3rem; - } - - input { - padding-left: ($desktop-nice-padding + 50px); - } - } + position: absolute; + color: $color-grey-4; + border: 2px solid $color-grey-4; + border-radius: 100%; + width: 1em; + padding: 0.3em; + left: $desktop-nice-padding; + margin-top: -1.1rem; + top: 50%; + font-size: 1.3rem; + } + + input { + padding-left: ($desktop-nice-padding + 50px); + } } + } } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/page-editor.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/page-editor.scss index f8f3039d98..8a027a7a25 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/page-editor.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/page-editor.scss @@ -1,695 +1,693 @@ -@use "sass:color"; -@use "sass:map"; -@use "sass:math"; +@use 'sass:color'; +@use 'sass:map'; +@use 'sass:math'; @import '../../../../../../client/scss/settings'; @import '../../../../../../client/scss/tools'; .page-editor { - .content-wrapper { - margin-bottom: 10em; + .content-wrapper { + margin-bottom: 10em; + } + + .breadcrumb { + margin: -1.2em 0 2em; // sass linter complains about $desktop-nice-padding here because of unit mismatch + padding-left: calc(#{$desktop-nice-padding} - 2em); + + @include media-breakpoint-up(sm) { + margin-top: -1.8em; + margin-left: -$desktop-nice-padding; + margin-right: -$desktop-nice-padding; + } + } + + .modal .breadcrumb { + margin: 0; + padding-left: 0.5em; + background-color: transparent; + + .breadcrumb-item { + padding-left: 0; + padding-right: 0; } - .breadcrumb { - margin: -1.2em 0 2em; // sass linter complains about $desktop-nice-padding here because of unit mismatch - padding-left: calc(#{$desktop-nice-padding} - 2em); + a { + color: $color-grey-2; + padding-left: 0.5em; + padding-right: 0.5em; - @include media-breakpoint-up(sm) { - margin-top: -1.8em; - margin-left: -$desktop-nice-padding; - margin-right: -$desktop-nice-padding; - } + &:hover { + color: $color-white; + } } - .modal .breadcrumb { - margin: 0; - padding-left: 0.5em; + li:hover { + background-color: transparent; + } + + .home_icon { + margin-left: 0; + } + + div.c-dropdown__button.u-btn-current { + color: $color-grey-2; + + &:hover { background-color: transparent; - - .breadcrumb-item { - padding-left: 0; - padding-right: 0; - } - - a { - color: $color-grey-2; - padding-left: 0.5em; - padding-right: 0.5em; - - &:hover { - color: $color-white; - } - } - - li:hover { - background-color: transparent; - } - - .home_icon { - margin-left: 0; - } - - div.c-dropdown__button.u-btn-current { - color: $color-grey-2; - - &:hover { - background-color: transparent; - cursor: default; - } - } - - .status-tag { - margin-bottom: 0; - } - - .u-link { - color: $color-white; - } + cursor: default; + } } - h1 { - font-size: 1.915em; // approximately 26px - - &.header-title { - text-transform: initial; - } + .status-tag { + margin-bottom: 0; } - .header-title { - padding-left: 0; + .u-link { + color: $color-white; + } + } + + h1 { + font-size: 1.915em; // approximately 26px + + &.header-title { + text-transform: initial; + } + } + + .header-title { + padding-left: 0; + } + + .modal-body .header-title h1 { + font-size: 1.5em; + } + + .header-meta { + list-style: none; + margin-top: 0; + margin-bottom: 0; + padding: 0; + + li { + border: 0; + float: left; + height: 1.5em; + line-height: 2em; + margin: 1em 0.75em 1.5em 0; + + .icon { + @include svg-icon(1.25em, text-bottom); + + margin-right: 0.2em; + } + + .icon-warning { + @include svg-icon(); + } + + &:first-child .button { + margin-left: -0.8em; + } + + .avatar { + margin-left: 0; + } } - .modal-body .header-title h1 { - font-size: 1.5em; + .button { + font-size: 1em; + font-weight: 600; + margin-top: -0.25em; // Account for the button border + overflow: initial; + height: 2.5em; + line-height: 2.5em; } - .header-meta { - list-style: none; - margin-top: 0; - margin-bottom: 0; - padding: 0; + .action-workflow-status { + font-weight: 600; - li { - border: 0; - float: left; - height: 1.5em; - line-height: 2em; - margin: 1em 0.75em 1.5em 0; - - .icon { - @include svg-icon(1.25em, text-bottom); - - margin-right: 0.2em; - } - - .icon-warning { - @include svg-icon(); - } - - &:first-child .button { - margin-left: -0.8em; - } - - .avatar { - margin-left: 0; - } - } - - .button { - font-size: 1em; - font-weight: 600; - margin-top: -0.25em; // Account for the button border - overflow: initial; - height: 2.5em; - line-height: 2.5em; - } - - .action-workflow-status { - font-weight: 600; - - span { - font-weight: 300; - } - } - - .human-readable-date { - display: inline; - } - - &--status { - padding-right: 0.8em; - } - - &--type { - padding: 0 0.8em; - } + span { + font-weight: 300; + } } + .human-readable-date { + display: inline; + } + + &--status { + padding-right: 0.8em; + } + + &--type { + padding: 0 0.8em; + } + } } // An object is the basic wrapper around any field or group of fields in the editor interface .object { - @include nice-padding(); - position: relative; + @include nice-padding(); + position: relative; - &:first-child { - border: 0; - } + &:first-child { + border: 0; + } - &.focused { - border-color: $color-input-focus-border; - } + &.focused { + border-color: $color-input-focus-border; + } + + fieldset, + .field-row { + padding-top: $object-title-height + 12px; + } + + fieldset { + padding-left: 0; + padding-right: 0; - fieldset, .field-row { - padding-top: $object-title-height + 12px; + padding-top: 0; + } + } + + .object-help { + display: block; + position: relative; + z-index: 1; + top: $object-title-height; + margin-top: 0; + margin-bottom: -1em; + padding: 1em math.div($grid-gutter-width, 2) 1em 3em; + opacity: 1; + + .icon-help { + margin-left: -1.75em; + } + } + + &:hover .object-help { + opacity: 1; + } + + > .title-wrapper { + box-sizing: border-box; + height: $object-title-height; + -webkit-font-smoothing: auto; + background: $color-salmon-light; + color: #200200; + text-transform: uppercase; + padding: 0.9em 0 0.9em 5em; + font-size: 0.95em; + margin: 0; + line-height: 1.5em; + font-weight: normal; + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1; + overflow: hidden; + + label { + display: inline; + text-transform: inherit; + font-weight: inherit; + float: none; + width: auto; + color: inherit; + font-size: inherit; } + &:before { + @include font-smoothing; + text-shadow: none; + font-family: $font-wagtail-icons; + text-transform: none; + content: map.get($icons, 'arrow-down'); + text-align: center; + display: block; + position: absolute; + z-index: 2; + font-size: 2em; + top: 0; + line-height: 1.8em; + left: 0; + width: $desktop-nice-padding; + color: $color-white; + padding: 0; + margin: 0; + background-color: $color-salmon; + } + } + + .halloeditor { + padding-top: 1em; // to provide space for editor buttons + padding-bottom: 1em; + + &.expanded { + padding-top: 5em; // to provide space for editor buttons + padding-bottom: 2em; + } + } + + &.required { + > .title-wrapper label:after { + content: '*'; + color: $color-red; + font-weight: bold; + display: inline-block; + margin-left: 0.5em; + line-height: 1em; + font-size: 13px; + } + } + + // Special full-width, one-off fields i.e a single text or textarea input + &.full { fieldset { - padding-left: 0; - padding-right: 0; - - .field-row { - padding-top: 0; - } + display: block; + float: none; } - .object-help { - display: block; - position: relative; - z-index: 1; - top: $object-title-height; - margin-top: 0; - margin-bottom: -1em; - padding: 1em math.div($grid-gutter-width, 2) 1em 3em; - opacity: 1; - - .icon-help { - margin-left: -1.75em; - } - } - - &:hover .object-help { - opacity: 1; - } - - > .title-wrapper { - box-sizing: border-box; - height: $object-title-height; - -webkit-font-smoothing: auto; - background: $color-salmon-light; - color: #200200; - text-transform: uppercase; - padding: 0.9em 0 0.9em 5em; - font-size: 0.95em; - margin: 0; - line-height: 1.5em; - font-weight: normal; - position: absolute; - top: 0; - left: 0; - right: 0; - z-index: 1; - overflow: hidden; - - label { - display: inline; - text-transform: inherit; - font-weight: inherit; - float: none; - width: auto; - color: inherit; - font-size: inherit; - } - - &:before { - @include font-smoothing; - text-shadow: none; - font-family: $font-wagtail-icons; - text-transform: none; - content: map.get($icons, 'arrow-down'); - text-align: center; - display: block; - position: absolute; - z-index: 2; - font-size: 2em; - top: 0; - line-height: 1.8em; - left: 0; - width: $desktop-nice-padding; - color: $color-white; - padding: 0; - margin: 0; - background-color: $color-salmon; - } + li { + padding: 0; } .halloeditor { - padding-top: 1em; // to provide space for editor buttons - padding-bottom: 1em; - - &.expanded { - padding-top: 5em; // to provide space for editor buttons - padding-bottom: 2em; - } + padding-top: 3em; // to provide space for editor buttons + padding-bottom: 3em; + &.expanded { + padding-top: 5em; // to provide space for editor buttons + padding-bottom: 5em; + } } - &.required { - > .title-wrapper label:after { - content: '*'; - color: $color-red; - font-weight: bold; - display: inline-block; - margin-left: 0.5em; - line-height: 1em; - font-size: 13px; - } + .error-message { + @include nice-padding(); + padding-bottom: 2em; } - // Special full-width, one-off fields i.e a single text or textarea input - &.full { - fieldset { - display: block; - float: none; - } + .error, + .error input:not([type='submit']), + .error textarea, + .error .halloeditor { + background-color: $color-input-error-bg; + } + } - li { - padding: 0; - } + // cursory styling for streamfield. Main styling in client/src/components/StreamField/StreamField.scss + &.stream-field { + padding-left: 20px; + padding-right: 20px; - .halloeditor { - padding-top: 3em; // to provide space for editor buttons - padding-bottom: 3em; - - &.expanded { - padding-top: 5em; // to provide space for editor buttons - padding-bottom: 5em; - } - } - - .error-message { - @include nice-padding(); - padding-bottom: 2em; - } - - .error, - .error input:not([type=submit]), - .error textarea, - .error .halloeditor { - background-color: $color-input-error-bg; - } + .object-layout_big-part { + max-width: 100%; } - // cursory styling for streamfield. Main styling in client/src/components/StreamField/StreamField.scss - &.stream-field { - padding-left: 20px; - padding-right: 20px; - - .object-layout_big-part { - max-width: 100%; - } - - fieldset { - padding-bottom: 0; - max-width: unset; - // Workaround to make sure blocks do not overflow horizontally. - min-width: 0; - } - - .block_field > .field-content { - width: 100%; - } + fieldset { + padding-bottom: 0; + max-width: unset; + // Workaround to make sure blocks do not overflow horizontally. + min-width: 0; } - // special panel for the publishing fields, requires a bit more pizzazz - &.publishing { - > .title-wrapper:before { - content: map.get($icons, 'date'); - font-size: 1.8rem; - line-height: 1.4em; - width: 1.4em; - } + .block_field > .field-content { + width: 100%; + } + } + + // special panel for the publishing fields, requires a bit more pizzazz + &.publishing { + > .title-wrapper:before { + content: map.get($icons, 'date'); + font-size: 1.8rem; + line-height: 1.4em; + width: 1.4em; + } + } + + &.privacy { + > .title-wrapper:before { + content: map.get($icons, 'view'); + } + } + + .multiple { + padding: 4.5em 0 0; + + fieldset { + padding-top: 0; + padding-bottom: 0; + } + } + + .fields { + max-width: 100%; + } + + // removes top padding from multiples used within another panel + .fields .multiple { + padding-top: 0; + } + + .add { + padding-top: 1em; + } + + &.empty { + border-bottom: 1px solid $color-white; + + > h3 { + margin: 0; + border-bottom: 1px solid $color-white; } - &.privacy { - > .title-wrapper:before { - content: map.get($icons, 'view'); + // wrapper around add button for multiple objects. Default version is wordless plus button for contracted groups of fields + .add { + @include transition(background-color 0.2s ease); + position: relative; + z-index: 2; + top: 0; + left: 0; + width: 3.3em; + padding: 0; + margin: 0 0 0 -20px; + cursor: pointer; + + .button { + border-radius: 0; + overflow: visible; + background-color: $color-salmon-light; + font-size: 0; // helps fake the effect of t.ext-replace class, which can't be used here. + width: 2em; + + // stylelint-disable max-nesting-depth + &:before { + position: relative; + padding: 0; + line-height: 1.8em; // specific height required as parent 'a' has no height + font-size: 1.4rem; + width: 1.8em; + background-color: $color-salmon; } + + &:hover:before { + background-color: color.adjust($color-salmon, $lightness: -5%); + } + } } .multiple { - padding: 4.5em 0 0; + padding: 0; + } + } - fieldset { - padding-top: 0; - padding-bottom: 0; - } + &.collapsible { + // li.collapsed gets its height from the fieldset only, which is now hidden + // and h2 has position: absolute which doesn't add to it either, so it would be 0 without this + min-height: 41px; + + .title-wrapper { + &:before { + content: map.get($icons, 'collapse-up'); + cursor: pointer; + } } - .fields { - max-width: 100%; - } - - // removes top padding from multiples used within another panel - .fields .multiple { - padding-top: 0; - } - - .add { - padding-top: 1em; - } - - &.empty { - border-bottom: 1px solid $color-white; - - > h3 { - margin: 0; - border-bottom: 1px solid $color-white; - } - - // wrapper around add button for multiple objects. Default version is wordless plus button for contracted groups of fields - .add { - @include transition(background-color 0.2s ease); - position: relative; - z-index: 2; - top: 0; - left: 0; - width: 3.3em; - padding: 0; - margin: 0 0 0 -20px; - cursor: pointer; - - .button { - border-radius: 0; - overflow: visible; - background-color: $color-salmon-light; - font-size: 0; // helps fake the effect of t.ext-replace class, which can't be used here. - width: 2em; - - // stylelint-disable max-nesting-depth - &:before { - position: relative; - padding: 0; - line-height: 1.8em; // specific height required as parent 'a' has no height - font-size: 1.4rem; - width: 1.8em; - background-color: $color-salmon; - } - - &:hover:before { - background-color: color.adjust($color-salmon, $lightness: -5%); - } - } - } - - .multiple { - padding: 0; - } - } - - &.collapsible { - // li.collapsed gets its height from the fieldset only, which is now hidden - // and h2 has position: absolute which doesn't add to it either, so it would be 0 without this - min-height: 41px; - - .title-wrapper { - &:before { - content: map.get($icons, 'collapse-up'); - cursor: pointer; - } - } - - &.collapsed { - .title-wrapper { - &:before { - content: map.get($icons, 'collapse-down'); - } - } + &.collapsed { + .title-wrapper { + &:before { + content: map.get($icons, 'collapse-down'); } + } } + } } // Custom styles that make some fields look more important .full { - input:not([type=submit]), - textarea, - .halloeditor { - @include nice-padding; - border-radius: 0; - padding-top: 1.5em; - padding-bottom: 1.5em; - font-size: 1.2em; - line-height: 1.6em; - } + input:not([type='submit']), + textarea, + .halloeditor { + @include nice-padding; + border-radius: 0; + padding-top: 1.5em; + padding-bottom: 1.5em; + font-size: 1.2em; + line-height: 1.6em; + } } .title { - input:not([type=submit]), - textarea, - .halloeditor { - font-size: 2em; - font-family: $font-sans; - } + input:not([type='submit']), + textarea, + .halloeditor { + font-size: 2em; + font-family: $font-sans; + } } // Footer control bar for performing actions on the page footer .actions { - .button { - font-weight: 600; - text-overflow: ellipsis; - } + .button { + font-weight: 600; + text-overflow: ellipsis; + } } footer .preview { + button, + .button { + padding: 0 1em; + + .icon { + margin-right: 0.5em; + } + + @include media-breakpoint-down(xs) { + width: 100%; + margin-top: 2px; + margin-bottom: 2px; + height: 3em; + } + + background-color: color.adjust($color-grey-2, $lightness: 10%); + border-color: color.adjust($color-grey-2, $lightness: 10%); + + &:hover { + background-color: $color-grey-2; + border-color: $color-grey-2; + } + } + + .dropdown { + input[type='button'], + input[type='submit'], button, .button { - padding: 0 1em; + background-color: color.adjust($color-grey-2, $lightness: 10%); + border-color: color.adjust($color-grey-2, $lightness: 10%); - .icon { - margin-right: 0.5em; - } - - @include media-breakpoint-down(xs) { - width: 100%; - margin-top: 2px; - margin-bottom: 2px; - height: 3em; - } - - background-color: color.adjust($color-grey-2, $lightness: 10%); - border-color: color.adjust($color-grey-2, $lightness: 10%); - - &:hover { - background-color: $color-grey-2; - border-color: $color-grey-2; - } + &:hover { + background-color: $color-grey-2; + border-color: $color-grey-2; + } } - .dropdown { - input[type=button], - input[type=submit], - button, - .button { - background-color: color.adjust($color-grey-2, $lightness: 10%); - border-color: color.adjust($color-grey-2, $lightness: 10%); - - &:hover { - background-color: $color-grey-2; - border-color: $color-grey-2; - } - } - - ul, - .dropdown-toggle { - background-color: color.adjust($color-grey-2, $lightness: 10%); - } - - .dropdown-toggle:hover, - &.open > .button + .dropdown-toggle { - background-color: $color-grey-2; - } + ul, + .dropdown-toggle { + background-color: color.adjust($color-grey-2, $lightness: 10%); } + + .dropdown-toggle:hover, + &.open > .button + .dropdown-toggle { + background-color: $color-grey-2; + } + } } @include media-breakpoint-up(sm) { - .object { - fieldset { - // Override column mixin for column items. - display: block; - // Override column mixin for column items. - float: none; - max-width: 1024px; - padding-left: 0; - padding-right: 0; + .object { + fieldset { + // Override column mixin for column items. + display: block; + // Override column mixin for column items. + float: none; + max-width: 1024px; + padding-left: 0; + padding-right: 0; - fieldset { - width: 100%; - } - } - - .object-layout { - display: flex; - flex-flow: row-reverse wrap; - - &_small-part { - // IE11 requires units on the flex basis. Unitless breaks. - // stylelint-disable-next-line length-zero-no-unit - flex: 1 0 0%; - } - - &_big-part { - // IE11 requires units on the flex basis. Unitless breaks. - // stylelint-disable-next-line length-zero-no-unit - flex: 5 0 0%; - } - } - - .object-help { - padding-bottom: 40px; - margin-left: 10px; - margin-bottom: 0; - opacity: 0; - } - - &.stream-field { - .object-help { - padding-left: 6.4em; - } - } - - &.full { - fieldset { - // Override column mixin for column items. - display: block; - // Override column mixin for column items. - float: none; - margin-left: -51px; - padding: 0; - padding-top: $object-title-height; - } - - input:not([type=submit]), - textarea, - .halloeditor { - border-width: 0 1px; - } - - .field { - padding: 0; - } - - .field-content { - display: block; - float: none; - width: auto; - padding: inherit; - } - } - - .multiple { - @include column(10); - padding-left: 0; - padding-right: 0; - } - - &.empty .add { - margin: 0 0 0 -50px; - } + fieldset { + width: 100%; + } } - // Make room for comments on the right when enabled - .tab-content--comments-enabled .object { - padding-right: 27%; + .object-layout { + display: flex; + flex-flow: row-reverse wrap; - &.full { - padding-right: 36%; - } + &_small-part { + // IE11 requires units on the flex basis. Unitless breaks. + // stylelint-disable-next-line length-zero-no-unit + flex: 1 0 0%; + } - @include media-breakpoint-up(lg) { - padding-right: 30%; - - &.full { - padding-right: 36%; - } - } + &_big-part { + // IE11 requires units on the flex basis. Unitless breaks. + // stylelint-disable-next-line length-zero-no-unit + flex: 5 0 0%; + } } + + .object-help { + padding-bottom: 40px; + margin-left: 10px; + margin-bottom: 0; + opacity: 0; + } + + &.stream-field { + .object-help { + padding-left: 6.4em; + } + } + + &.full { + fieldset { + // Override column mixin for column items. + display: block; + // Override column mixin for column items. + float: none; + margin-left: -51px; + padding: 0; + padding-top: $object-title-height; + } + + input:not([type='submit']), + textarea, + .halloeditor { + border-width: 0 1px; + } + + .field { + padding: 0; + } + + .field-content { + display: block; + float: none; + width: auto; + padding: inherit; + } + } + + .multiple { + @include column(10); + padding-left: 0; + padding-right: 0; + } + + &.empty .add { + margin: 0 0 0 -50px; + } + } + + // Make room for comments on the right when enabled + .tab-content--comments-enabled .object { + padding-right: 27%; + + &.full { + padding-right: 36%; + } + + @include media-breakpoint-up(lg) { + padding-right: 30%; + + &.full { + padding-right: 36%; + } + } + } } .button.button--live { - background-color: $color-white; - color: $color-teal; - border-radius: 2px; - font-size: 14px; - font-weight: 600; - line-height: 2.3em; - padding: 0 0.75em; + background-color: $color-white; + color: $color-teal; + border-radius: 2px; + font-size: 14px; + font-weight: 600; + line-height: 2.3em; + padding: 0 0.75em; - .icon { - @include svg-icon(1.25em); - margin-right: 0.25em; - } + .icon { + @include svg-icon(1.25em); + margin-right: 0.25em; + } - &:hover { - background-color: $color-teal-darker; - color: $color-white; - } + &:hover { + background-color: $color-teal-darker; + color: $color-white; + } } .workflow-timeline { - list-style: none; - padding: 0; + list-style: none; + padding: 0; - li { - margin-bottom: 1.25em; - position: relative; - } + li { + margin-bottom: 1.25em; + position: relative; + } - li:not(:last-child)::after { - content: ''; - color: $color-grey-3; - position: absolute; - top: 1.5em; - left: calc(0.75em - 1px); - height: 100%; - border-left: 2px dotted; - } + li:not(:last-child)::after { + content: ''; + color: $color-grey-3; + position: absolute; + top: 1.5em; + left: calc(0.75em - 1px); + height: 100%; + border-left: 2px dotted; + } + + .icon { + fill: $color-grey-3; + padding-right: 0.5em; + } + + .in_progress { + font-weight: 700; .icon { - fill: $color-grey-3; - padding-right: 0.5em; + fill: $color-text-base; } + } - .in_progress { - font-weight: 700; + .approved .icon { + fill: $color-teal; + } - .icon { - fill: $color-text-base; - } - } + .needs_changes .icon, + .rejected .icon { + fill: $color-orange; + } - .approved .icon { - fill: $color-teal; - } - - .needs_changes .icon, - .rejected .icon { - fill: $color-orange; - } - - .cancelled .icon { - fill: $color-red-dark; - } + .cancelled .icon { + fill: $color-red-dark; + } } // Media for Windows High Contrast @media (forced-colors: $media-forced-colours) { - .object { - border-top: 1px solid GrayText; + .object { + border-top: 1px solid GrayText; - .object-help { - margin-bottom: 0; - } + .object-help { + margin-bottom: 0; } + } } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/report.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/report.scss index 3744aba575..97a15d8a37 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/report.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/report.scss @@ -2,73 +2,73 @@ @import '../../../../../../client/scss/tools'; .report { - display: grid; - grid-template-columns: auto; - grid-column-gap: 50px; - margin-left: 50px; - margin-right: 50px; + display: grid; + grid-template-columns: auto; + grid-column-gap: 50px; + margin-left: 50px; + margin-right: 50px; - &--has-filters { - grid-template-columns: auto 300px; + &--has-filters { + grid-template-columns: auto 300px; + } + + &__results { + grid-column-start: col-start 1 col-end 2; + + &--text { + margin: 0 0.5em 0.5em 0; + + + .status-tag { + margin-left: 0; + } } - &__results { - grid-column-start: col-start 1 col-end 2; + &--comment { + display: block; + } + } - &--text { - margin: 0 0.5em 0.5em 0; + &__filters { + grid-column-start: col-start -2 col-end -1; - + .status-tag { - margin-left: 0; - } - } + button[type='submit'] { + display: block; + margin-bottom: 20px; + } - &--comment { - display: block; - } + input[type='checkbox'] { + display: block; + width: unset; + height: unset; + margin-bottom: 10px; + } + + // Get rid of Wagtail's overrides + label { + float: unset; + display: block; + width: unset; + padding-top: 1.2em; + } + } + + &__actions > div { + float: right; + display: block; + margin-right: 10px; + } + + @include media-breakpoint-down(sm) { + &--has-filters { + grid-template-columns: auto; } &__filters { - grid-column-start: col-start -2 col-end -1; - - button[type='submit'] { - display: block; - margin-bottom: 20px; - } - - input[type='checkbox'] { - display: block; - width: unset; - height: unset; - margin-bottom: 10px; - } - - // Get rid of Wagtail's overrides - label { - float: unset; - display: block; - width: unset; - padding-top: 1.2em; - } + grid-row: 1; } - &__actions > div { - float: right; - display: block; - margin-right: 10px; - } - - @include media-breakpoint-down(sm) { - &--has-filters { - grid-template-columns: auto; - } - - &__filters { - grid-row: 1; - } - - form { - margin-bottom: 1em; - } + form { + margin-bottom: 1em; } + } } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/workflow-edit.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/workflow-edit.scss index 42dbaeba0f..5222cef81e 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/workflow-edit.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/workflow-edit.scss @@ -2,30 +2,30 @@ @import '../../../../../../client/scss/settings/variables'; .listing { - .field label { - @include visuallyhidden; - } + .field label { + @include visuallyhidden; + } - input, - select, - textarea { - font-size: 1em; - } + input, + select, + textarea { + font-size: 1em; + } - select + span:after { - // stylelint-disable-next-line declaration-no-important - font-size: 2.5em !important; - } + select + span:after { + // stylelint-disable-next-line declaration-no-important + font-size: 2.5em !important; + } } .workflow-pages-listing { - max-width: 1024px - 50px; + max-width: 1024px - 50px; - .admin_page_chooser .field-content { - width: 100%; // so that 'choose another page' button displays in its entirety - } + .admin_page_chooser .field-content { + width: 100%; // so that 'choose another page' button displays in its entirety + } } .top-padding { - padding-top: $object-title-height + 12px; + padding-top: $object-title-height + 12px; } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/layouts/workflow-progress.scss b/wagtail/admin/static_src/wagtailadmin/scss/layouts/workflow-progress.scss index d618760979..9177e41f12 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/layouts/workflow-progress.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/layouts/workflow-progress.scss @@ -4,60 +4,60 @@ */ .workflow-progress-tabs { - &__buttons { - margin-top: 30px; + &__buttons { + margin-top: 30px; + } + + &__button { + background: none; + border: 0; + padding-bottom: 5px; + font-weight: bold; + + &--active { + border-bottom: 3px solid #007d7e; } + } - &__button { - background: none; - border: 0; - padding-bottom: 5px; - font-weight: bold; + &__tab { + display: none; - &--active { - border-bottom: 3px solid #007d7e; - } - } - - &__tab { - display: none; - - &--active { - display: block; - } + &--active { + display: block; } + } } .workflow-progress-table { - width: 100%; - border-top: 1px solid rgb(229, 229, 229); + width: 100%; + border-top: 1px solid rgb(229, 229, 229); + border-bottom: 1px solid rgb(229, 229, 229); + font-size: 0.8em; + + td, + th { + padding: 20px; + } + + th { + font-size: 1.2em; + } + + &__left-column { + font-size: 1.2em; + font-weight: bold; + color: #555; + } + + &--tasks { + background-color: rgb(250, 250, 250); + + .workflow-progress-table__left-column { + background-color: rgb(245, 245, 245); + } + } + + &--timeline tr { border-bottom: 1px solid rgb(229, 229, 229); - font-size: 0.8em; - - td, - th { - padding: 20px; - } - - th { - font-size: 1.2em; - } - - &__left-column { - font-size: 1.2em; - font-weight: bold; - color: #555; - } - - &--tasks { - background-color: rgb(250, 250, 250); - - .workflow-progress-table__left-column { - background-color: rgb(245, 245, 245); - } - } - - &--timeline tr { - border-bottom: 1px solid rgb(229, 229, 229); - } + } } diff --git a/wagtail/admin/static_src/wagtailadmin/scss/userbar.scss b/wagtail/admin/static_src/wagtailadmin/scss/userbar.scss index 4a1bddbd67..e5ea23cf4a 100644 --- a/wagtail/admin/static_src/wagtailadmin/scss/userbar.scss +++ b/wagtail/admin/static_src/wagtailadmin/scss/userbar.scss @@ -1,7 +1,7 @@ -@use "sass:color"; -@use "sass:map"; -@use "sass:math"; -@use "sass:string"; +@use 'sass:color'; +@use 'sass:map'; +@use 'sass:math'; +@use 'sass:string'; @import '../../../../../client/scss/settings'; @import '../../../../../client/scss/tools'; @@ -17,265 +17,256 @@ $box-shadow-props: 0 0 1px 0 rgba(107, 214, 230, 1); $max-items: 12; $userbar-radius: 6px; - // Classnames will start with this parameter, eg .wagtail- $namespace: 'wagtail'; // Possible positions for the userbar to exist in. These are set through the // {% wagtailuserbar 'bottom-left' %} template tag. $positions: ( - 'top-left': ( - 'vertical': 'top', - 'horizontal': 'left', - 'arrow': 'bottom' - ), - 'top-right': ( - 'vertical': 'top', - 'horizontal': 'right', - 'arrow': 'bottom' - ), - 'bottom-left': ( - 'vertical': 'bottom', - 'horizontal': 'left', - 'arrow': 'top' - ), - 'bottom-right': ( - 'vertical': 'bottom', - 'horizontal': 'right', - 'arrow': 'top' - ) + 'top-left': ( + 'vertical': 'top', + 'horizontal': 'left', + 'arrow': 'bottom', + ), + 'top-right': ( + 'vertical': 'top', + 'horizontal': 'right', + 'arrow': 'bottom', + ), + 'bottom-left': ( + 'vertical': 'bottom', + 'horizontal': 'left', + 'arrow': 'top', + ), + 'bottom-right': ( + 'vertical': 'bottom', + 'horizontal': 'right', + 'arrow': 'top', + ), ); // ============================================================================= // Wagtail userbar proper // ============================================================================= .#{$namespace}-userbar-reset { - all: initial; + all: initial; } - .#{$namespace}-userbar { - position: fixed; - z-index: 9999; - // stylelint-disable-next-line declaration-no-important - font-size: initial !important; - line-height: initial; - margin: 0; - padding: 0; - display: block; - border: 0; - width: auto; - height: auto; + position: fixed; + z-index: 9999; + // stylelint-disable-next-line declaration-no-important + font-size: initial !important; + line-height: initial; + margin: 0; + padding: 0; + display: block; + border: 0; + width: auto; + height: auto; - &-icon { - @include svg-icon(2em); - } + &-icon { + @include svg-icon(2em); + } } - @media print { - .#{$namespace}-userbar { - display: none; - } + .#{$namespace}-userbar { + display: none; + } } // stylelint-disable declaration-no-important .#{$namespace}-userbar-trigger { - all: initial; - display: flex; - align-items: center; - justify-content: center; - width: $size-home-button; - height: $size-home-button; - margin: 0 !important; + all: initial; + display: flex; + align-items: center; + justify-content: center; + width: $size-home-button; + height: $size-home-button; + margin: 0 !important; + overflow: hidden; + background-color: $color-white; + border: 2px solid transparent; + border-radius: 50%; + color: $color-black; + padding: 0 !important; + cursor: pointer; + box-shadow: $box-shadow-props, 0 1px 10px 0 rgba(107, 214, 230, 0.7); + transition: all 0.2s ease-in-out; + font-size: 16px; + text-decoration: none !important; + position: relative; + + .#{$namespace}-userbar-help-text { + // Visually hide the help text + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; overflow: hidden; - background-color: $color-white; - border: 2px solid transparent; - border-radius: 50%; - color: $color-black; - padding: 0 !important; - cursor: pointer; - box-shadow: $box-shadow-props, 0 1px 10px 0 rgba(107, 214, 230, 0.7); - transition: all 0.2s ease-in-out; - font-size: 16px; - text-decoration: none !important; - position: relative; + position: absolute; + white-space: nowrap; + width: 1px; + } - .#{$namespace}-userbar-help-text { - // Visually hide the help text - clip: rect(0 0 0 0); - clip-path: inset(50%); - height: 1px; - overflow: hidden; - position: absolute; - white-space: nowrap; - width: 1px; - } + .#{$namespace}-icon:before { + transition: color 0.2s ease; + font-size: 32px; + width: auto; + margin: 0; + } - .#{$namespace}-icon:before { - transition: color 0.2s ease; - font-size: 32px; - width: auto; - margin: 0; - } - - &:focus { - outline: $color-focus-outline solid 3px; - } + &:focus { + outline: $color-focus-outline solid 3px; + } } .#{$namespace}-userbar-items { - all: revert; - display: block; - list-style: none; - position: absolute; - margin: 0; - min-width: 210px; - visibility: hidden; - font-family: $font-sans; - font-size: 14px; - box-sizing: border-box; - padding-left: 0; - text-decoration: none; + all: revert; + display: block; + list-style: none; + position: absolute; + margin: 0; + min-width: 210px; + visibility: hidden; + font-family: $font-sans; + font-size: 14px; + box-sizing: border-box; + padding-left: 0; + text-decoration: none; - .#{$namespace}-userbar.is-active & { - visibility: visible; - } + .#{$namespace}-userbar.is-active & { + visibility: visible; + } } // Arrow .#{$namespace}-userbar-items:after { - content: ''; - position: absolute; - width: 0; - height: 0; - opacity: 0; - border: solid $width-arrow transparent; - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.55, 0, 0.1, 1); + content: ''; + position: absolute; + width: 0; + height: 0; + opacity: 0; + border: solid $width-arrow transparent; + transition-duration: 0.15s; + transition-timing-function: cubic-bezier(0.55, 0, 0.1, 1); - // stylelint-disable-next-line scss/media-feature-value-dollar-variable - @media (prefers-reduced-motion: reduce) { - transition: none !important; - } + // stylelint-disable-next-line scss/media-feature-value-dollar-variable + @media (prefers-reduced-motion: reduce) { + transition: none !important; + } - .#{$namespace}-userbar.is-active & { - opacity: 1; - transform: translateY(0); - transition-delay: 0.3s; - } + .#{$namespace}-userbar.is-active & { + opacity: 1; + transform: translateY(0); + transition-delay: 0.3s; + } } - .#{$namespace}-userbar-nav { - background: transparent !important; - padding: 0; - margin: 0 !important; - display: block !important; + background: transparent !important; + padding: 0; + margin: 0 !important; + display: block !important; - .#{$namespace}-action { - - background: transparent; - } + .#{$namespace}-action { + background: transparent; + } } - - .#{$namespace}-userbar__item { - all: revert; - margin: 0; - background-color: $color-grey-1; - opacity: 0; - overflow: hidden; - transition-duration: 0.125s; - transition-timing-function: cubic-bezier(0.55, 0, 0.1, 1); - font-family: $font-sans; - font-size: 16px !important; + all: revert; + margin: 0; + background-color: $color-grey-1; + opacity: 0; + overflow: hidden; + transition-duration: 0.125s; + transition-timing-function: cubic-bezier(0.55, 0, 0.1, 1); + font-family: $font-sans; + font-size: 16px !important; + text-decoration: none !important; + + // stylelint-disable-next-line scss/media-feature-value-dollar-variable + @media (prefers-reduced-motion: reduce) { + transition: none !important; + + // Force disable transitions for all items + transition-delay: 0s !important; + } + + &:first-child { + border-top-left-radius: $userbar-radius; + border-top-right-radius: $userbar-radius; + } + + &:last-child { + border-bottom-right-radius: $userbar-radius; + border-bottom-left-radius: $userbar-radius; + } + + & + & { + border-top: 1px solid color.adjust($color-grey-1, $lightness: -3%); + } + + a, + .#{$namespace}-action { + color: #aaa; + display: block; text-decoration: none !important; + transform: none !important; + transition: none !important; + margin: 0 !important; + font-size: 14px !important; - // stylelint-disable-next-line scss/media-feature-value-dollar-variable - @media (prefers-reduced-motion: reduce) { - transition: none !important; - - // Force disable transitions for all items - transition-delay: 0s !important; + &:hover, + &:focus { + color: $color-white; + background-color: rgba(100, 100, 100, 0.15); } - &:first-child { - border-top-left-radius: $userbar-radius; - border-top-right-radius: $userbar-radius; + &:focus { + outline: $color-focus-outline solid 3px; } - &:last-child { - border-bottom-right-radius: $userbar-radius; - border-bottom-left-radius: $userbar-radius; + &-icon { + @include svg-icon(1.1em, middle); + margin-right: 0.5em; + fill: currentColor; } + } - & + & { - border-top: 1px solid color.adjust($color-grey-1, $lightness: -3%); + .#{$namespace}-icon { + position: relative; + + &:before { + position: absolute; + top: 50%; + transform: translateY(-50%); + left: 14px; } + } + a, + button { + font-size: 14px !important; + text-align: left; + padding: 0.8em; + } - a, - .#{$namespace}-action { - color: #aaa; - display: block; - text-decoration: none !important; - transform: none !important; - transition: none !important; - margin: 0 !important; - font-size: 14px !important; - - &:hover, - &:focus { - color: $color-white; - background-color: rgba(100, 100, 100, 0.15); - } - - &:focus { - outline: $color-focus-outline solid 3px; - } - - &-icon { - @include svg-icon(1.1em, middle); - margin-right: 0.5em; - fill: currentColor; - } - - } - - .#{$namespace}-icon { - position: relative; - - &:before { - position: absolute; - top: 50%; - transform: translateY(-50%); - left: 14px; - } - } - - a, - button { - font-size: 14px !important; - text-align: left; - padding: 0.8em; - } - - button { - border: 0; - width: 100%; - background-color: transparent; - outline: none; - } + button { + border: 0; + width: 100%; + background-color: transparent; + outline: none; + } } //Media for Windows High Contrast @media (forced-colors: $media-forced-colours) { - .#{$namespace}-userbar-icon { - fill: $system-color-link-text; - } + .#{$namespace}-userbar-icon { + fill: $system-color-link-text; + } } // ============================================================================= @@ -283,61 +274,61 @@ $positions: ( // ============================================================================= @each $pos, $attrs in $positions { - $vertical: map.get($attrs, vertical); - $horizontal: map.get($attrs, horizontal); - $arrow: map.get($attrs, arrow); + $vertical: map.get($attrs, vertical); + $horizontal: map.get($attrs, horizontal); + $arrow: map.get($attrs, arrow); - .#{$namespace}-userbar--#{$pos} { - #{$vertical}: $position; - #{$horizontal}: $position; + .#{$namespace}-userbar--#{$pos} { + #{$vertical}: $position; + #{$horizontal}: $position; - .#{$namespace}-userbar-items { - #{$vertical}: 100%; - #{$horizontal}: 0; - padding-#{$vertical}: $width-arrow * 2; - } - - .#{$namespace}-userbar-nav .#{$namespace}-userbar__item { - @if $vertical == 'bottom' { - transform: translateY(1em); - } @else { - transform: translateY(-1em); - } - } - - .#{$namespace}-userbar-items:after { - #{$vertical}: 2px; - #{$horizontal}: math.div($size-home-button, 2) - math.div($width-arrow, 2); - border-#{$arrow}-color: $color-grey-1; - - @if $vertical == 'bottom' { - transform: translateY(-$width-arrow); - } - @if $vertical == 'top' { - transform: translateY($width-arrow); - } - } - - &.is-active .#{$namespace}-userbar__item { - @for $i from 1 through $max-items { - // stylelint-disable max-nesting-depth - - @if $vertical == 'bottom' { - &:nth-last-child(#{$i}) { - transition-delay: 0.05s * $i; - } - } - - @if $vertical == 'top' { - &:nth-child(#{$i}) { - transition-delay: 0.05s * $i; - } - } - } - } + .#{$namespace}-userbar-items { + #{$vertical}: 100%; + #{$horizontal}: 0; + padding-#{$vertical}: $width-arrow * 2; } -} + .#{$namespace}-userbar-nav .#{$namespace}-userbar__item { + @if $vertical == 'bottom' { + transform: translateY(1em); + } @else { + transform: translateY(-1em); + } + } + + .#{$namespace}-userbar-items:after { + #{$vertical}: 2px; + #{$horizontal}: math.div($size-home-button, 2) - + math.div($width-arrow, 2); + border-#{$arrow}-color: $color-grey-1; + + @if $vertical == 'bottom' { + transform: translateY(-$width-arrow); + } + @if $vertical == 'top' { + transform: translateY($width-arrow); + } + } + + &.is-active .#{$namespace}-userbar__item { + @for $i from 1 through $max-items { + // stylelint-disable max-nesting-depth + + @if $vertical == 'bottom' { + &:nth-last-child(#{$i}) { + transition-delay: 0.05s * $i; + } + } + + @if $vertical == 'top' { + &:nth-child(#{$i}) { + transition-delay: 0.05s * $i; + } + } + } + } + } +} // ============================================================================= // States @@ -345,6 +336,6 @@ $positions: ( // Active state for the list items comes last. .#{$namespace}-userbar.is-active .#{$namespace}-userbar__item { - transform: translateY(0); - opacity: 1; + transform: translateY(0); + opacity: 1; } diff --git a/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/js/prepopulate.js b/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/js/prepopulate.js index 6eaa8889d6..b59101886a 100644 --- a/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/js/prepopulate.js +++ b/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/js/prepopulate.js @@ -1,7 +1,7 @@ /* global URLify */ -(function($) { - $.fn.prepopulate = function(dependencies, maxLength, allowUnicode) { - /* +(function ($) { + $.fn.prepopulate = function (dependencies, maxLength, allowUnicode) { + /* Depends on urlify.js Populates a selected field with the values of the dependent fields, URLifies and shortens the string. @@ -9,50 +9,54 @@ maxLength - maximum length of the URLify'd string allowUnicode - Unicode support of the URLify'd string */ - return this.each(function() { - var prepopulatedField = $(this); + return this.each(function () { + var prepopulatedField = $(this); - var populate = function() { - // Bail if the field's value has been changed by the user - if (prepopulatedField.data('_changed')) { - return; - } + var populate = function () { + // Bail if the field's value has been changed by the user + if (prepopulatedField.data('_changed')) { + return; + } - var values = []; - $.each(dependencies, function(i, field) { - field = $(field); - if (field.val().length > 0) { - values.push(field.val()); - } - }); - prepopulatedField.val(URLify(values.join(' '), maxLength, allowUnicode)); - }; - - prepopulatedField.data('_changed', false); - prepopulatedField.on('change', function() { - prepopulatedField.data('_changed', true); - }); - - if (!prepopulatedField.val()) { - $(dependencies.join(',')).on('keyup change focus', populate); - } + var values = []; + $.each(dependencies, function (i, field) { + field = $(field); + if (field.val().length > 0) { + values.push(field.val()); + } }); - }; -}(jQuery)); + prepopulatedField.val( + URLify(values.join(' '), maxLength, allowUnicode), + ); + }; + + prepopulatedField.data('_changed', false); + prepopulatedField.on('change', function () { + prepopulatedField.data('_changed', true); + }); + + if (!prepopulatedField.val()) { + $(dependencies.join(',')).on('keyup change focus', populate); + } + }); + }; +})(jQuery); (function ($) { - $(function () { - var fields = $('#modeladmin-prepopulated-fields-constants').data('prepopulatedFields'); - $.each(fields, function (index, field) { - $( - '.empty-form .form-row .field-' + - field.name + - ', .empty-form.form-row .field-' + - field.name - ).addClass('prepopulated_field'); - $(field.id).data('dependency_list', field.dependency_list).prepopulate( - field.dependency_ids, field.maxLength, field.allowUnicode - ); - }); + $(function () { + var fields = $('#modeladmin-prepopulated-fields-constants').data( + 'prepopulatedFields', + ); + $.each(fields, function (index, field) { + $( + '.empty-form .form-row .field-' + + field.name + + ', .empty-form.form-row .field-' + + field.name, + ).addClass('prepopulated_field'); + $(field.id) + .data('dependency_list', field.dependency_list) + .prepopulate(field.dependency_ids, field.maxLength, field.allowUnicode); }); -}(jQuery)); + }); +})(jQuery); diff --git a/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/breadcrumbs_page.scss b/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/breadcrumbs_page.scss index 9f85f0be1e..c7ce98fd1b 100644 --- a/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/breadcrumbs_page.scss +++ b/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/breadcrumbs_page.scss @@ -2,11 +2,11 @@ @import '../../../../../../client/scss/tools'; .breadcrumb { - margin: -1.2em 0 2em; + margin: -1.2em 0 2em; } @include media-breakpoint-up(sm) { - .breadcrumb { - margin-top: -1.8em; - } + .breadcrumb { + margin-top: -1.8em; + } } diff --git a/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/choose_parent_page.scss b/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/choose_parent_page.scss index a9b4ee562b..82a011768c 100644 --- a/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/choose_parent_page.scss +++ b/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/choose_parent_page.scss @@ -1,9 +1,9 @@ // stylelint-disable-next-line selector-max-id #id_parent_page li { - margin: 15px 0; + margin: 15px 0; } // stylelint-disable-next-line selector-max-id #id_parent_page li label { - float: none; + float: none; } diff --git a/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/index.scss b/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/index.scss index 9fcf1351c9..ee3c95d579 100644 --- a/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/index.scss +++ b/wagtail/contrib/modeladmin/static_src/wagtailmodeladmin/scss/index.scss @@ -2,206 +2,200 @@ @import '../../../../../../client/scss/tools'; .content header { - margin-bottom: 0; + margin-bottom: 0; } .result-count { - display: block; - font-weight: 500; + display: block; + font-weight: 500; - &:before { - content: '('; - } + &:before { + content: '('; + } - &:after { - content: ')'; - } + &:after { + content: ')'; + } - @include media-breakpoint-up(lg) { - display: inline-block; - margin-left: 0.25em; - } + @include media-breakpoint-up(lg) { + display: inline-block; + margin-left: 0.25em; + } } .result-list { - margin-bottom: 0; + margin-bottom: 0; } .listing { - td, - th { - vertical-align: top; + td, + th { + vertical-align: top; + } + + thead th.sorted a { + color: $color-teal; + } + + tbody { + overflow: auto; + + tr:hover ul.actions { + visibility: visible; } - thead th.sorted a { - color: $color-teal; - } - - tbody { - overflow: auto; - - tr:hover ul.actions { - visibility: visible; - } - - tr > td { - background-color: inherit; - - a.edit-obj { - color: inherit; - font-weight: 600; - } - } - + tr > td { + background-color: inherit; + + a.edit-obj { + color: inherit; + font-weight: 600; + } } + } } - .changelist-filter { - padding: 0 15px; + padding: 0 15px; - h2 { - background-color: #fafafa; - font-size: 13px; - line-height: 31px; - margin-top: 0; - padding-left: 8px; - border-bottom: 1px solid #e6e6e6; + h2 { + background-color: #fafafa; + font-size: 13px; + line-height: 31px; + margin-top: 0; + padding-left: 8px; + border-bottom: 1px solid #e6e6e6; + } + + h3 { + font-size: 12px; + margin-bottom: 0; + } + + ul { + padding-left: 0; + margin-bottom: 25px; + } + + li { + list-style-type: none; + margin: 0 0 4px; + padding-left: 0; + } + + a { + font-family: $font-sans; + border-radius: 3px; + width: auto; + line-height: 1.2em; + padding: 8px 12px; + font-size: 0.9em; + font-weight: normal; + vertical-align: middle; + display: block; + background-color: #fff; + border: 1px solid $color-teal; + color: $color-teal; + text-decoration: none; + text-transform: uppercase; + position: relative; + overflow: hidden; + box-sizing: border-box; + -webkit-font-smoothing: auto; + // stylelint-disable-next-line property-no-vendor-prefix + -moz-appearance: none; + + &:hover { + background-color: $color-teal-dark; + border-color: $color-teal-dark; + color: #fff; } + } - h3 { - font-size: 12px; - margin-bottom: 0; - } - - ul { - padding-left: 0; - margin-bottom: 25px; - } - - li { - list-style-type: none; - margin: 0 0 4px; - padding-left: 0; - } - - a { - font-family: $font-sans; - border-radius: 3px; - width: auto; - line-height: 1.2em; - padding: 8px 12px; - font-size: 0.9em; - font-weight: normal; - vertical-align: middle; - display: block; - background-color: #fff; - border: 1px solid $color-teal; - color: $color-teal; - text-decoration: none; - text-transform: uppercase; - position: relative; - overflow: hidden; - box-sizing: border-box; - -webkit-font-smoothing: auto; - // stylelint-disable-next-line property-no-vendor-prefix - -moz-appearance: none; - - &:hover { - background-color: $color-teal-dark; - border-color: $color-teal-dark; - color: #fff; - } - } - - li.selected a { - // stylelint-disable-next-line declaration-no-important - color: #fff !important; - // stylelint-disable-next-line declaration-no-important - border-color: $color-teal !important; - background-color: $color-teal; - } + li.selected a { + // stylelint-disable-next-line declaration-no-important + color: #fff !important; + // stylelint-disable-next-line declaration-no-important + border-color: $color-teal !important; + background-color: $color-teal; + } } - .no-search-results { - margin-top: 30px; + margin-top: 30px; - h2 { - padding-top: 0.3em; - margin-bottom: 0.3em; - } + h2 { + padding-top: 0.3em; + margin-bottom: 0.3em; + } - img { - float: left; - margin: 0 15px 15px 0; - width: 50px; - } + img { + float: left; + margin: 0 15px 15px 0; + width: 50px; + } } - .pagination { - margin-top: 3em; - border-top: 1px dashed #d9d9d9; - padding: 2em 1em 0; + margin-top: 3em; + border-top: 1px dashed #d9d9d9; + padding: 2em 1em 0; - ul { - margin-top: -1.25em; - } + ul { + margin-top: -1.25em; + } } - p.no-results { - margin: 30px 1em 0; + margin: 30px 1em 0; } - @include media-breakpoint-up(sm) { - .changelist-filter { - float: right; - padding: 0 1.5%; - } + .changelist-filter { + float: right; + padding: 0 1.5%; + } - .result-list { - padding: 0 1.5% 0 0; + .result-list { + padding: 0 1.5% 0 0; - &.col12 { - padding-right: 0; + &.col12 { + padding-right: 0; - tbody td:last-child { - padding-right: 50px; - } - } - - tbody th:first-child { - padding-left: 50px; - } - } - - .pagination { - padding-left: 50px; + tbody td:last-child { padding-right: 50px; + } } - .pagination.col9 { - width: 73.5%; + tbody th:first-child { + padding-left: 50px; } + } - p.no-results { - margin: 30px 50px 0; - } + .pagination { + padding-left: 50px; + padding-right: 50px; + } + + .pagination.col9 { + width: 73.5%; + } + + p.no-results { + margin: 30px 50px 0; + } } @include media-breakpoint-up(lg) { - .result-list.col9 { - width: 79%; - } + .result-list.col9 { + width: 79%; + } - .changelist-filter { - width: 21%; - } + .changelist-filter { + width: 21%; + } - .pagination.col9 { - width: 77.5%; - } + .pagination.col9 { + width: 77.5%; + } } diff --git a/wagtail/contrib/redirects/tests/files/example.json b/wagtail/contrib/redirects/tests/files/example.json index f47092e3f7..1fb5c85568 100644 --- a/wagtail/contrib/redirects/tests/files/example.json +++ b/wagtail/contrib/redirects/tests/files/example.json @@ -1,14 +1,14 @@ [ - { - "from": "/hello-from-json", - "to": "http://hello.com/random/" - }, - { - "from": "/goodbye-from-json", - "to": "http://hello.com/goodbye/" - }, - { - "from": "/goodbye-from-json", - "to": "/goodbye-not-working/" - } + { + "from": "/hello-from-json", + "to": "http://hello.com/random/" + }, + { + "from": "/goodbye-from-json", + "to": "http://hello.com/goodbye/" + }, + { + "from": "/goodbye-from-json", + "to": "/goodbye-not-working/" + } ] diff --git a/wagtail/contrib/settings/static_src/wagtailsettings/js/site-switcher.js b/wagtail/contrib/settings/static_src/wagtailsettings/js/site-switcher.js index 5de3fbc003..f95917cc0e 100644 --- a/wagtail/contrib/settings/static_src/wagtailsettings/js/site-switcher.js +++ b/wagtail/contrib/settings/static_src/wagtailsettings/js/site-switcher.js @@ -1,12 +1,12 @@ -$(function() { - var $switcher = $('form#settings-site-switch select'); - if (!$switcher.length) return; +$(function () { + var $switcher = $('form#settings-site-switch select'); + if (!$switcher.length) return; - var initial = $switcher.val(); - $switcher.on('change', function() { - var url = $switcher.val(); - if (url !== initial) { - window.location = url; - } - }); + var initial = $switcher.val(); + $switcher.on('change', function () { + var url = $switcher.val(); + if (url !== initial) { + window.location = url; + } + }); }); diff --git a/wagtail/contrib/styleguide/static_src/wagtailstyleguide/scss/styleguide.scss b/wagtail/contrib/styleguide/static_src/wagtailstyleguide/scss/styleguide.scss index 42cacc38da..e7dc899d63 100644 --- a/wagtail/contrib/styleguide/static_src/wagtailstyleguide/scss/styleguide.scss +++ b/wagtail/contrib/styleguide/static_src/wagtailstyleguide/scss/styleguide.scss @@ -2,224 +2,224 @@ @import '../../../../../../client/scss/tools'; section { - border-top: 1px solid $color-grey-3; - margin-top: 2em; - padding: 0 0 2em; + border-top: 1px solid $color-grey-3; + margin-top: 2em; + padding: 0 0 2em; - > h2:first-child { - margin: 0; - font-size: 1em; - background: $color-grey-4; - padding: 1em; - margin-bottom: 1em; - } + > h2:first-child { + margin: 0; + font-size: 1em; + background: $color-grey-4; + padding: 1em; + margin-bottom: 1em; + } } .palette { - @include clearfix(); + @include clearfix(); - ul { - @include clearfix(); - @include unlist(); - } + ul { + @include clearfix(); + @include unlist(); + } + + li { + float: left; + width: 100px; + height: 100px; + padding: 10px; + color: $color-black; + } + + .contrast li { + height: 50px; + } + + .contrast-large { + font-size: 18px; li { - float: left; - width: 100px; - height: 100px; - padding: 10px; - color: $color-black; + width: 150px; + height: 55px; } + } - .contrast li { - height: 50px; - } + .color-black-text { + color: $color-black; + } - .contrast-large { - font-size: 18px; + .color-grey-1-text { + color: $color-grey-1; + } - li { - width: 150px; - height: 55px; - } - } + .color-white-text { + color: $color-white; + } - .color-black-text { - color: $color-black; - } + .color-salmon-light-text { + color: $color-salmon-light; + } - .color-grey-1-text { - color: $color-grey-1; - } + .color-green-text { + color: $color-green; + } - .color-white-text { - color: $color-white; - } + .color-green-dark-text { + color: $color-green-dark; + } - .color-salmon-light-text { - color: $color-salmon-light; - } + .color-teal-darker-text { + color: $color-teal-darker; + } - .color-green-text { - color: $color-green; - } + .color-teal-dark-text { + color: $color-teal-dark; + } - .color-green-dark-text { - color: $color-green-dark; - } + .color-grey-2-text { + color: $color-grey-2; + } - .color-teal-darker-text { - color: $color-teal-darker; - } + .color-red-text { + color: $color-red; + } - .color-teal-dark-text { - color: $color-teal-dark; - } + .color-red-dark-text { + color: $color-red-dark; + } - .color-grey-2-text { - color: $color-grey-2; - } + .color-teal-text { + color: $color-teal; + } - .color-red-text { - color: $color-red; - } + .color-orange-text { + color: $color-orange; + } - .color-red-dark-text { - color: $color-red-dark; - } + .color-orange-dark-text { + color: $color-orange-dark; + } - .color-teal-text { - color: $color-teal; - } + .color-blue-text { + color: $color-blue; + } - .color-orange-text { - color: $color-orange; - } + .color-salmon-text { + color: $color-salmon; + } - .color-orange-dark-text { - color: $color-orange-dark; - } + .color-grey-3-text { + color: $color-grey-3; + } - .color-blue-text { - color: $color-blue; - } + .color-grey-4-text { + color: $color-grey-4; + } - .color-salmon-text { - color: $color-salmon; - } + .color-grey-5-text { + color: $color-grey-5; + } - .color-grey-3-text { - color: $color-grey-3; - } + .color-menu-text { + color: $color-menu-text; + } - .color-grey-4-text { - color: $color-grey-4; - } + .color-teal { + background-color: $color-teal; + } - .color-grey-5-text { - color: $color-grey-5; - } + .color-teal-darker { + background-color: $color-teal-darker; + } - .color-menu-text { - color: $color-menu-text; - } + .color-teal-dark { + background-color: $color-teal-dark; + } - .color-teal { - background-color: $color-teal; - } + .color-red { + background-color: $color-red; + } - .color-teal-darker { - background-color: $color-teal-darker; - } + .color-red-dark { + background-color: $color-red-dark; + } - .color-teal-dark { - background-color: $color-teal-dark; - } + .color-orange { + background-color: $color-orange; + } - .color-red { - background-color: $color-red; - } + .color-orange-dark { + background-color: $color-orange-dark; + } - .color-red-dark { - background-color: $color-red-dark; - } + .color-green { + background-color: $color-green; + } - .color-orange { - background-color: $color-orange; - } + .color-green-dark { + background-color: $color-green-dark; + } - .color-orange-dark { - background-color: $color-orange-dark; - } + .color-blue { + background-color: $color-blue; + } - .color-green { - background-color: $color-green; - } + .color-grey-1 { + background-color: $color-grey-1; + } - .color-green-dark { - background-color: $color-green-dark; - } + .color-grey-2 { + background-color: $color-grey-2; + } - .color-blue { - background-color: $color-blue; - } + .color-grey-3 { + background-color: $color-grey-3; + } - .color-grey-1 { - background-color: $color-grey-1; - } + .color-grey-4 { + background-color: $color-grey-4; + } - .color-grey-2 { - background-color: $color-grey-2; - } + .color-grey-5 { + background-color: $color-grey-5; + } - .color-grey-3 { - background-color: $color-grey-3; - } + .color-salmon { + background-color: $color-salmon; + } - .color-grey-4 { - background-color: $color-grey-4; - } - - .color-grey-5 { - background-color: $color-grey-5; - } - - .color-salmon { - background-color: $color-salmon; - } - - .color-salmon-light { - background-color: $color-salmon-light; - } + .color-salmon-light { + background-color: $color-salmon-light; + } } .icons { - :before, - :after { - font-size: 2em; - } + :before, + :after { + font-size: 2em; + } - .icon { - @include svg-icon(1.5em); - } + .icon { + @include svg-icon(1.5em); + } - ul { - column-count: 3; - } + ul { + column-count: 3; + } - li { - margin-bottom: 1em; - } + li { + margin-bottom: 1em; + } - .spinner { - position: relative; - } + .spinner { + position: relative; + } - // .spinner .icon-spinner:after { - // position: absolute; - // } + // .spinner .icon-spinner:after { + // position: absolute; + // } } .timepicker { - height: 150px; + height: 150px; } diff --git a/wagtail/contrib/typed_table_block/static_src/typed_table_block/scss/typed_table_block.scss b/wagtail/contrib/typed_table_block/static_src/typed_table_block/scss/typed_table_block.scss index a177aa83b6..f54f9890bb 100644 --- a/wagtail/contrib/typed_table_block/static_src/typed_table_block/scss/typed_table_block.scss +++ b/wagtail/contrib/typed_table_block/static_src/typed_table_block/scss/typed_table_block.scss @@ -2,91 +2,91 @@ @import '../../../../../../client/scss/tools'; .typed-table-block { - &__wrapper { - overflow-x: auto; - // Reserve space for the add column menu. - min-height: 20rem; + &__wrapper { + overflow-x: auto; + // Reserve space for the add column menu. + min-height: 20rem; + } + + table { + margin: 2.5rem 0 1rem 2.5rem; + } + + th, + td { + padding: 0.25rem; + } + + th { + position: relative; + border: 2px solid $color-grey-2; + + button.prepend-column, + button.append-column { + position: absolute; + left: -0.9rem; + top: -2rem; } - table { - margin: 2.5rem 0 1rem 2.5rem; + button.delete-column { + position: absolute; + right: 0.625rem; + top: 0.875rem; } - th, - td { - padding: 0.25rem; + input.column-heading { + padding-right: 3rem; + font-weight: bold; + } + } + + td { + position: relative; + border-left: 1px solid $color-grey-2; + } + + th:first-child, + th:last-child, + td:first-child { + border-width: 0; + } + + tbody tr, + tfoot tr { + position: relative; + border-top: 1px dotted $color-grey-2; + + button.prepend-row { + left: -2rem; + position: absolute; + top: -0.9rem; } - th { - position: relative; - border: 2px solid $color-grey-2; - - button.prepend-column, - button.append-column { - position: absolute; - left: -0.9rem; - top: -2rem; - } - - button.delete-column { - position: absolute; - right: 0.625rem; - top: 0.875rem; - } - - input.column-heading { - padding-right: 3rem; - font-weight: bold; - } + button.delete-row { + margin-left: 0.25rem; } - td { - position: relative; - border-left: 1px solid $color-grey-2; + td:not(.control-cell) { + vertical-align: top; + min-width: 20rem; } + } - th:first-child, - th:last-child, - td:first-child { - border-width: 0; + ul.add-column-menu { + position: absolute; + top: 0; + left: -0.9rem; + z-index: 100; + min-width: 10rem; + background-color: $color-white; + + button { + width: 100%; + margin: 1px; } + } - tbody tr, - tfoot tr { - position: relative; - border-top: 1px dotted $color-grey-2; - - button.prepend-row { - left: -2rem; - position: absolute; - top: -0.9rem; - } - - button.delete-row { - margin-left: 0.25rem; - } - - td:not(.control-cell) { - vertical-align: top; - min-width: 20rem; - } - } - - ul.add-column-menu { - position: absolute; - top: 0; - left: -0.9rem; - z-index: 100; - min-width: 10rem; - background-color: $color-white; - - button { - width: 100%; - margin: 1px; - } - } - - .control-cell ul.add-column-menu { - top: 0; - } + .control-cell ul.add-column-menu { + top: 0; + } } diff --git a/wagtail/documents/static_src/wagtaildocs/js/add-multiple.js b/wagtail/documents/static_src/wagtaildocs/js/add-multiple.js index 4da1890652..df928c6bcf 100644 --- a/wagtail/documents/static_src/wagtaildocs/js/add-multiple.js +++ b/wagtail/documents/static_src/wagtaildocs/js/add-multiple.js @@ -1,174 +1,192 @@ -$(function() { - // Redirect users that don't support filereader - if (!$('html').hasClass('filereader')) { - document.location.href = window.fileupload_opts.simple_upload_url; +$(function () { + // Redirect users that don't support filereader + if (!$('html').hasClass('filereader')) { + document.location.href = window.fileupload_opts.simple_upload_url; + return false; + } + + // prevents browser default drag/drop + $(document).on('drop dragover', function (e) { + e.preventDefault(); + }); + + $('#fileupload').fileupload({ + dataType: 'html', + sequentialUploads: true, + dropZone: $('.drop-zone'), + add: function (e, data) { + var $this = $(this); + var that = $this.data('blueimp-fileupload') || $this.data('fileupload'); + var li = $($('#upload-list-item').html()).addClass('upload-uploading'); + var options = that.options; + + $('#upload-list').append(li); + data.context = li; + + data + .process(function () { + return $this.fileupload('process', data); + }) + .always(function () { + data.context.removeClass('processing'); + data.context.find('.left').each(function (index, elm) { + $(elm).append(escapeHtml(data.files[index].name)); + }); + }) + .done(function () { + data.context.find('.start').prop('disabled', false); + if ( + that._trigger('added', e, data) !== false && + (options.autoUpload || data.autoUpload) && + data.autoUpload !== false + ) { + data.submit(); + } + }) + .fail(function () { + if (data.files.error) { + data.context.each(function (index) { + var error = data.files[index].error; + if (error) { + $(this).find('.error_messages').text(error); + } + }); + } + }); + }, + + processfail: function (e, data) { + var itemElement = $(data.context); + itemElement.removeClass('upload-uploading').addClass('upload-failure'); + }, + + progress: function (e, data) { + if (e.isDefaultPrevented()) { return false; - } + } - // prevents browser default drag/drop - $(document).on('drop dragover', function(e) { - e.preventDefault(); - }); + var progress = Math.floor((data.loaded / data.total) * 100); + data.context.each(function () { + $(this) + .find('.progress') + .addClass('active') + .attr('aria-valuenow', progress) + .find('.bar') + .css('width', progress + '%') + .html(progress + '%'); + }); + }, - $('#fileupload').fileupload({ - dataType: 'html', - sequentialUploads: true, - dropZone: $('.drop-zone'), - add: function(e, data) { - var $this = $(this); - var that = $this.data('blueimp-fileupload') || $this.data('fileupload'); - var li = $($('#upload-list-item').html()).addClass('upload-uploading'); - var options = that.options; + progressall: function (e, data) { + var progress = parseInt((data.loaded / data.total) * 100, 10); + $('#overall-progress') + .addClass('active') + .attr('aria-valuenow', progress) + .find('.bar') + .css('width', progress + '%') + .html(progress + '%'); - $('#upload-list').append(li); - data.context = li; + if (progress >= 100) { + $('#overall-progress') + .removeClass('active') + .find('.bar') + .css('width', '0%'); + } + }, - data.process(function() { - return $this.fileupload('process', data); - }).always(function() { - data.context.removeClass('processing'); - data.context.find('.left').each(function(index, elm) { - $(elm).append(escapeHtml(data.files[index].name)); - }); - }).done(function() { - data.context.find('.start').prop('disabled', false); - if ((that._trigger('added', e, data) !== false) && - (options.autoUpload || data.autoUpload) && - data.autoUpload !== false) { - data.submit(); - } - }).fail(function() { - if (data.files.error) { - data.context.each(function(index) { - var error = data.files[index].error; - if (error) { - $(this).find('.error_messages').text(error); - } - }); - } - }); - }, + /** + * Allow a custom title to be defined by an event handler for this form. + * If event.preventDefault is called, the original behaviour of using the raw + * filename (with extension) as the title is preserved. + * + * @param {HtmlElement[]} form + * @returns {{name: 'string', value: *}[]} + */ + formData: function (form) { + var filename = this.files[0].name; + var data = { title: filename.replace(/\.[^.]+$/, '') }; + var maxTitleLength = window.fileupload_opts.max_title_length; - processfail: function(e, data) { - var itemElement = $(data.context); - itemElement.removeClass('upload-uploading').addClass('upload-failure'); - }, + var event = form.get(0).dispatchEvent( + new CustomEvent('wagtail:documents-upload', { + bubbles: true, + cancelable: true, + detail: { + data: data, + filename: filename, + maxTitleLength: maxTitleLength, + }, + }), + ); - progress: function(e, data) { - if (e.isDefaultPrevented()) { - return false; - } + // default behaviour (title is just file name) + return event + ? form.serializeArray().concat({ name: 'title', value: data.title }) + : form.serializeArray(); + }, - var progress = Math.floor(data.loaded / data.total * 100); - data.context.each(function() { - $(this).find('.progress').addClass('active').attr('aria-valuenow', progress).find('.bar').css( - 'width', - progress + '%' - ).html(progress + '%'); - }); - }, + done: function (e, data) { + var itemElement = $(data.context); + var response = JSON.parse(data.result); - progressall: function(e, data) { - var progress = parseInt(data.loaded / data.total * 100, 10); - $('#overall-progress').addClass('active').attr('aria-valuenow', progress).find('.bar').css( - 'width', - progress + '%' - ).html(progress + '%'); + if (response.success) { + itemElement.addClass('upload-success'); - if (progress >= 100) { - $('#overall-progress').removeClass('active').find('.bar').css('width', '0%'); - } - }, + $('.right', itemElement).append(response.form); + } else { + itemElement.addClass('upload-failure'); + $('.right .error_messages', itemElement).append(response.error_message); + } + }, - /** - * Allow a custom title to be defined by an event handler for this form. - * If event.preventDefault is called, the original behaviour of using the raw - * filename (with extension) as the title is preserved. - * - * @param {HtmlElement[]} form - * @returns {{name: 'string', value: *}[]} - */ - formData: function(form) { - var filename = this.files[0].name; - var data = { title: filename.replace(/\.[^.]+$/, '') }; - var maxTitleLength = window.fileupload_opts.max_title_length; + fail: function (e, data) { + var itemElement = $(data.context); + itemElement.addClass('upload-failure'); + }, - var event = form - .get(0) - .dispatchEvent( - new CustomEvent('wagtail:documents-upload', { - bubbles: true, - cancelable: true, - detail: { - data: data, - filename: filename, - maxTitleLength: maxTitleLength, - }, - }) - ); + always: function (e, data) { + var itemElement = $(data.context); + itemElement.removeClass('upload-uploading').addClass('upload-complete'); + }, + }); - // default behaviour (title is just file name) - return event ? form.serializeArray().concat({ name:'title', value: data.title }) : form.serializeArray(); - }, + // ajax-enhance forms added on done() + $('#upload-list').on('submit', 'form', function (e) { + var form = $(this); + var itemElement = form.closest('#upload-list > li'); - done: function(e, data) { - var itemElement = $(data.context); - var response = JSON.parse(data.result); + e.preventDefault(); - if (response.success) { - itemElement.addClass('upload-success'); - - $('.right', itemElement).append(response.form); - } else { - itemElement.addClass('upload-failure'); - $('.right .error_messages', itemElement).append(response.error_message); - } - }, - - fail: function(e, data) { - var itemElement = $(data.context); - itemElement.addClass('upload-failure'); - }, - - always: function(e, data) { - var itemElement = $(data.context); - itemElement.removeClass('upload-uploading').addClass('upload-complete'); - } - }); - - // ajax-enhance forms added on done() - $('#upload-list').on('submit', 'form', function(e) { - var form = $(this); - var itemElement = form.closest('#upload-list > li'); - - e.preventDefault(); - - $.post(this.action, form.serialize(), function(data) { - if (data.success) { - var statusText = $('.status-msg.update-success').text(); - addMessage('success', statusText); - itemElement.slideUp(function() { $(this).remove(); }); - } else { - form.replaceWith(data.form); - - // run tagit enhancement on new form - $('.tag_field input', form).tagit(window.tagit_opts); - } + $.post(this.action, form.serialize(), function (data) { + if (data.success) { + var statusText = $('.status-msg.update-success').text(); + addMessage('success', statusText); + itemElement.slideUp(function () { + $(this).remove(); }); + } else { + form.replaceWith(data.form); + + // run tagit enhancement on new form + $('.tag_field input', form).tagit(window.tagit_opts); + } }); + }); - $('#upload-list').on('click', '.delete', function(e) { - var form = $(this).closest('form'); - var itemElement = form.closest('#upload-list > li'); + $('#upload-list').on('click', '.delete', function (e) { + var form = $(this).closest('form'); + var itemElement = form.closest('#upload-list > li'); - e.preventDefault(); + e.preventDefault(); - var CSRFToken = $('input[name="csrfmiddlewaretoken"]', form).val(); + var CSRFToken = $('input[name="csrfmiddlewaretoken"]', form).val(); - $.post(this.href, { csrfmiddlewaretoken: CSRFToken }, function(data) { - if (data.success) { - itemElement.slideUp(function() { $(this).remove(); }); - } + $.post(this.href, { csrfmiddlewaretoken: CSRFToken }, function (data) { + if (data.success) { + itemElement.slideUp(function () { + $(this).remove(); }); + } }); + }); }); diff --git a/wagtail/documents/static_src/wagtaildocs/js/document-chooser-modal.js b/wagtail/documents/static_src/wagtaildocs/js/document-chooser-modal.js index e8b1eda704..44f6a8ac63 100644 --- a/wagtail/documents/static_src/wagtaildocs/js/document-chooser-modal.js +++ b/wagtail/documents/static_src/wagtaildocs/js/document-chooser-modal.js @@ -1,135 +1,146 @@ function ajaxifyDocumentUploadForm(modal) { - $('form.document-upload', modal.body).on('submit', function() { - var formdata = new FormData(this); + $('form.document-upload', modal.body).on('submit', function () { + var formdata = new FormData(this); - $.ajax({ - url: this.action, - data: formdata, - processData: false, - contentType: false, - type: 'POST', - dataType: 'text', - success: modal.loadResponseText, - error: function(response, textStatus, errorThrown) { - var message = jsonData.error_message + '<br />' + errorThrown + ' - ' + response.status; - $('#upload', modal.body).append( - '<div class="help-block help-critical">' + - '<strong>' + jsonData.error_label + ': </strong>' + message + '</div>'); - } - }); - - return false; + $.ajax({ + url: this.action, + data: formdata, + processData: false, + contentType: false, + type: 'POST', + dataType: 'text', + success: modal.loadResponseText, + error: function (response, textStatus, errorThrown) { + var message = + jsonData.error_message + + '<br />' + + errorThrown + + ' - ' + + response.status; + $('#upload', modal.body).append( + '<div class="help-block help-critical">' + + '<strong>' + + jsonData.error_label + + ': </strong>' + + message + + '</div>', + ); + }, }); - var fileWidget = $('#id_document-chooser-upload-file', modal.body); - fileWidget.on('change', function () { - var titleWidget = $('#id_document-chooser-upload-title', modal.body); - var title = titleWidget.val(); - // do not override a title that already exists (from manual editing or previous upload) - if (title === '') { - // The file widget value example: `C:\fakepath\image.jpg` - var parts = fileWidget.val().split('\\'); - var filename = parts[parts.length - 1]; + return false; + }); - // allow event handler to override filename (used for title) & provide maxLength as int to event - var maxTitleLength = parseInt(titleWidget.attr('maxLength') || '0', 10) || null; - var data = { title: filename.replace(/\.[^.]+$/, '') }; + var fileWidget = $('#id_document-chooser-upload-file', modal.body); + fileWidget.on('change', function () { + var titleWidget = $('#id_document-chooser-upload-title', modal.body); + var title = titleWidget.val(); + // do not override a title that already exists (from manual editing or previous upload) + if (title === '') { + // The file widget value example: `C:\fakepath\image.jpg` + var parts = fileWidget.val().split('\\'); + var filename = parts[parts.length - 1]; - // allow an event handler to customise data or call event.preventDefault to stop any title pre-filling - var form = fileWidget.closest('form').get(0); - var event = form.dispatchEvent( - new CustomEvent('wagtail:documents-upload', { - bubbles: true, - cancelable: true, - detail: { - data: data, - filename: filename, - maxTitleLength: maxTitleLength, - }, - }) - ); + // allow event handler to override filename (used for title) & provide maxLength as int to event + var maxTitleLength = + parseInt(titleWidget.attr('maxLength') || '0', 10) || null; + var data = { title: filename.replace(/\.[^.]+$/, '') }; - if (!event) return; // do not set a title if event.preventDefault(); is called by handler + // allow an event handler to customise data or call event.preventDefault to stop any title pre-filling + var form = fileWidget.closest('form').get(0); + var event = form.dispatchEvent( + new CustomEvent('wagtail:documents-upload', { + bubbles: true, + cancelable: true, + detail: { + data: data, + filename: filename, + maxTitleLength: maxTitleLength, + }, + }), + ); - titleWidget.val(data.title); - } - }); + if (!event) return; // do not set a title if event.preventDefault(); is called by handler + + titleWidget.val(data.title); + } + }); } DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS = { - 'chooser': function(modal, jsonData) { - function ajaxifyLinks (context) { - $('a.document-choice', context).on('click', function() { - modal.loadUrl(this.href); - return false; - }); + chooser: function (modal, jsonData) { + function ajaxifyLinks(context) { + $('a.document-choice', context).on('click', function () { + modal.loadUrl(this.href); + return false; + }); - $('.pagination a', context).on('click', function() { - loadResults(this.href); - return false; - }); + $('.pagination a', context).on('click', function () { + loadResults(this.href); + return false; + }); - $('a.upload-one-now').on('click', function(e) { - // Set current collection ID at upload form tab - const collectionId = $('#collection_chooser_collection_id').val(); - if (collectionId) { - $('#id_document-chooser-upload-collection').val(collectionId); - } - - // Select upload form tab - $('a[href="#upload"]').tab('show'); - e.preventDefault(); - }); + $('a.upload-one-now').on('click', function (e) { + // Set current collection ID at upload form tab + const collectionId = $('#collection_chooser_collection_id').val(); + if (collectionId) { + $('#id_document-chooser-upload-collection').val(collectionId); } - var searchForm = $('form.document-search', modal.body); - var searchUrl = searchForm.attr('action'); - var request; - function search() { - loadResults(searchUrl, searchForm.serialize()); - return false; - } - - function loadResults(url, data) { - var opts = { - url: url, - success: function(resultsData, status) { - request = null; - $('#search-results').html(resultsData); - ajaxifyLinks($('#search-results')); - }, - error: function() { - request = null; - } - }; - if (data) { - opts.data = data; - } - request = $.ajax(opts); - } - - ajaxifyLinks(modal.body); - ajaxifyDocumentUploadForm(modal); - - $('form.document-search', modal.body).on('submit', search); - - $('#id_q').on('input', function() { - if (request) { - request.abort(); - } - clearTimeout($.data(this, 'timer')); - var wait = setTimeout(search, 50); - $(this).data('timer', wait); - }); - - $('#collection_chooser_collection_id').on('change', search); - }, - 'document_chosen': function(modal, jsonData) { - modal.respond('documentChosen', jsonData.result); - modal.close(); - }, - 'reshow_upload_form': function(modal, jsonData) { - $('#upload', modal.body).html(jsonData.htmlFragment); - ajaxifyDocumentUploadForm(modal); + // Select upload form tab + $('a[href="#upload"]').tab('show'); + e.preventDefault(); + }); } + + var searchForm = $('form.document-search', modal.body); + var searchUrl = searchForm.attr('action'); + var request; + function search() { + loadResults(searchUrl, searchForm.serialize()); + return false; + } + + function loadResults(url, data) { + var opts = { + url: url, + success: function (resultsData, status) { + request = null; + $('#search-results').html(resultsData); + ajaxifyLinks($('#search-results')); + }, + error: function () { + request = null; + }, + }; + if (data) { + opts.data = data; + } + request = $.ajax(opts); + } + + ajaxifyLinks(modal.body); + ajaxifyDocumentUploadForm(modal); + + $('form.document-search', modal.body).on('submit', search); + + $('#id_q').on('input', function () { + if (request) { + request.abort(); + } + clearTimeout($.data(this, 'timer')); + var wait = setTimeout(search, 50); + $(this).data('timer', wait); + }); + + $('#collection_chooser_collection_id').on('change', search); + }, + document_chosen: function (modal, jsonData) { + modal.respond('documentChosen', jsonData.result); + modal.close(); + }, + reshow_upload_form: function (modal, jsonData) { + $('#upload', modal.body).html(jsonData.htmlFragment); + ajaxifyDocumentUploadForm(modal); + }, }; diff --git a/wagtail/documents/static_src/wagtaildocs/js/hallo-plugins/hallo-wagtaildoclink.js b/wagtail/documents/static_src/wagtaildocs/js/hallo-plugins/hallo-wagtaildoclink.js index 33770745fb..0ae555648e 100644 --- a/wagtail/documents/static_src/wagtaildocs/js/hallo-plugins/hallo-wagtaildoclink.js +++ b/wagtail/documents/static_src/wagtaildocs/js/hallo-plugins/hallo-wagtaildoclink.js @@ -1,53 +1,56 @@ // Generated by CoffeeScript 1.6.2 -(function() { - (function($) { - return $.widget('IKS.hallowagtaildoclink', { - options: { - uuid: '', - editable: null - }, - populateToolbar: function(toolbar) { - var button; - var widget; +(function () { + (function ($) { + return $.widget('IKS.hallowagtaildoclink', { + options: { + uuid: '', + editable: null, + }, + populateToolbar: function (toolbar) { + var button; + var widget; - widget = this; - button = $('<span class="' + this.widgetName + '"></span>'); - button.hallobutton({ - uuid: this.options.uuid, - editable: this.options.editable, - label: 'Documents', - icon: 'icon-doc-full', - command: null - }); - toolbar.append(button); - return button.on('click', function(event) { - var lastSelection; - - lastSelection = widget.options.editable.getSelection(); - return ModalWorkflow({ - url: window.chooserUrls.documentChooser, - onload: DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS, - responses: { - documentChosen: function(docData) { - var a; - - a = document.createElement('a'); - a.setAttribute('href', docData.url); - a.setAttribute('data-id', docData.id); - a.setAttribute('data-linktype', 'document'); - if ((!lastSelection.collapsed) && lastSelection.canSurroundContents()) { - lastSelection.surroundContents(a); - } else { - a.appendChild(document.createTextNode(docData.title)); - lastSelection.insertNode(a); - } - - return widget.options.editable.element.trigger('change'); - } - } - }); - }); - } + widget = this; + button = $('<span class="' + this.widgetName + '"></span>'); + button.hallobutton({ + uuid: this.options.uuid, + editable: this.options.editable, + label: 'Documents', + icon: 'icon-doc-full', + command: null, }); - }(jQuery)); -}).call(this); + toolbar.append(button); + return button.on('click', function (event) { + var lastSelection; + + lastSelection = widget.options.editable.getSelection(); + return ModalWorkflow({ + url: window.chooserUrls.documentChooser, + onload: DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS, + responses: { + documentChosen: function (docData) { + var a; + + a = document.createElement('a'); + a.setAttribute('href', docData.url); + a.setAttribute('data-id', docData.id); + a.setAttribute('data-linktype', 'document'); + if ( + !lastSelection.collapsed && + lastSelection.canSurroundContents() + ) { + lastSelection.surroundContents(a); + } else { + a.appendChild(document.createTextNode(docData.title)); + lastSelection.insertNode(a); + } + + return widget.options.editable.element.trigger('change'); + }, + }, + }); + }); + }, + }); + })(jQuery); +}.call(this)); diff --git a/wagtail/documents/static_src/wagtaildocs/scss/add-multiple.scss b/wagtail/documents/static_src/wagtaildocs/scss/add-multiple.scss index 3aa438d273..c55d9e1460 100644 --- a/wagtail/documents/static_src/wagtaildocs/scss/add-multiple.scss +++ b/wagtail/documents/static_src/wagtaildocs/scss/add-multiple.scss @@ -2,90 +2,90 @@ @import '../../../../../client/scss/tools'; .replace-file-input { - display: inline-block; - position: relative; - overflow: hidden; - padding-bottom: 2px; + display: inline-block; + position: relative; + overflow: hidden; + padding-bottom: 2px; - [type=file] { - padding: 0; - opacity: 0; - position: absolute; - top: 0; - right: 0; - direction: ltr; - width: auto; - display: block; - font-size: 5em; - - &:hover { - cursor: pointer; - } - } + [type='file'] { + padding: 0; + opacity: 0; + position: absolute; + top: 0; + right: 0; + direction: ltr; + width: auto; + display: block; + font-size: 5em; &:hover { - cursor: pointer; - - button { - background-color: $color-teal-darker; - } + cursor: pointer; } + } + + &:hover { + cursor: pointer; + + button { + background-color: $color-teal-darker; + } + } } .upload-list { - > li { - padding: 1em; - } + > li { + padding: 1em; + } - .left { - text-align: center; - word-break: break-all; + .left { + text-align: center; + word-break: break-all; + } + + .preview { + width: 150px; + min-height: 150px; + display: block; + position: relative; + text-align: center; + max-width: 100%; + margin: auto; + } + + .progress { + box-shadow: 0 0 5px 2px rgba(255, 255, 255, 0.4); + max-width: 100%; + z-index: 4; + margin-left: 20%; + margin-right: 20%; + width: 60%; + } + + .status-msg { + display: none; + } + + .upload-complete { + .progress { + opacity: 0; } + } + + .upload-success { + .status-msg.success { + display: block; + } + } + + .upload-failure { + border-color: $color-red; .preview { - width: 150px; - min-height: 150px; - display: block; - position: relative; - text-align: center; - max-width: 100%; - margin: auto; + display: none; } - .progress { - box-shadow: 0 0 5px 2px rgba(255, 255, 255, 0.4); - max-width: 100%; - z-index: 4; - margin-left: 20%; - margin-right: 20%; - width: 60%; - } - - .status-msg { - display: none; - } - - .upload-complete { - .progress { - opacity: 0; - } - } - - .upload-success { - .status-msg.success { - display: block; - } - } - - .upload-failure { - border-color: $color-red; - - .preview { - display: none; - } - - .status-msg.failure { - display: block; - } + .status-msg.failure { + display: block; } + } } diff --git a/wagtail/embeds/static_src/wagtailembeds/js/embed-chooser-modal.js b/wagtail/embeds/static_src/wagtailembeds/js/embed-chooser-modal.js index 200c3bf11e..31df839f20 100644 --- a/wagtail/embeds/static_src/wagtailembeds/js/embed-chooser-modal.js +++ b/wagtail/embeds/static_src/wagtailembeds/js/embed-chooser-modal.js @@ -1,23 +1,23 @@ EMBED_CHOOSER_MODAL_ONLOAD_HANDLERS = { - 'chooser': function(modal, jsonData) { - $('form.embed-form', modal.body).on('submit', function() { - var formdata = new FormData(this); + chooser: function (modal, jsonData) { + $('form.embed-form', modal.body).on('submit', function () { + var formdata = new FormData(this); - $.ajax({ - url: this.action, - data: formdata, - processData: false, - contentType: false, - type: 'POST', - dataType: 'text', - success: modal.loadResponseText - }); + $.ajax({ + url: this.action, + data: formdata, + processData: false, + contentType: false, + type: 'POST', + dataType: 'text', + success: modal.loadResponseText, + }); - return false; - }); - }, - 'embed_chosen': function(modal, jsonData) { - modal.respond('embedChosen', jsonData.embed_html, jsonData.embed_data); - modal.close(); - } + return false; + }); + }, + embed_chosen: function (modal, jsonData) { + modal.respond('embedChosen', jsonData.embed_html, jsonData.embed_data); + modal.close(); + }, }; diff --git a/wagtail/embeds/static_src/wagtailembeds/js/hallo-plugins/hallo-wagtailembeds.js b/wagtail/embeds/static_src/wagtailembeds/js/hallo-plugins/hallo-wagtailembeds.js index d55647f952..c09ae07482 100644 --- a/wagtail/embeds/static_src/wagtailembeds/js/hallo-plugins/hallo-wagtailembeds.js +++ b/wagtail/embeds/static_src/wagtailembeds/js/hallo-plugins/hallo-wagtailembeds.js @@ -1,53 +1,55 @@ // Generated by CoffeeScript 1.6.2 -(function() { - (function($) { - return $.widget('IKS.hallowagtailembeds', { - options: { - uuid: '', - editable: null - }, - populateToolbar: function(toolbar) { - var button; - var widget; +(function () { + (function ($) { + return $.widget('IKS.hallowagtailembeds', { + options: { + uuid: '', + editable: null, + }, + populateToolbar: function (toolbar) { + var button; + var widget; - widget = this; - button = $('<span class="' + this.widgetName + '"></span>'); - button.hallobutton({ - uuid: this.options.uuid, - editable: this.options.editable, - label: 'Embed', - icon: 'icon-media', - command: null - }); - - toolbar.append(button); - - return button.on('click', function(event) { - var insertionPoint; - var lastSelection; - - lastSelection = widget.options.editable.getSelection(); - insertionPoint = $(lastSelection.endContainer).parentsUntil('[data-hallo-editor]').last(); - - return ModalWorkflow({ - url: window.chooserUrls.embedsChooser, - onload: global.EMBED_CHOOSER_MODAL_ONLOAD_HANDLERS, - responses: { - embedChosen: function(embedData) { - var elem; - - elem = $(embedData).get(0); - lastSelection.insertNode(elem); - if (elem.getAttribute('contenteditable') === 'false') { - insertRichTextDeleteControl(elem); - } - - return widget.options.editable.element.trigger('change'); - } - } - }); - }); - } + widget = this; + button = $('<span class="' + this.widgetName + '"></span>'); + button.hallobutton({ + uuid: this.options.uuid, + editable: this.options.editable, + label: 'Embed', + icon: 'icon-media', + command: null, }); - }(jQuery)); -}).call(this); + + toolbar.append(button); + + return button.on('click', function (event) { + var insertionPoint; + var lastSelection; + + lastSelection = widget.options.editable.getSelection(); + insertionPoint = $(lastSelection.endContainer) + .parentsUntil('[data-hallo-editor]') + .last(); + + return ModalWorkflow({ + url: window.chooserUrls.embedsChooser, + onload: global.EMBED_CHOOSER_MODAL_ONLOAD_HANDLERS, + responses: { + embedChosen: function (embedData) { + var elem; + + elem = $(embedData).get(0); + lastSelection.insertNode(elem); + if (elem.getAttribute('contenteditable') === 'false') { + insertRichTextDeleteControl(elem); + } + + return widget.options.editable.element.trigger('change'); + }, + }, + }); + }); + }, + }); + })(jQuery); +}.call(this)); diff --git a/wagtail/images/static_src/wagtailimages/js/add-multiple.js b/wagtail/images/static_src/wagtailimages/js/add-multiple.js index 7d9c9a54cd..30e7bd0bf7 100644 --- a/wagtail/images/static_src/wagtailimages/js/add-multiple.js +++ b/wagtail/images/static_src/wagtailimages/js/add-multiple.js @@ -1,201 +1,219 @@ -$(function() { - // Redirect users that don't support filereader - if (!$('html').hasClass('filereader')) { - document.location.href = window.fileupload_opts.simple_upload_url; +$(function () { + // Redirect users that don't support filereader + if (!$('html').hasClass('filereader')) { + document.location.href = window.fileupload_opts.simple_upload_url; + return false; + } + + // prevents browser default drag/drop + $(document).on('drop dragover', function (e) { + e.preventDefault(); + }); + + $('#fileupload').fileupload({ + dataType: 'html', + sequentialUploads: true, + dropZone: $('.drop-zone'), + acceptFileTypes: window.fileupload_opts.accepted_file_types, + maxFileSize: window.fileupload_opts.max_file_size, + previewMinWidth: 150, + previewMaxWidth: 150, + previewMinHeight: 150, + previewMaxHeight: 150, + messages: { + acceptFileTypes: window.fileupload_opts.errormessages.accepted_file_types, + maxFileSize: window.fileupload_opts.errormessages.max_file_size, + }, + add: function (e, data) { + $('.messages').empty(); + var $this = $(this); + var that = $this.data('blueimp-fileupload') || $this.data('fileupload'); + var li = $($('#upload-list-item').html()).addClass('upload-uploading'); + var options = that.options; + + $('#upload-list').append(li); + data.context = li; + + data + .process(function () { + return $this.fileupload('process', data); + }) + .always(function () { + data.context.removeClass('processing'); + data.context.find('.left').each(function (index, elm) { + $(elm).append(escapeHtml(data.files[index].name)); + }); + + data.context.find('.preview .thumb').each(function (index, elm) { + $(elm).addClass('hasthumb'); + $(elm).append(data.files[index].preview); + }); + }) + .done(function () { + data.context.find('.start').prop('disabled', false); + if ( + that._trigger('added', e, data) !== false && + (options.autoUpload || data.autoUpload) && + data.autoUpload !== false + ) { + data.submit(); + } + }) + .fail(function () { + if (data.files.error) { + data.context.each(function (index) { + var error = data.files[index].error; + if (error) { + $(this).find('.error_messages').html(error); + } + }); + } + }); + }, + + processfail: function (e, data) { + var itemElement = $(data.context); + itemElement.removeClass('upload-uploading').addClass('upload-failure'); + }, + + progress: function (e, data) { + if (e.isDefaultPrevented()) { return false; - } + } - // prevents browser default drag/drop - $(document).on('drop dragover', function(e) { - e.preventDefault(); - }); + var progress = Math.floor((data.loaded / data.total) * 100); + data.context.each(function () { + $(this) + .find('.progress') + .addClass('active') + .attr('aria-valuenow', progress) + .find('.bar') + .css('width', progress + '%') + .html(progress + '%'); + }); + }, - $('#fileupload').fileupload({ - dataType: 'html', - sequentialUploads: true, - dropZone: $('.drop-zone'), - acceptFileTypes: window.fileupload_opts.accepted_file_types, - maxFileSize: window.fileupload_opts.max_file_size, - previewMinWidth:150, - previewMaxWidth:150, - previewMinHeight:150, - previewMaxHeight:150, - messages: { - acceptFileTypes: window.fileupload_opts.errormessages.accepted_file_types, - maxFileSize: window.fileupload_opts.errormessages.max_file_size - }, - add: function(e, data) { - $('.messages').empty(); - var $this = $(this); - var that = $this.data('blueimp-fileupload') || $this.data('fileupload'); - var li = $($('#upload-list-item').html()).addClass('upload-uploading'); - var options = that.options; + progressall: function (e, data) { + var progress = parseInt((data.loaded / data.total) * 100, 10); + $('#overall-progress') + .addClass('active') + .attr('aria-valuenow', progress) + .find('.bar') + .css('width', progress + '%') + .html(progress + '%'); - $('#upload-list').append(li); - data.context = li; + if (progress >= 100) { + $('#overall-progress') + .removeClass('active') + .find('.bar') + .css('width', '0%'); + } + }, - data.process(function() { - return $this.fileupload('process', data); - }).always(function() { - data.context.removeClass('processing'); - data.context.find('.left').each(function(index, elm) { - $(elm).append(escapeHtml(data.files[index].name)); - }); + /** + * Allow a custom title to be defined by an event handler for this form. + * If event.preventDefault is called, the original behaviour of using the raw + * filename (with extension) as the title is preserved. + * + * @example + * document.addEventListener('wagtail:images-upload', function(event) { + * // remove file extension + * var newTitle = (event.detail.data.title || '').replace(/\.[^.]+$/, ''); + * event.detail.data.title = newTitle; + * }); + * + * @param {HtmlElement[]} form + * @returns {{name: 'string', value: *}[]} + */ + formData: function (form) { + var filename = this.files[0].name; + var data = { title: filename.replace(/\.[^.]+$/, '') }; + var maxTitleLength = window.fileupload_opts.max_title_length; - data.context.find('.preview .thumb').each(function(index, elm) { - $(elm).addClass('hasthumb'); - $(elm).append(data.files[index].preview); - }); - }).done(function() { - data.context.find('.start').prop('disabled', false); - if ((that._trigger('added', e, data) !== false) && - (options.autoUpload || data.autoUpload) && - data.autoUpload !== false) { - data.submit(); - } - }).fail(function() { - if (data.files.error) { - data.context.each(function(index) { - var error = data.files[index].error; - if (error) { - $(this).find('.error_messages').html(error); - } - }); - } - }); - }, + var event = form.get(0).dispatchEvent( + new CustomEvent('wagtail:images-upload', { + bubbles: true, + cancelable: true, + detail: { + data: data, + filename: filename, + maxTitleLength: maxTitleLength, + }, + }), + ); - processfail: function(e, data) { - var itemElement = $(data.context); - itemElement.removeClass('upload-uploading').addClass('upload-failure'); - }, + // default behaviour (title is just file name) + return event + ? form.serializeArray().concat({ name: 'title', value: data.title }) + : form.serializeArray(); + }, - progress: function(e, data) { - if (e.isDefaultPrevented()) { - return false; - } + done: function (e, data) { + var itemElement = $(data.context); + var response = JSON.parse(data.result); - var progress = Math.floor(data.loaded / data.total * 100); - data.context.each(function() { - $(this).find('.progress').addClass('active').attr('aria-valuenow', progress).find('.bar').css( - 'width', - progress + '%' - ).html(progress + '%'); - }); - }, + if (response.success) { + itemElement.addClass('upload-success'); - progressall: function(e, data) { - var progress = parseInt(data.loaded / data.total * 100, 10); - $('#overall-progress').addClass('active').attr('aria-valuenow', progress).find('.bar').css( - 'width', - progress + '%' - ).html(progress + '%'); + $('.right', itemElement).append(response.form); + } else { + itemElement.addClass('upload-failure'); + $('.right .error_messages', itemElement).append(response.error_message); + } + }, - if (progress >= 100) { - $('#overall-progress').removeClass('active').find('.bar').css('width', '0%'); - } - }, + fail: function (e, data) { + var itemElement = $(data.context); + var errorMessage = $('.server-error', itemElement); + $('.error-text', errorMessage).text(data.errorThrown); + $('.error-code', errorMessage).text(data.jqXHR.status); - /** - * Allow a custom title to be defined by an event handler for this form. - * If event.preventDefault is called, the original behaviour of using the raw - * filename (with extension) as the title is preserved. - * - * @example - * document.addEventListener('wagtail:images-upload', function(event) { - * // remove file extension - * var newTitle = (event.detail.data.title || '').replace(/\.[^.]+$/, ''); - * event.detail.data.title = newTitle; - * }); - * - * @param {HtmlElement[]} form - * @returns {{name: 'string', value: *}[]} - */ - formData: function(form) { - var filename = this.files[0].name; - var data = { title: filename.replace(/\.[^.]+$/, '') }; - var maxTitleLength = window.fileupload_opts.max_title_length; + itemElement.addClass('upload-server-error'); + }, - var event = form - .get(0) - .dispatchEvent( - new CustomEvent('wagtail:images-upload', { - bubbles: true, - cancelable: true, - detail: { - data: data, - filename: filename, - maxTitleLength: maxTitleLength, - }, - }) - ); + always: function (e, data) { + var itemElement = $(data.context); + itemElement.removeClass('upload-uploading').addClass('upload-complete'); + }, + }); - // default behaviour (title is just file name) - return event ? form.serializeArray().concat({ name:'title', value: data.title }) : form.serializeArray(); - }, + // ajax-enhance forms added on done() + $('#upload-list').on('submit', 'form', function (e) { + var form = $(this); + var itemElement = form.closest('#upload-list > li'); - done: function(e, data) { - var itemElement = $(data.context); - var response = JSON.parse(data.result); + e.preventDefault(); - if (response.success) { - itemElement.addClass('upload-success'); - - $('.right', itemElement).append(response.form); - } else { - itemElement.addClass('upload-failure'); - $('.right .error_messages', itemElement).append(response.error_message); - } - }, - - fail: function(e, data) { - var itemElement = $(data.context); - var errorMessage = $('.server-error', itemElement); - $('.error-text', errorMessage).text(data.errorThrown); - $('.error-code', errorMessage).text(data.jqXHR.status); - - itemElement.addClass('upload-server-error'); - }, - - always: function(e, data) { - var itemElement = $(data.context); - itemElement.removeClass('upload-uploading').addClass('upload-complete'); - } - }); - - // ajax-enhance forms added on done() - $('#upload-list').on('submit', 'form', function(e) { - var form = $(this); - var itemElement = form.closest('#upload-list > li'); - - e.preventDefault(); - - $.post(this.action, form.serialize(), function(data) { - if (data.success) { - var statusText = $('.status-msg.update-success').text(); - addMessage('success', statusText); - itemElement.slideUp(function() { $(this).remove(); }); - } else { - form.replaceWith(data.form); - - // run tagit enhancement on new form - $('.tag_field input', form).tagit(window.tagit_opts); - } + $.post(this.action, form.serialize(), function (data) { + if (data.success) { + var statusText = $('.status-msg.update-success').text(); + addMessage('success', statusText); + itemElement.slideUp(function () { + $(this).remove(); }); + } else { + form.replaceWith(data.form); + + // run tagit enhancement on new form + $('.tag_field input', form).tagit(window.tagit_opts); + } }); + }); - $('#upload-list').on('click', '.delete', function(e) { - var form = $(this).closest('form'); - var itemElement = form.closest('#upload-list > li'); + $('#upload-list').on('click', '.delete', function (e) { + var form = $(this).closest('form'); + var itemElement = form.closest('#upload-list > li'); - e.preventDefault(); + e.preventDefault(); - var CSRFToken = $('input[name="csrfmiddlewaretoken"]', form).val(); + var CSRFToken = $('input[name="csrfmiddlewaretoken"]', form).val(); - $.post(this.href, { csrfmiddlewaretoken: CSRFToken }, function(data) { - if (data.success) { - itemElement.slideUp(function() { $(this).remove(); }); - } + $.post(this.href, { csrfmiddlewaretoken: CSRFToken }, function (data) { + if (data.success) { + itemElement.slideUp(function () { + $(this).remove(); }); + } }); + }); }); diff --git a/wagtail/images/static_src/wagtailimages/js/focal-point-chooser.js b/wagtail/images/static_src/wagtailimages/js/focal-point-chooser.js index 2c699e7665..d4353b439b 100644 --- a/wagtail/images/static_src/wagtailimages/js/focal-point-chooser.js +++ b/wagtail/images/static_src/wagtailimages/js/focal-point-chooser.js @@ -1,93 +1,99 @@ var jcropapi; function setupJcrop(image, original, focalPointOriginal, fields) { - image.Jcrop({ - trueSize: [original.width, original.height], - bgColor: 'rgb(192, 192, 192)', - onSelect: function(box) { - var x = Math.floor((box.x + box.x2) / 2); - var y = Math.floor((box.y + box.y2) / 2); - var w = Math.floor(box.w); - var h = Math.floor(box.h); + image.Jcrop( + { + trueSize: [original.width, original.height], + bgColor: 'rgb(192, 192, 192)', + onSelect: function (box) { + var x = Math.floor((box.x + box.x2) / 2); + var y = Math.floor((box.y + box.y2) / 2); + var w = Math.floor(box.w); + var h = Math.floor(box.h); - fields.x.val(x); - fields.y.val(y); - fields.width.val(w); - fields.height.val(h); - }, + fields.x.val(x); + fields.y.val(y); + fields.width.val(w); + fields.height.val(h); + }, - onRelease: function() { - fields.x.val(focalPointOriginal.x); - fields.y.val(focalPointOriginal.y); - fields.width.val(focalPointOriginal.width); - fields.height.val(focalPointOriginal.height); - } - }, function() { - jcropapi = this; + onRelease: function () { + fields.x.val(focalPointOriginal.x); + fields.y.val(focalPointOriginal.y); + fields.width.val(focalPointOriginal.width); + fields.height.val(focalPointOriginal.height); + }, + }, + function () { + jcropapi = this; - // Set alt="" on the image so its src is not read out loud to screen reader users. - var $holderImage = $('img', jcropapi.ui.holder); - $holderImage.attr('alt', ''); - }); + // Set alt="" on the image so its src is not read out loud to screen reader users. + var $holderImage = $('img', jcropapi.ui.holder); + $holderImage.attr('alt', ''); + }, + ); } -$(function() { - var $chooser = $('div.focal-point-chooser'); - var $indicator = $('.current-focal-point-indicator', $chooser); - var $image = $('img', $chooser); +$(function () { + var $chooser = $('div.focal-point-chooser'); + var $indicator = $('.current-focal-point-indicator', $chooser); + var $image = $('img', $chooser); - var original = { - width: $image.data('originalWidth'), - height: $image.data('originalHeight') - }; + var original = { + width: $image.data('originalWidth'), + height: $image.data('originalHeight'), + }; - var focalPointOriginal = { - x: $chooser.data('focalPointX'), - y: $chooser.data('focalPointY'), - width: $chooser.data('focalPointWidth'), - height: $chooser.data('focalPointHeight') - }; + var focalPointOriginal = { + x: $chooser.data('focalPointX'), + y: $chooser.data('focalPointY'), + width: $chooser.data('focalPointWidth'), + height: $chooser.data('focalPointHeight'), + }; - var fields = { - x: $('input.focal_point_x'), - y: $('input.focal_point_y'), - width: $('input.focal_point_width'), - height: $('input.focal_point_height') - }; + var fields = { + x: $('input.focal_point_x'), + y: $('input.focal_point_y'), + width: $('input.focal_point_width'), + height: $('input.focal_point_height'), + }; - var left = focalPointOriginal.x - focalPointOriginal.width / 2; - var top = focalPointOriginal.y - focalPointOriginal.height / 2; - var width = focalPointOriginal.width; - var height = focalPointOriginal.height; + var left = focalPointOriginal.x - focalPointOriginal.width / 2; + var top = focalPointOriginal.y - focalPointOriginal.height / 2; + var width = focalPointOriginal.width; + var height = focalPointOriginal.height; - $indicator.css('left', (left * 100 / original.width) + '%'); - $indicator.css('top', (top * 100 / original.height) + '%'); - $indicator.css('width', (width * 100 / original.width) + '%'); - $indicator.css('height', (height * 100 / original.height) + '%'); + $indicator.css('left', (left * 100) / original.width + '%'); + $indicator.css('top', (top * 100) / original.height + '%'); + $indicator.css('width', (width * 100) / original.width + '%'); + $indicator.css('height', (height * 100) / original.height + '%'); - var params = [$image, original, focalPointOriginal, fields]; + var params = [$image, original, focalPointOriginal, fields]; + setupJcrop.apply(this, params); + + $(window).on( + 'resize', + $.debounce(300, function () { + // jcrop doesn't support responsive images so to cater for resizing the browser + // we have to destroy() it, which doesn't properly do it, + // so destroy it some more, then re-apply it + jcropapi.destroy(); + $image.removeAttr('style'); + $('.jcrop-holder').remove(); + setupJcrop.apply(this, params); + }), + ); + + $('.remove-focal-point').on('click', function () { + jcropapi.destroy(); + $image.removeAttr('style'); + $('.jcrop-holder').remove(); + $('.current-focal-point-indicator').remove(); + fields.x.val(''); + fields.y.val(''); + fields.width.val(''); + fields.height.val(''); setupJcrop.apply(this, params); - - $(window).on('resize', $.debounce(300, function() { - // jcrop doesn't support responsive images so to cater for resizing the browser - // we have to destroy() it, which doesn't properly do it, - // so destroy it some more, then re-apply it - jcropapi.destroy(); - $image.removeAttr('style'); - $('.jcrop-holder').remove(); - setupJcrop.apply(this, params); - })); - - $('.remove-focal-point').on('click', function() { - jcropapi.destroy(); - $image.removeAttr('style'); - $('.jcrop-holder').remove(); - $('.current-focal-point-indicator').remove(); - fields.x.val(''); - fields.y.val(''); - fields.width.val(''); - fields.height.val(''); - setupJcrop.apply(this, params); - }); + }); }); diff --git a/wagtail/images/static_src/wagtailimages/js/hallo-plugins/hallo-wagtailimage.js b/wagtail/images/static_src/wagtailimages/js/hallo-plugins/hallo-wagtailimage.js index ca05b1289a..4c342e39ac 100644 --- a/wagtail/images/static_src/wagtailimages/js/hallo-plugins/hallo-wagtailimage.js +++ b/wagtail/images/static_src/wagtailimages/js/hallo-plugins/hallo-wagtailimage.js @@ -1,50 +1,52 @@ // Generated by CoffeeScript 1.6.2 -(function() { - (function($) { - return $.widget('IKS.hallowagtailimage', { - options: { - uuid: '', - editable: null - }, - populateToolbar: function(toolbar) { - var button; - var widget; +(function () { + (function ($) { + return $.widget('IKS.hallowagtailimage', { + options: { + uuid: '', + editable: null, + }, + populateToolbar: function (toolbar) { + var button; + var widget; - widget = this; - button = $('<span class="' + this.widgetName + '"></span>'); - button.hallobutton({ - uuid: this.options.uuid, - editable: this.options.editable, - label: 'Images', - icon: 'icon-image', - command: null - }); - toolbar.append(button); - return button.on('click', function(event) { - var insertionPoint; - var lastSelection; - - lastSelection = widget.options.editable.getSelection(); - insertionPoint = $(lastSelection.endContainer).parentsUntil('[data-hallo-editor]').last(); - return ModalWorkflow({ - url: window.chooserUrls.imageChooser + '?select_format=true', - onload: IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS, - responses: { - imageChosen: function(imageData) { - var elem; - - elem = $(imageData.html).get(0); - lastSelection.insertNode(elem); - if (elem.getAttribute('contenteditable') === 'false') { - insertRichTextDeleteControl(elem); - } - - return widget.options.editable.element.trigger('change'); - } - } - }); - }); - } + widget = this; + button = $('<span class="' + this.widgetName + '"></span>'); + button.hallobutton({ + uuid: this.options.uuid, + editable: this.options.editable, + label: 'Images', + icon: 'icon-image', + command: null, }); - }(jQuery)); -}).call(this); + toolbar.append(button); + return button.on('click', function (event) { + var insertionPoint; + var lastSelection; + + lastSelection = widget.options.editable.getSelection(); + insertionPoint = $(lastSelection.endContainer) + .parentsUntil('[data-hallo-editor]') + .last(); + return ModalWorkflow({ + url: window.chooserUrls.imageChooser + '?select_format=true', + onload: IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS, + responses: { + imageChosen: function (imageData) { + var elem; + + elem = $(imageData.html).get(0); + lastSelection.insertNode(elem); + if (elem.getAttribute('contenteditable') === 'false') { + insertRichTextDeleteControl(elem); + } + + return widget.options.editable.element.trigger('change'); + }, + }, + }); + }); + }, + }); + })(jQuery); +}.call(this)); diff --git a/wagtail/images/static_src/wagtailimages/js/image-chooser-modal.js b/wagtail/images/static_src/wagtailimages/js/image-chooser-modal.js index 95dbf51698..985cb3e95d 100644 --- a/wagtail/images/static_src/wagtailimages/js/image-chooser-modal.js +++ b/wagtail/images/static_src/wagtailimages/js/image-chooser-modal.js @@ -1,179 +1,196 @@ function ajaxifyImageUploadForm(modal) { - $('form.image-upload', modal.body).on('submit', function() { - var formdata = new FormData(this); + $('form.image-upload', modal.body).on('submit', function () { + var formdata = new FormData(this); - if (!$('#id_image-chooser-upload-title', modal.body).val()) { - var li = $('#id_image-chooser-upload-title', modal.body).closest('li'); - if (!li.hasClass('error')) { - li.addClass('error'); - $('#id_image-chooser-upload-title', modal.body) - .closest('.field-content') - .append( - '<p class="error-message"><span>This field is required.</span></p>' - ); - } - setTimeout(cancelSpinner, 500); - } else { - $.ajax({ - url: this.action, - data: formdata, - processData: false, - contentType: false, - type: 'POST', - dataType: 'text', - success: modal.loadResponseText, - error: function(response, textStatus, errorThrown) { - var message = jsonData.error_message + '<br />' + errorThrown + ' - ' + response.status; - $('#upload').append( - '<div class="help-block help-critical">' + - '<strong>' + jsonData.error_label + ': </strong>' + message + '</div>'); - } - }); - } + if (!$('#id_image-chooser-upload-title', modal.body).val()) { + var li = $('#id_image-chooser-upload-title', modal.body).closest('li'); + if (!li.hasClass('error')) { + li.addClass('error'); + $('#id_image-chooser-upload-title', modal.body) + .closest('.field-content') + .append( + '<p class="error-message"><span>This field is required.</span></p>', + ); + } + setTimeout(cancelSpinner, 500); + } else { + $.ajax({ + url: this.action, + data: formdata, + processData: false, + contentType: false, + type: 'POST', + dataType: 'text', + success: modal.loadResponseText, + error: function (response, textStatus, errorThrown) { + var message = + jsonData.error_message + + '<br />' + + errorThrown + + ' - ' + + response.status; + $('#upload').append( + '<div class="help-block help-critical">' + + '<strong>' + + jsonData.error_label + + ': </strong>' + + message + + '</div>', + ); + }, + }); + } - return false; - }); + return false; + }); - var fileWidget = $('#id_image-chooser-upload-file', modal.body); - fileWidget.on('change', function () { - var titleWidget = $('#id_image-chooser-upload-title', modal.body); - var title = titleWidget.val(); - // do not override a title that already exists (from manual editing or previous upload) - if (title === '') { - // The file widget value example: `C:\fakepath\image.jpg` - var parts = fileWidget.val().split('\\'); - var filename = parts[parts.length - 1]; + var fileWidget = $('#id_image-chooser-upload-file', modal.body); + fileWidget.on('change', function () { + var titleWidget = $('#id_image-chooser-upload-title', modal.body); + var title = titleWidget.val(); + // do not override a title that already exists (from manual editing or previous upload) + if (title === '') { + // The file widget value example: `C:\fakepath\image.jpg` + var parts = fileWidget.val().split('\\'); + var filename = parts[parts.length - 1]; - // allow event handler to override filename (used for title) & provide maxLength as int to event - var maxTitleLength = parseInt(titleWidget.attr('maxLength') || '0', 10) || null; - var data = { title: filename.replace(/\.[^.]+$/, '') }; + // allow event handler to override filename (used for title) & provide maxLength as int to event + var maxTitleLength = + parseInt(titleWidget.attr('maxLength') || '0', 10) || null; + var data = { title: filename.replace(/\.[^.]+$/, '') }; - // allow an event handler to customise data or call event.preventDefault to stop any title pre-filling - var form = fileWidget.closest('form').get(0); - var event = form.dispatchEvent( - new CustomEvent('wagtail:images-upload', { - bubbles: true, - cancelable: true, - detail: { - data: data, - filename: filename, - maxTitleLength: maxTitleLength, - }, - }) - ); + // allow an event handler to customise data or call event.preventDefault to stop any title pre-filling + var form = fileWidget.closest('form').get(0); + var event = form.dispatchEvent( + new CustomEvent('wagtail:images-upload', { + bubbles: true, + cancelable: true, + detail: { + data: data, + filename: filename, + maxTitleLength: maxTitleLength, + }, + }), + ); - if (!event) return; // do not set a title if event.preventDefault(); is called by handler + if (!event) return; // do not set a title if event.preventDefault(); is called by handler - titleWidget.val(data.title); - } - }); + titleWidget.val(data.title); + } + }); } IMAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = { - 'chooser': function(modal, jsonData) { - var searchForm = $('form.image-search', modal.body); - var searchUrl = searchForm.attr('action'); + chooser: function (modal, jsonData) { + var searchForm = $('form.image-search', modal.body); + var searchUrl = searchForm.attr('action'); - function ajaxifyLinks (context) { - $('.listing a', context).on('click', function() { - modal.loadUrl(this.href); - return false; - }); + function ajaxifyLinks(context) { + $('.listing a', context).on('click', function () { + modal.loadUrl(this.href); + return false; + }); - $('.pagination a', context).on('click', function() { - fetchResults(this.href); - return false; - }); - } - var request; - - function fetchResults(url, requestData) { - var opts = { - url: url, - success: function(data, status) { - request = null; - $('#image-results').html(data); - ajaxifyLinks($('#image-results')); - }, - error: function() { - request = null; - } - }; - if (requestData) { - opts.data = requestData; - } - request = $.ajax(opts); - } - - function search() { - fetchResults(searchUrl, searchForm.serialize()); - return false; - } - - ajaxifyLinks(modal.body); - ajaxifyImageUploadForm(modal); - - $('form.image-search', modal.body).on('submit', search); - - $('#id_q').on('input', function() { - if (request) { - request.abort(); - } - clearTimeout($.data(this, 'timer')); - var wait = setTimeout(search, 200); - $(this).data('timer', wait); - }); - $('#collection_chooser_collection_id').on('change', search); - $('a.suggested-tag').on('click', function() { - $('#id_q').val(''); - fetchResults(searchUrl, { - 'tag': $(this).text(), - collection_id: $('#collection_chooser_collection_id').val() - }); - return false; - }); - }, - 'image_chosen': function(modal, jsonData) { - modal.respond('imageChosen', jsonData.result); - modal.close(); - }, - 'reshow_upload_form': function(modal, jsonData) { - $('#upload', modal.body).replaceWith(jsonData.htmlFragment); - ajaxifyImageUploadForm(modal); - }, - 'select_format': function(modal) { - var decorativeImage = document.querySelector('#id_image-chooser-insertion-image_is_decorative'); - var altText = document.querySelector('#id_image-chooser-insertion-alt_text'); - var altTextLabel = document.querySelector('[for="id_image-chooser-insertion-alt_text"]'); - - if (decorativeImage.checked) { - disableAltText(); - } else { - enableAltText(); - } - - decorativeImage.addEventListener('change', function(event) { - if (event.target.checked) { - disableAltText(); - } else { - enableAltText(); - } - }); - - function disableAltText() { - altText.setAttribute('disabled', 'disabled'); - altTextLabel.classList.remove('required'); - } - - function enableAltText() { - altText.removeAttribute('disabled'); - altTextLabel.classList.add('required'); - } - - $('form', modal.body).on('submit', function() { - $.post(this.action, $(this).serialize(), modal.loadResponseText, 'text'); - - return false; - }); + $('.pagination a', context).on('click', function () { + fetchResults(this.href); + return false; + }); } + var request; + + function fetchResults(url, requestData) { + var opts = { + url: url, + success: function (data, status) { + request = null; + $('#image-results').html(data); + ajaxifyLinks($('#image-results')); + }, + error: function () { + request = null; + }, + }; + if (requestData) { + opts.data = requestData; + } + request = $.ajax(opts); + } + + function search() { + fetchResults(searchUrl, searchForm.serialize()); + return false; + } + + ajaxifyLinks(modal.body); + ajaxifyImageUploadForm(modal); + + $('form.image-search', modal.body).on('submit', search); + + $('#id_q').on('input', function () { + if (request) { + request.abort(); + } + clearTimeout($.data(this, 'timer')); + var wait = setTimeout(search, 200); + $(this).data('timer', wait); + }); + $('#collection_chooser_collection_id').on('change', search); + $('a.suggested-tag').on('click', function () { + $('#id_q').val(''); + fetchResults(searchUrl, { + tag: $(this).text(), + collection_id: $('#collection_chooser_collection_id').val(), + }); + return false; + }); + }, + image_chosen: function (modal, jsonData) { + modal.respond('imageChosen', jsonData.result); + modal.close(); + }, + reshow_upload_form: function (modal, jsonData) { + $('#upload', modal.body).replaceWith(jsonData.htmlFragment); + ajaxifyImageUploadForm(modal); + }, + select_format: function (modal) { + var decorativeImage = document.querySelector( + '#id_image-chooser-insertion-image_is_decorative', + ); + var altText = document.querySelector( + '#id_image-chooser-insertion-alt_text', + ); + var altTextLabel = document.querySelector( + '[for="id_image-chooser-insertion-alt_text"]', + ); + + if (decorativeImage.checked) { + disableAltText(); + } else { + enableAltText(); + } + + decorativeImage.addEventListener('change', function (event) { + if (event.target.checked) { + disableAltText(); + } else { + enableAltText(); + } + }); + + function disableAltText() { + altText.setAttribute('disabled', 'disabled'); + altTextLabel.classList.remove('required'); + } + + function enableAltText() { + altText.removeAttribute('disabled'); + altTextLabel.classList.add('required'); + } + + $('form', modal.body).on('submit', function () { + $.post(this.action, $(this).serialize(), modal.loadResponseText, 'text'); + + return false; + }); + }, }; diff --git a/wagtail/images/static_src/wagtailimages/js/image-url-generator.js b/wagtail/images/static_src/wagtailimages/js/image-url-generator.js index bf6b2aab57..70ef76702c 100644 --- a/wagtail/images/static_src/wagtailimages/js/image-url-generator.js +++ b/wagtail/images/static_src/wagtailimages/js/image-url-generator.js @@ -1,78 +1,88 @@ -$(function() { - $('.image-url-generator').each(function() { - var $this = $(this); - var $form = $this.find('form'); - var $filterMethodField = $form.find('select#id_filter_method'); - var $widthField = $form.find('input#id_width'); - var $heightField = $form.find('input#id_height'); - var $closenessField = $form.find('input#id_closeness'); - var $result = $this.find('#result-url'); - var $loadingMask = $this.find('.loading-mask'); - var $preview = $this.find('img.preview'); - var $sizeNote = $('#note-size'); +$(function () { + $('.image-url-generator').each(function () { + var $this = $(this); + var $form = $this.find('form'); + var $filterMethodField = $form.find('select#id_filter_method'); + var $widthField = $form.find('input#id_width'); + var $heightField = $form.find('input#id_height'); + var $closenessField = $form.find('input#id_closeness'); + var $result = $this.find('#result-url'); + var $loadingMask = $this.find('.loading-mask'); + var $preview = $this.find('img.preview'); + var $sizeNote = $('#note-size'); - var generatorUrl = $this.data('generatorUrl'); + var generatorUrl = $this.data('generatorUrl'); - function formChangeHandler() { - var filterSpec = $filterMethodField.val(); + function formChangeHandler() { + var filterSpec = $filterMethodField.val(); - $loadingMask.addClass('loading'); + $loadingMask.addClass('loading'); - if (filterSpec === 'original') { - $widthField.prop('disabled', true); - $heightField.prop('disabled', true); - $closenessField.prop('disabled', true); - } else if (filterSpec === 'width') { - $widthField.prop('disabled', false); - $heightField.prop('disabled', true); - $closenessField.prop('disabled', true); - filterSpec += '-' + $widthField.val(); - } else if (filterSpec === 'height') { - $widthField.prop('disabled', true); - $heightField.prop('disabled', false); - $closenessField.prop('disabled', true); - filterSpec += '-' + $heightField.val(); - } else if (filterSpec === 'min' || filterSpec === 'max' || filterSpec === 'fill') { - $widthField.prop('disabled', false); - $heightField.prop('disabled', false); + if (filterSpec === 'original') { + $widthField.prop('disabled', true); + $heightField.prop('disabled', true); + $closenessField.prop('disabled', true); + } else if (filterSpec === 'width') { + $widthField.prop('disabled', false); + $heightField.prop('disabled', true); + $closenessField.prop('disabled', true); + filterSpec += '-' + $widthField.val(); + } else if (filterSpec === 'height') { + $widthField.prop('disabled', true); + $heightField.prop('disabled', false); + $closenessField.prop('disabled', true); + filterSpec += '-' + $heightField.val(); + } else if ( + filterSpec === 'min' || + filterSpec === 'max' || + filterSpec === 'fill' + ) { + $widthField.prop('disabled', false); + $heightField.prop('disabled', false); - if (filterSpec === 'fill') { - $closenessField.prop('disabled', false); - filterSpec += '-' + $widthField.val() + 'x' + $heightField.val() + '-c' + $closenessField.val(); - } else { - $closenessField.prop('disabled', true); - filterSpec += '-' + $widthField.val() + 'x' + $heightField.val(); - } - } - - // Display note about scaled down images if image is large - if ($widthField.val() > $(window).width()) { - $sizeNote.show(); - } else { - $sizeNote.hide(); - } - - // Fields with width and height - $.getJSON(generatorUrl.replace('__filterspec__', filterSpec)) - .done(function(data) { - $result.val(data.url); - $preview.attr('src', data.preview_url); - $loadingMask.removeClass('loading'); - }) - .fail(function(data) { - $result.val(data.responseJSON.error); - $preview.attr('src', ''); - $loadingMask.removeClass('loading'); - }); + if (filterSpec === 'fill') { + $closenessField.prop('disabled', false); + filterSpec += + '-' + + $widthField.val() + + 'x' + + $heightField.val() + + '-c' + + $closenessField.val(); + } else { + $closenessField.prop('disabled', true); + filterSpec += '-' + $widthField.val() + 'x' + $heightField.val(); } + } - $form.on('change', $.debounce(500, formChangeHandler)); - $form.on('keyup', $.debounce(500, formChangeHandler)); - formChangeHandler(); + // Display note about scaled down images if image is large + if ($widthField.val() > $(window).width()) { + $sizeNote.show(); + } else { + $sizeNote.hide(); + } - // When the user clicks the URL, automatically select the whole thing (for easier copying) - $result.on('click', function() { - $(this).trigger('select'); + // Fields with width and height + $.getJSON(generatorUrl.replace('__filterspec__', filterSpec)) + .done(function (data) { + $result.val(data.url); + $preview.attr('src', data.preview_url); + $loadingMask.removeClass('loading'); + }) + .fail(function (data) { + $result.val(data.responseJSON.error); + $preview.attr('src', ''); + $loadingMask.removeClass('loading'); }); + } + + $form.on('change', $.debounce(500, formChangeHandler)); + $form.on('keyup', $.debounce(500, formChangeHandler)); + formChangeHandler(); + + // When the user clicks the URL, automatically select the whole thing (for easier copying) + $result.on('click', function () { + $(this).trigger('select'); }); + }); }); diff --git a/wagtail/images/static_src/wagtailimages/scss/add-multiple.scss b/wagtail/images/static_src/wagtailimages/scss/add-multiple.scss index 348521660a..50c6dbc07f 100644 --- a/wagtail/images/static_src/wagtailimages/scss/add-multiple.scss +++ b/wagtail/images/static_src/wagtailimages/scss/add-multiple.scss @@ -1,153 +1,153 @@ -@use "sass:color"; +@use 'sass:color'; @import '../../../../../client/scss/settings'; @import '../../../../../client/scss/tools'; .replace-file-input { - display: inline-block; - position: relative; - overflow: hidden; - padding-bottom: 2px; + display: inline-block; + position: relative; + overflow: hidden; + padding-bottom: 2px; - [type=file] { - padding: 0; - opacity: 0; - position: absolute; - top: 0; - right: 0; - direction: ltr; - width: auto; - display: block; - font-size: 5em; - - &:hover { - cursor: pointer; - } - } + [type='file'] { + padding: 0; + opacity: 0; + position: absolute; + top: 0; + right: 0; + direction: ltr; + width: auto; + display: block; + font-size: 5em; &:hover { - cursor: pointer; - - button { - background-color: $color-teal-darker; - } + cursor: pointer; } + } + + &:hover { + cursor: pointer; + + button { + background-color: $color-teal-darker; + } + } } .upload-list { - > li { - padding: 1em; - } + > li { + padding: 1em; + } - .left { - text-align: center; - word-break: break-all; + .left { + text-align: center; + word-break: break-all; + } + + .preview { + width: 150px; + min-height: 150px; + display: block; + position: relative; + text-align: center; + max-width: 100%; + margin: auto; + } + + .progress, + .thumb, + .thumb:before, + canvas, + img { + position: absolute; + max-width: 100%; + } + + .progress { + box-shadow: 0 0 5px 2px rgba(255, 255, 255, 0.4); + z-index: 4; + top: 60%; + left: 20%; + right: 20%; + width: 60%; + } + + .thumb { + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + width: 100%; + } + + .thumb:before, + canvas, + img { + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + } + + .thumb:before { + z-index: 2; + top: 0; + width: 1em; + font-size: 10em; + line-height: 1.4em; + color: color.adjust($color-grey-4, $lightness: 4%); + } + + canvas, + img { + z-index: 3; + } + + .hasthumb { + &:before { + display: none; } + } + + .status-msg { + display: none; + } + + .upload-complete { + .progress { + opacity: 0; + } + } + + .upload-success { + .status-msg.success { + display: block; + } + } + + .upload-failure { + border-color: $color-red; .preview { - width: 150px; - min-height: 150px; - display: block; - position: relative; - text-align: center; - max-width: 100%; - margin: auto; + display: none; } - .progress, - .thumb, - .thumb:before, - canvas, - img { - position: absolute; - max-width: 100%; + .status-msg.failure { + display: block; + } + } + + .upload-server-error { + border-color: $color-red; + + .preview { + display: none; } - .progress { - box-shadow: 0 0 5px 2px rgba(255, 255, 255, 0.4); - z-index: 4; - top: 60%; - left: 20%; - right: 20%; - width: 60%; - } - - .thumb { - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - width: 100%; - } - - .thumb:before, - canvas, - img { - left: 0; - right: 0; - top: 0; - bottom: 0; - margin: auto; - } - - .thumb:before { - z-index: 2; - top: 0; - width: 1em; - font-size: 10em; - line-height: 1.4em; - color: color.adjust($color-grey-4, $lightness: 4%); - } - - canvas, - img { - z-index: 3; - } - - .hasthumb { - &:before { - display: none; - } - } - - .status-msg { - display: none; - } - - .upload-complete { - .progress { - opacity: 0; - } - } - - .upload-success { - .status-msg.success { - display: block; - } - } - - .upload-failure { - border-color: $color-red; - - .preview { - display: none; - } - - .status-msg.failure { - display: block; - } - } - - .upload-server-error { - border-color: $color-red; - - .preview { - display: none; - } - - .status-msg.server-error { - display: block; - } + .status-msg.server-error { + display: block; } + } } diff --git a/wagtail/images/static_src/wagtailimages/scss/focal-point-chooser.scss b/wagtail/images/static_src/wagtailimages/scss/focal-point-chooser.scss index 507f0d8461..a96232e103 100644 --- a/wagtail/images/static_src/wagtailimages/scss/focal-point-chooser.scss +++ b/wagtail/images/static_src/wagtailimages/scss/focal-point-chooser.scss @@ -2,22 +2,22 @@ @import '../../../../../client/scss/tools'; .focal-point-chooser { - position: relative; - margin-bottom: 20px; + position: relative; + margin-bottom: 20px; - .current-focal-point-indicator { - @include transition(opacity 0.2s ease); - box-shadow: 1px 1px 10px 0 rgba(0, 0, 0, 1); - position: absolute; - border: 3px solid $color-teal; - opacity: 0.7; + .current-focal-point-indicator { + @include transition(opacity 0.2s ease); + box-shadow: 1px 1px 10px 0 rgba(0, 0, 0, 1); + position: absolute; + border: 3px solid $color-teal; + opacity: 0.7; - &.hidden { - display: none; - } + &.hidden { + display: none; } + } - &:hover .current-focal-point-indicator { - opacity: 0; - } + &:hover .current-focal-point-indicator { + opacity: 0; + } } diff --git a/wagtail/search/static_src/wagtailsearch/js/query-chooser-modal.js b/wagtail/search/static_src/wagtailsearch/js/query-chooser-modal.js index 4cc1c79229..275602751b 100644 --- a/wagtail/search/static_src/wagtailsearch/js/query-chooser-modal.js +++ b/wagtail/search/static_src/wagtailsearch/js/query-chooser-modal.js @@ -1,74 +1,74 @@ QUERY_CHOOSER_MODAL_ONLOAD_HANDLERS = { - 'chooser': function(modal, jsonData) { - function ajaxifyLinks (context) { - $('.listing a.choose-query', context).on('click', chooseQuery); + chooser: function (modal, jsonData) { + function ajaxifyLinks(context) { + $('.listing a.choose-query', context).on('click', chooseQuery); - $('.pagination a', context).on('click', function() { - var page = this.getAttribute('data-page'); - setPage(page); - return false; - }); - } - - var searchUrl = $('form.query-search', modal.body).attr('action'); - var request; - - function search() { - request = $.ajax({ - url: searchUrl, - data: { q: $('#id_q').val() }, - success: function(data, status) { - request = null; - $('#query-results').html(data); - ajaxifyLinks($('#query-results')); - }, - error: function() { - request = null; - } - }); - return false; - } - function setPage(page) { - var dataObj; - - if ($('#id_q').val().length) { - dataObj = { q: $('#id_q').val(), p: page }; - } else { - dataObj = { p: page }; - } - - request = $.ajax({ - url: searchUrl, - data: dataObj, - success: function(data, status) { - request = null; - $('#query-results').html(data); - ajaxifyLinks($('#query-results')); - }, - error: function() { - request = null; - } - }); - return false; - } - function chooseQuery() { - modal.respond('queryChosen', $(this).data()); - modal.close(); - - return false; - } - - ajaxifyLinks(modal.body); - - $('form.query-search', modal.body).on('submit', search); - - $('#id_q').on('input', function() { - if (request) { - request.abort(); - } - clearTimeout($.data(this, 'timer')); - var wait = setTimeout(search, 200); - $(this).data('timer', wait); - }); + $('.pagination a', context).on('click', function () { + var page = this.getAttribute('data-page'); + setPage(page); + return false; + }); } + + var searchUrl = $('form.query-search', modal.body).attr('action'); + var request; + + function search() { + request = $.ajax({ + url: searchUrl, + data: { q: $('#id_q').val() }, + success: function (data, status) { + request = null; + $('#query-results').html(data); + ajaxifyLinks($('#query-results')); + }, + error: function () { + request = null; + }, + }); + return false; + } + function setPage(page) { + var dataObj; + + if ($('#id_q').val().length) { + dataObj = { q: $('#id_q').val(), p: page }; + } else { + dataObj = { p: page }; + } + + request = $.ajax({ + url: searchUrl, + data: dataObj, + success: function (data, status) { + request = null; + $('#query-results').html(data); + ajaxifyLinks($('#query-results')); + }, + error: function () { + request = null; + }, + }); + return false; + } + function chooseQuery() { + modal.respond('queryChosen', $(this).data()); + modal.close(); + + return false; + } + + ajaxifyLinks(modal.body); + + $('form.query-search', modal.body).on('submit', search); + + $('#id_q').on('input', function () { + if (request) { + request.abort(); + } + clearTimeout($.data(this, 'timer')); + var wait = setTimeout(search, 200); + $(this).data('timer', wait); + }); + }, }; diff --git a/wagtail/search/templates/wagtailsearch/queries/chooser_field.js b/wagtail/search/templates/wagtailsearch/queries/chooser_field.js index 40ad8283cc..65cbc13d31 100644 --- a/wagtail/search/templates/wagtailsearch/queries/chooser_field.js +++ b/wagtail/search/templates/wagtailsearch/queries/chooser_field.js @@ -1,18 +1,18 @@ function createQueryChooser(id) { - var chooserElement = $('#' + id + '-chooser'); - var input = $('#' + id); + var chooserElement = $('#' + id + '-chooser'); + var input = $('#' + id); - chooserElement.on('click', function() { - var initialUrl = '{% url "wagtailsearch_admin:queries_chooser" %}'; + chooserElement.on('click', function () { + var initialUrl = '{% url "wagtailsearch_admin:queries_chooser" %}'; - ModalWorkflow({ - url: initialUrl, - onload: QUERY_CHOOSER_MODAL_ONLOAD_HANDLERS, - responses: { - queryChosen: function(queryData) { - input.val(queryData.querystring); - } - } - }); + ModalWorkflow({ + url: initialUrl, + onload: QUERY_CHOOSER_MODAL_ONLOAD_HANDLERS, + responses: { + queryChosen: function (queryData) { + input.val(queryData.querystring); + }, + }, }); + }); } diff --git a/wagtail/snippets/static_src/wagtailsnippets/js/snippet-chooser-modal.js b/wagtail/snippets/static_src/wagtailsnippets/js/snippet-chooser-modal.js index 26a7dfb0a0..03723ec681 100644 --- a/wagtail/snippets/static_src/wagtailsnippets/js/snippet-chooser-modal.js +++ b/wagtail/snippets/static_src/wagtailsnippets/js/snippet-chooser-modal.js @@ -1,60 +1,60 @@ SNIPPET_CHOOSER_MODAL_ONLOAD_HANDLERS = { - 'choose': function(modal, jsonData) { - function ajaxifyLinks(context) { - $('a.snippet-choice', modal.body).on('click', function() { - modal.loadUrl(this.href); - return false; - }); + choose: function (modal, jsonData) { + function ajaxifyLinks(context) { + $('a.snippet-choice', modal.body).on('click', function () { + modal.loadUrl(this.href); + return false; + }); - $('.pagination a', context).on('click', function() { - loadResults(this.href); - return false; - }); - } - - var searchForm$ = $('form.snippet-search', modal.body); - var searchUrl = searchForm$.attr('action'); - var request; - - function search() { - loadResults(searchUrl, searchForm$.serialize()); - return false; - } - - function loadResults(url, data) { - var opts = { - url: url, - success: function(resultsData, status) { - request = null; - $('#search-results').html(resultsData); - ajaxifyLinks($('#search-results')); - }, - error: function() { - request = null; - } - }; - if (data) { - opts.data = data; - } - request = $.ajax(opts); - } - - $('form.snippet-search', modal.body).on('submit', search); - $('#snippet-chooser-locale', modal.body).on('change', search); - - $('#id_q').on('input', function() { - if (request) { - request.abort(); - } - clearTimeout($.data(this, 'timer')); - var wait = setTimeout(search, 200); - $(this).data('timer', wait); - }); - - ajaxifyLinks(modal.body); - }, - 'chosen': function(modal, jsonData) { - modal.respond('snippetChosen', jsonData.result); - modal.close(); + $('.pagination a', context).on('click', function () { + loadResults(this.href); + return false; + }); } + + var searchForm$ = $('form.snippet-search', modal.body); + var searchUrl = searchForm$.attr('action'); + var request; + + function search() { + loadResults(searchUrl, searchForm$.serialize()); + return false; + } + + function loadResults(url, data) { + var opts = { + url: url, + success: function (resultsData, status) { + request = null; + $('#search-results').html(resultsData); + ajaxifyLinks($('#search-results')); + }, + error: function () { + request = null; + }, + }; + if (data) { + opts.data = data; + } + request = $.ajax(opts); + } + + $('form.snippet-search', modal.body).on('submit', search); + $('#snippet-chooser-locale', modal.body).on('change', search); + + $('#id_q').on('input', function () { + if (request) { + request.abort(); + } + clearTimeout($.data(this, 'timer')); + var wait = setTimeout(search, 200); + $(this).data('timer', wait); + }); + + ajaxifyLinks(modal.body); + }, + chosen: function (modal, jsonData) { + modal.respond('snippetChosen', jsonData.result); + modal.close(); + }, }; diff --git a/wagtail/snippets/static_src/wagtailsnippets/js/snippet-multiple-select.js b/wagtail/snippets/static_src/wagtailsnippets/js/snippet-multiple-select.js index c1ae7e1f52..ae2c3908c1 100644 --- a/wagtail/snippets/static_src/wagtailsnippets/js/snippet-multiple-select.js +++ b/wagtail/snippets/static_src/wagtailsnippets/js/snippet-multiple-select.js @@ -1,103 +1,111 @@ -var updateRow = function(id, newValue) { - var $row = $('table.listing tr#snippet-row-' + id); - var $checklist = $row.find('input[type=checkbox].toggle-select-row'); - $checklist.prop('checked', newValue); +var updateRow = function (id, newValue) { + var $row = $('table.listing tr#snippet-row-' + id); + var $checklist = $row.find('input[type=checkbox].toggle-select-row'); + $checklist.prop('checked', newValue); + if (newValue) { + $row.addClass('selected'); + } else { + $row.removeClass('selected'); + } +}; + +var updateDeleteButton = function (anySelected, newState) { + var $deleteButton = $('a.button.delete-button'); + var ids = []; + $.each(newState, function (id, newValue) { if (newValue) { - $row.addClass('selected'); - } else { - $row.removeClass('selected'); + ids.push(id); } + }); + if (anySelected) { + // hide button and add url + $deleteButton.removeClass('u-hidden'); + var url = $deleteButton.data('url'); + url = url + $.param({ id: ids }, true); + $deleteButton.attr('href', url); + } else { + // show button and remove url + $deleteButton.addClass('u-hidden'); + $deleteButton.attr('href', null); + } }; -var updateDeleteButton = function(anySelected, newState) { - var $deleteButton = $('a.button.delete-button'); - var ids = []; - $.each(newState, function(id, newValue) { - if (newValue) { - ids.push(id); - } +var updateSelectAllCheckbox = function (value) { + var $selectAllCheckbox = $( + 'table.listing input[type=checkbox].toggle-select-all', + ); + $selectAllCheckbox.prop('checked', value); +}; + +var buildSelectedState = function () { + // prepare the selected state -- {3: true, 4: false} + var state = {}; + var $rows = $( + 'table.listing tbody tr input[type=checkbox].toggle-select-row', + ); + $.each($rows, function (index, row) { + var $row = $(row); + var selected = $row.prop('checked'); + var id = $row.attr('value'); + state[id] = selected; + }); + return state; +}; + +var updateSelectedState = function (state, newValue, idToUpdate) { + if (idToUpdate === null) { + // update all rows + $.each(state, function (id, currentValue) { + state[id] = newValue; }); - if (anySelected) { - // hide button and add url - $deleteButton.removeClass('u-hidden'); - var url = $deleteButton.data('url'); - url = url + $.param({ id: ids }, true); - $deleteButton.attr('href', url); + } else { + // update single row + state[idToUpdate] = newValue; + } + return state; +}; + +var updateView = function (newState) { + var allSelected = true; + var anySelected = false; + var countOfUnselected = 0; + var countOfSelected = 0; + + // update each row with the new value (selected or not) + $.each(newState, function (id, newValue) { + updateRow(id, newValue); + if (newValue === false) { + countOfUnselected += 1; } else { - // show button and remove url - $deleteButton.addClass('u-hidden'); - $deleteButton.attr('href', null); + countOfSelected += 1; } + }); + + // update the main checkbox for select all (if all are true) + if (countOfUnselected >= 1) { + allSelected = false; + } + updateSelectAllCheckbox(allSelected); + + // update the delete button + if (countOfSelected >= 1) { + anySelected = true; + } + updateDeleteButton(anySelected, newState); }; -var updateSelectAllCheckbox = function(value) { - var $selectAllCheckbox = $('table.listing input[type=checkbox].toggle-select-all'); - $selectAllCheckbox.prop('checked', value); -}; - -var buildSelectedState = function() { - // prepare the selected state -- {3: true, 4: false} - var state = {}; - var $rows = $('table.listing tbody tr input[type=checkbox].toggle-select-row'); - $.each($rows, function (index, row) { - var $row = $(row); - var selected = $row.prop('checked'); - var id = $row.attr('value'); - state[id] = selected; - }); - return state; -}; - -var updateSelectedState = function(state, newValue, idToUpdate) { - if (idToUpdate === null) { - // update all rows - $.each(state, function (id, currentValue) { - state[id] = newValue; - }); - } else { - // update single row - state[idToUpdate] = newValue; +var onListingCheckboxClick = function () { + $('table.listing input[type="checkbox"]').on('click', function (event) { + var $target = $(event.target); + var setToValue = $target.prop('checked'); + var currentState = buildSelectedState(); + var id = null; + if ($target.hasClass('toggle-select-row')) { + id = $target.attr('value'); } - return state; -}; - -var updateView = function(newState) { - var allSelected = true; - var anySelected = false; - var countOfUnselected = 0; - var countOfSelected = 0; - - // update each row with the new value (selected or not) - $.each(newState, function (id, newValue) { - updateRow(id, newValue); - if (newValue === false) { - countOfUnselected += 1; - } else { - countOfSelected += 1; - } - }); - - // update the main checkbox for select all (if all are true) - if (countOfUnselected >= 1) { allSelected = false; } - updateSelectAllCheckbox(allSelected); - - // update the delete button - if (countOfSelected >= 1) { anySelected = true; } - updateDeleteButton(anySelected, newState); -}; - -var onListingCheckboxClick = function() { - $('table.listing input[type="checkbox"]').on('click', function(event) { - var $target = $(event.target); - var setToValue = $target.prop('checked'); - var currentState = buildSelectedState(); - var id = null; - if ($target.hasClass('toggle-select-row')) { - id = $target.attr('value'); - } - var newState = updateSelectedState(currentState, setToValue, id); - updateView(newState); - }); + var newState = updateSelectedState(currentState, setToValue, id); + updateView(newState); + }); }; $(document).ready(onListingCheckboxClick); diff --git a/wagtail/tests/customuser/fixtures/test.json b/wagtail/tests/customuser/fixtures/test.json index 4b6f2780eb..cee81dc563 100644 --- a/wagtail/tests/customuser/fixtures/test.json +++ b/wagtail/tests/customuser/fixtures/test.json @@ -1,127 +1,114 @@ [ -{ + { "pk": 1, "model": "customuser.customuser", "fields": { - "username": "superuser", - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": true, - "is_staff": true, - "groups": [ - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "superuser@example.com" + "username": "superuser", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": true, + "is_staff": true, + "groups": [], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "superuser@example.com" } -}, -{ + }, + { "pk": 2, "model": "customuser.customuser", "fields": { - "username": "eventeditor", - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Event editors"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "eventeditor@example.com" + "username": "eventeditor", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Event editors"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "eventeditor@example.com" } -}, -{ + }, + { "pk": 3, "model": "customuser.customuser", "fields": { - "username": "eventmoderator", - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Event moderators"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "eventmoderator@example.com" + "username": "eventmoderator", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Event moderators"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "eventmoderator@example.com" } -}, -{ + }, + { "pk": 4, "model": "customuser.customuser", "fields": { - "username": "inactiveuser", - "first_name": "", - "last_name": "", - "is_active": false, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Event moderators"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "inactiveuser@example.com" + "username": "inactiveuser", + "first_name": "", + "last_name": "", + "is_active": false, + "is_superuser": false, + "is_staff": false, + "groups": [["Event moderators"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "inactiveuser@example.com" } -}, -{ + }, + { "pk": 5, "model": "customuser.customuser", "fields": { - "username": "siteeditor", - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Site-wide editors"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "siteeditor@example.com" + "username": "siteeditor", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Site-wide editors"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "siteeditor@example.com" } -}, -{ + }, + { "pk": 6, "model": "customuser.customuser", "fields": { - "username": "admin_only_user", - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Admin non-editors"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "admin_only_user@example.com" + "username": "admin_only_user", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Admin non-editors"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "admin_only_user@example.com" } -}, -{ + }, + { "pk": 7, "model": "customuser.customuser", "fields": { - "username": "corporateeditor", - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Corporate Editor"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "corporateeditor@example.com" + "username": "corporateeditor", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Corporate Editor"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "corporateeditor@example.com" } -} + } ] diff --git a/wagtail/tests/customuser/fixtures/test_explorable_pages.json b/wagtail/tests/customuser/fixtures/test_explorable_pages.json index 5b96cb9e4d..f72ec76669 100644 --- a/wagtail/tests/customuser/fixtures/test_explorable_pages.json +++ b/wagtail/tests/customuser/fixtures/test_explorable_pages.json @@ -1,112 +1,98 @@ [ -{ + { "pk": 1, "model": "customuser.customuser", "fields": { - "username": "superman", - "first_name": "Clark", - "last_name": "Kent", - "is_active": true, - "is_superuser": true, - "is_staff": true, - "groups": [ - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "superman@example.com" + "username": "superman", + "first_name": "Clark", + "last_name": "Kent", + "is_active": true, + "is_superuser": true, + "is_staff": true, + "groups": [], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "superman@example.com" } -}, -{ + }, + { "pk": 2, "model": "customuser.customuser", "fields": { - "username": "jane", - "first_name": "Jane", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ["Group 1"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "jane@example.com" + "username": "jane", + "first_name": "Jane", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [["Group 1"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "jane@example.com" } -}, -{ + }, + { "pk": 3, "model": "customuser.customuser", "fields": { - "username": "bob", - "first_name": "Bob", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ["Group 2"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "bob@example.com" + "username": "bob", + "first_name": "Bob", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [["Group 2"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "bob@example.com" } -}, -{ + }, + { "pk": 4, "model": "customuser.customuser", "fields": { - "username": "sam", - "first_name": "Sam", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ["Group 1"], - ["Group 2"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "sam@example.com" + "username": "sam", + "first_name": "Sam", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [["Group 1"], ["Group 2"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "sam@example.com" } -}, -{ + }, + { "pk": 5, "model": "customuser.customuser", "fields": { - "username": "mary", - "first_name": "Mary", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ], - "user_permissions": [ - ["access_admin", "wagtailadmin", "admin"] - ], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "mary@example.com" + "username": "mary", + "first_name": "Mary", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [], + "user_permissions": [["access_admin", "wagtailadmin", "admin"]], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "mary@example.com" } -}, -{ + }, + { "pk": 6, "model": "customuser.customuser", "fields": { - "username": "josh", - "first_name": "Josh", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ["Group 2"], - ["Group 3"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "josh@example.com" + "username": "josh", + "first_name": "Josh", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [["Group 2"], ["Group 3"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "josh@example.com" } -} -] \ No newline at end of file + } +] diff --git a/wagtail/tests/customuser/fixtures/test_specific.json b/wagtail/tests/customuser/fixtures/test_specific.json index c74987563a..1221daec87 100644 --- a/wagtail/tests/customuser/fixtures/test_specific.json +++ b/wagtail/tests/customuser/fixtures/test_specific.json @@ -1,19 +1,18 @@ [ - { - "pk": 1, - "model": "customuser.customuser", - "fields": { - "username": "superuser", - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": true, - "is_staff": true, - "groups": [ - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "superuser@example.com" - } + { + "pk": 1, + "model": "customuser.customuser", + "fields": { + "username": "superuser", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": true, + "is_staff": true, + "groups": [], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "superuser@example.com" } + } ] diff --git a/wagtail/tests/demosite/fixtures/demosite.json b/wagtail/tests/demosite/fixtures/demosite.json index 3e712971c1..64c0ff2de6 100644 --- a/wagtail/tests/demosite/fixtures/demosite.json +++ b/wagtail/tests/demosite/fixtures/demosite.json @@ -1,1170 +1,1110 @@ [ -{ + { "pk": 1, "model": "wagtailcore.page", "fields": { - "title": "Root", - "draft_title": "Root", - "numchild": 1, - "show_in_menus": false, - "live": true, - "seo_title": "", - "depth": 1, - "search_description": "", - "content_type": [ - "wagtailcore", - "page" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001", - "url_path": "/", - "slug": "root" + "title": "Root", + "draft_title": "Root", + "numchild": 1, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 1, + "search_description": "", + "content_type": ["wagtailcore", "page"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001", + "url_path": "/", + "slug": "root" } -}, -{ + }, + { "pk": 2, "model": "wagtailcore.page", "fields": { - "title": "Home page", - "draft_title": "Home page", - "numchild": 5, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 2, - "search_description": "", - "content_type": [ - "demosite", - "homepage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "00010002", - "url_path": "/home-page/", - "slug": "home-page" + "title": "Home page", + "draft_title": "Home page", + "numchild": 5, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 2, + "search_description": "", + "content_type": ["demosite", "homepage"], + "has_unpublished_changes": false, + "owner": null, + "path": "00010002", + "url_path": "/home-page/", + "slug": "home-page" } -}, -{ + }, + { "pk": 4, "model": "wagtailcore.page", "fields": { - "title": "Events index", - "draft_title": "Events index", - "numchild": 2, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 3, - "search_description": "", - "content_type": [ - "demosite", - "eventindexpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "000100020001", - "url_path": "/home-page/events-index/", - "slug": "events-index" + "title": "Events index", + "draft_title": "Events index", + "numchild": 2, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "", + "content_type": ["demosite", "eventindexpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020001", + "url_path": "/home-page/events-index/", + "slug": "events-index" } -}, -{ + }, + { "pk": 5, "model": "wagtailcore.page", "fields": { - "title": "Blog index", - "draft_title": "Blog index", - "numchild": 3, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 3, - "search_description": "", - "content_type": [ - "demosite", - "blogindexpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "000100020002", - "url_path": "/home-page/blog-index/", - "slug": "blog-index" + "title": "Blog index", + "draft_title": "Blog index", + "numchild": 3, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "", + "content_type": ["demosite", "blogindexpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020002", + "url_path": "/home-page/blog-index/", + "slug": "blog-index" } -}, -{ + }, + { "pk": 6, "model": "wagtailcore.page", "fields": { - "title": "Standard index", - "draft_title": "Standard index", - "numchild": 4, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 3, - "search_description": "", - "content_type": [ - "demosite", - "standardindexpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "000100020003", - "url_path": "/home-page/standard-index/", - "slug": "standard-index" + "title": "Standard index", + "draft_title": "Standard index", + "numchild": 4, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "", + "content_type": ["demosite", "standardindexpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020003", + "url_path": "/home-page/standard-index/", + "slug": "standard-index" } -}, -{ + }, + { "pk": 8, "model": "wagtailcore.page", "fields": { - "title": "Event 1", - "draft_title": "Event 1", - "numchild": 0, - "show_in_menus": false, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one", - "content_type": [ - "demosite", - "eventpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200010001", - "url_path": "/home-page/events-index/event-1/", - "slug": "event-1" + "title": "Event 1", + "draft_title": "Event 1", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one", + "content_type": ["demosite", "eventpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200010001", + "url_path": "/home-page/events-index/event-1/", + "slug": "event-1" } -}, -{ + }, + { "pk": 9, "model": "wagtailcore.page", "fields": { - "title": "Event 2", - "draft_title": "Event 2", - "numchild": 0, - "show_in_menus": false, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.", - "content_type": [ - "demosite", - "eventpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200010002", - "url_path": "/home-page/events-index/event-2/", - "slug": "event-2" + "title": "Event 2", + "draft_title": "Event 2", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.", + "content_type": ["demosite", "eventpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200010002", + "url_path": "/home-page/events-index/event-2/", + "slug": "event-2" } -}, -{ + }, + { "pk": 10, "model": "wagtailcore.page", "fields": { - "title": "Standard page 1", - "draft_title": "Standard page 1", - "numchild": 0, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters.", - "content_type": [ - "demosite", - "standardpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200030001", - "url_path": "/home-page/standard-index/standard-page-1/", - "slug": "standard-page-1" + "title": "Standard page 1", + "draft_title": "Standard page 1", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters.", + "content_type": ["demosite", "standardpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200030001", + "url_path": "/home-page/standard-index/standard-page-1/", + "slug": "standard-page-1" } -}, -{ + }, + { "pk": 12, "model": "wagtailcore.page", "fields": { - "title": "Contact page", - "draft_title": "Contact page", - "numchild": 0, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 3, - "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean...", - "content_type": [ - "demosite", - "contactpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "000100020005", - "url_path": "/home-page/contact-page/", - "slug": "contact-page" + "title": "Contact page", + "draft_title": "Contact page", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean...", + "content_type": ["demosite", "contactpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020005", + "url_path": "/home-page/contact-page/", + "slug": "contact-page" } -}, -{ + }, + { "pk": 13, "model": "wagtailcore.page", "fields": { - "title": "James Joyce", - "draft_title": "James Joyce", - "numchild": 0, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa", - "content_type": [ - "demosite", - "personpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200040001", - "url_path": "/home-page/people/james-joyce/", - "slug": "james-joyce" + "title": "James Joyce", + "draft_title": "James Joyce", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa", + "content_type": ["demosite", "personpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200040001", + "url_path": "/home-page/people/james-joyce/", + "slug": "james-joyce" } -}, -{ + }, + { "pk": 14, "model": "wagtailcore.page", "fields": { - "title": "David Mitchell", - "draft_title": "David Mitchell", - "numchild": 0, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.", - "content_type": [ - "demosite", - "personpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200040002", - "url_path": "/home-page/people/david-mitchell/", - "slug": "david-mitchell" + "title": "David Mitchell", + "draft_title": "David Mitchell", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.", + "content_type": ["demosite", "personpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200040002", + "url_path": "/home-page/people/david-mitchell/", + "slug": "david-mitchell" } -}, -{ + }, + { "pk": 15, "model": "wagtailcore.page", "fields": { - "title": "Standard page 2", - "draft_title": "Standard page 2", - "numchild": 0, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.", - "content_type": [ - "demosite", - "standardpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200030002", - "url_path": "/home-page/standard-index/standard-page-2/", - "slug": "standard-page-2" + "title": "Standard page 2", + "draft_title": "Standard page 2", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.", + "content_type": ["demosite", "standardpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200030002", + "url_path": "/home-page/standard-index/standard-page-2/", + "slug": "standard-page-2" } -}, -{ + }, + { "pk": 16, "model": "wagtailcore.page", "fields": { - "title": "Blog post", - "draft_title": "Blog post", - "numchild": 0, - "show_in_menus": false, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", - "content_type": [ - "demosite", - "blogentrypage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200020001", - "url_path": "/home-page/blog-index/blog-post/", - "slug": "blog-post" + "title": "Blog post", + "draft_title": "Blog post", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", + "content_type": ["demosite", "blogentrypage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200020001", + "url_path": "/home-page/blog-index/blog-post/", + "slug": "blog-post" } -}, -{ + }, + { "pk": 17, "model": "wagtailcore.page", "fields": { - "title": "Photo credits", - "draft_title": "Photo credits", - "numchild": 0, - "show_in_menus": false, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "", - "content_type": [ - "demosite", - "standardpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200030007", - "url_path": "/home-page/standard-index/photo-credits/", - "slug": "photo-credits" + "title": "Photo credits", + "draft_title": "Photo credits", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "", + "content_type": ["demosite", "standardpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200030007", + "url_path": "/home-page/standard-index/photo-credits/", + "slug": "photo-credits" } -}, -{ + }, + { "pk": 18, "model": "wagtailcore.page", "fields": { - "title": "Blog post again", - "draft_title": "Blog post again", - "numchild": 0, - "show_in_menus": false, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.", - "content_type": [ - "demosite", - "blogentrypage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200020002", - "url_path": "/home-page/blog-index/blog-post-again/", - "slug": "blog-post-again" + "title": "Blog post again", + "draft_title": "Blog post again", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.", + "content_type": ["demosite", "blogentrypage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200020002", + "url_path": "/home-page/blog-index/blog-post-again/", + "slug": "blog-post-again" } -}, -{ + }, + { "pk": 19, "model": "wagtailcore.page", "fields": { - "title": "Another blog post", - "draft_title": "Another blog post", - "numchild": 0, - "show_in_menus": false, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.", - "content_type": [ - "demosite", - "blogentrypage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200020003", - "url_path": "/home-page/blog-index/another-blog-post/", - "slug": "another-blog-post" + "title": "Another blog post", + "draft_title": "Another blog post", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.", + "content_type": ["demosite", "blogentrypage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200020003", + "url_path": "/home-page/blog-index/another-blog-post/", + "slug": "another-blog-post" } -}, -{ + }, + { "pk": 20, "model": "wagtailcore.page", "fields": { - "title": "People", - "draft_title": "People", - "numchild": 2, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 3, - "search_description": "", - "content_type": [ - "demosite", - "standardindexpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "000100020004", - "url_path": "/home-page/people/", - "slug": "people" + "title": "People", + "draft_title": "People", + "numchild": 2, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "", + "content_type": ["demosite", "standardindexpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020004", + "url_path": "/home-page/people/", + "slug": "people" } -}, -{ + }, + { "pk": 21, "model": "wagtailcore.page", "fields": { - "title": "A deeper menu level", - "draft_title": "A deeper menu level", - "numchild": 2, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 4, - "search_description": "", - "content_type": [ - "demosite", - "standardindexpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "0001000200030008", - "url_path": "/home-page/standard-index/a-deeper-menu-level/", - "slug": "a-deeper-menu-level" + "title": "A deeper menu level", + "draft_title": "A deeper menu level", + "numchild": 2, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "", + "content_type": ["demosite", "standardindexpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200030008", + "url_path": "/home-page/standard-index/a-deeper-menu-level/", + "slug": "a-deeper-menu-level" } -}, -{ + }, + { "pk": 22, "model": "wagtailcore.page", "fields": { - "title": "A grandchild page", - "draft_title": "A grandchild page", - "numchild": 0, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 5, - "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", - "content_type": [ - "demosite", - "standardpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "00010002000300080001", - "url_path": "/home-page/standard-index/a-deeper-menu-level/a-grandchild-page/", - "slug": "a-grandchild-page" + "title": "A grandchild page", + "draft_title": "A grandchild page", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 5, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", + "content_type": ["demosite", "standardpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "00010002000300080001", + "url_path": "/home-page/standard-index/a-deeper-menu-level/a-grandchild-page/", + "slug": "a-grandchild-page" } -}, -{ + }, + { "pk": 23, "model": "wagtailcore.page", "fields": { - "title": "Another grandchild page", - "draft_title": "Another grandchild page", - "numchild": 0, - "show_in_menus": true, - "live": true, - "seo_title": "", - "depth": 5, - "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", - "content_type": [ - "demosite", - "standardpage" - ], - "has_unpublished_changes": false, - "owner": null, - "path": "00010002000300080002", - "url_path": "/home-page/standard-index/a-deeper-menu-level/another-grandchild-page/", - "slug": "another-grandchild-page" + "title": "Another grandchild page", + "draft_title": "Another grandchild page", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 5, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", + "content_type": ["demosite", "standardpage"], + "has_unpublished_changes": false, + "owner": null, + "path": "00010002000300080002", + "url_path": "/home-page/standard-index/a-deeper-menu-level/another-grandchild-page/", + "slug": "another-grandchild-page" } -}, -{ + }, + { "pk": 4, "model": "wagtailimages.image", "fields": { - "title": "Wagtail by mark Harkin", - "created_at": "2014-02-06T10:14:47.173Z", - "height": 427, - "width": 640, - "file": "original_images/wagtail_by_markyharky.jpg", - "uploaded_by_user": null + "title": "Wagtail by mark Harkin", + "created_at": "2014-02-06T10:14:47.173Z", + "height": 427, + "width": 640, + "file": "original_images/wagtail_by_markyharky.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 5, "model": "wagtailimages.image", "fields": { - "title": "James Joyce", - "created_at": "2014-02-06T10:37:10.518Z", - "height": 392, - "width": 500, - "file": "original_images/James_Joyce_in_1915.jpg", - "uploaded_by_user": null + "title": "James Joyce", + "created_at": "2014-02-06T10:37:10.518Z", + "height": 392, + "width": 500, + "file": "original_images/James_Joyce_in_1915.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 6, "model": "wagtailimages.image", "fields": { - "title": "David Mitchell", - "created_at": "2014-02-06T10:42:46.536Z", - "height": 282, - "width": 360, - "file": "original_images/David_Mitchell_by_Kubik.JPG", - "uploaded_by_user": null + "title": "David Mitchell", + "created_at": "2014-02-06T10:42:46.536Z", + "height": 282, + "width": 360, + "file": "original_images/David_Mitchell_by_Kubik.JPG", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 7, "model": "wagtailimages.image", "fields": { - "title": "Wagtail by joe Buckingham", - "created_at": "2014-02-06T10:49:29.579Z", - "height": 397, - "width": 640, - "file": "original_images/wagtail_by_joe_buckingham.jpg", - "uploaded_by_user": null + "title": "Wagtail by joe Buckingham", + "created_at": "2014-02-06T10:49:29.579Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_by_joe_buckingham.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 8, "model": "wagtailimages.image", "fields": { - "title": "Wagtail by fs-phil", - "created_at": "2014-02-06T10:54:29.963Z", - "height": 397, - "width": 640, - "file": "original_images/wagtail_by_fs-phil.jpg", - "uploaded_by_user": null + "title": "Wagtail by fs-phil", + "created_at": "2014-02-06T10:54:29.963Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_by_fs-phil.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 9, "model": "wagtailimages.image", "fields": { - "title": "White wagtail by Koshy Koshy", - "created_at": "2014-02-06T10:57:05.536Z", - "height": 397, - "width": 640, - "file": "original_images/white_wagtail_by_Koshyk.jpg", - "uploaded_by_user": null + "title": "White wagtail by Koshy Koshy", + "created_at": "2014-02-06T10:57:05.536Z", + "height": 397, + "width": 640, + "file": "original_images/white_wagtail_by_Koshyk.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 10, "model": "wagtailimages.image", "fields": { - "title": "Pied wagtail by Marie Hale", - "created_at": "2014-02-06T11:05:12.370Z", - "height": 397, - "width": 640, - "file": "original_images/pied_wagtail_by_Marie_Hale.jpg", - "uploaded_by_user": null + "title": "Pied wagtail by Marie Hale", + "created_at": "2014-02-06T11:05:12.370Z", + "height": 397, + "width": 640, + "file": "original_images/pied_wagtail_by_Marie_Hale.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 11, "model": "wagtailimages.image", "fields": { - "title": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", - "created_at": "2014-02-06T11:08:09.355Z", - "height": 397, - "width": 640, - "file": "original_images/wagtail_at_Borovoye_Kazakhstan_by_Ken_and_Nyetta.jpg", - "uploaded_by_user": null + "title": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", + "created_at": "2014-02-06T11:08:09.355Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_at_Borovoye_Kazakhstan_by_Ken_and_Nyetta.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 12, "model": "wagtailimages.image", "fields": { - "title": "Wagtail sproing by Jim Bendon", - "created_at": "2014-02-06T11:10:01.185Z", - "height": 397, - "width": 640, - "file": "original_images/wagtail_sproing_by_Jim_Bendon.jpg", - "uploaded_by_user": null + "title": "Wagtail sproing by Jim Bendon", + "created_at": "2014-02-06T11:10:01.185Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_sproing_by_Jim_Bendon.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 13, "model": "wagtailimages.image", "fields": { - "title": "Hopalong wagtail by Ruth Flickr", - "created_at": "2014-02-06T11:15:32.454Z", - "height": 397, - "width": 640, - "file": "original_images/hopalong_wagtail_by_Ruth_Flickr.jpg", - "uploaded_by_user": null + "title": "Hopalong wagtail by Ruth Flickr", + "created_at": "2014-02-06T11:15:32.454Z", + "height": 397, + "width": 640, + "file": "original_images/hopalong_wagtail_by_Ruth_Flickr.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 14, "model": "wagtailimages.image", "fields": { - "title": "Wagtail collects insects by Margrit", - "created_at": "2014-02-06T11:21:13.596Z", - "height": 397, - "width": 640, - "file": "original_images/wagtail_collects_insects_by_Maggi_94.jpg", - "uploaded_by_user": null + "title": "Wagtail collects insects by Margrit", + "created_at": "2014-02-06T11:21:13.596Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_collects_insects_by_Maggi_94.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 15, "model": "wagtailimages.image", "fields": { - "title": "Grey wagtail by Lip Kee", - "created_at": "2014-02-06T11:23:36.409Z", - "height": 397, - "width": 640, - "file": "original_images/grey_wagtail_by_lip_kee.jpg", - "uploaded_by_user": null + "title": "Grey wagtail by Lip Kee", + "created_at": "2014-02-06T11:23:36.409Z", + "height": 397, + "width": 640, + "file": "original_images/grey_wagtail_by_lip_kee.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 5, "model": "demosite.blogindexpage", "fields": { - "intro": "<p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</p>" + "intro": "<p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</p>" } -}, -{ + }, + { "pk": 1, "model": "demosite.blogindexpagerelatedlink", "fields": { - "link_page": 4, - "title": "Events index", - "link_external": "", - "sort_order": 0, - "link_document": null, - "page": 5 + "link_page": 4, + "title": "Events index", + "link_external": "", + "sort_order": 0, + "link_document": null, + "page": 5 } -}, -{ + }, + { "pk": 16, "model": "demosite.blogentrypage", "fields": { - "body": "<p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.</p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p><p><embed alt=\"Pied wagtail by Marie Hale\" embedtype=\"image\" format=\"left\" id=\"10\"/><embed alt=\"Wagtail Sproing by Jim Bendon\" embedtype=\"image\" format=\"right\" id=\"12\"/><br/></p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.</p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.</p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p>", - "date": "2013-12-02", - "feed_image": 7 + "body": "<p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.</p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p><p><embed alt=\"Pied wagtail by Marie Hale\" embedtype=\"image\" format=\"left\" id=\"10\"/><embed alt=\"Wagtail Sproing by Jim Bendon\" embedtype=\"image\" format=\"right\" id=\"12\"/><br/></p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.</p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.</p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p>", + "date": "2013-12-02", + "feed_image": 7 } -}, -{ + }, + { "pk": 18, "model": "demosite.blogentrypage", "fields": { - "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p><br/></p><p><embed alt=\"Grey wagtail by Lip Kee\" embedtype=\"image\" format=\"fullwidth\" id=\"15\"/><br/></p><p><br/></p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.</p><p><br/></p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.<br/></p><p><br/></p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p><p><br/></p><p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.<br/></p><p><br/></p><p>Prehistoric wagtails known from fossils are Motacilla humata and Motacilla major.<br/></p><p><br/></p><p>See the species accounts for more on individual species' relationships.</p>", - "date": "2014-01-10", - "feed_image": 15 + "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p><br/></p><p><embed alt=\"Grey wagtail by Lip Kee\" embedtype=\"image\" format=\"fullwidth\" id=\"15\"/><br/></p><p><br/></p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.</p><p><br/></p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.<br/></p><p><br/></p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p><p><br/></p><p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.<br/></p><p><br/></p><p>Prehistoric wagtails known from fossils are Motacilla humata and Motacilla major.<br/></p><p><br/></p><p>See the species accounts for more on individual species' relationships.</p>", + "date": "2014-01-10", + "feed_image": 15 } -}, -{ + }, + { "pk": 19, "model": "demosite.blogentrypage", "fields": { - "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p><br/></p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.<br/></p><p><br/></p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.<br/></p><p><br/></p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p><p><br/></p><p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.<br/></p><p><br/></p><p>Prehistoric wagtails known from fossils are Motacilla humata and Motacilla major.<br/></p><p><br/></p><p>See the species accounts for more on individual species' relationships.<br/></p>", - "date": "2014-02-01", - "feed_image": 14 + "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p><br/></p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.<br/></p><p><br/></p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.<br/></p><p><br/></p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p><p><br/></p><p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.<br/></p><p><br/></p><p>Prehistoric wagtails known from fossils are Motacilla humata and Motacilla major.<br/></p><p><br/></p><p>See the species accounts for more on individual species' relationships.<br/></p>", + "date": "2014-02-01", + "feed_image": 14 } -}, -{ + }, + { "pk": 1, "model": "demosite.blogentrypagecarouselitem", "fields": { - "link_page": null, - "embed_url": "", - "image": 9, - "link_external": "http://www.flickr.com/photos/kkoshy/", - "caption": "White wagtail by Koshy Koshy", - "sort_order": 0, - "link_document": null, - "page": 16 + "link_page": null, + "embed_url": "", + "image": 9, + "link_external": "http://www.flickr.com/photos/kkoshy/", + "caption": "White wagtail by Koshy Koshy", + "sort_order": 0, + "link_document": null, + "page": 16 } -}, -{ + }, + { "pk": 2, "model": "demosite.blogentrypagecarouselitem", "fields": { - "link_page": null, - "embed_url": "", - "image": 7, - "link_external": "http://www.flickr.com/photos/jim_bendon_1957/", - "caption": "Wagtail by Jim Bendon", - "sort_order": 1, - "link_document": null, - "page": 16 + "link_page": null, + "embed_url": "", + "image": 7, + "link_external": "http://www.flickr.com/photos/jim_bendon_1957/", + "caption": "Wagtail by Jim Bendon", + "sort_order": 1, + "link_document": null, + "page": 16 } -}, -{ + }, + { "pk": 4, "model": "demosite.blogentrypagetag", "fields": { - "content_object": 19, - "tag": 5 + "content_object": 19, + "tag": 5 } -}, -{ + }, + { "pk": 5, "model": "demosite.blogentrypagetag", "fields": { - "content_object": 16, - "tag": 4 + "content_object": 16, + "tag": 4 } -}, -{ + }, + { "pk": 6, "model": "demosite.blogentrypagetag", "fields": { - "content_object": 16, - "tag": 5 + "content_object": 16, + "tag": 5 } -}, -{ + }, + { "pk": 7, "model": "demosite.blogentrypagetag", "fields": { - "content_object": 18, - "tag": 4 + "content_object": 18, + "tag": 4 } -}, -{ + }, + { "pk": 12, "model": "demosite.contactpage", "fields": { - "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p>", - "city": "Birdland", - "post_code": "W1A 1AA", - "country": "Birdshire", - "telephone": "012345 123456", - "address_1": "21 Tweety Mansions", - "address_2": "3 Bird Lane", - "email": "foo@example.com", - "feed_image": 7 + "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p>", + "city": "Birdland", + "post_code": "W1A 1AA", + "country": "Birdshire", + "telephone": "012345 123456", + "address_1": "21 Tweety Mansions", + "address_2": "3 Bird Lane", + "email": "foo@example.com", + "feed_image": 7 } -}, -{ + }, + { "pk": 4, "model": "demosite.eventindexpage", "fields": { - "intro": "<p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</p>" + "intro": "<p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</p>" } -}, -{ + }, + { "pk": 8, "model": "demosite.eventpage", "fields": { - "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.<br/></p>", - "feed_image": 8, - "date_from": "2018-02-28", - "time_from": "11:30:00", - "audience": "public", - "cost": "\u00a330 per person, \u00a310 concessions", - "location": "Royal Albert Hall", - "date_to": "2018-03-02", - "time_to": "19:00:00", - "signup_link": "http://www.eventbrite.com/" + "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.<br/></p>", + "feed_image": 8, + "date_from": "2018-02-28", + "time_from": "11:30:00", + "audience": "public", + "cost": "\u00a330 per person, \u00a310 concessions", + "location": "Royal Albert Hall", + "date_to": "2018-03-02", + "time_to": "19:00:00", + "signup_link": "http://www.eventbrite.com/" } -}, -{ + }, + { "pk": 9, "model": "demosite.eventpage", "fields": { - "body": "<p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution</p>", - "feed_image": 10, - "date_from": "2018-04-26", - "time_from": null, - "audience": "private", - "cost": "\u00a350", - "location": "O2 Arena", - "date_to": null, - "time_to": null, - "signup_link": "" + "body": "<p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution</p>", + "feed_image": 10, + "date_from": "2018-04-26", + "time_from": null, + "audience": "private", + "cost": "\u00a350", + "location": "O2 Arena", + "date_to": null, + "time_to": null, + "signup_link": "" } -}, -{ + }, + { "pk": 1, "model": "demosite.eventpagespeaker", "fields": { - "last_name": "Joyce", - "first_name": "James", - "link_page": null, - "image": 5, - "link_external": "", - "sort_order": 0, - "link_document": null, - "page": 8 + "last_name": "Joyce", + "first_name": "James", + "link_page": null, + "image": 5, + "link_external": "", + "sort_order": 0, + "link_document": null, + "page": 8 } -}, -{ + }, + { "pk": 2, "model": "demosite.eventpagespeaker", "fields": { - "last_name": "Mitchell", - "first_name": "David", - "link_page": null, - "image": 6, - "link_external": "", - "sort_order": 1, - "link_document": null, - "page": 8 + "last_name": "Mitchell", + "first_name": "David", + "link_page": null, + "image": 6, + "link_external": "", + "sort_order": 1, + "link_document": null, + "page": 8 } -}, -{ + }, + { "pk": 2, "model": "demosite.homepage", "fields": { - "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p>" + "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p>" } -}, -{ + }, + { "pk": 1, "model": "demosite.homepagecarouselitem", "fields": { - "link_page": null, - "embed_url": "", - "image": 15, - "link_external": "http://www.flickr.com/photos/lipkee/", - "caption": "Grey wagtail by Lip Kee", - "sort_order": 0, - "link_document": null, - "page": 2 + "link_page": null, + "embed_url": "", + "image": 15, + "link_external": "http://www.flickr.com/photos/lipkee/", + "caption": "Grey wagtail by Lip Kee", + "sort_order": 0, + "link_document": null, + "page": 2 } -}, -{ + }, + { "pk": 2, "model": "demosite.homepagecarouselitem", "fields": { - "link_page": null, - "embed_url": "", - "image": 12, - "link_external": "http://www.flickr.com/photos/jim_bendon_1957/", - "caption": "Wagtail sproing by Jim Bendon", - "sort_order": 1, - "link_document": null, - "page": 2 + "link_page": null, + "embed_url": "", + "image": 12, + "link_external": "http://www.flickr.com/photos/jim_bendon_1957/", + "caption": "Wagtail sproing by Jim Bendon", + "sort_order": 1, + "link_document": null, + "page": 2 } -}, -{ + }, + { "pk": 3, "model": "demosite.homepagecarouselitem", "fields": { - "link_page": null, - "embed_url": "", - "image": 11, - "link_external": "http://www.flickr.com/photos/kjfnjy/", - "caption": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", - "sort_order": 2, - "link_document": null, - "page": 2 + "link_page": null, + "embed_url": "", + "image": 11, + "link_external": "http://www.flickr.com/photos/kjfnjy/", + "caption": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", + "sort_order": 2, + "link_document": null, + "page": 2 } -}, -{ + }, + { "pk": 13, "model": "demosite.personpage", "fields": { - "feed_image": 5, - "city": "Birdland", - "first_name": "James", - "post_code": "W1A 1AA", - "country": "Birdshire", - "image": 5, - "telephone": "012345 123456", - "last_name": "Joyce", - "address_1": "21 Tweety Mansions", - "address_2": "3 Bird Lane", - "intro": "<p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean</p>", - "email": "foo@example.com", - "biography": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p><br/></p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.<br/></p>" + "feed_image": 5, + "city": "Birdland", + "first_name": "James", + "post_code": "W1A 1AA", + "country": "Birdshire", + "image": 5, + "telephone": "012345 123456", + "last_name": "Joyce", + "address_1": "21 Tweety Mansions", + "address_2": "3 Bird Lane", + "intro": "<p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean</p>", + "email": "foo@example.com", + "biography": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p><br/></p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.<br/></p>" } -}, -{ + }, + { "pk": 14, "model": "demosite.personpage", "fields": { - "feed_image": 6, - "city": "", - "first_name": "David", - "post_code": "W1A 1AA", - "country": "", - "image": 6, - "telephone": "", - "last_name": "Mitchell", - "address_1": "", - "address_2": "", - "intro": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World.</p>", - "email": "foo@example.com", - "biography": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p>" + "feed_image": 6, + "city": "", + "first_name": "David", + "post_code": "W1A 1AA", + "country": "", + "image": 6, + "telephone": "", + "last_name": "Mitchell", + "address_1": "", + "address_2": "", + "intro": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World.</p>", + "email": "foo@example.com", + "biography": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p>" } -}, -{ + }, + { "pk": 6, "model": "demosite.standardindexpage", "fields": { - "feed_image": null, - "intro": "<p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</p>" + "feed_image": null, + "intro": "<p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</p>" } -}, -{ + }, + { "pk": 20, "model": "demosite.standardindexpage", "fields": { - "feed_image": null, - "intro": "" + "feed_image": null, + "intro": "" } -}, -{ + }, + { "pk": 21, "model": "demosite.standardindexpage", "fields": { - "feed_image": null, - "intro": "<p>A listing of pages at the next level down</p>" + "feed_image": null, + "intro": "<p>A listing of pages at the next level down</p>" } -}, -{ + }, + { "pk": 10, "model": "demosite.standardpage", "fields": { - "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p><br/></p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.<br/></p><p><br/></p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.<br/></p><p><br/></p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p>", - "feed_image": 12, - "intro": "<p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.</p>" + "body": "<p>Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.</p><p><br/></p><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.<br/></p><p><br/></p><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.<br/></p><p><br/></p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p>", + "feed_image": 12, + "intro": "<p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.</p>" } -}, -{ + }, + { "pk": 15, "model": "demosite.standardpage", "fields": { - "body": "<h4>Wagtails are great</h4><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.</p><p><embed alt=\"Wagtail Sproing by Jim Bendon\" embedtype=\"image\" format=\"left\" id=\"12\"/><br/></p><h4>Wagtails are pretty</h4><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.<br/></p><p><embed alt=\"Pied wagtail by Marie Hale\" embedtype=\"image\" format=\"right\" id=\"10\"/><br/></p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p><p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.<br/></p>", - "feed_image": 10, - "intro": "<p>The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.</p>" + "body": "<h4>Wagtails are great</h4><p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.</p><p><embed alt=\"Wagtail Sproing by Jim Bendon\" embedtype=\"image\" format=\"left\" id=\"12\"/><br/></p><h4>Wagtails are pretty</h4><p>mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.<br/></p><p><embed alt=\"Pied wagtail by Marie Hale\" embedtype=\"image\" format=\"right\" id=\"10\"/><br/></p><p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.<br/></p><p>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.<br/></p>", + "feed_image": 10, + "intro": "<p>The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.</p>" } -}, -{ + }, + { "pk": 17, "model": "demosite.standardpage", "fields": { - "body": "<br/><p>James Joyce,\u00a01915,\u00a0<a href=\"http://epc.buffalo.edu/authors/joyce/image/1915.jpg\">Cornell Joyce Collection</a>\u00a0by C. Ruf - public domain in the United States</p>\n<p>David Mitchell (b. 1969), British writer,\u00a0Warsaw (Poland), April 7, 2006, \u00a0<a href=\"http://www.mariuszkubik.pl/\">Mariusz Kubik</a>, GDFL licence</p><p>Wagtail,\u00a0October 14, 2012\u00a0by <a href=\"http://www.flickr.com/photos/markyharky/\">Mark Harkin</a>, \u00a0\u00a0<a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Wagtail, February 18th 2008, by <a href=\"http://www.flickr.com/photos/oufoufsworld/\">Joe Buckingham</a>, <a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Wagtail, August 4th 2009, by <a href=\"http://www.flickr.com/photos/fsphil/\">fs-phil</a>, <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative Commons</a></p><p>White wagtail, February 5th 2012, by <a href=\"http://www.flickr.com/photos/kkoshy/\">Koshy Koshy</a>, <a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Pied wagtail, January 20th 2013, by <a href=\"http://www.flickr.com/photos/15016964@N02/\">Marie Hale</a>, <a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Wagtail at Borovoye, Kazakhstan, June 16th 2012, by <a href=\"http://www.flickr.com/photos/kjfnjy/\">Ken and Nyetta</a>,\u00a0<a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Wagtail sproing, April 29 2012, by <a href=\"http://www.flickr.com/photos/jim_bendon_1957/\">Jim Bendon</a>, <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative Commons</a></p><p>Hopalong wagtail, June 17th 2008, by <a href=\"http://www.flickr.com/photos/ruthhb/\">Ruth Flickr</a>, <a href=\"http://creativecommons.org/licenses/by-nc-sa/2.0/\">Creative Commons</a></p><p>Wagtail collects insects, June 10th 2010, by <a href=\"http://www.flickr.com/photos/27126314@N03/\">Margrit</a>, <a href=\"http://creativecommons.org/licenses/by-nc-sa/2.0/\">Creative Commons</a></p><p>Grey wagtail, March 13th 2009, by <a href=\"http://www.flickr.com/photos/lipkee/\">Lip Kee</a>, <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative Commons</a></p>", - "feed_image": 15, - "intro": "<p>The following photos have been used in the sample wagtailapi tests database</p>" + "body": "<br/><p>James Joyce,\u00a01915,\u00a0<a href=\"http://epc.buffalo.edu/authors/joyce/image/1915.jpg\">Cornell Joyce Collection</a>\u00a0by C. Ruf - public domain in the United States</p>\n<p>David Mitchell (b. 1969), British writer,\u00a0Warsaw (Poland), April 7, 2006, \u00a0<a href=\"http://www.mariuszkubik.pl/\">Mariusz Kubik</a>, GDFL licence</p><p>Wagtail,\u00a0October 14, 2012\u00a0by <a href=\"http://www.flickr.com/photos/markyharky/\">Mark Harkin</a>, \u00a0\u00a0<a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Wagtail, February 18th 2008, by <a href=\"http://www.flickr.com/photos/oufoufsworld/\">Joe Buckingham</a>, <a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Wagtail, August 4th 2009, by <a href=\"http://www.flickr.com/photos/fsphil/\">fs-phil</a>, <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative Commons</a></p><p>White wagtail, February 5th 2012, by <a href=\"http://www.flickr.com/photos/kkoshy/\">Koshy Koshy</a>, <a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Pied wagtail, January 20th 2013, by <a href=\"http://www.flickr.com/photos/15016964@N02/\">Marie Hale</a>, <a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Wagtail at Borovoye, Kazakhstan, June 16th 2012, by <a href=\"http://www.flickr.com/photos/kjfnjy/\">Ken and Nyetta</a>,\u00a0<a href=\"http://creativecommons.org/licenses/by/2.0/\">Creative Commons</a></p><p>Wagtail sproing, April 29 2012, by <a href=\"http://www.flickr.com/photos/jim_bendon_1957/\">Jim Bendon</a>, <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative Commons</a></p><p>Hopalong wagtail, June 17th 2008, by <a href=\"http://www.flickr.com/photos/ruthhb/\">Ruth Flickr</a>, <a href=\"http://creativecommons.org/licenses/by-nc-sa/2.0/\">Creative Commons</a></p><p>Wagtail collects insects, June 10th 2010, by <a href=\"http://www.flickr.com/photos/27126314@N03/\">Margrit</a>, <a href=\"http://creativecommons.org/licenses/by-nc-sa/2.0/\">Creative Commons</a></p><p>Grey wagtail, March 13th 2009, by <a href=\"http://www.flickr.com/photos/lipkee/\">Lip Kee</a>, <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">Creative Commons</a></p>", + "feed_image": 15, + "intro": "<p>The following photos have been used in the sample wagtailapi tests database</p>" } -}, -{ + }, + { "pk": 22, "model": "demosite.standardpage", "fields": { - "body": "<p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p><p><span><br/></span></p><p><span>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</span><br/></p>", - "feed_image": null, - "intro": "<p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours.</p>" + "body": "<p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p><p><span><br/></span></p><p><span>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</span><br/></p>", + "feed_image": null, + "intro": "<p>At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours.</p>" } -}, -{ + }, + { "pk": 23, "model": "demosite.standardpage", "fields": { - "body": "<p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p><p><span><br/></span></p><p><span>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</span><br/></p>", - "feed_image": null, - "intro": "<p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p>" + "body": "<p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p><p><span><br/></span></p><p><span>Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.</span><br/></p>", + "feed_image": null, + "intro": "<p>The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.</p>" } -}, -{ + }, + { "pk": 1, "model": "demosite.standardpagecarouselitem", "fields": { - "link_page": null, - "embed_url": "", - "image": 13, - "link_external": "http://www.flickr.com/photos/ruthhb/", - "caption": "Hopalong wagtail by Ruth Flickr", - "sort_order": 0, - "link_document": null, - "page": 10 + "link_page": null, + "embed_url": "", + "image": 13, + "link_external": "http://www.flickr.com/photos/ruthhb/", + "caption": "Hopalong wagtail by Ruth Flickr", + "sort_order": 0, + "link_document": null, + "page": 10 } -}, -{ + }, + { "pk": 2, "model": "demosite.standardpagecarouselitem", "fields": { - "link_page": null, - "embed_url": "", - "image": 15, - "link_external": "http://www.flickr.com/photos/lipkee/", - "caption": "Grey wagtail by Lip Kee", - "sort_order": 1, - "link_document": null, - "page": 10 + "link_page": null, + "embed_url": "", + "image": 15, + "link_external": "http://www.flickr.com/photos/lipkee/", + "caption": "Grey wagtail by Lip Kee", + "sort_order": 1, + "link_document": null, + "page": 10 } -}, -{ + }, + { "pk": 1, "model": "demosite.standardpagerelatedlink", "fields": { - "link_page": 4, - "title": "Internal link to events", - "link_external": "", - "sort_order": 0, - "link_document": null, - "page": 10 + "link_page": 4, + "title": "Internal link to events", + "link_external": "", + "sort_order": 0, + "link_document": null, + "page": 10 } -}, -{ + }, + { "pk": 2, "model": "demosite.standardpagerelatedlink", "fields": { - "link_page": null, - "title": "External link to google", - "link_external": "http://www.google.com/", - "sort_order": 1, - "link_document": null, - "page": 10 + "link_page": null, + "title": "External link to google", + "link_external": "http://www.google.com/", + "sort_order": 1, + "link_document": null, + "page": 10 } -}, -{ + }, + { "pk": 1, "model": "taggit.tag", "fields": { - "name": "writers", - "slug": "writers" + "name": "writers", + "slug": "writers" } -}, -{ + }, + { "pk": 2, "model": "taggit.tag", "fields": { - "name": "people", - "slug": "people" + "name": "people", + "slug": "people" } -}, -{ + }, + { "pk": 3, "model": "taggit.tag", "fields": { - "name": "person", - "slug": "person" + "name": "person", + "slug": "person" } -}, -{ + }, + { "pk": 4, "model": "taggit.tag", "fields": { - "name": "wagtail", - "slug": "wagtail" + "name": "wagtail", + "slug": "wagtail" } -}, -{ + }, + { "pk": 5, "model": "taggit.tag", "fields": { - "name": "bird", - "slug": "bird" + "name": "bird", + "slug": "bird" } -}, -{ + }, + { "pk": 6, "model": "taggit.tag", "fields": { - "name": "writer", - "slug": "writer" + "name": "writer", + "slug": "writer" } -}, -{ + }, + { "pk": 1, "model": "wagtaildocs.document", "fields": { - "title": "Wagtail by mark Harkin", - "created_at": "2014-02-06T10:14:47.173Z", - "file": "original_images/wagtail_by_markyharky.jpg", - "uploaded_by_user": null + "title": "Wagtail by mark Harkin", + "created_at": "2014-02-06T10:14:47.173Z", + "file": "original_images/wagtail_by_markyharky.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 2, "model": "wagtaildocs.document", "fields": { - "title": "James Joyce", - "created_at": "2014-02-06T10:37:10.518Z", - "file": "original_images/James_Joyce_in_1915.jpg", - "uploaded_by_user": null + "title": "James Joyce", + "created_at": "2014-02-06T10:37:10.518Z", + "file": "original_images/James_Joyce_in_1915.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 3, "model": "wagtaildocs.document", "fields": { - "title": "David Mitchell", - "created_at": "2014-02-06T10:42:46.536Z", - "file": "original_images/David_Mitchell_by_Kubik.JPG", - "uploaded_by_user": null + "title": "David Mitchell", + "created_at": "2014-02-06T10:42:46.536Z", + "file": "original_images/David_Mitchell_by_Kubik.JPG", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 4, "model": "wagtaildocs.document", "fields": { - "title": "Wagtail by joe Buckingham", - "created_at": "2014-02-06T10:49:29.579Z", - "file": "original_images/wagtail_by_joe_buckingham.jpg", - "uploaded_by_user": null + "title": "Wagtail by joe Buckingham", + "created_at": "2014-02-06T10:49:29.579Z", + "file": "original_images/wagtail_by_joe_buckingham.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 5, "model": "wagtaildocs.document", "fields": { - "title": "Wagtail by fs-phil", - "created_at": "2014-02-06T10:54:29.963Z", - "file": "original_images/wagtail_by_fs-phil.jpg", - "uploaded_by_user": null + "title": "Wagtail by fs-phil", + "created_at": "2014-02-06T10:54:29.963Z", + "file": "original_images/wagtail_by_fs-phil.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 6, "model": "wagtaildocs.document", "fields": { - "title": "White wagtail by Koshy Koshy", - "created_at": "2014-02-06T10:57:05.536Z", - "file": "original_images/white_wagtail_by_Koshyk.jpg", - "uploaded_by_user": null + "title": "White wagtail by Koshy Koshy", + "created_at": "2014-02-06T10:57:05.536Z", + "file": "original_images/white_wagtail_by_Koshyk.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 7, "model": "wagtaildocs.document", "fields": { - "title": "Pied wagtail by Marie Hale", - "created_at": "2014-02-06T11:05:12.370Z", - "file": "original_images/pied_wagtail_by_Marie_Hale.jpg", - "uploaded_by_user": null + "title": "Pied wagtail by Marie Hale", + "created_at": "2014-02-06T11:05:12.370Z", + "file": "original_images/pied_wagtail_by_Marie_Hale.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 8, "model": "wagtaildocs.document", "fields": { - "title": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", - "created_at": "2014-02-06T11:08:09.355Z", - "file": "original_images/wagtail_at_Borovoye_Kazakhstan_by_Ken_and_Nyetta.jpg", - "uploaded_by_user": null + "title": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", + "created_at": "2014-02-06T11:08:09.355Z", + "file": "original_images/wagtail_at_Borovoye_Kazakhstan_by_Ken_and_Nyetta.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 9, "model": "wagtaildocs.document", "fields": { - "title": "Wagtail sproing by Jim Bendon", - "created_at": "2014-02-06T11:10:01.185Z", - "file": "original_images/wagtail_sproing_by_Jim_Bendon.jpg", - "uploaded_by_user": null + "title": "Wagtail sproing by Jim Bendon", + "created_at": "2014-02-06T11:10:01.185Z", + "file": "original_images/wagtail_sproing_by_Jim_Bendon.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 10, "model": "wagtaildocs.document", "fields": { - "title": "Hopalong wagtail by Ruth Flickr", - "created_at": "2014-02-06T11:15:32.454Z", - "file": "original_images/hopalong_wagtail_by_Ruth_Flickr.jpg", - "uploaded_by_user": null + "title": "Hopalong wagtail by Ruth Flickr", + "created_at": "2014-02-06T11:15:32.454Z", + "file": "original_images/hopalong_wagtail_by_Ruth_Flickr.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 11, "model": "wagtaildocs.document", "fields": { - "title": "Wagtail collects insects by Margrit", - "created_at": "2014-02-06T11:21:13.596Z", - "file": "original_images/wagtail_collects_insects_by_Maggi_94.jpg", - "uploaded_by_user": null + "title": "Wagtail collects insects by Margrit", + "created_at": "2014-02-06T11:21:13.596Z", + "file": "original_images/wagtail_collects_insects_by_Maggi_94.jpg", + "uploaded_by_user": null } -}, -{ + }, + { "pk": 12, "model": "wagtaildocs.document", "fields": { - "title": "Grey wagtail by Lip Kee", - "created_at": "2014-02-06T11:23:36.409Z", - "file": "original_images/grey_wagtail_by_lip_kee.jpg", - "uploaded_by_user": null + "title": "Grey wagtail by Lip Kee", + "created_at": "2014-02-06T11:23:36.409Z", + "file": "original_images/grey_wagtail_by_lip_kee.jpg", + "uploaded_by_user": null } -} + } ] diff --git a/wagtail/tests/emailuser/fixtures/test.json b/wagtail/tests/emailuser/fixtures/test.json index 4be543b975..e85bb37b57 100644 --- a/wagtail/tests/emailuser/fixtures/test.json +++ b/wagtail/tests/emailuser/fixtures/test.json @@ -1,120 +1,107 @@ [ - { - "pk": "00000000-0000-0000-0000-000000000001", - "model": "emailuser.emailuser", - "fields": { - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": true, - "is_staff": true, - "groups": [ - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "superuser@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000002", - "model": "emailuser.emailuser", - "fields": { - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Event editors"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "eventeditor@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000003", - "model": "emailuser.emailuser", - "fields": { - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Event moderators"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "eventmoderator@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000004", - "model": "emailuser.emailuser", - "fields": { - "first_name": "", - "last_name": "", - "is_active": false, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Event moderators"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "inactiveuser@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000005", - "model": "emailuser.emailuser", - "fields": { - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Site-wide editors"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "siteeditor@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000006", - "model": "emailuser.emailuser", - "fields": { - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Admin non-editors"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "admin_only_user@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000007", - "model": "emailuser.emailuser", - "fields": { - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": false, - "is_staff": false, - "groups": [ - ["Corporate Editor"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "corporateeditor@example.com" - } + { + "pk": "00000000-0000-0000-0000-000000000001", + "model": "emailuser.emailuser", + "fields": { + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": true, + "is_staff": true, + "groups": [], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "superuser@example.com" } + }, + { + "pk": "00000000-0000-0000-0000-000000000002", + "model": "emailuser.emailuser", + "fields": { + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Event editors"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "eventeditor@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000003", + "model": "emailuser.emailuser", + "fields": { + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Event moderators"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "eventmoderator@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000004", + "model": "emailuser.emailuser", + "fields": { + "first_name": "", + "last_name": "", + "is_active": false, + "is_superuser": false, + "is_staff": false, + "groups": [["Event moderators"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "inactiveuser@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000005", + "model": "emailuser.emailuser", + "fields": { + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Site-wide editors"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "siteeditor@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000006", + "model": "emailuser.emailuser", + "fields": { + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Admin non-editors"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "admin_only_user@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000007", + "model": "emailuser.emailuser", + "fields": { + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "groups": [["Corporate Editor"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "corporateeditor@example.com" + } + } ] diff --git a/wagtail/tests/emailuser/fixtures/test_explorable_pages.json b/wagtail/tests/emailuser/fixtures/test_explorable_pages.json index e46c6f630a..80deedb943 100644 --- a/wagtail/tests/emailuser/fixtures/test_explorable_pages.json +++ b/wagtail/tests/emailuser/fixtures/test_explorable_pages.json @@ -1,106 +1,92 @@ [ - { - "pk": "00000000-0000-0000-0000-000000000001", - "model": "emailuser.emailuser", - "fields": { - "first_name": "Clark", - "last_name": "Kent", - "is_active": true, - "is_superuser": true, - "is_staff": true, - "groups": [ - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "superman@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000002", - "model": "emailuser.emailuser", - "fields": { - "first_name": "Jane", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ["Group 1"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "jane@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000003", - "model": "emailuser.emailuser", - "fields": { - "first_name": "Bob", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ["Group 2"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "bob@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000004", - "model": "emailuser.emailuser", - "fields": { - "first_name": "Sam", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ["Group 1"], - ["Group 2"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "sam@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000005", - "model": "emailuser.emailuser", - "fields": { - "first_name": "Mary", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ], - "user_permissions": [ - ["access_admin", "wagtailadmin", "admin"] - ], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "mary@example.com" - } - }, - { - "pk": "00000000-0000-0000-0000-000000000006", - "model": "emailuser.emailuser", - "fields": { - "first_name": "Josh", - "last_name": "Smith", - "is_active": true, - "is_superuser": false, - "is_staff": true, - "groups": [ - ["Group 2"], - ["Group 3"] - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "josh@example.com" - } + { + "pk": "00000000-0000-0000-0000-000000000001", + "model": "emailuser.emailuser", + "fields": { + "first_name": "Clark", + "last_name": "Kent", + "is_active": true, + "is_superuser": true, + "is_staff": true, + "groups": [], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "superman@example.com" } -] \ No newline at end of file + }, + { + "pk": "00000000-0000-0000-0000-000000000002", + "model": "emailuser.emailuser", + "fields": { + "first_name": "Jane", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [["Group 1"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "jane@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000003", + "model": "emailuser.emailuser", + "fields": { + "first_name": "Bob", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [["Group 2"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "bob@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000004", + "model": "emailuser.emailuser", + "fields": { + "first_name": "Sam", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [["Group 1"], ["Group 2"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "sam@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000005", + "model": "emailuser.emailuser", + "fields": { + "first_name": "Mary", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [], + "user_permissions": [["access_admin", "wagtailadmin", "admin"]], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "mary@example.com" + } + }, + { + "pk": "00000000-0000-0000-0000-000000000006", + "model": "emailuser.emailuser", + "fields": { + "first_name": "Josh", + "last_name": "Smith", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "groups": [["Group 2"], ["Group 3"]], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "josh@example.com" + } + } +] diff --git a/wagtail/tests/emailuser/fixtures/test_specific.json b/wagtail/tests/emailuser/fixtures/test_specific.json index 8a4e2cdf6a..a357012d90 100644 --- a/wagtail/tests/emailuser/fixtures/test_specific.json +++ b/wagtail/tests/emailuser/fixtures/test_specific.json @@ -1,18 +1,17 @@ [ - { - "pk": "00000000-0000-0000-0000-000000000001", - "model": "emailuser.emailuser", - "fields": { - "first_name": "", - "last_name": "", - "is_active": true, - "is_superuser": true, - "is_staff": true, - "groups": [ - ], - "user_permissions": [], - "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", - "email": "superuser@example.com" - } + { + "pk": "00000000-0000-0000-0000-000000000001", + "model": "emailuser.emailuser", + "fields": { + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": true, + "is_staff": true, + "groups": [], + "user_permissions": [], + "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22", + "email": "superuser@example.com" } + } ] diff --git a/wagtail/tests/modeladmintest/fixtures/modeladmintest_test.json b/wagtail/tests/modeladmintest/fixtures/modeladmintest_test.json index d167fcf89e..b4e3e9952d 100644 --- a/wagtail/tests/modeladmintest/fixtures/modeladmintest_test.json +++ b/wagtail/tests/modeladmintest/fixtures/modeladmintest_test.json @@ -1,238 +1,238 @@ [ -{ + { "pk": 1, "model": "modeladmintest.author", "fields": { - "name": "J. R. R. Tolkien", - "date_of_birth": "1892-01-03" + "name": "J. R. R. Tolkien", + "date_of_birth": "1892-01-03" } -}, -{ + }, + { "pk": 2, "model": "modeladmintest.author", "fields": { - "name": "Roald Dahl", - "date_of_birth": "1916-09-13" + "name": "Roald Dahl", + "date_of_birth": "1916-09-13" } -}, -{ + }, + { "pk": 3, "model": "modeladmintest.author", "fields": { - "name": "Roald Dahl", - "date_of_birth": "1898-11-29" + "name": "Roald Dahl", + "date_of_birth": "1898-11-29" } -}, -{ + }, + { "pk": 4, "model": "modeladmintest.author", "fields": { - "name": "J. R. Hartley", - "date_of_birth": "1898-11-29" + "name": "J. R. Hartley", + "date_of_birth": "1898-11-29" } -}, -{ + }, + { "pk": 5, "model": "modeladmintest.author", "fields": { - "name": "Harper Lee", - "date_of_birth": "1926-04-28" + "name": "Harper Lee", + "date_of_birth": "1926-04-28" } -}, -{ + }, + { "pk": 1, "model": "modeladmintest.book", "fields": { - "title": "The Lord of the Rings", - "author_id": 1 + "title": "The Lord of the Rings", + "author_id": 1 } -}, -{ + }, + { "pk": 2, "model": "modeladmintest.book", "fields": { - "title": "The Hobbit", - "author_id": 1 + "title": "The Hobbit", + "author_id": 1 } -}, -{ + }, + { "pk": 3, "model": "modeladmintest.book", "fields": { - "title": "Charlie and the Chocolate Factory", - "author_id": 2 + "title": "Charlie and the Chocolate Factory", + "author_id": 2 } -}, -{ + }, + { "pk": 4, "model": "modeladmintest.book", "fields": { - "title": "The Chronicles of Narnia", - "author_id": 3 + "title": "The Chronicles of Narnia", + "author_id": 3 } -}, -{ + }, + { "pk": 4, "model": "modeladmintest.solobook", "fields": { - "title": "To Kill a Mockingbird", - "author_id": 5 + "title": "To Kill a Mockingbird", + "author_id": 5 } -}, -{ + }, + { "pk": "boom", "model": "modeladmintest.token", "fields": { - "key": "boom" + "key": "boom" } -}, -{ + }, + { "pk": 1, "model": "wagtailcore.page", "fields": { - "title": "Root", - "draft_title": "Root", - "numchild": 1, - "show_in_menus": false, - "live": true, - "depth": 1, - "content_type": ["wagtailcore", "page"], - "path": "0001", - "url_path": "/", - "slug": "root" + "title": "Root", + "draft_title": "Root", + "numchild": 1, + "show_in_menus": false, + "live": true, + "depth": 1, + "content_type": ["wagtailcore", "page"], + "path": "0001", + "url_path": "/", + "slug": "root" } -}, + }, -{ + { "pk": 2, "model": "wagtailcore.page", "fields": { - "title": "Welcome to the Wagtail test site!", - "draft_title": "Welcome to the Wagtail test site!", - "numchild": 5, - "show_in_menus": false, - "live": true, - "depth": 2, - "content_type": ["wagtailcore", "page"], - "path": "00010001", - "url_path": "/home/", - "slug": "home" + "title": "Welcome to the Wagtail test site!", + "draft_title": "Welcome to the Wagtail test site!", + "numchild": 5, + "show_in_menus": false, + "live": true, + "depth": 2, + "content_type": ["wagtailcore", "page"], + "path": "00010001", + "url_path": "/home/", + "slug": "home" } -}, + }, -{ + { "pk": 3, "model": "wagtailcore.page", "fields": { - "title": "Events", - "draft_title": "Events", - "numchild": 4, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "eventindex"], - "path": "000100010001", - "url_path": "/home/events/", - "slug": "events" + "title": "Events", + "draft_title": "Events", + "numchild": 4, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "eventindex"], + "path": "000100010001", + "url_path": "/home/events/", + "slug": "events" } -}, -{ + }, + { "pk": 3, "model": "tests.eventindex", "fields": { - "intro": "Look at our lovely events." + "intro": "Look at our lovely events." } -}, + }, -{ + { "pk": 4, "model": "wagtailcore.page", "fields": { - "title": "Christmas", - "draft_title": "Christmas", - "numchild": 3, - "show_in_menus": true, - "live": true, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100010001", - "url_path": "/home/events/christmas/", - "slug": "christmas" + "title": "Christmas", + "draft_title": "Christmas", + "numchild": 3, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010001", + "url_path": "/home/events/christmas/", + "slug": "christmas" } -}, -{ + }, + { "pk": 4, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Chestnuts roasting on an open fire</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Chestnuts roasting on an open fire</p>", + "cost": "Free" } -}, -{ + }, + { "pk": 5, "model": "wagtailcore.page", "fields": { - "title": "Santa's Grotto", - "draft_title": "Santa's Grotto", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 5, - "content_type": ["modeladmintest", "venuepage"], - "path": "00010001000100010001", - "url_path": "/home/events/christmas/santas-grotto/", - "slug": "santas-grotto" + "title": "Santa's Grotto", + "draft_title": "Santa's Grotto", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 5, + "content_type": ["modeladmintest", "venuepage"], + "path": "00010001000100010001", + "url_path": "/home/events/christmas/santas-grotto/", + "slug": "santas-grotto" } -}, -{ + }, + { "pk": 5, "model": "modeladmintest.venuepage", "fields": { - "address": "The North Pole", - "capacity": 3 + "address": "The North Pole", + "capacity": 3 } -}, -{ + }, + { "pk": 6, "model": "wagtailcore.page", "fields": { - "title": "Santa's Workshop", - "draft_title": "Santa's Workshop", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 5, - "content_type": ["modeladmintest", "venuepage"], - "path": "00010001000100010002", - "url_path": "/home/events/christmas/santas-workshop/", - "slug": "santas-workshop" + "title": "Santa's Workshop", + "draft_title": "Santa's Workshop", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 5, + "content_type": ["modeladmintest", "venuepage"], + "path": "00010001000100010002", + "url_path": "/home/events/christmas/santas-workshop/", + "slug": "santas-workshop" } -}, -{ + }, + { "pk": 6, "model": "modeladmintest.venuepage", "fields": { - "address": "The North Pole", - "capacity": 25 + "address": "The North Pole", + "capacity": 25 } -}, -{ + }, + { "pk": 7, "model": "wagtailcore.page", "fields": { - "title": "Claim your free present!", - "draft_title": "Claim your free present!", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 5, - "content_type": ["wagtailcore", "page"], - "path": "00010001000100010003", - "url_path": "/home/events/christmas/claim-free-present/", - "slug": "claim-free-present" + "title": "Claim your free present!", + "draft_title": "Claim your free present!", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 5, + "content_type": ["wagtailcore", "page"], + "path": "00010001000100010003", + "url_path": "/home/events/christmas/claim-free-present/", + "slug": "claim-free-present" } -} + } ] diff --git a/wagtail/tests/testapp/fixtures/test.json b/wagtail/tests/testapp/fixtures/test.json index 71226c31d3..074a0b696f 100644 --- a/wagtail/tests/testapp/fixtures/test.json +++ b/wagtail/tests/testapp/fixtures/test.json @@ -1,960 +1,949 @@ [ -{ + { "pk": 1, "model": "wagtailcore.page", "fields": { - "title": "Root", - "draft_title": "Root", - "numchild": 2, - "show_in_menus": false, - "live": true, - "depth": 1, - "content_type": [ - "wagtailcore", - "page" - ], - "path": "0001", - "url_path": "/", - "slug": "root" + "title": "Root", + "draft_title": "Root", + "numchild": 2, + "show_in_menus": false, + "live": true, + "depth": 1, + "content_type": ["wagtailcore", "page"], + "path": "0001", + "url_path": "/", + "slug": "root" } -}, + }, -{ + { "pk": 2, "model": "wagtailcore.page", "fields": { - "title": "Welcome to the Wagtail test site!", - "draft_title": "Welcome to the Wagtail test site!", - "numchild": 9, - "show_in_menus": false, - "live": true, - "depth": 2, - "content_type": ["wagtailcore", "page"], - "path": "00010001", - "url_path": "/home/", - "slug": "home", - "first_published_at": "2014-01-01T12:00:00.000Z", - "last_published_at": "2014-02-01T12:00:00.000Z" + "title": "Welcome to the Wagtail test site!", + "draft_title": "Welcome to the Wagtail test site!", + "numchild": 9, + "show_in_menus": false, + "live": true, + "depth": 2, + "content_type": ["wagtailcore", "page"], + "path": "00010001", + "url_path": "/home/", + "slug": "home", + "first_published_at": "2014-01-01T12:00:00.000Z", + "last_published_at": "2014-02-01T12:00:00.000Z" } -}, -{ + }, + { "pk": 3, "model": "wagtailcore.page", "fields": { - "title": "Events", - "draft_title": "Events", - "numchild": 6, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "eventindex"], - "path": "000100010001", - "url_path": "/home/events/", - "slug": "events" + "title": "Events", + "draft_title": "Events", + "numchild": 6, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "eventindex"], + "path": "000100010001", + "url_path": "/home/events/", + "slug": "events" } -}, -{ + }, + { "pk": 3, "model": "tests.eventindex", "fields": { - "intro": "Look at our lovely events." + "intro": "Look at our lovely events." } -}, + }, -{ + { "pk": 4, "model": "wagtailcore.page", "fields": { - "title": "Christmas", - "draft_title": "Christmas", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100010001", - "url_path": "/home/events/christmas/", - "slug": "christmas", - "owner": 2 + "title": "Christmas", + "draft_title": "Christmas", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010001", + "url_path": "/home/events/christmas/", + "slug": "christmas", + "owner": 2 } -}, -{ + }, + { "pk": 4, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Chestnuts roasting on an open fire</p>", - "cost": "Free", - "feed_image": 1 + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Chestnuts roasting on an open fire</p>", + "cost": "Free", + "feed_image": 1 } -}, + }, -{ + { "pk": 13, "model": "wagtailcore.page", "fields": { - "title": "Saint Patrick", - "draft_title": "Saint Patrick", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 4, - "content_type": ["tests", "singleeventpage"], - "path": "0001000100010005", - "url_path": "/home/events/saint-patrick/", - "slug": "saint-patrick", - "owner": 2 + "title": "Saint Patrick", + "draft_title": "Saint Patrick", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "singleeventpage"], + "path": "0001000100010005", + "url_path": "/home/events/saint-patrick/", + "slug": "saint-patrick", + "owner": 2 } -}, -{ + }, + { "pk": 13, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "private", - "location": "Wellington", - "body": "<p>The day when nothing makes sense.</p>", - "cost": "Semi-free" + "date_from": "2014-12-25", + "audience": "private", + "location": "Wellington", + "body": "<p>The day when nothing makes sense.</p>", + "cost": "Semi-free" } -}, -{ + }, + { "pk": 13, "model": "tests.singleeventpage", "fields": { - "excerpt": "A little tiny excerpt for Saint Patrick." + "excerpt": "A little tiny excerpt for Saint Patrick." } -}, + }, -{ + { "pk": 1, "model": "tests.eventpagespeaker", "fields": { - "page": 4, - "first_name": "Father", - "last_name": "Christmas", - "sort_order": 0 + "page": 4, + "first_name": "Father", + "last_name": "Christmas", + "sort_order": 0 } -}, + }, -{ + { "pk": 5, "model": "wagtailcore.page", "fields": { - "title": "Tentative Unpublished Event", - "draft_title": "Tentative Unpublished Event", - "numchild": 0, - "show_in_menus": true, - "live": false, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100010002", - "url_path": "/home/events/tentative-unpublished-event/", - "slug": "tentative-unpublished-event", - "owner": 2 + "title": "Tentative Unpublished Event", + "draft_title": "Tentative Unpublished Event", + "numchild": 0, + "show_in_menus": true, + "live": false, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010002", + "url_path": "/home/events/tentative-unpublished-event/", + "slug": "tentative-unpublished-event", + "owner": 2 } -}, -{ + }, + { "pk": 5, "model": "tests.eventpage", "fields": { - "date_from": "2015-07-04", - "audience": "public", - "location": "The moon", - "body": "<p>I haven't worked out the details yet, but it's going to have cake and ponies</p>", - "cost": "Free" + "date_from": "2015-07-04", + "audience": "public", + "location": "The moon", + "body": "<p>I haven't worked out the details yet, but it's going to have cake and ponies</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 6, "model": "wagtailcore.page", "fields": { - "title": "Someone Else's Event", - "draft_title": "Someone Else's Event", - "numchild": 0, - "show_in_menus": true, - "live": false, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100010003", - "url_path": "/home/events/someone-elses-event/", - "slug": "someone-elses-event", - "owner": 3 + "title": "Someone Else's Event", + "draft_title": "Someone Else's Event", + "numchild": 0, + "show_in_menus": true, + "live": false, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010003", + "url_path": "/home/events/someone-elses-event/", + "slug": "someone-elses-event", + "owner": 3 } -}, -{ + }, + { "pk": 6, "model": "tests.eventpage", "fields": { - "date_from": "2015-07-04", - "audience": "private", - "location": "The moon", - "body": "<p>your name's not down, you're not coming in</p>", - "cost": "Free (but not for you)" + "date_from": "2015-07-04", + "audience": "private", + "location": "The moon", + "body": "<p>your name's not down, you're not coming in</p>", + "cost": "Free (but not for you)" } -}, + }, -{ + { "pk": 7, "model": "wagtailcore.page", "fields": { - "title": "About us", - "draft_title": "About us", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "simplepage"], - "path": "000100010002", - "url_path": "/home/about-us/", - "slug": "about-us", - "first_published_at": "2014-01-01T12:00:00.000Z", - "last_published_at": "2014-02-01T12:00:00.000Z" + "title": "About us", + "draft_title": "About us", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "simplepage"], + "path": "000100010002", + "url_path": "/home/about-us/", + "slug": "about-us", + "first_published_at": "2014-01-01T12:00:00.000Z", + "last_published_at": "2014-02-01T12:00:00.000Z" } -}, -{ + }, + { "pk": 7, "model": "tests.simplepage", "fields": { - "content": "<p>We are really good.</p>" + "content": "<p>We are really good.</p>" } -}, + }, -{ + { "pk": 8, "model": "wagtailcore.page", "fields": { - "title": "Contact us", - "draft_title": "Contact us", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "formpage"], - "path": "000100010003", - "url_path": "/home/contact-us/", - "slug": "contact-us" + "title": "Contact us", + "draft_title": "Contact us", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "formpage"], + "path": "000100010003", + "url_path": "/home/contact-us/", + "slug": "contact-us" } -}, -{ + }, + { "pk": 8, "model": "tests.formpage", "fields": { - "to_address": "to@email.com", - "from_address": "from@email.com", - "subject": "The subject" + "to_address": "to@email.com", + "from_address": "from@email.com", + "subject": "The subject" } -}, + }, -{ + { "pk": 9, "model": "wagtailcore.page", "fields": { - "title": "Ameristralia Day", - "draft_title": "Ameristralia Day", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100010004", - "url_path": "/home/events/final-event/", - "slug": "final-event", - "owner": 3 + "title": "Ameristralia Day", + "draft_title": "Ameristralia Day", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010004", + "url_path": "/home/events/final-event/", + "slug": "final-event", + "owner": 3 } -}, -{ + }, + { "pk": 9, "model": "tests.eventpage", "fields": { - "date_from": "2015-04-22", - "audience": "public", - "location": "Ameristralia", - "body": "<p>come celebrate the independence of Ameristralia <embed embedtype=\"image\" format=\"fullwidth\" id=\"1\" alt=\"where did my image go?\" /></p>", - "cost": "Free" + "date_from": "2015-04-22", + "audience": "public", + "location": "Ameristralia", + "body": "<p>come celebrate the independence of Ameristralia <embed embedtype=\"image\" format=\"fullwidth\" id=\"1\" alt=\"where did my image go?\" /></p>", + "cost": "Free" } -}, + }, -{ + { "pk": 1, "model": "tests.formfield", "fields": { - "clean_name": "your_email", - "sort_order": 1, - "label": "Your email", - "field_type": "email", - "required": true, - "choices": "", - "default_value": "", - "help_text": "", - "page": 8 + "clean_name": "your_email", + "sort_order": 1, + "label": "Your email", + "field_type": "email", + "required": true, + "choices": "", + "default_value": "", + "help_text": "", + "page": 8 } -}, -{ + }, + { "pk": 2, "model": "tests.formfield", "fields": { - "clean_name": "your_message", - "sort_order": 2, - "label": "Your message", - "field_type": "multiline", - "required": true, - "choices": "", - "default_value": "", - "help_text": "", - "page": 8 + "clean_name": "your_message", + "sort_order": 2, + "label": "Your message", + "field_type": "multiline", + "required": true, + "choices": "", + "default_value": "", + "help_text": "", + "page": 8 } -}, -{ + }, + { "pk": 3, "model": "tests.formfield", "fields": { - "clean_name": "your_choices", - "sort_order": 3, - "label": "Your choices", - "field_type": "checkboxes", - "required": false, - "choices": "foo,bar,baz", - "default_value": "", - "help_text": "", - "page": 8 + "clean_name": "your_choices", + "sort_order": 3, + "label": "Your choices", + "field_type": "checkboxes", + "required": false, + "choices": "foo,bar,baz", + "default_value": "", + "help_text": "", + "page": 8 } -}, + }, -{ + { "pk": 10, "model": "wagtailcore.page", "fields": { - "title": "Old style route method", - "draft_title": "Old style route method", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "pagewitholdstyleroutemethod"], - "path": "000100010004", - "url_path": "/home/old-style-route/", - "slug": "old-style-route" + "title": "Old style route method", + "draft_title": "Old style route method", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "pagewitholdstyleroutemethod"], + "path": "000100010004", + "url_path": "/home/old-style-route/", + "slug": "old-style-route" } -}, -{ + }, + { "pk": 10, "model": "tests.pagewitholdstyleroutemethod", "fields": { - "content": "<p>Test with old style route method</p>" + "content": "<p>Test with old style route method</p>" } -}, + }, -{ + { "pk": 11, "model": "wagtailcore.page", "fields": { - "title": "Secret plans", - "draft_title": "Secret plans", - "numchild": 1, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "simplepage"], - "path": "000100010005", - "url_path": "/home/secret-plans/", - "slug": "secret-plans" + "title": "Secret plans", + "draft_title": "Secret plans", + "numchild": 1, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "simplepage"], + "path": "000100010005", + "url_path": "/home/secret-plans/", + "slug": "secret-plans" } -}, -{ + }, + { "pk": 11, "model": "tests.simplepage", "fields": { - "content": "<p>muahahahaha</p>" + "content": "<p>muahahahaha</p>" } -}, + }, -{ + { "pk": 12, "model": "wagtailcore.page", "fields": { - "title": "Steal underpants", - "draft_title": "Steal underpants", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100050001", - "url_path": "/home/secret-plans/steal-underpants/", - "slug": "steal-underpants" + "title": "Steal underpants", + "draft_title": "Steal underpants", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100050001", + "url_path": "/home/secret-plans/steal-underpants/", + "slug": "steal-underpants" } -}, -{ + }, + { "pk": 12, "model": "tests.eventpage", "fields": { - "date_from": "2015-07-04", - "audience": "private", - "location": "Marks and Spencer", - "body": "<p>meet in the menswear department at noon</p>", - "cost": "free" + "date_from": "2015-07-04", + "audience": "private", + "location": "Marks and Spencer", + "body": "<p>meet in the menswear department at noon</p>", + "cost": "free" } -}, -{ + }, + { "pk": 14, "model": "wagtailcore.page", "fields": { - "title": "My locked page", - "draft_title": "My locked page", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["wagtailcore", "page"], - "path": "000100010006", - "url_path": "/home/my-locked-page/", - "slug": "my-locked-page", - "locked": true + "title": "My locked page", + "draft_title": "My locked page", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["wagtailcore", "page"], + "path": "000100010006", + "url_path": "/home/my-locked-page/", + "slug": "my-locked-page", + "locked": true } -}, -{ + }, + { "pk": 15, "model": "wagtailcore.page", "fields": { - "title": "Businessy events", - "draft_title": "Businessy events", - "numchild": 1, - "show_in_menus": true, - "live": false, - "depth": 4, - "content_type": ["tests", "businessindex"], - "path": "0001000100010006", - "url_path": "/home/events/businessy-events/", - "slug": "businessy-events", - "owner": 2 + "title": "Businessy events", + "draft_title": "Businessy events", + "numchild": 1, + "show_in_menus": true, + "live": false, + "depth": 4, + "content_type": ["tests", "businessindex"], + "path": "0001000100010006", + "url_path": "/home/events/businessy-events/", + "slug": "businessy-events", + "owner": 2 } -}, -{ + }, + { "pk": 15, "model": "tests.businessindex", - "fields": { - } -}, + "fields": {} + }, -{ + { "pk": 16, "model": "wagtailcore.page", "fields": { - "title": "Board meetings", - "draft_title": "Board meetings", - "numchild": 0, - "show_in_menus": true, - "live": false, - "depth": 5, - "content_type": ["tests", "businesssubindex"], - "path": "00010001000100060001", - "url_path": "/home/events/businessy-events/board-meetings/", - "slug": "board-meetings", - "owner": 2 + "title": "Board meetings", + "draft_title": "Board meetings", + "numchild": 0, + "show_in_menus": true, + "live": false, + "depth": 5, + "content_type": ["tests", "businesssubindex"], + "path": "00010001000100060001", + "url_path": "/home/events/businessy-events/board-meetings/", + "slug": "board-meetings", + "owner": 2 } -}, -{ + }, + { "pk": 16, "model": "tests.businesssubindex", - "fields": { - } -}, + "fields": {} + }, -{ + { "pk": 17, "model": "wagtailcore.page", "fields": { - "title": "Contact us one more time", - "draft_title": "Contact us one more time", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "formpagewithcustomsubmission"], - "path": "000100010007", - "url_path": "/home/contact-us-one-more-time/", - "slug": "contact-us-one-more-time" + "title": "Contact us one more time", + "draft_title": "Contact us one more time", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "formpagewithcustomsubmission"], + "path": "000100010007", + "url_path": "/home/contact-us-one-more-time/", + "slug": "contact-us-one-more-time" } -}, -{ + }, + { "pk": 17, "model": "tests.formpagewithcustomsubmission", "fields": { - "to_address": "to@email.com", - "from_address": "from@email.com", - "subject": "The subject" + "to_address": "to@email.com", + "from_address": "from@email.com", + "subject": "The subject" } -}, -{ + }, + { "pk": 1, "model": "tests.formfieldwithcustomsubmission", "fields": { - "clean_name": "", - "sort_order": 1, - "label": "Your email", - "field_type": "email", - "required": true, - "choices": "", - "default_value": "", - "help_text": "", - "page": 17 + "clean_name": "", + "sort_order": 1, + "label": "Your email", + "field_type": "email", + "required": true, + "choices": "", + "default_value": "", + "help_text": "", + "page": 17 } -}, -{ + }, + { "pk": 2, "model": "tests.formfieldwithcustomsubmission", "fields": { - "clean_name": "", - "sort_order": 2, - "label": "Your message", - "field_type": "multiline", - "required": true, - "choices": "", - "default_value": "", - "help_text": "", - "page": 17 + "clean_name": "", + "sort_order": 2, + "label": "Your message", + "field_type": "multiline", + "required": true, + "choices": "", + "default_value": "", + "help_text": "", + "page": 17 } -}, -{ + }, + { "pk": 3, "model": "tests.formfieldwithcustomsubmission", "fields": { - "clean_name": "", - "sort_order": 3, - "label": "Your choices", - "field_type": "checkboxes", - "required": false, - "choices": "foo,bar,baz", - "default_value": "", - "help_text": "", - "page": 17 + "clean_name": "", + "sort_order": 3, + "label": "Your choices", + "field_type": "checkboxes", + "required": false, + "choices": "foo,bar,baz", + "default_value": "", + "help_text": "", + "page": 17 } -}, -{ + }, + { "pk": 1, "model": "tests.customformpagesubmission", "fields": { - "form_data": "{\"your-email\": \"old@example.com\", \"your-message\": \"this is a really old message\"}", - "user": 2, - "page": 17, - "submit_time": "2013-01-01T12:00:00.000Z" + "form_data": "{\"your-email\": \"old@example.com\", \"your-message\": \"this is a really old message\"}", + "user": 2, + "page": 17, + "submit_time": "2013-01-01T12:00:00.000Z" } -}, -{ + }, + { "pk": 2, "model": "tests.customformpagesubmission", "fields": { - "form_data": "{\"your-email\": \"new@example.com\", \"your-message\": \"this is a fairly new message\"}", - "user": 5, - "page": 17, - "submit_time": "2014-01-01T12:00:00.000Z" + "form_data": "{\"your-email\": \"new@example.com\", \"your-message\": \"this is a fairly new message\"}", + "user": 5, + "page": 17, + "submit_time": "2014-01-01T12:00:00.000Z" } -}, -{ + }, + { "pk": 18, "model": "wagtailcore.page", "fields": { - "title": "Secret event editor plans", - "draft_title": "Secret event editor plans", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "simplepage"], - "path": "000100010008", - "url_path": "/home/secret-event-editor-plans/", - "slug": "secret-event-editor-plans" + "title": "Secret event editor plans", + "draft_title": "Secret event editor plans", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "simplepage"], + "path": "000100010008", + "url_path": "/home/secret-event-editor-plans/", + "slug": "secret-event-editor-plans" } -}, -{ + }, + { "pk": 18, "model": "tests.simplepage", "fields": { - "content": "<p>let's move Easter to Christmas</p>" + "content": "<p>let's move Easter to Christmas</p>" } -}, -{ + }, + { "pk": 19, "model": "wagtailcore.page", "fields": { - "title": "Secret login plans", - "draft_title": "Secret login plans", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "simplepage"], - "path": "000100010009", - "url_path": "/home/secret-login-plans/", - "slug": "secret-login-plans" + "title": "Secret login plans", + "draft_title": "Secret login plans", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "simplepage"], + "path": "000100010009", + "url_path": "/home/secret-login-plans/", + "slug": "secret-login-plans" } -}, -{ + }, + { "pk": 19, "model": "tests.simplepage", "fields": { - "content": "<p>collect logs</p>" + "content": "<p>collect logs</p>" } -}, + }, -{ + { "pk": 20, "model": "wagtailcore.page", "fields": { - "title": "This page doesn't get served", - "draft_title": "This page doesn't get served", - "numchild": 0, - "show_in_menus": false, - "live": true, - "depth": 2, - "content_type": ["wagtailcore", "page"], - "path": "00010002", - "url_path": "/does-not-exist/", - "slug": "does-not-exist" + "title": "This page doesn't get served", + "draft_title": "This page doesn't get served", + "numchild": 0, + "show_in_menus": false, + "live": true, + "depth": 2, + "content_type": ["wagtailcore", "page"], + "path": "00010002", + "url_path": "/does-not-exist/", + "slug": "does-not-exist" } -}, + }, -{ + { "pk": 1, "model": "wagtailcore.site", "fields": { - "root_page": 2, - "hostname": "localhost", - "port": 80, - "is_default_site": true + "root_page": 2, + "hostname": "localhost", + "port": 80, + "is_default_site": true } -}, + }, -{ + { "pk": 3, "model": "auth.group", "fields": { - "name": "Event editors", - "permissions": [ - ["access_admin", "wagtailadmin", "admin"], - ["add_image", "wagtailimages", "image"], - ["change_image", "wagtailimages", "image"], - ["delete_image", "wagtailimages", "image"] - ] + "name": "Event editors", + "permissions": [ + ["access_admin", "wagtailadmin", "admin"], + ["add_image", "wagtailimages", "image"], + ["change_image", "wagtailimages", "image"], + ["delete_image", "wagtailimages", "image"] + ] } -}, -{ + }, + { "pk": 4, "model": "auth.group", "fields": { - "name": "Event moderators", - "permissions": [ - ["access_admin", "wagtailadmin", "admin"], - ["add_image", "wagtailimages", "image"], - ["change_image", "wagtailimages", "image"], - ["delete_image", "wagtailimages", "image"] - ] + "name": "Event moderators", + "permissions": [ + ["access_admin", "wagtailadmin", "admin"], + ["add_image", "wagtailimages", "image"], + ["change_image", "wagtailimages", "image"], + ["delete_image", "wagtailimages", "image"] + ] } -}, -{ + }, + { "pk": 5, "model": "auth.group", "fields": { - "name": "Site-wide editors", - "permissions": [ - ["access_admin", "wagtailadmin", "admin"], - ["add_image", "wagtailimages", "image"], - ["change_image", "wagtailimages", "image"], - ["delete_image", "wagtailimages", "image"] - ] + "name": "Site-wide editors", + "permissions": [ + ["access_admin", "wagtailadmin", "admin"], + ["add_image", "wagtailimages", "image"], + ["change_image", "wagtailimages", "image"], + ["delete_image", "wagtailimages", "image"] + ] } -}, -{ + }, + { "pk": 6, "model": "auth.group", "fields": { - "name": "Admin non-editors", - "permissions": [ - ["access_admin", "wagtailadmin", "admin"] - ] + "name": "Admin non-editors", + "permissions": [["access_admin", "wagtailadmin", "admin"]] } -}, -{ + }, + { "pk": 7, "model": "auth.group", "fields": { - "name": "Corporate Editor", - "permissions": [ - ["access_admin", "wagtailadmin", "admin"], - ["add_image", "wagtailimages", "image"], - ["change_image", "wagtailimages", "image"], - ["delete_image", "wagtailimages", "image"] - ] + "name": "Corporate Editor", + "permissions": [ + ["access_admin", "wagtailadmin", "admin"], + ["add_image", "wagtailimages", "image"], + ["change_image", "wagtailimages", "image"], + ["delete_image", "wagtailimages", "image"] + ] } -}, -{ + }, + { "pk": 1, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Event editors"], - "page": 3, - "permission_type": "add" + "group": ["Event editors"], + "page": 3, + "permission_type": "add" } -}, -{ + }, + { "pk": 2, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Event moderators"], - "page": 3, - "permission_type": "add" + "group": ["Event moderators"], + "page": 3, + "permission_type": "add" } -}, -{ + }, + { "pk": 3, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Event moderators"], - "page": 3, - "permission_type": "edit" + "group": ["Event moderators"], + "page": 3, + "permission_type": "edit" } -}, -{ + }, + { "pk": 4, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Event moderators"], - "page": 3, - "permission_type": "publish" + "group": ["Event moderators"], + "page": 3, + "permission_type": "publish" } -}, -{ + }, + { "pk": 5, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Site-wide editors"], - "page": 2, - "permission_type": "edit" + "group": ["Site-wide editors"], + "page": 2, + "permission_type": "edit" } -}, -{ + }, + { "pk": 6, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Event moderators"], - "page": 3, - "permission_type": "lock" + "group": ["Event moderators"], + "page": 3, + "permission_type": "lock" } -}, -{ + }, + { "pk": 7, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Corporate Editor"], - "page": 7, - "permission_type": "edit" + "group": ["Corporate Editor"], + "page": 7, + "permission_type": "edit" } -}, -{ + }, + { "pk": 8, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Corporate Editor"], - "page": 15, - "permission_type": "add" + "group": ["Corporate Editor"], + "page": 15, + "permission_type": "add" } -}, -{ + }, + { "pk": 9, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Event moderators"], - "page": 3, - "permission_type": "unlock" + "group": ["Event moderators"], + "page": 3, + "permission_type": "unlock" } -}, + }, -{ + { "pk": 1, "model": "wagtailforms.formsubmission", "fields": { - "form_data": "{\"your_email\": \"old@example.com\", \"your_message\": \"this is a really old message\"}", - "page": 8, - "submit_time": "2013-01-01T12:00:00.000Z" + "form_data": "{\"your_email\": \"old@example.com\", \"your_message\": \"this is a really old message\"}", + "page": 8, + "submit_time": "2013-01-01T12:00:00.000Z" } -}, -{ + }, + { "pk": 2, "model": "wagtailforms.formsubmission", "fields": { - "form_data": "{\"your_email\": \"new@example.com\", \"your_message\": \"this is a fairly new message\"}", - "page": 8, - "submit_time": "2014-01-01T12:00:00.000Z" + "form_data": "{\"your_email\": \"new@example.com\", \"your_message\": \"this is a fairly new message\"}", + "page": 8, + "submit_time": "2014-01-01T12:00:00.000Z" } -}, -{ + }, + { "pk": 1, "model": "tests.AdvertPlacement", "fields": { - "page": 2, - "advert": 1, - "colour": "yellow" - } -}, -{ + "page": 2, + "advert": 1, + "colour": "yellow" + } + }, + { "pk": 2, "model": "tests.AdvertPlacement", "fields": { - "page": 4, - "advert": 1, - "colour": "greener than a Christmas tree" - } -}, -{ + "page": 4, + "advert": 1, + "colour": "greener than a Christmas tree" + } + }, + { "pk": 1, "model": "tests.advert", "fields": { - "text": "test_advert", - "url": "http://www.example.com" - } -}, -{ + "text": "test_advert", + "url": "http://www.example.com" + } + }, + { "pk": 1, "model": "tests.advertwithtabbedinterface", "fields": { - "text": "test_advert", - "url": "http://www.example.com", - "something_else": "Model with tabbed interface" + "text": "test_advert", + "url": "http://www.example.com", + "something_else": "Model with tabbed interface" } -}, -{ + }, + { "pk": 1, "model": "wagtaildocs.Document", "fields": { - "title": "test document", - "created_at": "2014-01-01T12:00:00.000Z", - "file": "documents/test.pdf" + "title": "test document", + "created_at": "2014-01-01T12:00:00.000Z", + "file": "documents/test.pdf" } -}, -{ + }, + { "pk": 1, "model": "wagtailcore.pageviewrestriction", "fields": { - "page": 11, - "restriction_type": "password", - "password": "swordfish" + "page": 11, + "restriction_type": "password", + "password": "swordfish" } -}, -{ + }, + { "pk": 2, "model": "wagtailcore.pageviewrestriction", "fields": { - "page": 18, - "restriction_type": "groups", - "groups": [ - ["Event editors"] - ] + "page": 18, + "restriction_type": "groups", + "groups": [["Event editors"]] } -}, -{ + }, + { "pk": 3, "model": "wagtailcore.pageviewrestriction", "fields": { - "page": 19, - "restriction_type": "login" + "page": 19, + "restriction_type": "login" } -}, -{ + }, + { "pk": 1, "model": "wagtailimages.image", "fields": { - "title": "A missing image", - "file": "original_images/missing.jpg", - "width": 1000, - "height": 1000, - "created_at": "2014-01-01T12:00:00.000Z" + "title": "A missing image", + "file": "original_images/missing.jpg", + "width": 1000, + "height": 1000, + "created_at": "2014-01-01T12:00:00.000Z" } -}, -{ + }, + { "model": "wagtailcore.collectionviewrestriction", "pk": 1, "fields": { - "restriction_type": "password", - "password": "swordfish", - "collection": 2 + "restriction_type": "password", + "password": "swordfish", + "collection": 2 } -}, -{ + }, + { "model": "wagtailcore.collectionviewrestriction", "pk": 2, "fields": { - "restriction_type": "login", - "collection": 3 + "restriction_type": "login", + "collection": 3 } -}, -{ + }, + { "model": "wagtailcore.collectionviewrestriction", "pk": 3, "fields": { - "restriction_type": "groups", - "collection": 4, - "groups": [ - ["Event editors"] - ] + "restriction_type": "groups", + "collection": 4, + "groups": [["Event editors"]] } -}, -{ + }, + { "model": "wagtailcore.collection", "pk": 1, "fields": { - "path": "0001", - "depth": 1, - "numchild": 3, - "name": "Root" + "path": "0001", + "depth": 1, + "numchild": 3, + "name": "Root" } -}, -{ + }, + { "model": "wagtailcore.collection", "pk": 2, "fields": { - "path": "00010001", - "depth": 2, - "numchild": 0, - "name": "Password protected" + "path": "00010001", + "depth": 2, + "numchild": 0, + "name": "Password protected" } -}, -{ + }, + { "model": "wagtailcore.collection", "pk": 3, "fields": { - "path": "00010002", - "depth": 2, - "numchild": 0, - "name": "Login protected" + "path": "00010002", + "depth": 2, + "numchild": 0, + "name": "Login protected" } -}, -{ + }, + { "model": "wagtailcore.collection", "pk": 4, "fields": { - "path": "00010003", - "depth": 2, - "numchild": 0, - "name": "Group protected" + "path": "00010003", + "depth": 2, + "numchild": 0, + "name": "Group protected" } -}, -{ - "pk": "advert/01", - "model": "tests.advertwithcustomprimarykey", - "fields": { + }, + { + "pk": "advert/01", + "model": "tests.advertwithcustomprimarykey", + "fields": { "text": "test_advert", "url": "http://www.example.com" - } -}, -{ - "pk": "2dbe9aa7-1f4a-428a-8cca-b929ee5bff5b", - "model": "tests.advertwithcustomuuidprimarykey", - "fields": { + } + }, + { + "pk": "2dbe9aa7-1f4a-428a-8cca-b929ee5bff5b", + "model": "tests.advertwithcustomuuidprimarykey", + "fields": { "text": "test_advert", "url": "http://www.example.com" - } -} + } + } ] diff --git a/wagtail/tests/testapp/fixtures/test_explorable_pages.json b/wagtail/tests/testapp/fixtures/test_explorable_pages.json index 0f1df85a63..1c65395b2d 100644 --- a/wagtail/tests/testapp/fixtures/test_explorable_pages.json +++ b/wagtail/tests/testapp/fixtures/test_explorable_pages.json @@ -1,410 +1,404 @@ [ -{ + { "pk": 1, "model": "wagtailcore.page", "fields": { - "title": "Root", - "draft_title": "Root", - "numchild": 1, - "show_in_menus": false, - "live": true, - "depth": 1, - "content_type": ["wagtailcore", "page"], - "path": "0001", - "url_path": "/", - "slug": "root" + "title": "Root", + "draft_title": "Root", + "numchild": 1, + "show_in_menus": false, + "live": true, + "depth": 1, + "content_type": ["wagtailcore", "page"], + "path": "0001", + "url_path": "/", + "slug": "root" } -}, + }, -{ + { "pk": 2, "model": "wagtailcore.page", "fields": { - "title": "Welcome to testserver!", - "draft_title": "Welcome to testserver!", - "numchild": 1, - "show_in_menus": false, - "live": true, - "depth": 2, - "content_type": ["tests", "eventpage"], - "path": "00010001", - "url_path": "/home/", - "slug": "home" + "title": "Welcome to testserver!", + "draft_title": "Welcome to testserver!", + "numchild": 1, + "show_in_menus": false, + "live": true, + "depth": 2, + "content_type": ["tests", "eventpage"], + "path": "00010001", + "url_path": "/home/", + "slug": "home" } -}, -{ + }, + { "pk": 2, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 3, "model": "wagtailcore.page", "fields": { - "title": "About us", - "draft_title": "About us", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "eventpage"], - "path": "000100010001", - "url_path": "/home/about-us/", - "slug": "about-us" + "title": "About us", + "draft_title": "About us", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "eventpage"], + "path": "000100010001", + "url_path": "/home/about-us/", + "slug": "about-us" } -}, -{ + }, + { "pk": 3, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 4, "model": "wagtailcore.page", "fields": { - "title": "Welcome to example.com!", - "draft_title": "Welcome to example.com!", - "numchild": 1, - "show_in_menus": false, - "live": true, - "depth": 2, - "content_type": ["tests", "eventpage"], - "path": "00010002", - "url_path": "/example-home/", - "slug": "example-home" + "title": "Welcome to example.com!", + "draft_title": "Welcome to example.com!", + "numchild": 1, + "show_in_menus": false, + "live": true, + "depth": 2, + "content_type": ["tests", "eventpage"], + "path": "00010002", + "url_path": "/example-home/", + "slug": "example-home" } -}, -{ + }, + { "pk": 4, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 5, "model": "wagtailcore.page", "fields": { - "title": "Content", - "draft_title": "Content", - "numchild": 2, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "eventpage"], - "path": "000100020001", - "url_path": "/example-home/content/", - "slug": "content", - "owner": 1 + "title": "Content", + "draft_title": "Content", + "numchild": 2, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "eventpage"], + "path": "000100020001", + "url_path": "/example-home/content/", + "slug": "content", + "owner": 1 } -}, -{ + }, + { "pk": 5, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 6, "model": "wagtailcore.page", "fields": { - "title": "Page 1", - "draft_title": "Page 1", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000200010001", - "url_path": "/example-home/content/page-1/", - "slug": "page-1", - "owner": 1 + "title": "Page 1", + "draft_title": "Page 1", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000200010001", + "url_path": "/example-home/content/page-1/", + "slug": "page-1", + "owner": 1 } -}, -{ + }, + { "pk": 6, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 7, "model": "wagtailcore.page", "fields": { - "title": "Page 2", - "draft_title": "Page 2", - "numchild": 1, - "show_in_menus": true, - "live": true, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000200010002", - "url_path": "/example-home/content/page-2/", - "slug": "page-2", - "owner": 1 + "title": "Page 2", + "draft_title": "Page 2", + "numchild": 1, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000200010002", + "url_path": "/example-home/content/page-2/", + "slug": "page-2", + "owner": 1 } -}, -{ + }, + { "pk": 7, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 8, "model": "wagtailcore.page", "fields": { - "title": "Other Content", - "draft_title": "Other Content", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "eventpage"], - "path": "000100020002", - "url_path": "/example-home/other-content/", - "slug": "other-content", - "owner": 1 + "title": "Other Content", + "draft_title": "Other Content", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "eventpage"], + "path": "000100020002", + "url_path": "/example-home/other-content/", + "slug": "other-content", + "owner": 1 } -}, -{ + }, + { "pk": 8, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 9, "model": "wagtailcore.page", "fields": { - "title": "Child 1 of Page 2", - "draft_title": "Child 1 of Page 2", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 5, - "content_type": ["tests", "eventpage"], - "path": "00010002000100020001", - "url_path": "/example-home/content/page-2/child-1/", - "slug": "child-1", - "owner": 1 + "title": "Child 1 of Page 2", + "draft_title": "Child 1 of Page 2", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 5, + "content_type": ["tests", "eventpage"], + "path": "00010002000100020001", + "url_path": "/example-home/content/page-2/child-1/", + "slug": "child-1", + "owner": 1 } -}, -{ + }, + { "pk": 9, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 10, "model": "wagtailcore.page", "fields": { - "title": "Welcome to example2.com!", - "draft_title": "Welcome to example2.com!", - "numchild": 0, - "show_in_menus": false, - "live": true, - "depth": 2, - "content_type": ["tests", "eventpage"], - "path": "00010003", - "url_path": "/home-2/", - "slug": "home-2" + "title": "Welcome to example2.com!", + "draft_title": "Welcome to example2.com!", + "numchild": 0, + "show_in_menus": false, + "live": true, + "depth": 2, + "content_type": ["tests", "eventpage"], + "path": "00010003", + "url_path": "/home-2/", + "slug": "home-2" } -}, -{ + }, + { "pk": 10, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "private", - "location": "The North Pole", - "body": "<p>Welcome!</p>", - "cost": "Free" + "date_from": "2014-12-25", + "audience": "private", + "location": "The North Pole", + "body": "<p>Welcome!</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 1, "model": "wagtailcore.site", "fields": { - "root_page": 2, - "hostname": "testserver", - "port": 80, - "is_default_site": true + "root_page": 2, + "hostname": "testserver", + "port": 80, + "is_default_site": true } -}, -{ + }, + { "pk": 2, "model": "wagtailcore.site", "fields": { - "root_page": 4, - "hostname": "example.com", - "port": 80, - "is_default_site": false + "root_page": 4, + "hostname": "example.com", + "port": 80, + "is_default_site": false } -}, -{ + }, + { "pk": 3, "model": "wagtailcore.site", "fields": { - "root_page": 10, - "hostname": "example2.com", - "port": 80, - "is_default_site": false + "root_page": 10, + "hostname": "example2.com", + "port": 80, + "is_default_site": false } -}, + }, -{ + { "pk": 3, "model": "auth.group", "fields": { - "name": "Group 1", - "permissions": [ - ["access_admin", "wagtailadmin", "admin"] - ] + "name": "Group 1", + "permissions": [["access_admin", "wagtailadmin", "admin"]] } -}, -{ + }, + { "pk": 4, "model": "auth.group", "fields": { - "name": "Group 2", - "permissions": [ - ["access_admin", "wagtailadmin", "admin"] - ] + "name": "Group 2", + "permissions": [["access_admin", "wagtailadmin", "admin"]] } -}, -{ + }, + { "pk": 5, "model": "auth.group", "fields": { - "name": "Group 3", - "permissions": [ - ["access_admin", "wagtailadmin", "admin"] - ] + "name": "Group 3", + "permissions": [["access_admin", "wagtailadmin", "admin"]] } -}, + }, -{ + { "pk": 1, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Group 1"], - "page": 2, - "permission_type": "add" + "group": ["Group 1"], + "page": 2, + "permission_type": "add" } -}, -{ + }, + { "pk": 2, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Group 1"], - "page": 2, - "permission_type": "edit" + "group": ["Group 1"], + "page": 2, + "permission_type": "edit" } -}, -{ + }, + { "pk": 3, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Group 1"], - "page": 2, - "permission_type": "publish" + "group": ["Group 1"], + "page": 2, + "permission_type": "publish" } -}, -{ + }, + { "pk": 3, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Group 1"], - "page": 2, - "permission_type": "choose" + "group": ["Group 1"], + "page": 2, + "permission_type": "choose" } -}, -{ + }, + { "pk": 5, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Group 2"], - "page": 6, - "permission_type": "edit" + "group": ["Group 2"], + "page": 6, + "permission_type": "edit" } -}, -{ + }, + { "pk": 6, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Group 2"], - "page": 6, - "permission_type": "choose" + "group": ["Group 2"], + "page": 6, + "permission_type": "choose" } -}, -{ + }, + { "pk": 7, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Group 3"], - "page": 8, - "permission_type": "edit" + "group": ["Group 3"], + "page": 8, + "permission_type": "edit" } -}, -{ + }, + { "pk": 8, "model": "wagtailcore.grouppagepermission", "fields": { - "group": ["Group 3"], - "page": 8, - "permission_type": "choose" + "group": ["Group 3"], + "page": 8, + "permission_type": "choose" } -} + } ] diff --git a/wagtail/tests/testapp/fixtures/test_specific.json b/wagtail/tests/testapp/fixtures/test_specific.json index d903beb71f..73d1a4c3c1 100644 --- a/wagtail/tests/testapp/fixtures/test_specific.json +++ b/wagtail/tests/testapp/fixtures/test_specific.json @@ -1,246 +1,246 @@ [ -{ + { "pk": 1, "model": "wagtailcore.page", "fields": { - "title": "Root", - "draft_title": "Root", - "numchild": 1, - "show_in_menus": false, - "live": true, - "depth": 1, - "content_type": ["wagtailcore", "page"], - "path": "0001", - "url_path": "/", - "slug": "root" + "title": "Root", + "draft_title": "Root", + "numchild": 1, + "show_in_menus": false, + "live": true, + "depth": 1, + "content_type": ["wagtailcore", "page"], + "path": "0001", + "url_path": "/", + "slug": "root" } -}, + }, -{ + { "pk": 2, "model": "wagtailcore.page", "fields": { - "title": "Welcome to the Wagtail test site!", - "draft_title": "Welcome to the Wagtail test site!", - "numchild": 5, - "show_in_menus": false, - "live": true, - "depth": 2, - "content_type": ["wagtailcore", "page"], - "path": "00010001", - "url_path": "/home/", - "slug": "home" + "title": "Welcome to the Wagtail test site!", + "draft_title": "Welcome to the Wagtail test site!", + "numchild": 5, + "show_in_menus": false, + "live": true, + "depth": 2, + "content_type": ["wagtailcore", "page"], + "path": "00010001", + "url_path": "/home/", + "slug": "home" } -}, + }, -{ + { "pk": 3, "model": "wagtailcore.page", "fields": { - "title": "Events", - "draft_title": "Events", - "numchild": 4, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "eventindex"], - "path": "000100010001", - "url_path": "/home/events/", - "slug": "events" + "title": "Events", + "draft_title": "Events", + "numchild": 4, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "eventindex"], + "path": "000100010001", + "url_path": "/home/events/", + "slug": "events" } -}, -{ + }, + { "pk": 3, "model": "tests.eventindex", "fields": { - "intro": "Look at our lovely events." + "intro": "Look at our lovely events." } -}, + }, -{ + { "pk": 4, "model": "wagtailcore.page", "fields": { - "title": "Christmas", - "draft_title": "Christmas", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100010001", - "url_path": "/home/events/christmas/", - "slug": "christmas", - "owner": 1 + "title": "Christmas", + "draft_title": "Christmas", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010001", + "url_path": "/home/events/christmas/", + "slug": "christmas", + "owner": 1 } -}, -{ + }, + { "pk": 4, "model": "tests.eventpage", "fields": { - "date_from": "2014-12-25", - "audience": "public", - "location": "The North Pole", - "body": "<p>Chestnuts roasting on an open fire</p>", - "cost": "Free", - "feed_image": 1 + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "<p>Chestnuts roasting on an open fire</p>", + "cost": "Free", + "feed_image": 1 } -}, + }, -{ + { "pk": 1, "model": "wagtailimages.image", "fields": { - "title": "A missing image", - "file": "original_images/missing.jpg", - "width": 1000, - "height": 1000, - "created_at": "2014-01-01T12:00:00.000Z" + "title": "A missing image", + "file": "original_images/missing.jpg", + "width": 1000, + "height": 1000, + "created_at": "2014-01-01T12:00:00.000Z" } -}, + }, -{ + { "pk": 5, "model": "wagtailcore.page", "fields": { - "title": "Tentative Unpublished Event", - "draft_title": "Tentative Unpublished Event", - "numchild": 0, - "show_in_menus": true, - "live": false, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100010002", - "url_path": "/home/events/tentative-unpublished-event/", - "slug": "tentative-unpublished-event", - "owner": 1 + "title": "Tentative Unpublished Event", + "draft_title": "Tentative Unpublished Event", + "numchild": 0, + "show_in_menus": true, + "live": false, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010002", + "url_path": "/home/events/tentative-unpublished-event/", + "slug": "tentative-unpublished-event", + "owner": 1 } -}, -{ + }, + { "pk": 5, "model": "tests.eventpage", "fields": { - "date_from": "2015-07-04", - "audience": "public", - "location": "The moon", - "body": "<p>I haven't worked out the details yet, but it's going to have cake and ponies</p>", - "cost": "Free" + "date_from": "2015-07-04", + "audience": "public", + "location": "The moon", + "body": "<p>I haven't worked out the details yet, but it's going to have cake and ponies</p>", + "cost": "Free" } -}, + }, -{ + { "pk": 6, "model": "wagtailcore.page", "fields": { - "title": "Someone Else's Event", - "draft_title": "Someone Else's Event", - "numchild": 0, - "show_in_menus": true, - "live": false, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100010003", - "url_path": "/home/events/someone-elses-event/", - "slug": "someone-elses-event", - "owner": 1 + "title": "Someone Else's Event", + "draft_title": "Someone Else's Event", + "numchild": 0, + "show_in_menus": true, + "live": false, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010003", + "url_path": "/home/events/someone-elses-event/", + "slug": "someone-elses-event", + "owner": 1 } -}, -{ + }, + { "pk": 6, "model": "tests.eventpage", "fields": { - "date_from": "2015-07-04", - "audience": "private", - "location": "The moon", - "body": "<p>your name's not down, you're not coming in</p>", - "cost": "Free (but not for you)" + "date_from": "2015-07-04", + "audience": "private", + "location": "The moon", + "body": "<p>your name's not down, you're not coming in</p>", + "cost": "Free (but not for you)" } -}, + }, -{ + { "pk": 7, "model": "wagtailcore.page", "fields": { - "title": "About us", - "draft_title": "About us", - "numchild": 0, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "simplepage"], - "path": "000100010002", - "url_path": "/home/about-us/", - "slug": "about-us" + "title": "About us", + "draft_title": "About us", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "simplepage"], + "path": "000100010002", + "url_path": "/home/about-us/", + "slug": "about-us" } -}, -{ + }, + { "pk": 7, "model": "tests.simplepage", "fields": { - "content": "<p>We are really good.</p>" + "content": "<p>We are really good.</p>" } -}, + }, -{ + { "pk": 11, "model": "wagtailcore.page", "fields": { - "title": "Other events", - "draft_title": "Other events", - "numchild": 1, - "show_in_menus": true, - "live": true, - "depth": 3, - "content_type": ["tests", "simplepage"], - "path": "000100010005", - "url_path": "/home/other/", - "slug": "other" + "title": "Other events", + "draft_title": "Other events", + "numchild": 1, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "simplepage"], + "path": "000100010005", + "url_path": "/home/other/", + "slug": "other" } -}, -{ + }, + { "pk": 11, "model": "tests.simplepage", "fields": { - "content": "<p>Other events</p>" + "content": "<p>Other events</p>" } -}, + }, -{ + { "pk": 12, "model": "wagtailcore.page", "fields": { - "title": "Special event", - "draft_title": "Special event", - "numchild": 0, - "show_in_menus": false, - "live": true, - "depth": 4, - "content_type": ["tests", "eventpage"], - "path": "0001000100050001", - "url_path": "/home/other/special-event/", - "slug": "special-event" + "title": "Special event", + "draft_title": "Special event", + "numchild": 0, + "show_in_menus": false, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100050001", + "url_path": "/home/other/special-event/", + "slug": "special-event" } -}, -{ + }, + { "pk": 12, "model": "tests.eventpage", "fields": { - "date_from": "2015-07-04", - "audience": "public", - "location": "Hobart", - "body": "<p>Party time</p>", - "cost": "free" + "date_from": "2015-07-04", + "audience": "public", + "location": "Hobart", + "body": "<p>Party time</p>", + "cost": "free" } -}, + }, -{ + { "pk": 1, "model": "wagtailcore.site", "fields": { - "root_page": 2, - "hostname": "localhost", - "port": 80, - "is_default_site": true + "root_page": 2, + "hostname": "localhost", + "port": 80, + "is_default_site": true } -} + } ] diff --git a/wagtail/users/static_src/wagtailusers/js/group-form.js b/wagtail/users/static_src/wagtailusers/js/group-form.js index 5879ba282c..e539a32bec 100644 --- a/wagtail/users/static_src/wagtailusers/js/group-form.js +++ b/wagtail/users/static_src/wagtailusers/js/group-form.js @@ -1,13 +1,13 @@ -$(function() { - buildExpandingFormset('id_page_permissions', { - onInit: function(index) { - var deleteInputId = 'id_page_permissions-' + index + '-DELETE'; - var childId = 'inline_child_page_permissions-' + index; - $('#' + deleteInputId + '-button').on('click', function() { - /* set 'deleted' form field to true */ - $('#' + deleteInputId).val('1'); - $('#' + childId).fadeOut(); - }); - } - }); +$(function () { + buildExpandingFormset('id_page_permissions', { + onInit: function (index) { + var deleteInputId = 'id_page_permissions-' + index + '-DELETE'; + var childId = 'inline_child_page_permissions-' + index; + $('#' + deleteInputId + '-button').on('click', function () { + /* set 'deleted' form field to true */ + $('#' + deleteInputId).val('1'); + $('#' + childId).fadeOut(); + }); + }, + }); }); diff --git a/wagtail/users/static_src/wagtailusers/scss/groups_edit.scss b/wagtail/users/static_src/wagtailusers/scss/groups_edit.scss index f78cc22912..9c5af96697 100644 --- a/wagtail/users/static_src/wagtailusers/scss/groups_edit.scss +++ b/wagtail/users/static_src/wagtailusers/scss/groups_edit.scss @@ -1,34 +1,33 @@ @import '../../../../../client/scss/tools/mixins.general'; .listing { - .field label { - @include visuallyhidden; - } + .field label { + @include visuallyhidden; + } - input, - select, + input, + select, + textarea { + font-size: 1em; + } - textarea { - font-size: 1em; - } + select + span:after { + // stylelint-disable-next-line declaration-no-important + font-size: 2.5em !important; + } - select + span:after { - // stylelint-disable-next-line declaration-no-important - font-size: 2.5em !important; - } + .custom-permissions { + padding-bottom: 0; + } - .custom-permissions { - padding-bottom: 0; - } - - .custom-permissions-item { - font-weight: inherit; - width: 100%; - } + .custom-permissions-item { + font-weight: inherit; + width: 100%; + } } .page-permissions-listing { - .admin_page_chooser .field-content { - width: 100%; // so that 'choose another page' button displays in its entirety - } + .admin_page_chooser .field-content { + width: 100%; // so that 'choose another page' button displays in its entirety + } }