Reformat codebase with Prettier (#7912)

- Automated reformatting
- Manually change code where Prettier reformatting causes issues
- Revert "Disable Prettier formatting in CI for now"
pull/7907/head
Thibaud Colas 2022-02-04 11:57:55 +00:00 zatwierdzone przez GitHub
rodzic 11ccf30f00
commit af942a27e4
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
328 zmienionych plików z 18951 dodań i 17086 usunięć

Wyświetl plik

@ -12,9 +12,9 @@ jobs:
key: pipenv-v1-{{ checksum "setup.py" }}
# Only install if .venv wasnt 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 wasnt 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 wasnt 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 wasnt 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

Wyświetl plik

@ -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 dont 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',
},
},
],
};

Wyświetl plik

@ -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

Wyświetl plik

@ -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:

Wyświetl plik

@ -1,4 +1,4 @@
python:
version: 3.7
pip_install: true
version: 3.7
pip_install: true
requirements_file: null

Wyświetl plik

@ -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',
],
},
];

Wyświetl plik

@ -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$/,
},
},
}
};

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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();
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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%;
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -1,5 +1,5 @@
@include media-breakpoint-up(sm) {
.browsermessage {
margin: 0 0 0 -150px;
}
.browsermessage {
margin: 0 0 0 -150px;
}
}

Wyświetl plik

@ -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';

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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%)
);
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -1,3 +1,3 @@
.homepage h1 {
text-transform: none;
text-transform: none;
}

Wyświetl plik

@ -1,3 +1,3 @@
.page-explorer h2 {
text-transform: none;
text-transform: none;
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -1,3 +1,3 @@
.unbold {
font-weight: normal;
font-weight: normal;
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -1,7 +1,7 @@
.visuallyvisible {
@include visuallyvisible;
@include visuallyvisible;
}
.visuallyhidden {
@include visuallyhidden;
@include visuallyhidden;
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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'),
);

Wyświetl plik

@ -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%
);

Wyświetl plik

@ -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';

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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`,
);
});
});

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);

Wyświetl plik

@ -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', () => {

Wyświetl plik

@ -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 (

Wyświetl plik

@ -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],
]),
};

Wyświetl plik

@ -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,

Wyświetl plik

@ -10,7 +10,7 @@ export interface UpdateGlobalSettingsAction {
export type Action = UpdateGlobalSettingsAction;
export function updateGlobalSettings(
update: SettingsStateUpdate
update: SettingsStateUpdate,
): UpdateGlobalSettingsAction {
return {
type: UPDATE_GLOBAL_SETTINGS,

Wyświetl plik

@ -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',
},
});

Wyświetl plik

@ -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,
);
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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>
);

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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',
},
});

Wyświetl plik

@ -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 (

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);
});

Wyświetl plik

@ -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,
);
}
}
});

Wyświetl plik

@ -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,
);

Wyświetl plik

@ -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,
);

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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),
),
);
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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', () => {

Wyświetl plik

@ -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}
/>
);

Wyświetl plik

@ -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);
}

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