@plone/volto 16.22.0 → 16.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.changelog.draft +12 -22
  2. package/.yarn/install-state.gz +0 -0
  3. package/CHANGELOG.md +50 -1
  4. package/CONTRIBUTING.md +5 -1
  5. package/locales/ca/LC_MESSAGES/volto.po +37 -2
  6. package/locales/ca.json +1 -1
  7. package/locales/de/LC_MESSAGES/volto.po +38 -3
  8. package/locales/de.json +1 -1
  9. package/locales/en/LC_MESSAGES/volto.po +37 -2
  10. package/locales/en.json +1 -1
  11. package/locales/es/LC_MESSAGES/volto.po +37 -2
  12. package/locales/es.json +1 -1
  13. package/locales/eu/LC_MESSAGES/volto.po +37 -2
  14. package/locales/eu.json +1 -1
  15. package/locales/fi/LC_MESSAGES/volto.po +37 -2
  16. package/locales/fi.json +1 -1
  17. package/locales/fr/LC_MESSAGES/volto.po +37 -2
  18. package/locales/fr.json +1 -1
  19. package/locales/it/LC_MESSAGES/volto.po +228 -193
  20. package/locales/it.json +1 -1
  21. package/locales/ja/LC_MESSAGES/volto.po +37 -2
  22. package/locales/ja.json +1 -1
  23. package/locales/nl/LC_MESSAGES/volto.po +37 -2
  24. package/locales/nl.json +1 -1
  25. package/locales/pt/LC_MESSAGES/volto.po +37 -2
  26. package/locales/pt.json +1 -1
  27. package/locales/pt_BR/LC_MESSAGES/volto.po +37 -2
  28. package/locales/pt_BR.json +1 -1
  29. package/locales/ro/LC_MESSAGES/volto.po +37 -2
  30. package/locales/ro.json +1 -1
  31. package/locales/volto.pot +38 -3
  32. package/locales/zh_CN/LC_MESSAGES/volto.po +37 -2
  33. package/locales/zh_CN.json +1 -1
  34. package/package.json +1 -1
  35. package/packages/volto-slate/package.json +1 -1
  36. package/src/components/manage/Blocks/Image/schema.js +5 -1
  37. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +18 -11
  38. package/src/components/manage/Blocks/Search/components/SearchInput.jsx +9 -2
  39. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +12 -1
  40. package/src/components/manage/Blocks/ToC/Schema.jsx +36 -7
  41. package/src/components/manage/Contents/Contents.jsx +27 -0
  42. package/src/components/manage/Contents/ContentsPropertiesModal.jsx +1 -13
  43. package/src/components/manage/Controlpanels/Groups/RenderGroups.jsx +2 -2
  44. package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +2 -2
  45. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +25 -10
  46. package/src/components/manage/Diff/DiffField.jsx +25 -1
  47. package/src/components/manage/Sharing/Sharing.jsx +11 -5
  48. package/src/components/manage/Widgets/FormFieldWrapper.jsx +1 -1
  49. package/src/components/manage/Widgets/SelectUtils.js +1 -1
  50. package/src/components/theme/Error/ServerError.jsx +29 -0
  51. package/src/components/theme/Sitemap/Sitemap.jsx +24 -13
  52. package/src/components/theme/Sitemap/Sitemap.test.jsx +23 -2
  53. package/src/config/Views.jsx +2 -0
  54. package/src/express-middleware/files.js +8 -6
  55. package/src/express-middleware/images.js +7 -1
  56. package/src/middleware/api.js +14 -2
  57. package/src/reducers/actions/actions.js +7 -5
  58. package/src/reducers/actions/actions.test.js +70 -0
  59. package/src/server.jsx +9 -0
  60. package/styles/Vocab/Plone/accept.txt +9 -2
  61. package/styles/Vocab/Plone/reject.txt +2 -5
  62. package/theme/themes/pastanaga/collections/form.overrides +46 -0
  63. package/theme/themes/pastanaga/elements/input.overrides +6 -0
  64. package/theme/themes/pastanaga/elements/label.overrides +10 -0
