@financial-times/n-myft-ui 25.0.0 → 25.0.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/.circleci/config.yml +0 -3
- package/README.md +0 -51
- package/build-state/npm-shrinkwrap.json +0 -442
- package/components/collections/collections.html +77 -0
- package/components/concept-list/concept-list.html +28 -0
- package/components/follow-button/__tests__/follow-button.test.js +3 -3
- package/components/follow-button/follow-button.jsx +3 -3
- package/components/pin-button/pin-button.html +20 -0
- package/components/save-for-later/save-for-later.html +67 -0
- package/demos/app.js +4 -2
- package/demos/templates/demo.html +7 -7
- package/demos/templates/demo.jsx +1 -93
- package/package.json +3 -7
- package/components/collections/collections.jsx +0 -68
- package/components/collections/collections.test.js +0 -83
- package/components/concept-list/concept-list.jsx +0 -55
- package/components/concept-list/concept-list.test.js +0 -116
- package/components/index.js +0 -15
- package/components/pin-button/pin-button.jsx +0 -40
- package/components/pin-button/pin-button.test.js +0 -57
- package/components/save-for-later/save-for-later.jsx +0 -103
- package/components/save-for-later/save-for-later.test.js +0 -59
- package/dist/bundles/bundle.js +0 -3133
- package/webpack.config.js +0 -34
@@ -0,0 +1,77 @@
|
|
1
|
+
<section
|
2
|
+
class="collection {{#if liteStyle}}collection--lite{{else}}collection--regular{{/if}}"
|
3
|
+
data-trackable="{{#if trackable}}{{trackable}}{{else}}collection{{/if}}">
|
4
|
+
<header class="collection__header {{#if liteStyle}}collection__header--lite{{else}}collection__header--regular{{/if}}">
|
5
|
+
<h2 class="collection__title {{#if liteStyle}}collection__title--lite{{else}}collection__title--regular{{/if}}">
|
6
|
+
{{title}}
|
7
|
+
</h2>
|
8
|
+
</header>
|
9
|
+
<ul class="collection__concepts">
|
10
|
+
{{#concepts}}
|
11
|
+
<li class="collection__concept">
|
12
|
+
{{#if ../liteStyle}}
|
13
|
+
{{{renderReactComponent localPath="components/follow-button/follow-button" variant="primary" buttonText=name flags=@root.flags collectionName=../collectionName}}}
|
14
|
+
{{else}}
|
15
|
+
{{{renderReactComponent localPath="components/follow-button/follow-button" variant="inverse" buttonText=name flags=@root.flags collectionName=../collectionName}}}
|
16
|
+
{{/if}}
|
17
|
+
</li>
|
18
|
+
{{/concepts}}
|
19
|
+
</ul>
|
20
|
+
<div class="collection__meta">
|
21
|
+
<form
|
22
|
+
method="POST"
|
23
|
+
action="#"
|
24
|
+
data-myft-ui="follow"
|
25
|
+
{{#if collectionName}}data-myft-tracking="collectionName={{collectionName}}"{{/if}}
|
26
|
+
data-concept-id="
|
27
|
+
{{~#concepts~}}
|
28
|
+
{{conceptId}}
|
29
|
+
{{~#unless @last~}}
|
30
|
+
,
|
31
|
+
{{~/unless~}}
|
32
|
+
{{~/concepts~}}"
|
33
|
+
class="n-myft-ui n-myft-ui--follow n-ui-hide-core collection-follow-all">
|
34
|
+
<input
|
35
|
+
type="hidden"
|
36
|
+
name="directType"
|
37
|
+
value="
|
38
|
+
{{~#concepts~}}
|
39
|
+
{{directType}}
|
40
|
+
{{~#unless @last~}}
|
41
|
+
,
|
42
|
+
{{~/unless~}}
|
43
|
+
{{~/concepts~}}"
|
44
|
+
/>
|
45
|
+
{{{renderReactComponent localPath="components/csrf-token/input"}}}
|
46
|
+
<input
|
47
|
+
type="hidden"
|
48
|
+
name="name"
|
49
|
+
value="
|
50
|
+
{{~#concepts~}}
|
51
|
+
{{name}}
|
52
|
+
{{~#unless @last~}}
|
53
|
+
,
|
54
|
+
{{~/unless~}}
|
55
|
+
{{~/concepts~}}"
|
56
|
+
/>
|
57
|
+
<button
|
58
|
+
type="submit"
|
59
|
+
aria-pressed="false"
|
60
|
+
class="collection-follow-all__button {{#if liteStyle}}collection-follow-all__button--lite{{else}}collection-follow-all__button--regular{{/if}}"
|
61
|
+
data-trackable="follow all"
|
62
|
+
data-concept-id="
|
63
|
+
{{~#concepts~}}
|
64
|
+
{{conceptId}}
|
65
|
+
{{~#unless @last~}}
|
66
|
+
,
|
67
|
+
{{~/unless~}}
|
68
|
+
{{~/concepts~}}"
|
69
|
+
aria-label="Add all topics in the {{title}} collection to my F T"
|
70
|
+
data-alternate-label="Remove all topics in the {{title}} collection from my F T"
|
71
|
+
data-alternate-text="Added"
|
72
|
+
title="Add all topics in the {{title}} collection to my F T">
|
73
|
+
Add all to myFT
|
74
|
+
</button>
|
75
|
+
</form>
|
76
|
+
</div>
|
77
|
+
</section>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
{{#ifAll @root.flags.myFtApi @root.flags.myFtApiWrite concepts concepts.length}}
|
2
|
+
<div
|
3
|
+
class="concept-list"
|
4
|
+
data-trackable="{{#if trackable}}{{trackable}}{{else}}concept-list{{/if}}">
|
5
|
+
{{#ifSome contentType conceptListTitle}}
|
6
|
+
<h2 class="concept-list__title">
|
7
|
+
{{#if conceptListTitle}}
|
8
|
+
{{conceptListTitle}}
|
9
|
+
{{else}}
|
10
|
+
Follow the topics in this {{contentType}}
|
11
|
+
{{/if}}
|
12
|
+
</h2>
|
13
|
+
{{/ifSome}}
|
14
|
+
<ul class="concept-list__list">
|
15
|
+
{{#each concepts}}
|
16
|
+
<li class="concept-list__list-item">
|
17
|
+
<a
|
18
|
+
href="{{relativeUrl}}"
|
19
|
+
data-trackable="{{#if conceptTrackable}}{{conceptTrackable}}{{else}}concept{{/if}}"
|
20
|
+
class="concept-list__concept">
|
21
|
+
{{prefLabel}}
|
22
|
+
</a>
|
23
|
+
{{{renderReactComponent localPath="components/follow-button/follow-button" conceptId=id name=prefLabel flags=@root.flags}}}
|
24
|
+
</li>
|
25
|
+
{{/each}}
|
26
|
+
</ul>
|
27
|
+
</div>
|
28
|
+
{{/ifAll}}
|
@@ -16,7 +16,7 @@ describe('Follow button', () => {
|
|
16
16
|
|
17
17
|
test('It renders default button', async () => {
|
18
18
|
render(<FollowButton {...props} />);
|
19
|
-
expect(
|
19
|
+
expect(screen.findByText('Add to myFT')).toBeTruthy();
|
20
20
|
});
|
21
21
|
|
22
22
|
test('It renders a variant', async () => {
|
@@ -29,12 +29,12 @@ describe('Follow button', () => {
|
|
29
29
|
expect(container.querySelector(`form[action='/myft/add/${props.conceptId}']`)).toBeTruthy();
|
30
30
|
});
|
31
31
|
|
32
|
-
test('Button state changes when attributes change',
|
32
|
+
test('Button state changes when attributes change', () => {
|
33
33
|
render(<FollowButton {...props}
|
34
34
|
variant={'standard'}
|
35
35
|
setFollowButtonStateToSelected={true}
|
36
36
|
cacheablePersonalisedUrl={true} />);
|
37
|
-
expect(
|
37
|
+
expect(screen.findByText('Added')).toBeTruthy();
|
38
38
|
});
|
39
39
|
|
40
40
|
});
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React
|
1
|
+
import React from 'react';
|
2
2
|
import CsrfToken from '../csrf-token/input';
|
3
3
|
|
4
4
|
function generateFormProps (props) {
|
@@ -148,7 +148,7 @@ export default function FollowButton (props) {
|
|
148
148
|
const getVariantClass = (variant) => variant ? `n-myft-follow-button--${variant}` : '';
|
149
149
|
|
150
150
|
return (
|
151
|
-
|
151
|
+
<>
|
152
152
|
{flags.myFtApiWrite && <form
|
153
153
|
className={`n-myft-ui n-myft-ui--follow ${extraClasses || ''}`}
|
154
154
|
method="GET"
|
@@ -168,7 +168,7 @@ export default function FollowButton (props) {
|
|
168
168
|
{getButtonText(props)}
|
169
169
|
</button>
|
170
170
|
</form>}
|
171
|
-
|
171
|
+
</>
|
172
172
|
);
|
173
173
|
|
174
174
|
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{{#if showPrioritiseButton}}
|
2
|
+
<span class="myft-pin-divider"></span>
|
3
|
+
<div class="myft-pin-button-wrapper">
|
4
|
+
<form method="post" action="/__myft/api/core/prioritised/concept/{{id}}?method={{#if prioritised}}delete{{else}}put{{/if}}" data-myft-prioritise>
|
5
|
+
{{{renderReactComponent localPath="components/csrf-token/input"}}}
|
6
|
+
<input type="hidden" value="{{name}}" name="name"> {{#if directType}}
|
7
|
+
<input type="hidden" value="{{directType}}" name="directType"> {{else}}
|
8
|
+
<input type="hidden" value="http://www.ft.com/ontology/concept/Concept" name="directType"> {{/if}}
|
9
|
+
<div
|
10
|
+
class="n-myft-ui__announcement o-normalise-visually-hidden"
|
11
|
+
aria-live="assertive"
|
12
|
+
data-pressed-text="{{name}} pinned in myFT."
|
13
|
+
data-unpressed-text="Unpinned {{name}} from myFT."
|
14
|
+
></div>
|
15
|
+
<button id="myft-pin-button__{{id}}" class="myft-pin-button" data-prioritise-button data-trackable="prioritised" data-concept-id="{{id}}" data-prioritised="{{#if prioritised}}true{{else}}false{{/if}}"
|
16
|
+
aria-label="{{#if prioritised}}Unpin{{else}}Pin{{/if}} {{name}} {{#if prioritised}}from{{else}}in{{/if}} myFT" aria-pressed="{{#if prioritised}}true{{else}}false{{/if}}" title="{{#if prioritised}}Unpin{{else}}Pin{{/if}} {{name}}">
|
17
|
+
</button>
|
18
|
+
</form>
|
19
|
+
</div>
|
20
|
+
{{/if}}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
{{#if @root.flags.myFtApiWrite}}
|
2
|
+
<form class="n-myft-ui n-myft-ui--save" method="GET"
|
3
|
+
data-content-id="{{contentId}}"
|
4
|
+
data-myft-ui="saved"
|
5
|
+
action="/myft/save/{{contentId}}"
|
6
|
+
data-js-action="/__myft/api/core/saved/content/{{contentId}}?method=put">
|
7
|
+
{{{renderReactComponent localPath="components/csrf-token/input"}}}
|
8
|
+
<div
|
9
|
+
class="n-myft-ui__announcement o-normalise-visually-hidden"
|
10
|
+
aria-live="assertive"
|
11
|
+
data-pressed-text="Article saved in My FT."
|
12
|
+
data-unpressed-text="Removed article from My FT."
|
13
|
+
></div>
|
14
|
+
<button
|
15
|
+
type="submit"
|
16
|
+
class="{{#if saveButtonWithIcon}}n-myft-ui__save-button-with-icon{{else}}n-myft-ui__button{{#variant}} n-myft-ui__button--{{this}}{{/variant}}{{/if}}"
|
17
|
+
data-trackable="{{#if trackableId}}{{trackableId}}{{else}}save-for-later{{/if}}"
|
18
|
+
{{#if isSaved}}
|
19
|
+
{{!-- The value of alternate label needs to be the opposite of label / the current saved state. This allows the client side JS to toggle the labels on state changes --}}
|
20
|
+
title="{{#if title}}{{title}} is{{/if}} Saved to myFT"
|
21
|
+
aria-label="{{#if title}}{{title}} is{{/if}} Saved to myFT"
|
22
|
+
data-alternate-label="{{#if title}}Save {{title}} to myFT for later{{else}}Save this article to myFT for later{{/if}}"
|
23
|
+
aria-pressed="true"
|
24
|
+
{{else}}
|
25
|
+
title="{{#if title}}Save {{title}} to myFT for later{{else}}Save this article to myFT for later{{/if}}"
|
26
|
+
aria-label="{{#if title}}Save {{title}} to myFT for later{{else}}Save this article to myFT for later{{/if}}"
|
27
|
+
data-alternate-label="{{#if title}}{{title}} is{{/if}} Saved to myFT"
|
28
|
+
aria-pressed="false"
|
29
|
+
{{/if}}
|
30
|
+
{{#unlessEquals appIsStreamPage true}}
|
31
|
+
{{#if saveButtonWithIcon}}
|
32
|
+
data-text-variant="save-button-with-icon-copy"
|
33
|
+
{{else}}
|
34
|
+
data-text-variant="save-button-longer-copy"
|
35
|
+
{{/if}}
|
36
|
+
{{/unlessEquals}}
|
37
|
+
{{#if alternateText}}
|
38
|
+
data-alternate-text="{{alternateText}}"
|
39
|
+
{{else}}
|
40
|
+
{{#if isSaved}}
|
41
|
+
data-alternate-text="Save "
|
42
|
+
{{else}}
|
43
|
+
data-alternate-text="Saved "
|
44
|
+
{{/if}}
|
45
|
+
{{/if}}
|
46
|
+
data-content-id="{{contentId}}" {{! duplicated here for tracking}}
|
47
|
+
>
|
48
|
+
{{#if saveButtonWithIcon}}
|
49
|
+
<span class="save-button-with-icon-copy" data-variant-label>{{#if buttonText~}}
|
50
|
+
{{buttonText}}
|
51
|
+
{{~else~}}
|
52
|
+
{{#if isSaved}}Saved{{else}}Save{{/if}}
|
53
|
+
{{~/if}}</span>
|
54
|
+
{{else}}
|
55
|
+
{{#if buttonText}}{{buttonText}}{{else}}
|
56
|
+
{{#unlessEquals appIsStreamPage true}}
|
57
|
+
<span class="save-button-longer-copy" data-variant-label>{{#if isSaved}}Saved {{else}}Save {{/if}}</span><span class="n-myft-ui__button--viewport-large" aria-hidden="true">to myFT</span>
|
58
|
+
{{else}}
|
59
|
+
<span>{{#if isSaved}}Saved{{else}}Save{{/if}}</span>
|
60
|
+
{{/unlessEquals}}
|
61
|
+
{{/if}}
|
62
|
+
{{/if}}
|
63
|
+
</button>
|
64
|
+
</form>
|
65
|
+
{{else}}
|
66
|
+
<!-- Save button hidden due to myFtApiWrite being off -->
|
67
|
+
{{/if }}
|
package/demos/app.js
CHANGED
@@ -41,7 +41,9 @@ app.set('view engine', '.html');
|
|
41
41
|
app.engine('.html', new PageKitHandlebars({
|
42
42
|
cache: false,
|
43
43
|
handlebars,
|
44
|
-
helpers
|
44
|
+
helpers: {
|
45
|
+
...helpers
|
46
|
+
}
|
45
47
|
}).engine);
|
46
48
|
|
47
49
|
app.use('/public', nExpress.static(path.join(__dirname, '../public'), { redirect: false }));
|
@@ -54,7 +56,7 @@ app.get('/', (req, res) => {
|
|
54
56
|
flags: {
|
55
57
|
myFtApi: true,
|
56
58
|
myFtApiWrite: true
|
57
|
-
}
|
59
|
+
}
|
58
60
|
}, fixtures));
|
59
61
|
});
|
60
62
|
|
@@ -48,35 +48,35 @@
|
|
48
48
|
<h2 class="demo-section__title">
|
49
49
|
Save button
|
50
50
|
</h2>
|
51
|
-
{{
|
51
|
+
{{> n-myft-ui/components/save-for-later/save-for-later }}
|
52
52
|
{{/saveButton}}
|
53
53
|
|
54
54
|
{{#saveButton}}
|
55
55
|
<h2 class="demo-section__title">
|
56
56
|
Unsave button
|
57
57
|
</h2>
|
58
|
-
{{
|
58
|
+
{{> n-myft-ui/components/save-for-later/save-for-later isSaved=true }}
|
59
59
|
{{/saveButton}}
|
60
60
|
|
61
61
|
{{#saveButton}}
|
62
62
|
<h2 class="demo-section__title">
|
63
63
|
Save button with icon
|
64
64
|
</h2>
|
65
|
-
{{
|
65
|
+
{{> n-myft-ui/components/save-for-later/save-for-later saveButtonWithIcon=true }}
|
66
66
|
{{/saveButton}}
|
67
67
|
|
68
68
|
{{#saveButton}}
|
69
69
|
<h2 class="demo-section__title">
|
70
70
|
Unsave button with icon
|
71
71
|
</h2>
|
72
|
-
{{
|
72
|
+
{{> n-myft-ui/components/save-for-later/save-for-later isSaved=true saveButtonWithIcon=true }}
|
73
73
|
{{/saveButton}}
|
74
74
|
|
75
75
|
<h2 class="demo-section__title">
|
76
76
|
Pin button
|
77
77
|
</h2>
|
78
78
|
{{#each pinButton}}
|
79
|
-
{{
|
79
|
+
{{> n-myft-ui/components/pin-button/pin-button }}
|
80
80
|
{{/each}}
|
81
81
|
|
82
82
|
{{#instantAlert}}
|
@@ -449,7 +449,7 @@
|
|
449
449
|
|
450
450
|
{{#collections}}
|
451
451
|
<div data-o-grid-colspan="3">
|
452
|
-
{{
|
452
|
+
{{> n-myft-ui/components/collections/collections }}
|
453
453
|
</div>
|
454
454
|
{{/collections}}
|
455
455
|
</div>
|
@@ -471,7 +471,7 @@
|
|
471
471
|
|
472
472
|
{{#each conceptList}}
|
473
473
|
<div data-o-grid-colspan="3">
|
474
|
-
{{
|
474
|
+
{{> n-myft-ui/components/concept-list/concept-list }}
|
475
475
|
</div>
|
476
476
|
{{/each}}
|
477
477
|
</div>
|
package/demos/templates/demo.jsx
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import FollowButton from '../../components/follow-button/follow-button';
|
3
|
-
import ConceptList from '../../components/concept-list/concept-list';
|
4
|
-
import Collections from '../../components/collections/collections';
|
5
|
-
import { SaveForLater } from '../../components';
|
6
|
-
import { PinButton } from '../../components';
|
7
3
|
|
8
4
|
export default function Demo (props) {
|
9
5
|
|
@@ -11,13 +7,9 @@ export default function Demo (props) {
|
|
11
7
|
title,
|
12
8
|
flags,
|
13
9
|
followButton,
|
14
|
-
conceptList,
|
15
|
-
collections,
|
16
|
-
saveButton,
|
17
|
-
pinButton
|
18
10
|
} = props;
|
19
11
|
|
20
|
-
const followButtonProps = {
|
12
|
+
const followButtonProps = {...followButton, flags};
|
21
13
|
|
22
14
|
return (
|
23
15
|
<div className="o-grid-container o-grid-container--snappy demo-container">
|
@@ -33,93 +25,9 @@ export default function Demo (props) {
|
|
33
25
|
Follow button
|
34
26
|
</h2>
|
35
27
|
<FollowButton {...followButtonProps} />
|
36
|
-
|
37
|
-
|
38
|
-
<h2
|
39
|
-
className="demo-section__title">
|
40
|
-
x-dash follow button
|
41
|
-
</h2>
|
42
|
-
|
43
|
-
<FollowButton {...followButtonProps} buttonText={followButton.name} />
|
44
|
-
|
45
|
-
|
46
|
-
<h2 className="demo-section__title">
|
47
|
-
Save button
|
48
|
-
</h2>
|
49
|
-
<SaveForLater flags={flags} {...saveButton} />
|
50
|
-
|
51
|
-
<h2 className="demo-section__title">
|
52
|
-
Unsave button
|
53
|
-
</h2>
|
54
|
-
<SaveForLater flags={flags} {...saveButton} isSaved={true} />
|
55
|
-
|
56
|
-
<h2 className="demo-section__title">
|
57
|
-
Unsave button with icon
|
58
|
-
</h2>
|
59
|
-
<SaveForLater flags={flags} {...saveButton} saveButtonWithIcon={true} />
|
60
|
-
|
61
|
-
<h2 className="demo-section__title">
|
62
|
-
Save button with icon
|
63
|
-
</h2>
|
64
|
-
<SaveForLater flags={flags} {...saveButton} isSaved={true} saveButtonWithIcon={true} />
|
65
|
-
|
66
|
-
<h2 className="demo-section__title">
|
67
|
-
Pin button
|
68
|
-
</h2>
|
69
|
-
|
70
|
-
{pinButton.map((item, index) => <PinButton key={index} {...item}/>)}
|
71
|
-
|
72
|
-
</div>
|
73
|
-
</div>
|
74
|
-
</section>
|
75
|
-
|
76
|
-
<section
|
77
|
-
id="topic-list"
|
78
|
-
className="demo-section">
|
79
|
-
<div className="o-grid-row">
|
80
|
-
<div data-o-grid-colspan="12">
|
81
|
-
<h2 className="demo-section__title">
|
82
|
-
Topic list
|
83
|
-
</h2>
|
84
|
-
|
85
|
-
<p className="demo-section__description">
|
86
|
-
A list of topics to follow
|
87
|
-
</p>
|
88
|
-
</div>
|
89
|
-
|
90
|
-
{
|
91
|
-
conceptList && conceptList.map((list, index) =>
|
92
|
-
<div key={index} data-o-grid-colspan="3">
|
93
|
-
<ConceptList {...list} flags={flags} />
|
94
|
-
</div>)
|
95
|
-
}
|
96
|
-
|
97
|
-
</div>
|
98
|
-
</section>
|
99
|
-
|
100
|
-
<section
|
101
|
-
id="collections"
|
102
|
-
className="demo-section">
|
103
|
-
<div className="o-grid-row">
|
104
|
-
<div data-o-grid-colspan="12">
|
105
|
-
<h2 className="demo-section__title">
|
106
|
-
Collections
|
107
|
-
</h2>
|
108
|
-
|
109
|
-
<p className="demo-section__description">
|
110
|
-
Curated collections of topics to follow.
|
111
|
-
</p>
|
112
28
|
</div>
|
113
|
-
|
114
|
-
{collections.map((collection, index) => (
|
115
|
-
<div key={index} data-o-grid-colspan="3">
|
116
|
-
<Collections {...collection} flags={flags} />
|
117
|
-
</div>
|
118
|
-
))}
|
119
|
-
|
120
29
|
</div>
|
121
30
|
</section>
|
122
|
-
|
123
31
|
</div>
|
124
32
|
)
|
125
33
|
}
|
package/package.json
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
{
|
2
2
|
"name": "@financial-times/n-myft-ui",
|
3
|
-
"version": "25.0.
|
3
|
+
"version": "25.0.1",
|
4
4
|
"description": "Client side component for interaction with myft",
|
5
|
-
"main": "
|
6
|
-
"module": "dist/bundles/bundle.js",
|
5
|
+
"main": "server.js",
|
7
6
|
"scripts": {
|
8
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
9
8
|
"commit": "commit-wizard",
|
10
9
|
"prepare": "npx snyk protect || npx snyk protect -d || true",
|
11
|
-
"preinstall": "npm_config_yes=true npx check-engine"
|
12
|
-
"build-package": "webpack"
|
10
|
+
"preinstall": "[ \"$INIT_CWD\" != \"$PWD\" ] || npm_config_yes=true npx check-engine"
|
13
11
|
},
|
14
12
|
"repository": {
|
15
13
|
"type": "git",
|
@@ -46,8 +44,6 @@
|
|
46
44
|
"babel-plugin-transform-runtime": "^6.9.0",
|
47
45
|
"babel-preset-env": "^1.7.0",
|
48
46
|
"babel-preset-es2015": "^6.6.0",
|
49
|
-
"babel-preset-react": "^6.24.1",
|
50
|
-
"babel-preset-stage-2": "^6.24.1",
|
51
47
|
"babel-runtime": "^6.9.2",
|
52
48
|
"bower": "^1.8.8",
|
53
49
|
"bower-resolve-webpack-plugin": "^1.0.5",
|
@@ -1,68 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import CsrfToken from '../csrf-token/input';
|
3
|
-
import FollowButton from '../follow-button/follow-button';
|
4
|
-
|
5
|
-
export default function Collections ({ title, liteStyle, flags, collectionName, trackable, concepts = [] }) {
|
6
|
-
const getLiteStyleModifier = () => liteStyle ? 'lite' : 'regular';
|
7
|
-
let formProps = {
|
8
|
-
method: 'POST',
|
9
|
-
action: '#',
|
10
|
-
'data-myft-ui': 'follow',
|
11
|
-
'data-concept-id': concepts.map(concept => concept.id).join(',')
|
12
|
-
};
|
13
|
-
|
14
|
-
if (collectionName) {
|
15
|
-
formProps['data-myft-tracking'] = collectionName;
|
16
|
-
}
|
17
|
-
|
18
|
-
return (
|
19
|
-
<section
|
20
|
-
className={`collection collection--${getLiteStyleModifier()}`}
|
21
|
-
data-trackable={trackable ? trackable : 'collection'}>
|
22
|
-
<header className={`collection__header collection__header--${getLiteStyleModifier()}`}>
|
23
|
-
<h2 className={`collection__title collection__title--${getLiteStyleModifier()}`}>
|
24
|
-
{title}
|
25
|
-
</h2>
|
26
|
-
</header>
|
27
|
-
<ul className="collection__concepts">
|
28
|
-
{concepts && concepts.map((concept, index) =>
|
29
|
-
<li className="collection__concept" key={index}>
|
30
|
-
<FollowButton variant={liteStyle ? 'primary' : 'inverse'} buttonText={concept.name} flags={flags} collectionName={collectionName} />
|
31
|
-
</li>)
|
32
|
-
}
|
33
|
-
</ul>
|
34
|
-
|
35
|
-
<div className="collection__meta">
|
36
|
-
<form
|
37
|
-
{...formProps}
|
38
|
-
className="n-myft-ui n-myft-ui--follow n-ui-hide-core collection-follow-all">
|
39
|
-
<input
|
40
|
-
type="hidden"
|
41
|
-
name="directType"
|
42
|
-
value={concepts.map(concept => concept.directType).join(',')}
|
43
|
-
/>
|
44
|
-
<CsrfToken />
|
45
|
-
<input
|
46
|
-
type="hidden"
|
47
|
-
name="name"
|
48
|
-
value={concepts.map(concept => concept.name).join(',')}
|
49
|
-
/>
|
50
|
-
<button
|
51
|
-
type="submit"
|
52
|
-
aria-pressed="false"
|
53
|
-
className={`collection-follow-all__button collection-follow-all__button--${getLiteStyleModifier()}`}
|
54
|
-
data-trackable="follow all"
|
55
|
-
data-concept-id={concepts.map(concept => concept.id).join(',')}
|
56
|
-
aria-label={`Add all topics in the ${title} collection to my F T`}
|
57
|
-
data-alternate-label={`Remove all topics in the ${title} collection from my F T`}
|
58
|
-
data-alternate-text="Added"
|
59
|
-
title={`Add all topics in the ${title} collection to my F T`}>
|
60
|
-
Add all to myFT
|
61
|
-
</button>
|
62
|
-
</form>
|
63
|
-
</div>
|
64
|
-
</section>
|
65
|
-
|
66
|
-
)
|
67
|
-
|
68
|
-
}
|
@@ -1,83 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import Collections from './collections';
|
3
|
-
import { render, screen } from '@testing-library/react';
|
4
|
-
import '@testing-library/jest-dom';
|
5
|
-
|
6
|
-
const fixtures = {
|
7
|
-
'title': 'European Union',
|
8
|
-
'concepts': [
|
9
|
-
{
|
10
|
-
'id': '00000000-0000-0000-0000-000000000000',
|
11
|
-
'prefLabel': 'EU immigration',
|
12
|
-
'directType': 'http://www.ft.com/ontology/Topic',
|
13
|
-
'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
|
14
|
-
'name': 'EU immigration'
|
15
|
-
},
|
16
|
-
{
|
17
|
-
'id': '00000000-0000-0000-0000-000000000001',
|
18
|
-
'prefLabel': 'Europe Quantitative Easing',
|
19
|
-
'directType': 'http://www.ft.com/ontology/Topic',
|
20
|
-
'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
|
21
|
-
'name': 'Europe Quantitative Easing'
|
22
|
-
},
|
23
|
-
{
|
24
|
-
'id': '00000000-0000-0000-0000-000000000002',
|
25
|
-
'prefLabel': 'EU financial regulation',
|
26
|
-
'directType': 'http://www.ft.com/ontology/Topic',
|
27
|
-
'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
|
28
|
-
'name': 'EU financial regulation'
|
29
|
-
},
|
30
|
-
{
|
31
|
-
'id': '00000000-0000-0000-0000-000000000003',
|
32
|
-
'prefLabel': 'EU nothing',
|
33
|
-
'directType': 'http://www.ft.com/ontology/Topic',
|
34
|
-
'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
|
35
|
-
'name': 'EU nothing'
|
36
|
-
},
|
37
|
-
{
|
38
|
-
'id': '00000000-0000-0000-0000-000000000004',
|
39
|
-
'prefLabel': 'EU trade',
|
40
|
-
'directType': 'http://www.ft.com/ontology/Topic',
|
41
|
-
'url': 'https://www.ft.com/stream/00000000-0000-0000-0000-000000000000',
|
42
|
-
'name': 'EU trade'
|
43
|
-
}
|
44
|
-
]
|
45
|
-
};
|
46
|
-
const joinedDirectTypes = 'http://www.ft.com/ontology/Topic,http://www.ft.com/ontology/Topic,http://www.ft.com/ontology/Topic,http://www.ft.com/ontology/Topic,http://www.ft.com/ontology/Topic';
|
47
|
-
|
48
|
-
const flags = {
|
49
|
-
myFtApi: true,
|
50
|
-
myFtApiWrite: true
|
51
|
-
};
|
52
|
-
|
53
|
-
describe('Concept List', () => {
|
54
|
-
|
55
|
-
test('It renders the title of the collection', async () => {
|
56
|
-
render(<Collections {...fixtures} flags={flags} />);
|
57
|
-
expect(await screen.findByText('European Union')).toBeTruthy();
|
58
|
-
});
|
59
|
-
|
60
|
-
test('It renders label for the concept button', async () => {
|
61
|
-
render(<Collections {...fixtures} flags={flags} />);
|
62
|
-
expect(await screen.findByText('EU immigration')).toBeTruthy();
|
63
|
-
expect(await screen.findByText('Europe Quantitative Easing')).toBeTruthy();
|
64
|
-
expect(await screen.findByText('EU financial regulation')).toBeTruthy();
|
65
|
-
expect(await screen.findByText('EU nothing')).toBeTruthy();
|
66
|
-
expect(await screen.findByText('EU trade')).toBeTruthy();
|
67
|
-
});
|
68
|
-
|
69
|
-
test('It renders form "Add all to my FT" from', () => {
|
70
|
-
const { container} = render(<Collections {...fixtures} flags={flags} />);
|
71
|
-
const formElement = container.querySelector('form[action="#"]');
|
72
|
-
expect(formElement).toBeTruthy();
|
73
|
-
expect(formElement.method).toEqual('post');
|
74
|
-
});
|
75
|
-
|
76
|
-
test('It renders directType input with value of types joined', () => {
|
77
|
-
const { container} = render(<Collections {...fixtures} flags={flags} />);
|
78
|
-
const directTypeElement = container.querySelector('input[name="directType"]');
|
79
|
-
expect(directTypeElement).toBeTruthy();
|
80
|
-
expect(directTypeElement.value).toEqual(joinedDirectTypes);
|
81
|
-
});
|
82
|
-
|
83
|
-
});
|
@@ -1,55 +0,0 @@
|
|
1
|
-
import React, { Fragment } from 'react';
|
2
|
-
import FollowButton from '../follow-button/follow-button';
|
3
|
-
|
4
|
-
export default function ConceptList ({ flags, concepts, contentType, conceptListTitle, trackable }) {
|
5
|
-
|
6
|
-
const {
|
7
|
-
myFtApi,
|
8
|
-
myFtApiWrite
|
9
|
-
} = flags;
|
10
|
-
|
11
|
-
const generateTrackableProps = (primary, seconday) => {
|
12
|
-
return {
|
13
|
-
'data-trackable': primary ? primary : seconday
|
14
|
-
}
|
15
|
-
}
|
16
|
-
|
17
|
-
|
18
|
-
return (
|
19
|
-
|
20
|
-
<Fragment>
|
21
|
-
{(myFtApi && myFtApiWrite && concepts && concepts.length) &&
|
22
|
-
<div
|
23
|
-
className='concept-list'
|
24
|
-
{...generateTrackableProps(trackable, 'concept-list')}>
|
25
|
-
{
|
26
|
-
(contentType || conceptListTitle) &&
|
27
|
-
<h2 className='concept-list__title'>
|
28
|
-
{conceptListTitle ? conceptListTitle : `Follow the topics in this ${contentType}`}
|
29
|
-
</h2>
|
30
|
-
}
|
31
|
-
<ul className='concept-list__list'>
|
32
|
-
{concepts.map((concept, index) => {
|
33
|
-
const {
|
34
|
-
relativeUrl,
|
35
|
-
url,
|
36
|
-
conceptTrackable,
|
37
|
-
prefLabel,
|
38
|
-
id
|
39
|
-
} = concept;
|
40
|
-
return (
|
41
|
-
<li key={index} className='concept-list__list-item'>
|
42
|
-
<a
|
43
|
-
href={relativeUrl || url}
|
44
|
-
{...generateTrackableProps(conceptTrackable, 'concept')}
|
45
|
-
className='concept-list__concept'>
|
46
|
-
{prefLabel}
|
47
|
-
</a>
|
48
|
-
<FollowButton conceptId={id} name={prefLabel} flags={flags} />
|
49
|
-
</li>
|
50
|
-
)
|
51
|
-
})}
|
52
|
-
</ul>
|
53
|
-
</div>}
|
54
|
-
</Fragment>)
|
55
|
-
}
|