@plone/volto 14.6.0 → 14.8.1
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.
- package/CHANGELOG.md +49 -0
- package/cypress.json +2 -1
- package/package.json +6 -4
- package/pyvenv.cfg +3 -0
- package/src/components/index.js +2 -0
- package/src/components/manage/Add/Add.jsx +39 -36
- package/src/components/manage/Contents/Contents.test.jsx +0 -7
- package/src/components/manage/Contents/ContentsItem.jsx +9 -14
- package/src/components/manage/Contents/ContentsUploadModal.jsx +2 -4
- package/src/components/manage/Controlpanels/ModerateComments.jsx +7 -5
- package/src/components/manage/Diff/Diff.jsx +13 -5
- package/src/components/manage/Diff/Diff.test.jsx +0 -6
- package/src/components/manage/Diff/DiffField.jsx +12 -3
- package/src/components/manage/Diff/DiffField.test.jsx +0 -6
- package/src/components/manage/History/History.jsx +6 -5
- package/src/components/manage/History/History.test.jsx +12 -7
- package/src/components/manage/Widgets/DatetimeWidget.jsx +10 -3
- package/src/components/manage/Widgets/DatetimeWidget.test.jsx +2 -2
- package/src/components/manage/Widgets/RecurrenceWidget/ByDayField.jsx +4 -3
- package/src/components/manage/Widgets/RecurrenceWidget/MonthOfTheYearField.jsx +10 -3
- package/src/components/manage/Widgets/RecurrenceWidget/Occurences.jsx +8 -4
- package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +21 -13
- package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.test.jsx +6 -0
- package/src/components/manage/Widgets/RecurrenceWidget/Utils.js +1 -2
- package/src/components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthField.jsx +5 -3
- package/src/components/manage/Widgets/UrlWidget.jsx +2 -0
- package/src/components/theme/App/App.jsx +4 -1
- package/src/components/theme/Comments/Comments.jsx +3 -1
- package/src/components/theme/Comments/Comments.test.jsx +6 -0
- package/src/components/theme/Component/Component.jsx +19 -0
- package/src/components/theme/Component/Component.test.jsx +31 -0
- package/src/components/theme/FormattedDate/FormattedDate.jsx +42 -0
- package/src/components/theme/FormattedDate/FormattedDate.stories.jsx +91 -0
- package/src/components/theme/FormattedDate/FormattedRelativeDate.jsx +57 -0
- package/src/components/theme/FormattedDate/FormattedRelativeDate.stories.jsx +122 -0
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +28 -1
- package/src/components/theme/View/EventDatesInfo.jsx +12 -6
- package/src/components/theme/View/EventDatesInfo.test.jsx +6 -0
- package/src/components/theme/View/EventView.test.jsx +6 -0
- package/src/config/index.js +1 -0
- package/src/helpers/Utils/Date.js +97 -0
- package/src/helpers/Utils/Date.test.js +197 -0
- package/src/helpers/Utils/Utils.js +1 -2
- package/src/helpers/Utils/Utils.test.js +25 -0
- package/src/registry.js +16 -0
- package/src/registry.test.js +28 -0
- package/theme/themes/pastanaga/extras/widgets.less +2 -1
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
9
|
-
import moment from 'moment';
|
|
10
9
|
import cx from 'classnames';
|
|
11
10
|
import { List, Button, Header, Label } from 'semantic-ui-react';
|
|
12
11
|
import { Icon } from '@plone/volto/components';
|
|
13
12
|
import addSVG from '@plone/volto/icons/circle-plus.svg';
|
|
14
13
|
import trashSVG from '@plone/volto/icons/delete.svg';
|
|
14
|
+
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
15
15
|
|
|
16
16
|
import { toISOString } from './Utils';
|
|
17
17
|
|
|
@@ -46,7 +46,7 @@ const messages = defineMessages({
|
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
const formatDate = (d) => {
|
|
49
|
+
const formatDate = (d, moment) => {
|
|
50
50
|
const m = moment(d);
|
|
51
51
|
return m.format('dddd') + ', ' + m.format('LL');
|
|
52
52
|
};
|
|
@@ -56,14 +56,16 @@ const formatDate = (d) => {
|
|
|
56
56
|
* @function Occurences
|
|
57
57
|
* @returns {string} Markup of the component.
|
|
58
58
|
*/
|
|
59
|
-
const
|
|
59
|
+
const Occurences_ = ({
|
|
60
60
|
rruleSet,
|
|
61
61
|
exclude,
|
|
62
62
|
undoExclude,
|
|
63
63
|
intl,
|
|
64
64
|
showTitle,
|
|
65
65
|
editOccurences,
|
|
66
|
+
moment: momentlib,
|
|
66
67
|
}) => {
|
|
68
|
+
const moment = momentlib.default;
|
|
67
69
|
moment.locale(intl.locale);
|
|
68
70
|
let all = [];
|
|
69
71
|
const isExcluded = (date) => {
|
|
@@ -154,7 +156,7 @@ const Occurences = ({
|
|
|
154
156
|
</List.Content>
|
|
155
157
|
)}
|
|
156
158
|
<List.Content className={cx({ excluded: excluded })}>
|
|
157
|
-
{formatDate(date)}
|
|
159
|
+
{formatDate(date, moment)}
|
|
158
160
|
{isAdditional(date) && (
|
|
159
161
|
<Label
|
|
160
162
|
pointing="left"
|
|
@@ -182,6 +184,8 @@ const Occurences = ({
|
|
|
182
184
|
);
|
|
183
185
|
};
|
|
184
186
|
|
|
187
|
+
export const Occurences = injectLazyLibs(['moment'])(Occurences_);
|
|
188
|
+
|
|
185
189
|
/**
|
|
186
190
|
* Property types.
|
|
187
191
|
* @property {Object} propTypes Property types.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import React, { Component } from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
|
-
import
|
|
8
|
+
import { compose } from 'redux';
|
|
9
9
|
import { RRule, RRuleSet, rrulestr } from 'rrule';
|
|
10
10
|
|
|
11
11
|
import cx from 'classnames';
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from 'semantic-ui-react';
|
|
23
23
|
|
|
24
24
|
import { SelectWidget, Icon, DatetimeWidget } from '@plone/volto/components';
|
|
25
|
+
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
25
26
|
|
|
26
27
|
import saveSVG from '@plone/volto/icons/save.svg';
|
|
27
28
|
import editingSVG from '@plone/volto/icons/editing.svg';
|
|
@@ -179,7 +180,8 @@ class RecurrenceWidget extends Component {
|
|
|
179
180
|
constructor(props, intl) {
|
|
180
181
|
super(props);
|
|
181
182
|
|
|
182
|
-
moment
|
|
183
|
+
this.moment = this.props.moment.default;
|
|
184
|
+
this.moment.locale(this.props.intl.locale);
|
|
183
185
|
|
|
184
186
|
let rruleSet = this.props.value
|
|
185
187
|
? rrulestr(props.value, {
|
|
@@ -197,7 +199,7 @@ class RecurrenceWidget extends Component {
|
|
|
197
199
|
open: false,
|
|
198
200
|
rruleSet: rruleSet,
|
|
199
201
|
formValues: this.getFormValues(rruleSet),
|
|
200
|
-
RRULE_LANGUAGE: rrulei18n(this.props.intl),
|
|
202
|
+
RRULE_LANGUAGE: rrulei18n(this.props.intl, this.moment),
|
|
201
203
|
};
|
|
202
204
|
}
|
|
203
205
|
|
|
@@ -257,8 +259,8 @@ class RecurrenceWidget extends Component {
|
|
|
257
259
|
|
|
258
260
|
getUTCDate = (date) => {
|
|
259
261
|
return date.match(/T(.)*(-|\+|Z)/g)
|
|
260
|
-
? moment(date).utc()
|
|
261
|
-
: moment(`${date}Z`).utc();
|
|
262
|
+
? this.moment(date).utc()
|
|
263
|
+
: this.moment(`${date}Z`).utc();
|
|
262
264
|
};
|
|
263
265
|
|
|
264
266
|
show = (dimmer) => () => {
|
|
@@ -412,7 +414,7 @@ class RecurrenceWidget extends Component {
|
|
|
412
414
|
}
|
|
413
415
|
break;
|
|
414
416
|
case 'until':
|
|
415
|
-
value = value ? moment(new Date(value)).utc().toDate() : null;
|
|
417
|
+
value = value ? this.moment(new Date(value)).utc().toDate() : null;
|
|
416
418
|
break;
|
|
417
419
|
default:
|
|
418
420
|
break;
|
|
@@ -437,7 +439,7 @@ class RecurrenceWidget extends Component {
|
|
|
437
439
|
? value
|
|
438
440
|
: rruleSet.dtstart()
|
|
439
441
|
? rruleSet.dtstart()
|
|
440
|
-
: moment().utc().toDate();
|
|
442
|
+
: this.moment().utc().toDate();
|
|
441
443
|
var exdates =
|
|
442
444
|
field === 'exdates' ? value : Object.assign([], rruleSet.exdates());
|
|
443
445
|
|
|
@@ -457,6 +459,7 @@ class RecurrenceWidget extends Component {
|
|
|
457
459
|
};
|
|
458
460
|
|
|
459
461
|
getDefaultUntil = (freq) => {
|
|
462
|
+
const moment = this.moment;
|
|
460
463
|
var end = this.props.formData?.end
|
|
461
464
|
? toISOString(this.getUTCDate(this.props.formData.end).toDate())
|
|
462
465
|
: null;
|
|
@@ -495,18 +498,19 @@ class RecurrenceWidget extends Component {
|
|
|
495
498
|
changeField = (formValues, field, value) => {
|
|
496
499
|
// git p.log('field', field, 'value', value);
|
|
497
500
|
//get weekday from state.
|
|
498
|
-
|
|
501
|
+
const moment = this.moment;
|
|
502
|
+
const byweekday =
|
|
499
503
|
this.state?.rruleSet?.rrules().length > 0
|
|
500
504
|
? this.state.rruleSet.rrules()[0].origOptions.byweekday
|
|
501
505
|
: null;
|
|
502
|
-
|
|
503
|
-
|
|
506
|
+
const currWeekday = this.getWeekday(moment().day() - 1);
|
|
507
|
+
const currMonth = moment().month() + 1;
|
|
504
508
|
|
|
505
|
-
|
|
509
|
+
const startMonth = this.props.formData?.start
|
|
506
510
|
? moment(this.props.formData.start).month() + 1
|
|
507
511
|
: currMonth;
|
|
508
512
|
|
|
509
|
-
|
|
513
|
+
const startWeekday = this.props.formData?.start
|
|
510
514
|
? this.getWeekday(moment(this.props.formData.start).day() - 1)
|
|
511
515
|
: currWeekday;
|
|
512
516
|
formValues[field] = value;
|
|
@@ -687,6 +691,7 @@ class RecurrenceWidget extends Component {
|
|
|
687
691
|
};
|
|
688
692
|
|
|
689
693
|
addDate = (date) => {
|
|
694
|
+
const moment = this.moment;
|
|
690
695
|
let all = concat(this.state.rruleSet.all(), this.state.rruleSet.exdates());
|
|
691
696
|
|
|
692
697
|
var simpleDate = moment(new Date(date)).startOf('day').toDate().getTime();
|
|
@@ -957,4 +962,7 @@ class RecurrenceWidget extends Component {
|
|
|
957
962
|
}
|
|
958
963
|
}
|
|
959
964
|
|
|
960
|
-
export default
|
|
965
|
+
export default compose(
|
|
966
|
+
injectLazyLibs(['moment']),
|
|
967
|
+
injectIntl,
|
|
968
|
+
)(RecurrenceWidget);
|
|
@@ -6,6 +6,12 @@ import { waitFor } from '@testing-library/react';
|
|
|
6
6
|
|
|
7
7
|
import RecurrenceWidget from './RecurrenceWidget';
|
|
8
8
|
|
|
9
|
+
jest.mock('@plone/volto/helpers/Loadable/Loadable');
|
|
10
|
+
beforeAll(
|
|
11
|
+
async () =>
|
|
12
|
+
await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(),
|
|
13
|
+
);
|
|
14
|
+
|
|
9
15
|
const mockStore = configureStore();
|
|
10
16
|
|
|
11
17
|
test('renders a recurrence widget component', async () => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RRule } from 'rrule';
|
|
2
|
-
import moment from 'moment';
|
|
3
2
|
import { defineMessages } from 'react-intl';
|
|
4
3
|
|
|
5
4
|
export const Days = {
|
|
@@ -43,7 +42,7 @@ export const toISOString = (date) => {
|
|
|
43
42
|
return date.toISOString().split('T')[0];
|
|
44
43
|
};
|
|
45
44
|
|
|
46
|
-
export const rrulei18n = (intl) => {
|
|
45
|
+
export const rrulei18n = (intl, moment) => {
|
|
47
46
|
moment.locale(intl.locale);
|
|
48
47
|
|
|
49
48
|
const messages = defineMessages({
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { map } from 'lodash';
|
|
8
|
-
import moment from 'moment';
|
|
9
8
|
import { useIntl } from 'react-intl';
|
|
10
9
|
import { Days } from './Utils';
|
|
11
10
|
import SelectInput from './SelectInput';
|
|
12
11
|
import { Form } from 'semantic-ui-react';
|
|
12
|
+
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* WeekdayOfTheMonthField component class.
|
|
@@ -18,8 +18,10 @@ import { Form } from 'semantic-ui-react';
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const WeekdayOfTheMonthField = (props) => {
|
|
21
|
-
const { disabled = false } = props;
|
|
21
|
+
const { disabled = false, moment: momentlib } = props;
|
|
22
22
|
const intl = useIntl();
|
|
23
|
+
|
|
24
|
+
const moment = momentlib.default;
|
|
23
25
|
moment.locale(intl.locale);
|
|
24
26
|
|
|
25
27
|
const weekdayOfTheMonthList = [
|
|
@@ -40,4 +42,4 @@ const WeekdayOfTheMonthField = (props) => {
|
|
|
40
42
|
);
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
export default WeekdayOfTheMonthField;
|
|
45
|
+
export default injectLazyLibs(['moment'])(WeekdayOfTheMonthField);
|
|
@@ -101,6 +101,7 @@ export const UrlWidget = (props) => {
|
|
|
101
101
|
<Button
|
|
102
102
|
basic
|
|
103
103
|
className="cancel"
|
|
104
|
+
aria-label="clearUrlBrowser"
|
|
104
105
|
onClick={(e) => {
|
|
105
106
|
e.preventDefault();
|
|
106
107
|
e.stopPropagation();
|
|
@@ -115,6 +116,7 @@ export const UrlWidget = (props) => {
|
|
|
115
116
|
<Button
|
|
116
117
|
basic
|
|
117
118
|
icon
|
|
119
|
+
aria-label="openUrlBrowser"
|
|
118
120
|
onClick={(e) => {
|
|
119
121
|
e.preventDefault();
|
|
120
122
|
e.stopPropagation();
|
|
@@ -146,7 +146,10 @@ class App extends Component {
|
|
|
146
146
|
<SkipLinks />
|
|
147
147
|
<Header pathname={path} />
|
|
148
148
|
<Breadcrumbs pathname={path} />
|
|
149
|
-
<MultilingualRedirector
|
|
149
|
+
<MultilingualRedirector
|
|
150
|
+
pathname={this.props.pathname}
|
|
151
|
+
contentLanguage={this.props.content?.language?.token}
|
|
152
|
+
>
|
|
150
153
|
<Segment basic className="content-area">
|
|
151
154
|
<main>
|
|
152
155
|
<OutdatedBrowser />
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
} from '@plone/volto/actions';
|
|
12
12
|
import { Avatar, CommentEditModal, Form } from '@plone/volto/components';
|
|
13
13
|
import { flattenToAppURL, getBaseUrl, getColor } from '@plone/volto/helpers';
|
|
14
|
-
import moment from 'moment';
|
|
15
14
|
import PropTypes from 'prop-types';
|
|
16
15
|
import React, { Component } from 'react';
|
|
17
16
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|
@@ -19,6 +18,7 @@ import { Portal } from 'react-portal';
|
|
|
19
18
|
import { connect } from 'react-redux';
|
|
20
19
|
import { compose } from 'redux';
|
|
21
20
|
import { Button, Comment, Container, Icon } from 'semantic-ui-react';
|
|
21
|
+
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
22
22
|
// import { Button, Grid, Segment, Container } from 'semantic-ui-react';
|
|
23
23
|
|
|
24
24
|
const messages = defineMessages({
|
|
@@ -297,6 +297,7 @@ class Comments extends Component {
|
|
|
297
297
|
*/
|
|
298
298
|
render() {
|
|
299
299
|
const { items } = this.props;
|
|
300
|
+
const moment = this.props.moment.default;
|
|
300
301
|
const { collapsedComments } = this.state;
|
|
301
302
|
// object with comment ids, to easily verify if any comment has children
|
|
302
303
|
const allCommentsWithCildren = this.addRepliesAsChildrenToComments(items);
|
|
@@ -471,6 +472,7 @@ class Comments extends Component {
|
|
|
471
472
|
|
|
472
473
|
export default compose(
|
|
473
474
|
injectIntl,
|
|
475
|
+
injectLazyLibs(['moment']),
|
|
474
476
|
connect(
|
|
475
477
|
(state) => ({
|
|
476
478
|
items: state.comments.items,
|
|
@@ -14,6 +14,12 @@ jest.mock('moment', () =>
|
|
|
14
14
|
})),
|
|
15
15
|
);
|
|
16
16
|
|
|
17
|
+
jest.mock('@plone/volto/helpers/Loadable/Loadable');
|
|
18
|
+
beforeAll(
|
|
19
|
+
async () =>
|
|
20
|
+
await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(),
|
|
21
|
+
);
|
|
22
|
+
|
|
17
23
|
describe('Comments', () => {
|
|
18
24
|
it('renders a comments component', () => {
|
|
19
25
|
const store = mockStore({
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import registry from '@plone/volto/registry';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A component that can autommatically look up its implementation from the
|
|
5
|
+
* registry based on the provided component name
|
|
6
|
+
*/
|
|
7
|
+
const Component = ({ name, ...rest }) => {
|
|
8
|
+
const Component = registry.resolve(name)?.component;
|
|
9
|
+
|
|
10
|
+
if (!Component) {
|
|
11
|
+
// eslint-disable-next-line no-console
|
|
12
|
+
console.warn(`Component not found in registry: ${name}`);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return <Component {...rest} />;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Component;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
3
|
+
import { render } from '@testing-library/react';
|
|
4
|
+
import Component from './Component';
|
|
5
|
+
import config from '@plone/volto/registry';
|
|
6
|
+
|
|
7
|
+
config.set('components', {
|
|
8
|
+
Toolbar: { component: (props) => <div>this is the Toolbar component</div> },
|
|
9
|
+
'Toolbar.Types': {
|
|
10
|
+
component: ({ teststring }) => (
|
|
11
|
+
<div>this is the Toolbar component with a prop {teststring} in it</div>
|
|
12
|
+
),
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('Component component :P', () => {
|
|
17
|
+
it('Render a Component in the registry', () => {
|
|
18
|
+
const { container } = render(<Component name="Toolbar" />);
|
|
19
|
+
expect(container).toMatchSnapshot();
|
|
20
|
+
});
|
|
21
|
+
it('Renders a Fallback Component that does not exists in the registry', () => {
|
|
22
|
+
const { container } = render(<Component name="Toolbar.Foo" />);
|
|
23
|
+
expect(container).toMatchSnapshot();
|
|
24
|
+
});
|
|
25
|
+
it('Renders a Component in the registry - passes props correctly', () => {
|
|
26
|
+
const { container } = render(
|
|
27
|
+
<Component name="Toolbar.Types" teststring="Hi!" />,
|
|
28
|
+
);
|
|
29
|
+
expect(container).toMatchSnapshot();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { formatDate, long_date_format } from '@plone/volto/helpers/Utils/Date';
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Friendly formatting of dates
|
|
7
|
+
*/
|
|
8
|
+
const FormattedDate = ({
|
|
9
|
+
date,
|
|
10
|
+
format,
|
|
11
|
+
long,
|
|
12
|
+
includeTime,
|
|
13
|
+
relative,
|
|
14
|
+
className,
|
|
15
|
+
locale,
|
|
16
|
+
children,
|
|
17
|
+
}) => {
|
|
18
|
+
const language = useSelector((state) => locale || state.intl.locale);
|
|
19
|
+
const toDate = (d) => (typeof d === 'string' ? new Date(d) : d);
|
|
20
|
+
const args = { date, long, includeTime, format, locale: language };
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<time
|
|
24
|
+
className={className}
|
|
25
|
+
dateTime={date}
|
|
26
|
+
title={new Intl.DateTimeFormat(language, long_date_format).format(
|
|
27
|
+
new Date(toDate(date)),
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
{children
|
|
31
|
+
? children(
|
|
32
|
+
formatDate({
|
|
33
|
+
...args,
|
|
34
|
+
formatToParts: true,
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
: formatDate(args)}
|
|
38
|
+
</time>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default FormattedDate;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Wrapper from '@plone/volto/storybook';
|
|
3
|
+
import FormattedDate from './FormattedDate';
|
|
4
|
+
|
|
5
|
+
const date = new Date();
|
|
6
|
+
|
|
7
|
+
function StoryComponent(args) {
|
|
8
|
+
const { date, format, long, includeTime, locale } = args;
|
|
9
|
+
return (
|
|
10
|
+
<Wrapper
|
|
11
|
+
customStore={{ intl: { locale } }}
|
|
12
|
+
location={{ pathname: '/folder2/folder21/doc212' }}
|
|
13
|
+
>
|
|
14
|
+
<FormattedDate
|
|
15
|
+
date={date}
|
|
16
|
+
format={format}
|
|
17
|
+
long={long}
|
|
18
|
+
locale={locale}
|
|
19
|
+
includeTime={includeTime}
|
|
20
|
+
>
|
|
21
|
+
{this.children}
|
|
22
|
+
</FormattedDate>
|
|
23
|
+
</Wrapper>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const Default = StoryComponent.bind({});
|
|
28
|
+
Default.args = {
|
|
29
|
+
date,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Localized = StoryComponent.bind({});
|
|
33
|
+
Localized.args = {
|
|
34
|
+
date,
|
|
35
|
+
locale: 'de',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Long = StoryComponent.bind({});
|
|
39
|
+
Long.args = {
|
|
40
|
+
date,
|
|
41
|
+
long: true,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const IncludeTime = StoryComponent.bind({});
|
|
45
|
+
IncludeTime.args = {
|
|
46
|
+
date,
|
|
47
|
+
includeTime: true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const CustomFormat = StoryComponent.bind({});
|
|
51
|
+
CustomFormat.args = {
|
|
52
|
+
date,
|
|
53
|
+
format: {
|
|
54
|
+
year: 'numeric',
|
|
55
|
+
month: 'short',
|
|
56
|
+
day: 'numeric',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const SplitParts = StoryComponent.bind({
|
|
61
|
+
children: (parts) =>
|
|
62
|
+
parts.map((p, i) => (
|
|
63
|
+
<div key={i}>
|
|
64
|
+
<strong>{p.value}</strong> <small>({p.type}</small>)
|
|
65
|
+
</div>
|
|
66
|
+
)),
|
|
67
|
+
});
|
|
68
|
+
SplitParts.args = {
|
|
69
|
+
date,
|
|
70
|
+
long: true,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default {
|
|
74
|
+
title: 'Internal Components/Formatted Date',
|
|
75
|
+
component: FormattedDate,
|
|
76
|
+
decorators: [
|
|
77
|
+
(Story) => (
|
|
78
|
+
<div style={{ width: '400px' }}>
|
|
79
|
+
<Story />
|
|
80
|
+
</div>
|
|
81
|
+
),
|
|
82
|
+
],
|
|
83
|
+
argTypes: {
|
|
84
|
+
locale: {
|
|
85
|
+
control: {
|
|
86
|
+
type: 'select',
|
|
87
|
+
options: ['en', 'de', 'us'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
formatRelativeDate,
|
|
4
|
+
long_date_format,
|
|
5
|
+
toDate,
|
|
6
|
+
} from '@plone/volto/helpers/Utils/Date';
|
|
7
|
+
import { useSelector } from 'react-redux';
|
|
8
|
+
|
|
9
|
+
const FormattedRelativeDate = ({
|
|
10
|
+
date,
|
|
11
|
+
style,
|
|
12
|
+
relativeTo,
|
|
13
|
+
className,
|
|
14
|
+
locale,
|
|
15
|
+
children,
|
|
16
|
+
live = false,
|
|
17
|
+
refresh = 5000,
|
|
18
|
+
}) => {
|
|
19
|
+
const language = useSelector((state) => locale || state.intl.locale);
|
|
20
|
+
const [liveRelativeTo, setLiveRelativeTo] = React.useState(
|
|
21
|
+
relativeTo ? toDate(relativeTo) : new Date(),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const interval = React.useRef();
|
|
25
|
+
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
if (live) {
|
|
28
|
+
interval.current = setInterval(() => {
|
|
29
|
+
setLiveRelativeTo(new Date());
|
|
30
|
+
}, refresh);
|
|
31
|
+
}
|
|
32
|
+
return () => interval.current && clearInterval(interval.current);
|
|
33
|
+
}, [refresh, live]);
|
|
34
|
+
|
|
35
|
+
const args = { locale: language, date, style, relativeTo: liveRelativeTo };
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<time
|
|
39
|
+
className={className}
|
|
40
|
+
dateTime={date}
|
|
41
|
+
title={new Intl.DateTimeFormat(language, long_date_format).format(
|
|
42
|
+
new Date(date),
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
{children
|
|
46
|
+
? children(
|
|
47
|
+
formatRelativeDate({
|
|
48
|
+
...args,
|
|
49
|
+
formatToParts: true,
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
: formatRelativeDate(args)}
|
|
53
|
+
</time>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default FormattedRelativeDate;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Wrapper from '@plone/volto/storybook';
|
|
3
|
+
import FormattedRelativeDate from './FormattedRelativeDate';
|
|
4
|
+
|
|
5
|
+
const date = new Date(new Date() - 1000);
|
|
6
|
+
const relativeTo = new Date(new Date().getTime() + 123213124);
|
|
7
|
+
|
|
8
|
+
const toDate = (date) => (typeof date === 'number' ? new Date(date) : date);
|
|
9
|
+
|
|
10
|
+
function StoryComponent(args) {
|
|
11
|
+
const { style, locale, live, refresh } = args;
|
|
12
|
+
const date = toDate(args.date);
|
|
13
|
+
const relativeTo = args.relativeTo
|
|
14
|
+
? toDate(args.relativeTo)
|
|
15
|
+
: args.relativeTo;
|
|
16
|
+
return (
|
|
17
|
+
<Wrapper
|
|
18
|
+
customStore={{ intl: { locale } }}
|
|
19
|
+
location={{ pathname: '/folder2/folder21/doc212' }}
|
|
20
|
+
>
|
|
21
|
+
<FormattedRelativeDate
|
|
22
|
+
date={date}
|
|
23
|
+
locale={locale}
|
|
24
|
+
style={style}
|
|
25
|
+
relativeTo={relativeTo}
|
|
26
|
+
live={live}
|
|
27
|
+
refresh={refresh}
|
|
28
|
+
>
|
|
29
|
+
{this.children}
|
|
30
|
+
</FormattedRelativeDate>
|
|
31
|
+
</Wrapper>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const Default = StoryComponent.bind({});
|
|
36
|
+
Default.args = {
|
|
37
|
+
date,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Localized = StoryComponent.bind({});
|
|
41
|
+
Localized.args = {
|
|
42
|
+
date,
|
|
43
|
+
locale: 'de',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Style = StoryComponent.bind({});
|
|
47
|
+
Style.args = {
|
|
48
|
+
date,
|
|
49
|
+
style: 'short',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const RelativeToDate = StoryComponent.bind({});
|
|
53
|
+
RelativeToDate.args = {
|
|
54
|
+
date,
|
|
55
|
+
relativeTo,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const LiveRefresh = StoryComponent.bind({});
|
|
59
|
+
LiveRefresh.args = {
|
|
60
|
+
date,
|
|
61
|
+
live: true,
|
|
62
|
+
refresh: 1000,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const SplitParts = StoryComponent.bind({
|
|
66
|
+
children: (parts) =>
|
|
67
|
+
parts.map((p, i) => (
|
|
68
|
+
<div key={i}>
|
|
69
|
+
<strong>{p.value}</strong> <small>({p.type}</small>)
|
|
70
|
+
</div>
|
|
71
|
+
)),
|
|
72
|
+
});
|
|
73
|
+
SplitParts.args = {
|
|
74
|
+
date,
|
|
75
|
+
long: true,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default {
|
|
79
|
+
title: 'Internal Components/Formatted Relative Date',
|
|
80
|
+
component: FormattedRelativeDate,
|
|
81
|
+
decorators: [
|
|
82
|
+
(Story) => (
|
|
83
|
+
<div style={{ width: '400px' }}>
|
|
84
|
+
<Story />
|
|
85
|
+
</div>
|
|
86
|
+
),
|
|
87
|
+
],
|
|
88
|
+
argTypes: {
|
|
89
|
+
live: {
|
|
90
|
+
control: {
|
|
91
|
+
type: 'disabled',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
refresh: {
|
|
95
|
+
control: {
|
|
96
|
+
type: 'disabled',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
date: {
|
|
100
|
+
control: {
|
|
101
|
+
type: 'date',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
relativeTo: {
|
|
105
|
+
control: {
|
|
106
|
+
type: 'date',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
locale: {
|
|
110
|
+
control: {
|
|
111
|
+
type: 'select',
|
|
112
|
+
options: ['en', 'de', 'us'],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
style: {
|
|
116
|
+
control: {
|
|
117
|
+
type: 'select',
|
|
118
|
+
options: ['long', 'short', 'narrow'],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|