@@ -57,7 +57,7 @@ class RenderGroups extends Component {
57
57
  * @memberof UsersControlpanelUser
58
58
  */
59
59
  onChange(event, { value }) {
60
- const [group, role] = value.split('.');
60
+ const [group, role] = value.split('&role=');
61
61
  this.props.updateGroups(group, role);
62
62
  }
63
63
 
@@ -97,7 +97,7 @@ class RenderGroups extends Component {
97
97
  : this.props.group.roles.includes(role.id)
98
98
  }
99
99
  onChange={this.onChange}
100
- value={`${this.props.group.id}.${role.id}`}
100
+ value={`${this.props.group.id}&role=${role.id}`}
101
101
  />
102
102
  )}
103
103
  </Table.Cell>
@@ -53,7 +53,7 @@ class RenderUsers extends Component {
53
53
  */
54
54
 
55
55
  onChange(event, { value }) {
56
- const [user, role] = value.split('.');
56
+ const [user, role] = value.split('&role=');
57
57
  this.props.updateUser(user, role);
58
58
  }
59
59
  /**
@@ -83,7 +83,7 @@ class RenderUsers extends Component {
83
83
  <Checkbox
84
84
  checked={this.props.user.roles.includes(role.id)}
85
85
  onChange={this.onChange}
86
- value={`${this.props.user.id}.${role.id}`}
86
+ value={`${this.props.user.id}&role=${role.id}`}
87
87
  />
88
88
  )}
89
89
  </Table.Cell>
@@ -107,6 +107,7 @@ class UsersControlpanel extends Component {
107
107
  isClient: false,
108
108
  currentPage: 0,
109
109
  pageSize: 10,
110
+ loginUsingEmail: false,
110
111
  };
111
112
  }
112
113
 
@@ -122,6 +123,14 @@ class UsersControlpanel extends Component {
122
123
  }
123
124
  };
124
125
 
126
+ // Because username field needs to be disabled if email login is enabled!
127
+ checkLoginUsingEmailStatus = async () => {
128
+ await this.props.getControlpanel('security');
129
+ this.setState({
130
+ loginUsingEmail: this.props.controlPanelData?.data.use_email_as_login,
131
+ });
132
+ };
133
+
125
134
  /**
126
135
  * Component did mount
127
136
  * @method componentDidMount
@@ -132,6 +141,7 @@ class UsersControlpanel extends Component {
132
141
  isClient: true,
133
142
  });
134
143
  this.fetchData();
144
+ this.checkLoginUsingEmailStatus();
135
145
  }
136
146
 
137
147
  UNSAFE_componentWillReceiveProps(nextProps) {
@@ -425,7 +435,7 @@ class UsersControlpanel extends Component {
425
435
  id: 'default',
426
436
  title: 'FIXME: User Data',
427
437
  fields: [
428
- 'username',
438
+ ...(!this.state.loginUsingEmail ? ['username'] : []),
429
439
  'fullname',
430
440
  'email',
431
441
  'password',
@@ -436,15 +446,19 @@ class UsersControlpanel extends Component {
436
446
  },
437
447
  ],
438
448
  properties: {
439
- username: {
440
- title: this.props.intl.formatMessage(
441
- messages.addUserFormUsernameTitle,
442
- ),
443
- type: 'string',
444
- description: this.props.intl.formatMessage(
445
- messages.addUserFormUsernameDescription,
446
- ),
447
- },
449
+ ...(!this.state.loginUsingEmail
450
+ ? {
451
+ username: {
452
+ title: this.props.intl.formatMessage(
453
+ messages.addUserFormUsernameTitle,
454
+ ),
455
+ type: 'string',
456
+ description: this.props.intl.formatMessage(
457
+ messages.addUserFormUsernameDescription,
458
+ ),
459
+ },
460
+ }
461
+ : {}),
448
462
  fullname: {
449
463
  title: this.props.intl.formatMessage(
450
464
  messages.addUserFormFullnameTitle,
@@ -670,6 +684,7 @@ export default compose(
670
684
  createRequest: state.users.create,
671
685
  loadRolesRequest: state.roles,
672
686
  inheritedRole: state.authRole.authenticatedRole,
687
+ controlPanelData: state.controlpanels?.controlpanel,
673
688
  }),
674
689
  (dispatch) =>
675
690
  bindActionCreators(
@@ -17,6 +17,7 @@ import { useSelector } from 'react-redux';
17
17
  import { Api } from '@plone/volto/helpers';
18
18
  import configureStore from '@plone/volto/store';
19
19
  import { DefaultView } from '@plone/volto/components/';
20
+ import { serializeNodes } from '@plone/volto-slate/editor/render';
20
21
 
21
22
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
22
23
 
@@ -69,7 +70,7 @@ const DiffField = ({
69
70
  .replace('\u202F', ' '),
70
71
  );
71
72
  break;
72
- case 'json':
73
+ case 'json': {
73
74
  const api = new Api();
74
75
  const history = createBrowserHistory();
75
76
  const store = configureStore(window.__data, history, api);
@@ -90,6 +91,29 @@ const DiffField = ({
90
91
  ),
91
92
  );
92
93
  break;
94
+ }
95
+ case 'slate': {
96
+ const api = new Api();
97
+ const history = createBrowserHistory();
98
+ const store = configureStore(window.__data, history, api);
99
+ parts = diffWords(
100
+ ReactDOMServer.renderToStaticMarkup(
101
+ <Provider store={store}>
102
+ <ConnectedRouter history={history}>
103
+ {serializeNodes(one)}
104
+ </ConnectedRouter>
105
+ </Provider>,
106
+ ),
107
+ ReactDOMServer.renderToStaticMarkup(
108
+ <Provider store={store}>
109
+ <ConnectedRouter history={history}>
110
+ {serializeNodes(two)}
111
+ </ConnectedRouter>
112
+ </Provider>,
113
+ ),
114
+ );
115
+ break;
116
+ }
93
117
  case 'textarea':
94
118
  default:
95
119
  parts = diffWords(one, two);
@@ -246,9 +246,9 @@ class SharingComponent extends Component {
246
246
  * @returns {undefined}
247
247
  */
248
248
  onToggleInherit() {
249
- this.setState({
250
- inherit: !this.state.inherit,
251
- });
249
+ this.setState((state) => ({
250
+ inherit: !state.inherit,
251
+ }));
252
252
  }
253
253
 
254
254
  /**
@@ -404,9 +404,15 @@ class SharingComponent extends Component {
404
404
  <Segment attached>
405
405
  <Form.Field>
406
406
  <Checkbox
407
- checked={this.state.inherit}
407
+ id="inherit-permissions-checkbox"
408
+ name="inherit-permissions-checkbox"
409
+ defaultChecked={this.state.inherit}
408
410
  onChange={this.onToggleInherit}
409
- label={this.props.intl.formatMessage(messages.inherit)}
411
+ label={
412
+ <label htmlFor="inherit-permissions-checkbox">
413
+ {this.props.intl.formatMessage(messages.inherit)}
414
+ </label>
415
+ }
410
416
  />
411
417
  </Form.Field>
412
418
  <p className="help">
@@ -96,7 +96,7 @@ class FormFieldWrapper extends Component {
96
96
  {this.props.children}
97
97
 
98
98
  {map(error, (message) => (
99
- <Label key={message} basic color="red" pointing>
99
+ <Label key={message} basic color="red" className="form-error-label">
100
100
  {message}
101
101
  </Label>
102
102
  ))}
@@ -111,7 +111,7 @@ export function normalizeValue(choices, value, intl) {
111
111
 
112
112
  if (Array.isArray(value)) {
113
113
  // a list of values, like ['foo', 'bar'];
114
- return value.map((v) => normalizeValue(choices, v));
114
+ return value.map((v) => normalizeValue(choices, v, intl));
115
115
  }
116
116
 
117
117
  if (isObject(value)) {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @module components/theme/Error/ServerError
3
+ */
4
+
5
+ import React from 'react';
6
+ import { FormattedMessage } from 'react-intl';
7
+ import { Container } from 'semantic-ui-react';
8
+ import { withServerErrorCode } from '@plone/volto/helpers/Utils/Utils';
9
+
10
+ /**
11
+ * server error
12
+ * @function ServerError
13
+ * @returns {string} Markup of the server error page.
14
+ */
15
+ const ServerError = () => (
16
+ <Container className="view-wrapper">
17
+ <h1>
18
+ <FormattedMessage id="Server Error" defaultMessage="Server Error" />
19
+ </h1>
20
+ <p className="description">
21
+ <FormattedMessage
22
+ id="We apologize for the inconvenience, but there was an unexpected error on the server."
23
+ defaultMessage="We apologize for the inconvenience, but there was an unexpected error on the server."
24
+ />
25
+ </p>
26
+ </Container>
27
+ );
28
+
29
+ export default withServerErrorCode(500)(ServerError);
@@ -22,6 +22,13 @@ const messages = defineMessages({
22
22
  defaultMessage: 'Sitemap',
23
23
  },
24
24
  });
