@financial-times/n-myft-ui 23.1.1 → 24.0.1
Sign up to get free protection for your applications and to get access to all the features.
- package/.circleci/config.yml +13 -16
- package/.circleci/shared-helpers/helper-npm-install-peer-deps +6 -5
- package/.github/settings.yml +1 -1
- package/.scss-lint.yml +3 -3
- package/Makefile +1 -0
- package/README.md +11 -8
- package/build-state/npm-shrinkwrap.json +12135 -11905
- package/components/collections/collections.html +3 -11
- package/components/concept-list/concept-list.html +1 -4
- package/components/csrf-token/__tests__/input.test.js +23 -0
- package/components/csrf-token/input.jsx +26 -0
- package/components/follow-button/__tests__/follow-button.test.js +40 -0
- package/components/follow-button/follow-button.jsx +174 -0
- package/components/instant-alert/instant-alert.html +1 -1
- package/components/pin-button/pin-button.html +1 -1
- package/components/save-for-later/save-for-later.html +6 -9
- package/components/unread-articles-indicator/date-fns.js +5 -12
- package/demos/app.js +39 -19
- package/demos/templates/demo-layout.html +1 -1
- package/demos/templates/demo.html +436 -415
- package/demos/templates/demo.jsx +33 -0
- package/jest.config.js +8 -0
- package/mixins/lozenge/_themes.scss +8 -4
- package/mixins/lozenge/main.scss +50 -4
- package/package.json +23 -9
- package/components/csrf-token/input.html +0 -5
- package/components/follow-button/follow-button.html +0 -79
- package/demos/fixtures/follow-button-plus-digest.json +0 -6
- package/demos/templates/digest-on-follow.html +0 -12
@@ -10,17 +10,9 @@
|
|
10
10
|
{{#concepts}}
|
11
11
|
<li class="collection__concept">
|
12
12
|
{{#if ../liteStyle}}
|
13
|
-
{{
|
14
|
-
variant="primary"
|
15
|
-
buttonText=name
|
16
|
-
collectionName=../collectionName
|
17
|
-
}}
|
13
|
+
{{{renderReactComponent localPath="components/follow-button/follow-button" variant="primary" buttonText=name flags=@root.flags collectionName=../collectionName}}}
|
18
14
|
{{else}}
|
19
|
-
{{
|
20
|
-
variant="inverse"
|
21
|
-
buttonText=name
|
22
|
-
collectionName=../collectionName
|
23
|
-
}}
|
15
|
+
{{{renderReactComponent localPath="components/follow-button/follow-button" variant="inverse" buttonText=name flags=@root.flags collectionName=../collectionName}}}
|
24
16
|
{{/if}}
|
25
17
|
</li>
|
26
18
|
{{/concepts}}
|
@@ -50,7 +42,7 @@
|
|
50
42
|
{{~/unless~}}
|
51
43
|
{{~/concepts~}}"
|
52
44
|
/>
|
53
|
-
{{
|
45
|
+
{{{renderReactComponent localPath="components/csrf-token/input"}}}
|
54
46
|
<input
|
55
47
|
type="hidden"
|
56
48
|
name="name"
|
@@ -20,10 +20,7 @@
|
|
20
20
|
class="concept-list__concept">
|
21
21
|
{{prefLabel}}
|
22
22
|
</a>
|
23
|
-
{{
|
24
|
-
conceptId=id
|
25
|
-
name=prefLabel
|
26
|
-
}}
|
23
|
+
{{{renderReactComponent localPath="components/follow-button/follow-button" conceptId=id name=prefLabel flags=@root.flags}}}
|
27
24
|
</li>
|
28
25
|
{{/each}}
|
29
26
|
</ul>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import CsrfToken from '../input';
|
3
|
+
import { render } from '@testing-library/react';
|
4
|
+
import '@testing-library/jest-dom';
|
5
|
+
|
6
|
+
const props = {
|
7
|
+
cacheablePersonalisedUrl: false
|
8
|
+
};
|
9
|
+
|
10
|
+
describe('Csrf Token Input', () => {
|
11
|
+
|
12
|
+
test('It renders default button', async () => {
|
13
|
+
let { container } = render(<CsrfToken {...props} />);
|
14
|
+
expect(container.querySelector('[name=\'token\']')).toBeTruthy();
|
15
|
+
});
|
16
|
+
|
17
|
+
test('It renders csrf token attribute', async () => {
|
18
|
+
let { container } = render(<CsrfToken cacheablePersonalisedUrl={true} csrfToken={'test-token'} />);
|
19
|
+
expect(container.querySelector('[data-myft-csrf-token=\'test-token\']')).toBeTruthy();
|
20
|
+
});
|
21
|
+
|
22
|
+
|
23
|
+
});
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
|
3
|
+
export default function CsrfToken ({ cacheablePersonalisedUrl, csrfToken }) {
|
4
|
+
|
5
|
+
let inputProps = {};
|
6
|
+
|
7
|
+
if (cacheablePersonalisedUrl) {
|
8
|
+
inputProps = {
|
9
|
+
...inputProps,
|
10
|
+
'data-myft-csrf-token': csrfToken
|
11
|
+
};
|
12
|
+
}
|
13
|
+
|
14
|
+
if(csrfToken) {
|
15
|
+
inputProps.value = csrfToken;
|
16
|
+
}
|
17
|
+
|
18
|
+
return (
|
19
|
+
<input
|
20
|
+
{...inputProps}
|
21
|
+
type="hidden"
|
22
|
+
name="token"
|
23
|
+
/>
|
24
|
+
);
|
25
|
+
|
26
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import FollowButton from '../follow-button';
|
3
|
+
import { render, screen } from '@testing-library/react';
|
4
|
+
import '@testing-library/jest-dom';
|
5
|
+
|
6
|
+
const props = {
|
7
|
+
flags: {
|
8
|
+
myFtApi: true,
|
9
|
+
myFtApiWrite: true
|
10
|
+
},
|
11
|
+
conceptId: '0000-000000-00000-0000',
|
12
|
+
name: 'Follow button'
|
13
|
+
};
|
14
|
+
|
15
|
+
describe('Follow button', () => {
|
16
|
+
|
17
|
+
test('It renders default button', async () => {
|
18
|
+
render(<FollowButton {...props} />);
|
19
|
+
expect(screen.findByText('Add to myFT')).toBeTruthy();
|
20
|
+
});
|
21
|
+
|
22
|
+
test('It renders a variant', async () => {
|
23
|
+
const { container } = render(<FollowButton {...props} variant={'standard'} />);
|
24
|
+
expect(container.getElementsByClassName('n-myft-follow-button--standard')).toHaveLength(1);
|
25
|
+
});
|
26
|
+
|
27
|
+
test('It renders follow button form', async () => {
|
28
|
+
const { container } = render(<FollowButton {...props} variant={'standard'} />);
|
29
|
+
expect(container.querySelector(`form[action='/myft/add/${props.conceptId}']`)).toBeTruthy();
|
30
|
+
});
|
31
|
+
|
32
|
+
test('Button state changes when attributes change', () => {
|
33
|
+
render(<FollowButton {...props}
|
34
|
+
variant={'standard'}
|
35
|
+
setFollowButtonStateToSelected={true}
|
36
|
+
cacheablePersonalisedUrl={true} />);
|
37
|
+
expect(screen.findByText('Added')).toBeTruthy();
|
38
|
+
});
|
39
|
+
|
40
|
+
});
|
@@ -0,0 +1,174 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import CsrfToken from '../csrf-token/input';
|
3
|
+
|
4
|
+
function generateFormProps (props) {
|
5
|
+
let generatedProps = {};
|
6
|
+
|
7
|
+
const {
|
8
|
+
collectionName,
|
9
|
+
followPlusDigestEmail,
|
10
|
+
conceptId,
|
11
|
+
setFollowButtonStateToSelected,
|
12
|
+
cacheablePersonalisedUrl
|
13
|
+
} = props;
|
14
|
+
|
15
|
+
if (collectionName) {
|
16
|
+
generatedProps['data-myft-tracking'] = `collectionName=${collectionName}`;
|
17
|
+
}
|
18
|
+
|
19
|
+
if(followPlusDigestEmail) {
|
20
|
+
generatedProps['action'] = `/__myft/api/core/follow-plus-digest-email/${conceptId}?method=put`;
|
21
|
+
generatedProps['data-myft-ui-variant'] = 'followPlusDigestEmail';
|
22
|
+
} else {
|
23
|
+
if(setFollowButtonStateToSelected && cacheablePersonalisedUrl) {
|
24
|
+
generatedProps['action'] = `/myft/remove/${conceptId}`;
|
25
|
+
generatedProps['data-js-action'] = `/__myft/api/core/followed/concept/${conceptId}?method=delete`;
|
26
|
+
} else {
|
27
|
+
generatedProps['action'] = `/myft/add/${conceptId}`;
|
28
|
+
generatedProps['data-js-action'] = `/__myft/api/core/followed/concept/${conceptId}?method=put`;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
return generatedProps;
|
33
|
+
|
34
|
+
}
|
35
|
+
|
36
|
+
function generateButtonProps (props) {
|
37
|
+
|
38
|
+
const {
|
39
|
+
cacheablePersonalisedUrl,
|
40
|
+
setFollowButtonStateToSelected,
|
41
|
+
name,
|
42
|
+
buttonText,
|
43
|
+
variant,
|
44
|
+
conceptId,
|
45
|
+
alternateText,
|
46
|
+
followPlusDigestEmail
|
47
|
+
} = props;
|
48
|
+
|
49
|
+
let generatedProps = {
|
50
|
+
'data-concept-id': conceptId,
|
51
|
+
'n-myft-follow-button': 'true',
|
52
|
+
'data-trackable': 'follow',
|
53
|
+
type: 'submit'
|
54
|
+
};
|
55
|
+
|
56
|
+
if (cacheablePersonalisedUrl && setFollowButtonStateToSelected) {
|
57
|
+
generatedProps['aria-label'] = `Remove ${name} from myFT`;
|
58
|
+
generatedProps['title'] = `Remove ${name} from myFT`
|
59
|
+
generatedProps['data-alternate-label'] = `Add ${name} to myFT`;
|
60
|
+
generatedProps['aria-pressed'] = true;
|
61
|
+
|
62
|
+
if(alternateText) {
|
63
|
+
generatedProps['data-alternate-text'] = alternateText;
|
64
|
+
} else {
|
65
|
+
if(buttonText) {
|
66
|
+
generatedProps['data-alternate-text'] = buttonText;
|
67
|
+
} else {
|
68
|
+
generatedProps['data-alternate-text'] = 'Add to myFT';
|
69
|
+
}
|
70
|
+
}
|
71
|
+
} else {
|
72
|
+
generatedProps['aria-pressed'] = false;
|
73
|
+
generatedProps['aria-label'] = `Add ${name} to myFT`;
|
74
|
+
generatedProps['title'] = `Add ${name} to myFT`;
|
75
|
+
generatedProps['data-alternate-label'] = `Remove ${name} from myFT`;
|
76
|
+
if (alternateText) {
|
77
|
+
generatedProps['data-alternate-text'] = alternateText;
|
78
|
+
} else {
|
79
|
+
if (buttonText) {
|
80
|
+
generatedProps['data-alternate-text'] = buttonText;
|
81
|
+
} else {
|
82
|
+
generatedProps['data-alternate-text'] = 'Added';
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
if(variant) {
|
88
|
+
generatedProps[`n-myft-follow-button--${variant}`] = 'true';
|
89
|
+
}
|
90
|
+
|
91
|
+
if(followPlusDigestEmail) {
|
92
|
+
generatedProps['data-trackable-context-messaging'] = 'add-to-myft-plus-digest-button';
|
93
|
+
}
|
94
|
+
|
95
|
+
return generatedProps;
|
96
|
+
}
|
97
|
+
|
98
|
+
function getButtonText (props) {
|
99
|
+
|
100
|
+
const {
|
101
|
+
buttonText,
|
102
|
+
setFollowButtonStateToSelected,
|
103
|
+
cacheablePersonalisedUrl
|
104
|
+
} = props;
|
105
|
+
let outputText;
|
106
|
+
|
107
|
+
if(buttonText) {
|
108
|
+
outputText = buttonText;
|
109
|
+
} else {
|
110
|
+
if(setFollowButtonStateToSelected && cacheablePersonalisedUrl) {
|
111
|
+
outputText = 'Added';
|
112
|
+
} else {
|
113
|
+
outputText = 'Add to myFT';
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
return outputText;
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
*
|
122
|
+
* @param {Object} props
|
123
|
+
* @param {string} props.name
|
124
|
+
* @param {Object} props.flags
|
125
|
+
* @param {string} props.extraClasses
|
126
|
+
* @param {string} props.conceptId
|
127
|
+
* @param {string} props.variant
|
128
|
+
* @param {string} props.buttonText
|
129
|
+
* @param {*} props.setFollowButtonStateToSelected
|
130
|
+
* @param {string} props.cacheablePersonalisedUrl
|
131
|
+
* @param {string} props.alternateText
|
132
|
+
* @param {*} props.followPlusDigestEmail
|
133
|
+
* @param {string} props.collectionName
|
134
|
+
*/
|
135
|
+
export default function FollowButton (props) {
|
136
|
+
|
137
|
+
const {
|
138
|
+
name,
|
139
|
+
flags,
|
140
|
+
extraClasses,
|
141
|
+
conceptId,
|
142
|
+
variant,
|
143
|
+
} = props;
|
144
|
+
|
145
|
+
const formProps = generateFormProps(props);
|
146
|
+
const buttonProps = generateButtonProps(props);
|
147
|
+
|
148
|
+
const getVariantClass = (variant) => variant ? `n-myft-follow-button--${variant}` : '';
|
149
|
+
|
150
|
+
return (
|
151
|
+
<>
|
152
|
+
{flags.myFtApiWrite && <form
|
153
|
+
className={`n-myft-ui n-myft-ui--follow ${extraClasses || ''}`}
|
154
|
+
method="GET"
|
155
|
+
data-myft-ui="follow"
|
156
|
+
data-concept-id={conceptId}
|
157
|
+
{...formProps}>
|
158
|
+
<CsrfToken cacheablePersonalisedUrl={props.cacheablePersonalisedUrl} csrfToken={props.csrfToken} />
|
159
|
+
<div
|
160
|
+
className="n-myft-ui__announcement o-normalise-visually-hidden"
|
161
|
+
aria-live="assertive"
|
162
|
+
data-pressed-text={`Now following ${name}.`}
|
163
|
+
data-unpressed-text={`No longer following ${name}.`}
|
164
|
+
></div>
|
165
|
+
<button
|
166
|
+
{...buttonProps}
|
167
|
+
className={[`n-myft-follow-button ${getVariantClass(variant)}`]}>
|
168
|
+
{getButtonText(props)}
|
169
|
+
</button>
|
170
|
+
</form>}
|
171
|
+
</>
|
172
|
+
);
|
173
|
+
|
174
|
+
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
data-concept-id="{{conceptId}}"
|
6
6
|
action="/myft/add/{{conceptId}}?instant=true"
|
7
7
|
data-js-action="/__myft/api/core/followed/concept/{{conceptId}}?method=put">
|
8
|
-
{{
|
8
|
+
{{{renderReactComponent localPath="components/csrf-token/input"}}}
|
9
9
|
<input type="hidden" value="{{name}}" name="name">
|
10
10
|
{{#if directType}}
|
11
11
|
<input type="hidden" value="{{directType}}" name="directType">
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<span class="myft-pin-divider"></span>
|
3
3
|
<div class="myft-pin-button-wrapper">
|
4
4
|
<form method="post" action="/__myft/api/core/prioritised/concept/{{id}}?method={{#if prioritised}}delete{{else}}put{{/if}}" data-myft-prioritise>
|
5
|
-
{{
|
5
|
+
{{{renderReactComponent localPath="components/csrf-token/input"}}}
|
6
6
|
<input type="hidden" value="{{name}}" name="name"> {{#if directType}}
|
7
7
|
<input type="hidden" value="{{directType}}" name="directType"> {{else}}
|
8
8
|
<input type="hidden" value="http://www.ft.com/ontology/concept/Concept" name="directType"> {{/if}}
|
@@ -4,7 +4,7 @@
|
|
4
4
|
data-myft-ui="saved"
|
5
5
|
action="/myft/save/{{contentId}}"
|
6
6
|
data-js-action="/__myft/api/core/saved/content/{{contentId}}?method=put">
|
7
|
-
{{
|
7
|
+
{{{renderReactComponent localPath="components/csrf-token/input"}}}
|
8
8
|
<div
|
9
9
|
class="n-myft-ui__announcement o-normalise-visually-hidden"
|
10
10
|
aria-live="assertive"
|
@@ -17,10 +17,14 @@
|
|
17
17
|
data-trackable="{{#if trackableId}}{{trackableId}}{{else}}save-for-later{{/if}}"
|
18
18
|
{{#if isSaved}}
|
19
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"
|
20
22
|
data-alternate-label="{{#if title}}Save {{title}} to myFT for later{{else}}Save this article to myFT for later{{/if}}"
|
21
23
|
aria-pressed="true"
|
22
24
|
{{else}}
|
23
|
-
|
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"
|
24
28
|
aria-pressed="false"
|
25
29
|
{{/if}}
|
26
30
|
{{#unlessEquals appIsStreamPage true}}
|
@@ -40,13 +44,6 @@
|
|
40
44
|
{{/if}}
|
41
45
|
{{/if}}
|
42
46
|
data-content-id="{{contentId}}" {{! duplicated here for tracking}}
|
43
|
-
{{#if isSaved}}
|
44
|
-
aria-label="{{#if prefixText}}{{prefixText}}{{/if}} {{#if title}}{{title}} is{{/if}} Saved to myFT"
|
45
|
-
title="{{#if title}}{{title}} is{{/if}} Saved to myFT"
|
46
|
-
{{else}}
|
47
|
-
aria-label="{{#if prefixText}}{{prefixText}}{{/if}} {{#if title}}Save {{title}} to myFT for later{{else}}Save this article to myFT for later{{/if}}"
|
48
|
-
title="{{#if title}}Save {{title}} to myFT for later{{else}}Save this article to myFT for later{{/if}}"
|
49
|
-
{{/if}}
|
50
47
|
>
|
51
48
|
{{#if saveButtonWithIcon}}
|
52
49
|
<span class="save-button-with-icon-copy" data-variant-label>{{#if buttonText~}}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
// date-fns from v2 doesn't accept String arguments anymore.
|
2
|
-
// the detail => https://github.com/date-fns/date-fns/blob/
|
2
|
+
// the detail => https://github.com/date-fns/date-fns/blob/HEAD/CHANGELOG.md#200---2019-08-20
|
3
3
|
// By adding validation for dates before their functions allows us to know it when unexpected value passed.
|
4
4
|
|
5
5
|
import isTodayOriginal from 'date-fns/src/isToday';
|
@@ -11,22 +11,15 @@ import parseISO from 'date-fns/src/parseISO';
|
|
11
11
|
|
12
12
|
const isValid = (date) => {
|
13
13
|
if (!isValidOriginal(date)) {
|
14
|
-
console.error(
|
14
|
+
console.error("Invalid date passed", [date]); //eslint-disable-line
|
15
15
|
}
|
16
16
|
return date;
|
17
17
|
};
|
18
18
|
|
19
19
|
const isToday = (date) => isTodayOriginal(isValid(date));
|
20
|
-
const isAfter = (date, dateToCompare) =>
|
20
|
+
const isAfter = (date, dateToCompare) =>
|
21
|
+
isAfterOriginal(isValid(date), isValid(dateToCompare));
|
21
22
|
const addMinutes = (date, amount) => addMinutesOriginal(isValid(date), amount);
|
22
23
|
const startOfDay = (date) => startOfDayOriginal(isValid(date));
|
23
24
|
|
24
|
-
|
25
|
-
export {
|
26
|
-
isToday,
|
27
|
-
isAfter,
|
28
|
-
addMinutes,
|
29
|
-
startOfDay,
|
30
|
-
isValid,
|
31
|
-
parseISO,
|
32
|
-
};
|
25
|
+
export { isToday, isAfter, addMinutes, startOfDay, isValid, parseISO };
|
package/demos/app.js
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
-
|
1
|
+
require('sucrase/register');
|
2
|
+
const nExpress = require('@financial-times/n-express');
|
2
3
|
const chalk = require('chalk');
|
3
4
|
const errorHighlight = chalk.bold.red;
|
4
5
|
const highlight = chalk.bold.green;
|
6
|
+
const { PageKitReactJSX } = require('@financial-times/dotcom-server-react-jsx');
|
7
|
+
const fs = require('fs');
|
8
|
+
const path = require('path');
|
9
|
+
const handlebars = require('handlebars');
|
10
|
+
const { PageKitHandlebars, helpers } = require('@financial-times/dotcom-server-handlebars');
|
5
11
|
|
6
|
-
const
|
12
|
+
const demoJSX = require('./templates/demo').default;
|
13
|
+
const demoLayoutSource = fs.readFileSync(path.join(__dirname, './templates/demo-layout.html'),'utf8').toString();
|
7
14
|
|
8
15
|
const fixtures = {
|
9
16
|
followButton: require('./fixtures/follow-button'),
|
@@ -14,29 +21,38 @@ const fixtures = {
|
|
14
21
|
instantAlert: require('./fixtures/instant-alert')
|
15
22
|
};
|
16
23
|
|
17
|
-
const app = module.exports =
|
24
|
+
const app = module.exports = nExpress({
|
18
25
|
name: 'public',
|
19
26
|
systemCode: 'n-myft-ui-demo',
|
20
27
|
withFlags: true,
|
21
|
-
|
22
|
-
|
28
|
+
withConsent: false,
|
29
|
+
withServiceMetrics: false,
|
23
30
|
withAnonMiddleware: false,
|
24
31
|
hasHeadCss: false,
|
25
|
-
layoutsDir: 'demos/templates',
|
26
|
-
viewsDirectory: '/demos/templates',
|
27
32
|
partialsDirectory: process.cwd(),
|
28
33
|
directory: process.cwd(),
|
29
34
|
demo: true,
|
30
|
-
|
35
|
+
withBackendAuthentication: false,
|
36
|
+
});
|
37
|
+
|
38
|
+
app.set('views', path.join(__dirname, '/templates'));
|
39
|
+
app.set('view engine', '.html');
|
40
|
+
|
41
|
+
app.engine('.html', new PageKitHandlebars({
|
42
|
+
cache: false,
|
43
|
+
handlebars,
|
31
44
|
helpers: {
|
32
|
-
|
45
|
+
...helpers
|
33
46
|
}
|
34
|
-
});
|
47
|
+
}).engine);
|
48
|
+
|
49
|
+
app.use('/public', nExpress.static(path.join(__dirname, '../public'), { redirect: false }));
|
50
|
+
|
51
|
+
const jsxRenderer = (new PageKitReactJSX({ includeDoctype: false }));
|
35
52
|
|
36
53
|
app.get('/', (req, res) => {
|
37
54
|
res.render('demo', Object.assign({
|
38
55
|
title: 'n-myft-ui demo',
|
39
|
-
layout: 'demo-layout',
|
40
56
|
flags: {
|
41
57
|
myFtApi: true,
|
42
58
|
myFtApiWrite: true
|
@@ -44,18 +60,22 @@ app.get('/', (req, res) => {
|
|
44
60
|
}, fixtures));
|
45
61
|
});
|
46
62
|
|
47
|
-
app.get('/
|
48
|
-
|
49
|
-
title: 'n-myft-ui
|
50
|
-
layout: 'demo-layout',
|
63
|
+
app.get('/demo-jsx', async (req, res) => {
|
64
|
+
let demo = await jsxRenderer.render(demoJSX, Object.assign({
|
65
|
+
title: 'n-myft-ui demo',
|
51
66
|
flags: {
|
52
67
|
myFtApi: true,
|
53
|
-
myFtApiWrite: true
|
54
|
-
}
|
55
|
-
|
56
|
-
|
68
|
+
myFtApiWrite: true
|
69
|
+
}
|
70
|
+
}, fixtures));
|
71
|
+
|
72
|
+
let template = handlebars.compile(demoLayoutSource);
|
73
|
+
let result = template({body: demo});
|
74
|
+
|
75
|
+
res.send(result);
|
57
76
|
});
|
58
77
|
|
78
|
+
|
59
79
|
function runPa11yTests () {
|
60
80
|
const spawn = require('child_process').spawn;
|
61
81
|
const pa11y = spawn('pa11y-ci');
|