@financial-times/n-myft-ui 25.0.0-beta.1 → 25.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
}
|