25
+
26
+ export function getSitemapPath(pathname = '', lang) {
27
+ const prefix = pathname.replace(/\/sitemap$/gm, '').replace(/^\//, '');
28
+ const path = prefix || lang || '';
29
+ return path;
30
+ }
31
+
25
32
  /**
26
33
  * Sitemap class.
27
34
  * @class Sitemap
@@ -39,11 +46,13 @@ class Sitemap extends Component {
39
46
 
40
47
  componentDidMount() {
41
48
  const { settings } = config;
42
- if (settings.isMultilingual) {
43
- this.props.getNavigation(`${toBackendLang(this.props.lang)}`, 4);
44
- } else {
45
- this.props.getNavigation('', 4);
46
- }
49
+
50
+ const lang = settings.isMultilingual
51
+ ? `${toBackendLang(this.props.lang)}`
52
+ : null;
53
+
54
+ const path = getSitemapPath(this.props.location.pathname, lang);
55
+ this.props.getNavigation(path, 4);
47
56
  }
48
57
 
49
58
  /**
@@ -105,15 +114,17 @@ export default compose(
105
114
  {
106
115
  key: 'navigation',
107
116
  promise: ({ location, store: { dispatch, getState } }) => {
117
+ if (!__SERVER__) return;
108
118
  const { settings } = config;
109
- const lang = getState().intl.locale;
110
- if (settings.isMultilingual) {
111
- return (
112
- __SERVER__ && dispatch(getNavigation(`${toBackendLang(lang)}`, 4))
113
- );
114
- } else {
115
- return __SERVER__ && dispatch(getNavigation('', 4));
116
- }
119
+
120
+ const path = getSitemapPath(
121
+ location.pathname,
122
+ settings.isMultilingual
123
+ ? toBackendLang(getState().intl.locale)
124
+ : null,
125
+ );
126
+
127
+ return dispatch(getNavigation(path, 4));
117
128
  },
118
129
  },
119
130
  ]),
@@ -4,7 +4,7 @@ import configureStore from 'redux-mock-store';
4
4
  import { Provider } from 'react-intl-redux';
5
5
  import { MemoryRouter } from 'react-router-dom';
6
6
 
7
- import { __test__ as Sitemap } from './Sitemap';
7
+ import { __test__ as Sitemap, getSitemapPath } from './Sitemap';
8
8
 
9
9
  const mockStore = configureStore();
10
10
 
@@ -46,7 +46,7 @@ describe('Sitemap', () => {
46
46
  const component = renderer.create(
47
47
  <Provider store={store}>
48
48
  <MemoryRouter>
49
- <Sitemap />
49
+ <Sitemap location={{ pathname: '/page-1' }} />
50
50
  </MemoryRouter>
51
51
  </Provider>,
52
52
  );
@@ -54,3 +54,24 @@ describe('Sitemap', () => {
54
54
  expect(json).toMatchSnapshot();
55
55
  });
56
56
  });
57
+
58
+ describe('getSitemapPath', () => {
59
+ it('accepts empty path', () => {
60
+ expect(getSitemapPath('', null)).toBe('');
61
+ });
62
+
63
+ it('accepts a path', () => {
64
+ expect(getSitemapPath('/page-1/sitemap', null)).toBe('page-1');
65
+ });
66
+ it('accepts a path', () => {
67
+ expect(getSitemapPath('/page-1/sitemap', null)).toBe('page-1');
68
+ });
69
+
70
+ it('uses a language as default root', () => {
71
+ expect(getSitemapPath('/', 'de')).toBe('de');
72
+ });
73
+
74
+ it('accepts language-rooted paths', () => {
75
+ expect(getSitemapPath('/de/mission', 'de')).toBe('de/mission');
76
+ });
77
+ });
@@ -16,6 +16,7 @@ import RequestTimeout from '@plone/volto/components/theme/RequestTimeout/Request
16
16
  import AlbumView from '@plone/volto/components/theme/View/AlbumView';
17
17
  import Unauthorized from '@plone/volto/components/theme/Unauthorized/Unauthorized';
18
18
  import Forbidden from '@plone/volto/components/theme/Forbidden/Forbidden';
19
+ import ServerError from '@plone/volto/components/theme/Error/ServerError';
19
20
 
20
21
  const EventView = loadable(() =>
21
22
  import('@plone/volto/components/theme/View/EventView'),
@@ -114,6 +115,7 @@ export const errorViews = {
114
115
  '401': Unauthorized,
115
116
  '403': Forbidden,
116
117
  '408': RequestTimeout,
118
+ '500': ServerError,
117
119
  ECONNREFUSED: ConnectionRefused,
118
120
  corsError: CorsError,
119
121
  };
@@ -2,11 +2,13 @@ import express from 'express';
2
2
  import { getAPIResourceWithAuth } from '@plone/volto/helpers';
3
3
 
4
4
  const HEADERS = [
5
- 'Accept-Ranges',
6
- 'Cache-Control',
7
- 'Content-Disposition',
8
- 'Content-Range',
9
- 'Content-Type',
5
+ 'accept-ranges',
6
+ 'cache-control',
7
+ 'content-disposition',
8
+ 'content-range',
9
+ 'content-type',
10
+ 'x-sendfile',
11
+ 'x-accel-redirect',
10
12
  ];
11
13
 
12
14
  function filesMiddlewareFn(req, res, next) {
@@ -14,7 +16,7 @@ function filesMiddlewareFn(req, res, next) {
14
16
  .then((resource) => {
15
17
  // Just forward the headers that we need
16
18
  HEADERS.forEach((header) => {
17
- if (resource.get(header)) {
19
+ if (resource.headers[header]) {
18
20
  res.set(header, resource.get(header));
19
21
  }
20
22
  });
@@ -1,7 +1,13 @@
1
1
  import express from 'express';
2
2
  import { getAPIResourceWithAuth } from '@plone/volto/helpers';
3
3
 
4
- const HEADERS = ['content-type', 'content-disposition', 'cache-control'];
4
+ const HEADERS = [
5
+ 'content-type',
6
+ 'content-disposition',
7
+ 'cache-control',
8
+ 'x-sendfile',
9
+ 'x-accel-redirect',
10
+ ];
5
11
 
6
12
  function imageMiddlewareFn(req, res, next) {
7
13
  getAPIResourceWithAuth(req)
@@ -250,10 +250,22 @@ const apiMiddlewareFactory = (api) => ({ dispatch, getState }) => (next) => (
250
250
  };
251
251
  });
252
252
  }
253
- return next({ ...rest, result, type: `${type}_SUCCESS` });
253
+ try {
254
+ return next({ ...rest, result, type: `${type}_SUCCESS` });
255
+ } catch (error) {
256
+ // There was an exception while processing reducers or downstream middleware.
257
+ next({
258
+ ...rest,
259
+ error: { status: 500, error },
260
+ type: `${type}_FAIL`,
261
+ });
262
+ // Rethrow the original exception on the client side only,
263
+ // so it doesn't fall through to express on the server.
264
+ if (__CLIENT__) throw error;
265
+ }
254
266
  },
255
267
  (error) => {
256
- // Only SRR can set ECONNREFUSED
268
+ // Only SSR can set ECONNREFUSED
257
269
  if (error.code === 'ECONNREFUSED') {
258
270
  next({
259
271
  ...rest,
@@ -57,11 +57,13 @@ export default function actions(state = initialState, action = {}) {
57
57
  }
58
58
  return state;
59
59
  case `${LIST_ACTIONS}_SUCCESS`:
60
- hasExpander = hasApiExpander(
61
- 'actions',
62
- getBaseUrl(flattenToAppURL(action.result['@id'])),
63
- );
64
- if (!hasExpander) {
60
+ // Even if the expander is set or not, if the LIST_ACTIONS is
61
+ // called, we want it to store the data if the actions data is
62
+ // not set in the expander data (['@components']) but in the "normal"
63
+ // action result (we look for the object property returned by the endpoint)
64
+ // Unfortunately, this endpoint returns all the actions in a plain object
65
+ // with no structure :(
66
+ if (action.result.object) {
65
67
  return {
66
68
  ...state,
67
69
  error: null,
@@ -207,4 +207,74 @@ describe('Actions reducer - (ACTIONS)GET_CONTENT', () => {
207
207
  loading: false,
208
208
  });
209
209
  });
210
+
211
+ it('should handle (ACTIONS)LIST_ACTIONS (standalone with apiExpander enabled)', () => {
212
+ expect(
213
+ actions(undefined, {
214
+ type: `${LIST_ACTIONS}_SUCCESS`,
215
+ result: {
216
+ object: [],
217
+ object_buttons: [],
218
+ site_actions: [],
219
+ user: [
220
+ {
221
+ icon: '',
222
+ id: 'preferences',
223
+ title: 'Preferences',
224
+ },
225
+ {
226
+ icon: '',
227
+ id: 'dashboard',
228
+ title: 'Dashboard',
229
+ },
230
+ {
231
+ icon: '',
232
+ id: 'plone_setup',
233
+ title: 'Site Setup',
234
+ },
235
+ {
236
+ icon: '',
237
+ id: 'logout',
238
+ title: 'Log out',
239
+ },
240
+ ],
241
+ document_actions: [],
242
+ portal_tabs: [],
243
+ },
244
+ }),
245
+ ).toEqual({
246
+ error: null,
247
+ actions: {
248
+ object: [],
249
+ object_buttons: [],
250
+ site_actions: [],
251
+ user: [
252
+ {
253
+ icon: '',
254
+ id: 'preferences',
255
+ title: 'Preferences',
256
+ },
257
+ {
258
+ icon: '',
259
+ id: 'dashboard',
260
+ title: 'Dashboard',
261
+ },
262
+ {
263
+ icon: '',
264
+ id: 'plone_setup',
265
+ title: 'Site Setup',
266
+ },
267
+ {
268
+ icon: '',
269
+ id: 'logout',
270
+ title: 'Log out',
271
+ },
272
+ ],
273
+ document_actions: [],
274
+ portal_tabs: [],
275
+ },
276
+ loaded: true,
277
+ loading: false,
278
+ });
279
+ });
210
280
  });
package/src/server.jsx CHANGED
@@ -263,6 +263,15 @@ server.get('/*', (req, res) => {
263
263
  const readCriticalCss =
264
264
  config.settings.serverConfig.readCriticalCss || defaultReadCriticalCss;
265
265
 
266
+ // If we are showing an "old browser" warning,
267
+ // make sure it doesn't get cached in a shared cache
268
+ const browserdetect = store.getState().browserdetect;
269
+ if (config.settings.notSupportedBrowsers.includes(browserdetect?.name)) {
270
+ res.set({
271
+ 'Cache-Control': 'private',
272
+ });
273
+ }
274
+
266
275
  if (context.url) {
267
276
  res.redirect(flattenToAppURL(context.url));
268
277
  } else if (context.error_code) {
@@ -1,10 +1,17 @@
1
+ -{0,1}plone-{0,1}
2
+ -{0,1}volto-{0,1}
1
3
  `plone.restapi`
2
4
  `plone.volto`
5
+ [Aa]sync
6
+ [Bb]ackend
7
+ JavaScript
3
8
  npm
9
+ nvm
10
+ Pastanaga
4
11
  Plone
5
12
  Razzle
6
13
  RichText
14
+ Sass
7
15
  Volto
16
+ Vue
8
17
  Zope
9
- JavaScript
10
- NodeJS
@@ -1,5 +1,2 @@
1
- node
2
- nodejs
3
- javascript
4
- js
5
- Javascript
1
+ [^.]js
2
+ NodeJS
@@ -49,6 +49,52 @@
49
49
  }
50
50
  }
51
51
 
52
+ .ui.form .fields.error .field textarea,
53
+ .ui.form .fields.error .field select,
54
+ .ui.form .fields.error .field input:not([type]),
55
+ .ui.form .fields.error .field input[type='date'],
56
+ .ui.form .fields.error .field input[type='datetime-local'],
57
+ .ui.form .fields.error .field input[type='email'],
58
+ .ui.form .fields.error .field input[type='number'],
59
+ .ui.form .fields.error .field input[type='password'],
60
+ .ui.form .fields.error .field input[type='search'],
61
+ .ui.form .fields.error .field input[type='tel'],
62
+ .ui.form .fields.error .field input[type='time'],
63
+ .ui.form .fields.error .field input[type='text'],
64
+ .ui.form .fields.error .field input[type='file'],
65
+ .ui.form .fields.error .field input[type='url'],
66
+ .ui.form .field.error textarea,
67
+ .ui.form .field.error select,
68
+ .ui.form .field.error input:not([type]),
69
+ .ui.form .field.error input[type='date'],
70
+ .ui.form .field.error input[type='datetime-local'],
71
+ .ui.form .field.error input[type='email'],
72
+ .ui.form .field.error input[type='number'],
73
+ .ui.form .field.error input[type='password'],
74
+ .ui.form .field.error input[type='search'],
75
+ .ui.form .field.error input[type='tel'],
76
+ .ui.form .field.error input[type='time'],
77
+ .ui.form .field.error input[type='text'],
78
+ .ui.form .field.error input[type='file'],
79
+ .ui.form .field.error input[type='url'],
80
+ .ui.form .field.error textarea:focus,
81
+ .ui.form .field.error select:focus,
82
+ .ui.form .field.error input:not([type]):focus,
83
+ .ui.form .field.error input[type='date']:focus,
84
+ .ui.form .field.error input[type='datetime-local']:focus,
85
+ .ui.form .field.error input[type='email']:focus,
86
+ .ui.form .field.error input[type='number']:focus,
87
+ .ui.form .field.error input[type='password']:focus,
88
+ .ui.form .field.error input[type='search']:focus,
89
+ .ui.form .field.error input[type='tel']:focus,
90
+ .ui.form .field.error input[type='time']:focus,
91
+ .ui.form .field.error input[type='text']:focus,
92
+ .ui.form .field.error input[type='file']:focus,
93
+ .ui.form .field.error input[type='url']:focus {
94
+ border-color: #d01157;
95
+ background: none;
96
+ }
97
+
52
98
  .ui.form .field > .selection.dropdown {
53
99
  height: 60px;
54
100
  }
@@ -6,6 +6,12 @@
6
6
  font-weight: @inputFontWeight;
7
7
  }
8
8
 
9
+ /* This aligns the height of the field name label to the other side in case
10
+ of an error is present, it overrides a default from SemanticUI grid definitions. */
11
+ .ui.grid > .stretched.row > .column > .wrapper {
12
+ flex-grow: 0;
13
+ }
14
+
9
15
  .inline.field {
10
16
  .wrapper {
11
17
  display: flex;
@@ -6,3 +6,13 @@
6
6
  margin: 0em 0em 0em 0.75em;
7
7
  }
8
8
  }
9
+
10
+ .ui.form {
11
+ .ui.basic.red.label.form-error-label {
12
+ padding-left: 0;
13
+ border: none;
14
+ margin-top: 0;
15
+ color: #d01157 !important;
16
+ font-weight: normal;
17
+ }
18
+ }