Switched eslint to use prettier (#2796)

* switched eslint to use prettier
This commit is contained in:
Pomax 2019-03-13 13:44:24 -07:00 коммит произвёл GitHub
Родитель 642ccb2387
Коммит b9adc34955
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
44 изменённых файлов: 1948 добавлений и 1090 удалений

22
.eslintrc.json Normal file
Просмотреть файл

@ -0,0 +1,22 @@
{
"extends": [
"prettier"
],
"plugins": [
"prettier",
"react"
],
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"env": {
"es6": true
},
"rules": {
"prettier/prettier": "error",
"react/jsx-uses-vars": "error"
}
}

Просмотреть файл

@ -1,16 +0,0 @@
plugins: ["react"]
extends: "./node_modules/mofo-style/.eslintrc.yaml"
env:
es6: true
node: true
browser: true
parserOptions:
ecmaFeatures:
jsx: true
sourceType: "module"
rules:
react/jsx-uses-vars: [2]
react/jsx-uses-react: [2]

41
package-lock.json сгенерированный
Просмотреть файл

@ -4782,6 +4782,29 @@
}
}
},
"eslint-config-prettier": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-4.1.0.tgz",
"integrity": "sha512-zILwX9/Ocz4SV2vX7ox85AsrAgXV3f2o2gpIicdMIOra48WYqgUnWNH/cR/iHtmD2Vb3dLSC3LiEJnS05Gkw7w==",
"requires": {
"get-stdin": "^6.0.0"
},
"dependencies": {
"get-stdin": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g=="
}
}
},
"eslint-plugin-prettier": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz",
"integrity": "sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==",
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-react": {
"version": "7.12.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz",
@ -5245,6 +5268,11 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w=="
},
"fast-glob": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.6.tgz",
@ -9728,6 +9756,19 @@
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
"prettier": {
"version": "1.16.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz",
"integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g=="
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-hrtime": {
"version": "1.0.3",
"resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",

Просмотреть файл

@ -27,7 +27,7 @@
"start": "run-p build-uncompressed server watch:**",
"snyk": "snyk test --file=package.json",
"test:procfile": "node test/test-procfile.js",
"test:eslint": "eslint --config ./.eslintrc.yaml \"source/js/**/*.js\" \"source/js/**/*.jsx\" webpack.config.js",
"test:eslint": "eslint --config ./.eslintrc.json \"source/js/**/*.js\" \"source/js/**/*.jsx\" webpack.config.js",
"test:scss": "stylelint \"source/sass/**/*.scss\" \"source/js/**/*.scss\" --syntax scss",
"test": "run-s test:** build",
"watch:images": "chokidar \"source/images/**/*\" -c \"npm run build:images\"",
@ -52,6 +52,8 @@
"classnames": "2.2.6",
"dotenv": "^6.2.0",
"eslint": "^5.15.1",
"eslint-config-prettier": "^4.1.0",
"eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-react": "^7.12.4",
"event-stream": "3.3.4",
"html-entities": "^1.2.1",
@ -64,6 +66,7 @@
"optipng-bin": "^5.1.0",
"postcss": "^7.0.14",
"postcss-cli": "^6.1.2",
"prettier": "^1.16.4",
"prop-types": "^15.7.2",
"react": "16.8.4",
"react-dom": "16.8.4",

Просмотреть файл

@ -13,13 +13,21 @@ var basketSignup = function(transaction, onSuccessCallback, onFailCallback) {
var errorArray = [];
var newsletterErrors = [];
while (newsletterErrors.firstChild) { newsletterErrors.removeChild(newsletterErrors.firstChild); }
while (newsletterErrors.firstChild) {
newsletterErrors.removeChild(newsletterErrors.firstChild);
}
var params = `email=` + encodeURIComponent(payload.email) +
`&newsletters=` + payload.newsletter +
`&privacy=`+ payload.privacy +
`&fmt=` + payload.format +
`&source_url=` + encodeURIComponent(document.location.href);
var params =
`email=` +
encodeURIComponent(payload.email) +
`&newsletters=` +
payload.newsletter +
`&privacy=` +
payload.privacy +
`&fmt=` +
payload.format +
`&source_url=` +
encodeURIComponent(document.location.href);
var xhr = new XMLHttpRequest();
@ -27,7 +35,7 @@ var basketSignup = function(transaction, onSuccessCallback, onFailCallback) {
if (r.target.status >= 200 && r.target.status < 300) {
var response = r.target.response;
if(response === null ) {
if (response === null) {
onFailCallback(new Error());
return;
}
@ -35,7 +43,7 @@ var basketSignup = function(transaction, onSuccessCallback, onFailCallback) {
if (response.success === true) {
onSuccessCallback();
} else {
if(response.errors) {
if (response.errors) {
for (var i = 0; i < response.errors.length; i++) {
errorArray.push(response.errors[i]);
}
@ -53,14 +61,13 @@ var basketSignup = function(transaction, onSuccessCallback, onFailCallback) {
xhr.open(`POST`, url, true);
xhr.setRequestHeader(`Content-type`, `application/x-www-form-urlencoded`);
xhr.setRequestHeader(`X-Requested-With`,`XMLHttpRequest`);
xhr.setRequestHeader(`X-Requested-With`, `XMLHttpRequest`);
xhr.timeout = 5000;
xhr.ontimeout = onFailCallback;
xhr.responseType = `json`;
xhr.send(params);
return false;
};
module.exports = basketSignup;

Просмотреть файл

@ -1,15 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ReactGA from '../react-ga-proxy.js';
import React from "react";
import ReactDOM from "react-dom";
import ReactGA from "../react-ga-proxy.js";
import primaryNav from './components/primary-nav/primary-nav.js';
import CreepVote from './components/creep-vote/creep-vote.jsx';
import Creepometer from './components/creepometer/creepometer.jsx';
import Filter from './components/filter/filter.jsx';
import primaryNav from "./components/primary-nav/primary-nav.js";
import CreepVote from "./components/creep-vote/creep-vote.jsx";
import Creepometer from "./components/creepometer/creepometer.jsx";
import Filter from "./components/filter/filter.jsx";
import copyToClipboard from './copy-to-clipboard.js';
import HomepageSlider from './homepage-c-slider.js';
import ProductGA from './product-analytics.js';
import copyToClipboard from "./copy-to-clipboard.js";
import HomepageSlider from "./homepage-c-slider.js";
import ProductGA from "./product-analytics.js";
// Track all ReactDOM.render calls so we can use a Promise.all()
// all the way at the end to make sure we don't report "we are done"
@ -32,9 +32,11 @@ let main = {
let filter = document.querySelector(`#product-filter`);
if (filter) {
apps.push(new Promise(resolve => {
ReactDOM.render(<Filter whenLoaded={() => resolve()}/>, filter);
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(<Filter whenLoaded={() => resolve()} />, filter);
})
);
}
}
@ -44,7 +46,9 @@ let main = {
// Set up help text accordions where necessary:
let productBox = document.querySelector(`.product-detail .h1-heading`);
let productName = productBox ? productBox.textContent : `unknown product`;
let criteriaWithHelp = document.querySelectorAll(`.criterion button.toggle`);
let criteriaWithHelp = document.querySelectorAll(
`.criterion button.toggle`
);
if (criteriaWithHelp.length > 0) {
Array.from(criteriaWithHelp).forEach(button => {
@ -75,11 +79,15 @@ let main = {
enableCopyLinks() {
if (document.querySelectorAll(`.copy-link`)) {
Array.from(document.querySelectorAll(`.copy-link`)).forEach(element => {
element.addEventListener(`click`, (event) => {
element.addEventListener(`click`, event => {
event.preventDefault();
let productBox = document.querySelector(`.product-detail .h1-heading`);
let productTitle = productBox ? productBox.textContent : `unknown product`;
let productBox = document.querySelector(
`.product-detail .h1-heading`
);
let productTitle = productBox
? productBox.textContent
: `unknown product`;
ReactGA.event({
category: `product`,
@ -105,20 +113,31 @@ let main = {
let votes = element.querySelector(`input[name=votes]`).value;
try {
votes = JSON.parse(votes.replace(/'/g,`"`));
votes = JSON.parse(votes.replace(/'/g, `"`));
} catch (e) {
votes = {
creepiness: {
average: 50,
'vote_breakdown': {'0': 0, '1': 0, '2': 0, '3': 0, '4': 0}
vote_breakdown: { "0": 0, "1": 0, "2": 0, "3": 0, "4": 0 }
},
confidence: {'0': 0, '1': 0}
confidence: { "0": 0, "1": 0 }
};
}
apps.push(new Promise(resolve => {
ReactDOM.render(<CreepVote csrf={csrf.value} productName={productName} productID={parseInt(productID,10)} votes={votes} whenLoaded={() => resolve()}/>, element);
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<CreepVote
csrf={csrf.value}
productName={productName}
productID={parseInt(productID, 10)}
votes={votes}
whenLoaded={() => resolve()}
/>,
element
);
})
);
});
}
@ -128,9 +147,17 @@ let main = {
Array.from(creepometerTargets).forEach(element => {
let initialValue = element.dataset.initialValue;
apps.push(new Promise(resolve => {
ReactDOM.render(<Creepometer initialValue={initialValue} whenLoaded={() => resolve()}/>, element);
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<Creepometer
initialValue={initialValue}
whenLoaded={() => resolve()}
/>,
element
);
})
);
});
}

Просмотреть файл

@ -1,8 +1,8 @@
import React from 'react';
import Creepometer from '../creepometer/creepometer.jsx';
import CreepChart from '../creepiness-chart/creepiness-chart.jsx';
import LikelyhoodChart from '../likelyhood-chart/likelyhood-chart.jsx';
import SocialShare from '../social-share/social-share.jsx';
import React from "react";
import Creepometer from "../creepometer/creepometer.jsx";
import CreepChart from "../creepiness-chart/creepiness-chart.jsx";
import LikelyhoodChart from "../likelyhood-chart/likelyhood-chart.jsx";
import SocialShare from "../social-share/social-share.jsx";
import CREEPINESS_LABELS from "../creepiness-labels.js";
@ -116,7 +116,7 @@ export default class CreepVote extends React.Component {
this.sendVoteFor({
attribute: `confidence`,
productID,
value: confidence,
value: confidence
});
this.sendVoteFor({
@ -138,60 +138,119 @@ export default class CreepVote extends React.Component {
* @returns {jsx} What users see when they haven't voted on this product yet.
*/
renderVoteAsk() {
return (<form method="post" id="creep-vote" className={this.state.submitAttempted ? `submit-attempted` : ``} onSubmit={evt => this.submitVote(evt)}>
<div className="row mb-5">
<div className="col-12 col-md-6">
<div className="mb-4 text-center">
<h3 className="h5-heading mb-2">How creepy do you think this is?</h3>
return (
<form
method="post"
id="creep-vote"
className={this.state.submitAttempted ? `submit-attempted` : ``}
onSubmit={evt => this.submitVote(evt)}
>
<div className="row mb-5">
<div className="col-12 col-md-6">
<div className="mb-4 text-center">
<h3 className="h5-heading mb-2">
How creepy do you think this is?
</h3>
</div>
<Creepometer
initialValue={this.state.creepiness}
onChange={value => this.setCreepiness(value)}
/>
</div>
<Creepometer initialValue={this.state.creepiness} onChange={value => this.setCreepiness(value)}></Creepometer>
</div>
<div className="col-12 col-md-6 mt-5 mt-md-0">
<div className="mb-4 text-center">
<h3 className="h5-heading mb-2">How likely are you to buy it?</h3>
</div>
<div className="text-center">
<div class="btn-group btn-group-toggle mt-3 mt-md-5" data-toggle="buttons" onAnimationEnd={evt => this.handleAnimationEnd(evt)}>
<label for="likely">
<input type="radio" name="wouldbuy" id="likely" autocomplete="off" required/>
<span class="likely btn" onClick={() => this.setConfidence(true)}><img alt="thumb up" src="/_images/buyers-guide/icon-thumb-up-black.svg" /> Likely</span>
</label>
<label for="unlikely">
<input type="radio" name="wouldbuy" id="unlikely" autocomplete="off" required/>
<span class="unlikely btn" onClick={() => this.setConfidence(false)}><img alt="thumb down" src="/_images/buyers-guide/icon-thumb-down-black.svg" /> Not likely</span>
</label>
<div className="col-12 col-md-6 mt-5 mt-md-0">
<div className="mb-4 text-center">
<h3 className="h5-heading mb-2">How likely are you to buy it?</h3>
</div>
<div className="text-center">
<div
class="btn-group btn-group-toggle mt-3 mt-md-5"
data-toggle="buttons"
onAnimationEnd={evt => this.handleAnimationEnd(evt)}
>
<label for="likely">
<input
type="radio"
name="wouldbuy"
id="likely"
autocomplete="off"
required
/>
<span
class="likely btn"
onClick={() => this.setConfidence(true)}
>
<img
alt="thumb up"
src="/_images/buyers-guide/icon-thumb-up-black.svg"
/>{" "}
Likely
</span>
</label>
<label for="unlikely">
<input
type="radio"
name="wouldbuy"
id="unlikely"
autocomplete="off"
required
/>
<span
class="unlikely btn"
onClick={() => this.setConfidence(false)}
>
<img
alt="thumb down"
src="/_images/buyers-guide/icon-thumb-down-black.svg"
/>{" "}
Not likely
</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-12 text-center">
<button id="creep-vote-btn" type="submit" className="btn btn-ghost mb-2" onClick={() => this.handleSubmitBtnClick()}>Vote & See Results</button>
<p class="h6-heading-uppercase mb-0">{this.state.totalVotes} votes</p>
<div className="row">
<div className="col-12 text-center">
<button
id="creep-vote-btn"
type="submit"
className="btn btn-ghost mb-2"
onClick={() => this.handleSubmitBtnClick()}
>
Vote & See Results
</button>
<p class="h6-heading-uppercase mb-0">
{this.state.totalVotes} votes
</p>
</div>
</div>
</div>
</form>);
</form>
);
}
/**
* @returns {jsx} What users see when they have voted on this product.
*/
renderDidVote(){
renderDidVote() {
let bins = CREEPINESS_LABELS.length;
let userVoteGroup = Math.floor(bins * (this.state.creepiness-1)/100);
let userVoteGroup = Math.floor((bins * (this.state.creepiness - 1)) / 100);
let creepType = CREEPINESS_LABELS[userVoteGroup];
return(
return (
<div>
<div className="mb-4">
<div className="col-12 text-center">
<h3 className="h3-heading mb-1">{this.state.totalVotes + 1} Votes invite your friends!</h3>
<div className="h6-heading-uppercase text-muted"></div>
<h3 className="h3-heading mb-1">
{this.state.totalVotes + 1} Votes invite your friends!
</h3>
<div className="h6-heading-uppercase text-muted" />
</div>
<div className="row mt-3">
<div className="col">
<CreepChart userVoteGroup={userVoteGroup} values={this.props.votes.creepiness.vote_breakdown} />
<CreepChart
userVoteGroup={userVoteGroup}
values={this.props.votes.creepiness.vote_breakdown}
/>
</div>
<div className="col likelyhood-chart p-5">
<LikelyhoodChart values={this.props.votes.confidence} />
@ -199,7 +258,10 @@ export default class CreepVote extends React.Component {
</div>
</div>
<div className="text-center">
<SocialShare productName={this.props.productName} creepType={creepType} />
<SocialShare
productName={this.props.productName}
creepType={creepType}
/>
</div>
</div>
);
@ -208,10 +270,14 @@ export default class CreepVote extends React.Component {
handleReadResearchClick() {
let research = document.getElementById(`product-research`);
if (!research) { return; }
if (!research) {
return;
}
window.scrollBy({
top: research.getBoundingClientRect().top - parseInt(window.getComputedStyle(research).marginTop, 10),
top:
research.getBoundingClientRect().top -
parseInt(window.getComputedStyle(research).marginTop, 10),
left: 0,
behavior: `smooth`
});
@ -220,7 +286,7 @@ export default class CreepVote extends React.Component {
render() {
let voteContent;
if(this.state.didVote) {
if (this.state.didVote) {
voteContent = this.renderDidVote();
} else {
voteContent = this.renderVoteAsk();
@ -228,9 +294,17 @@ export default class CreepVote extends React.Component {
return (
<div className="creep-vote mt-4 mb-5">
<div class="what-you-think-label h5-heading d-inline-block">Tell us what you think</div>
<button id="btn-read-search" className="btn btn-link info-help mb-4 mt-2" onClick={() => this.handleReadResearchClick()}>Read our research first</button>
{ voteContent }
<div class="what-you-think-label h5-heading d-inline-block">
Tell us what you think
</div>
<button
id="btn-read-search"
className="btn btn-link info-help mb-4 mt-2"
onClick={() => this.handleReadResearchClick()}
>
Read our research first
</button>
{voteContent}
</div>
);
}

Просмотреть файл

@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
import CREEPINESS_LABELS from "../creepiness-labels.js";
export default class CreepChart extends React.Component {
@ -10,11 +10,36 @@ export default class CreepChart extends React.Component {
getInitialState() {
let values = this.props.values;
let data = [
{c: `no-creep`, label: CREEPINESS_LABELS[0], value: values[0], offset: 0},
{c: `little-creep`, label: CREEPINESS_LABELS[1], value: values[1], offset: 225},
{c: `somewhat-creep`, label: CREEPINESS_LABELS[2], value: values[2], offset: 475},
{c: `very-creep`, label: CREEPINESS_LABELS[3], value: values[3], offset: 725},
{c: `super-creep`, label: CREEPINESS_LABELS[4], value: values[4], offset: 975}
{
c: `no-creep`,
label: CREEPINESS_LABELS[0],
value: values[0],
offset: 0
},
{
c: `little-creep`,
label: CREEPINESS_LABELS[1],
value: values[1],
offset: 225
},
{
c: `somewhat-creep`,
label: CREEPINESS_LABELS[2],
value: values[2],
offset: 475
},
{
c: `very-creep`,
label: CREEPINESS_LABELS[3],
value: values[3],
offset: 725
},
{
c: `super-creep`,
label: CREEPINESS_LABELS[4],
value: values[4],
offset: 975
}
];
let sum = data.reduce((tally, v) => tally + v.value, 0);
@ -29,23 +54,24 @@ export default class CreepChart extends React.Component {
<div>
<table id="creepiness-score">
<tbody>
{
this.state.creepinessData.map((data,index) => {
let percent = Math.round(100 * data.value / this.state.totalCreepiness);
let voteColumn = this.props.userVoteGroup === index ? `your-vote` : ``;
{this.state.creepinessData.map((data, index) => {
let percent = Math.round(
(100 * data.value) / this.state.totalCreepiness
);
let voteColumn =
this.props.userVoteGroup === index ? `your-vote` : ``;
return (
<tr key={data.c} className={`${voteColumn} ${data.c}`}>
<th>
<div className="bar" style={{height: `${percent}px`,}}></div>
<span className="creep-label">{data.label}</span>
<span className="creep-face"></span>
</th>
<td className="creepiness">{percent}%</td>
</tr>
);
})
}
return (
<tr key={data.c} className={`${voteColumn} ${data.c}`}>
<th>
<div className="bar" style={{ height: `${percent}px` }} />
<span className="creep-label">{data.label}</span>
<span className="creep-face" />
</th>
<td className="creepiness">{percent}%</td>
</tr>
);
})}
</tbody>
</table>
<div className="row">

Просмотреть файл

@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
export default class Creepometer extends React.Component {
constructor(props) {
@ -85,9 +85,10 @@ export default class Creepometer extends React.Component {
slideMove(e) {
if (this.state.dragging) {
let x = e.clientX, bbox = this.state.parentBBox;
let x = e.clientX,
bbox = this.state.parentBBox;
if (e.touches){
if (e.touches) {
x = e.touches[0].clientX;
}
@ -104,51 +105,62 @@ export default class Creepometer extends React.Component {
repositionTrackHead(x, bbox) {
// compute the handle offset
let percentage = Math.round(100 * (x - bbox.left) / bbox.width);
let percentage = Math.round((100 * (x - bbox.left)) / bbox.width);
let value = percentage ? percentage : 1;
this.setState({
percentage,
value
}, () => {
if (this.props.onChange) {
this.props.onChange(value);
this.setState(
{
percentage,
value
},
() => {
if (this.props.onChange) {
this.props.onChange(value);
}
}
});
);
}
render() {
let frameOffset = Math.round(this.state.percentage * (this.faceCount-1)/100);
let frameOffset = Math.round(
(this.state.percentage * (this.faceCount - 1)) / 100
);
let trackheadOpts = {
style: {
left: `${this.state.value}%`
},
}
};
let faceOpts = {
style: {
background: `url("${this.framePath}sprite-resized-64-colors.png"), #f2b946`,
background: `url("${
this.framePath
}sprite-resized-64-colors.png"), #f2b946`,
backgroundSize: `70px`,
backgroundPositionX: 0,
backgroundPositionY: `-${frameOffset * this.faceHeight}px`,
backgroundRepeat: `no-repeat`,
},
backgroundRepeat: `no-repeat`
}
};
let mouseOpts = {
onMouseDown: evt => this.slideStart(evt),
onTouchStart: evt => this.slideStart(evt),
onTouchStart: evt => this.slideStart(evt)
};
return (
<div className="creepometer">
<div className="slider-container p-2">
<div className="slider" ref={e => (this.sliderElement=e)} onClick={evt => this.slideClick(evt)}>
<div
className="slider"
ref={e => (this.sliderElement = e)}
onClick={evt => this.slideClick(evt)}
>
<div className="h6-heading copy copy-left">Not creepy</div>
<div className="trackhead" {...trackheadOpts}>
<div className="face" {...faceOpts} {...mouseOpts}/>
<div className="pip" {...mouseOpts}/>
<div className="face" {...faceOpts} {...mouseOpts} />
<div className="pip" {...mouseOpts} />
</div>
<div className="h6-heading copy copy-right">Super creepy</div>
</div>

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import ReactGA from '../../../react-ga-proxy.js';
import React from "react";
import ReactGA from "../../../react-ga-proxy.js";
const CLASS_HIDDEN = `d-none`;
const CLASS_PRODUCT_BOX = `product-box`;
@ -20,8 +20,20 @@ class SelectableOption extends React.Component {
render() {
return (
<div onClick={e => this.forward(e)} className={`radio-group-entry ` + (this.props.type || `radio-button`)}>
<span className={(this.props.square ? `square` : ``) + ` dot ` + (this.props.selected? `selected` : ``)}/> <span data-label={this.props.label} className="label">{this.props.label}</span>
<div
onClick={e => this.forward(e)}
className={`radio-group-entry ` + (this.props.type || `radio-button`)}
>
<span
className={
(this.props.square ? `square` : ``) +
` dot ` +
(this.props.selected ? `selected` : ``)
}
/>{" "}
<span data-label={this.props.label} className="label">
{this.props.label}
</span>
</div>
);
}
@ -119,28 +131,34 @@ export default class Filter extends React.Component {
}
toggleSealOfApproval() {
this.setState({
sealOfApproval: !this.state.sealOfApproval
}, () => {
// Only fire once (per app lifecycle).
if (!this.toggledSealOfApproval) {
this.toggledSealOfApproval = true;
this.setState(
{
sealOfApproval: !this.state.sealOfApproval
},
() => {
// Only fire once (per app lifecycle).
if (!this.toggledSealOfApproval) {
this.toggledSealOfApproval = true;
ReactGA.event({
category: `buyersguide`,
action: `filter by standards`,
label: `filter by standards on buyersguide homepage`
});
ReactGA.event({
category: `buyersguide`,
action: `filter by standards`,
label: `filter by standards on buyersguide homepage`
});
}
this.setVisibilities();
}
this.setVisibilities();
});
);
}
handleLikelihood(label) {
this.setState({
likelihood: label
}, () => this.setVisibilities());
this.setState(
{
likelihood: label
},
() => this.setVisibilities()
);
}
slideStart(e) {
@ -170,9 +188,10 @@ export default class Filter extends React.Component {
slideMove(e) {
if (this.state.dragging) {
let x = e.clientX, bbox = this.state.parentBBox;
let x = e.clientX,
bbox = this.state.parentBBox;
if (e.touches){
if (e.touches) {
x = e.touches[0].clientX;
}
@ -183,8 +202,8 @@ export default class Filter extends React.Component {
x = bbox.left;
}
let fraction = (x-bbox.left)/bbox.width;
let percentage = (fraction*100) | 0;
let fraction = (x - bbox.left) / bbox.width;
let percentage = (fraction * 100) | 0;
let value = percentage ? percentage : 1;
let update = {};
@ -195,7 +214,7 @@ export default class Filter extends React.Component {
if (update.creepinessMin > this.state.creepinessMax - creepInterval) {
update.creepinessMax = update.creepinessMin + creepInterval;
}
} else if(this.activeSlider === this.maxHead) {
} else if (this.activeSlider === this.maxHead) {
update.creepinessMax = Math.max(value, 1 + creepInterval);
if (update.creepinessMax < this.state.creepinessMin + creepInterval) {
update.creepinessMin = update.creepinessMax - creepInterval;
@ -236,12 +255,12 @@ export default class Filter extends React.Component {
//
// So: we need to non-linear-maths it up, to fix that!
let interval = (max - min),
left = min,
pivot = (100 - interval) / 100,
naiveScaledLeft = left * pivot,
difference = left - naiveScaledLeft,
leftMatchingPercentage = left + difference/pivot;
let interval = max - min,
left = min,
pivot = (100 - interval) / 100,
naiveScaledLeft = left * pivot,
difference = left - naiveScaledLeft,
leftMatchingPercentage = left + difference / pivot;
update.trackStyle = {
backgroundSize: `${interval}% 100%`,
@ -251,9 +270,9 @@ export default class Filter extends React.Component {
setVisibilities() {
let minC = this.state.creepinessMin,
maxC = this.state.creepinessMax,
like = this.state.likelihood,
all = Array.from(document.querySelectorAll(`.${CLASS_PRODUCT_BOX}`));
maxC = this.state.creepinessMax,
like = this.state.likelihood,
all = Array.from(document.querySelectorAll(`.${CLASS_PRODUCT_BOX}`));
all.forEach(productBox => {
let c = parseInt(productBox.dataset.creepiness, 10);
@ -270,7 +289,9 @@ export default class Filter extends React.Component {
}
}
if (hidden) { return; }
if (hidden) {
return;
}
// Filter out for creepiness
if (c < minC || c > maxC) {
@ -280,7 +301,9 @@ export default class Filter extends React.Component {
classes.remove(CLASS_HIDDEN);
}
if (hidden) { return; }
if (hidden) {
return;
}
// not hidden by creepiness: do we need to hide it due to buyers likelihood?
let recommendation = productBox.querySelector(`.recommendation`);
@ -293,12 +316,20 @@ export default class Filter extends React.Component {
recommendation.classList.remove(CLASS_HIDDEN);
}
if (hidden) { return; }
if (hidden) {
return;
}
if (like === `Likely` && recommendation.classList.contains(`negative`)) {
if (
like === `Likely` &&
recommendation.classList.contains(`negative`)
) {
classes.add(CLASS_HIDDEN);
hidden = true;
} else if (like === `Not likely` && recommendation.classList.contains(`positive`)) {
} else if (
like === `Not likely` &&
recommendation.classList.contains(`positive`)
) {
classes.add(CLASS_HIDDEN);
hidden = true;
}
@ -306,7 +337,9 @@ export default class Filter extends React.Component {
});
let noMatchingNote = document.querySelector(`.no-matching-products-note`);
let numHidden = document.querySelectorAll(`.${CLASS_PRODUCT_BOX}.${CLASS_HIDDEN}`).length;
let numHidden = document.querySelectorAll(
`.${CLASS_PRODUCT_BOX}.${CLASS_HIDDEN}`
).length;
if (numHidden === all.length) {
noMatchingNote.classList.remove(CLASS_HIDDEN);
@ -318,14 +351,14 @@ export default class Filter extends React.Component {
getFilterContent() {
let mouseOpts = {
onMouseDown: evt => this.slideStart(evt),
onTouchStart: evt => this.slideStart(evt),
onTouchStart: evt => this.slideStart(evt)
};
let likelihoods = [`Likely`, `Not likely`, `Both`].map(label => {
return {
label,
onClick: () => this.handleLikelihood(label),
selected: (this.state.likelihood === label)
selected: this.state.likelihood === label
};
});
@ -336,11 +369,14 @@ export default class Filter extends React.Component {
<h2 className="filter-caption">Filter by</h2>
<div className="seal-of-approval">
<h3 className="h6-heading-uppercase">minimum security standards <img
src="/_images/buyers-guide/mini-badge.svg"
width="30px"
height="30px"
/></h3>
<h3 className="h6-heading-uppercase">
minimum security standards{" "}
<img
src="/_images/buyers-guide/mini-badge.svg"
width="30px"
height="30px"
/>
</h3>
<SelectableOption
type="checkbox"
label="Meets standards"
@ -354,9 +390,23 @@ export default class Filter extends React.Component {
<h3 className="h6-heading-uppercase">creepiness</h3>
<div className="slider">
<label>nice</label>
<div className="track" ref={e => (this.track=e)} style={this.state.trackStyle}>
<span className="min track-head" style={{ left: this.state.offsetMin }} ref={e => (this.minHead=e)} {...mouseOpts} />
<span className="max track-head" style={{ left: this.state.offsetMax }} ref={e => (this.maxHead=e)} {...mouseOpts} />
<div
className="track"
ref={e => (this.track = e)}
style={this.state.trackStyle}
>
<span
className="min track-head"
style={{ left: this.state.offsetMin }}
ref={e => (this.minHead = e)}
{...mouseOpts}
/>
<span
className="max track-head"
style={{ left: this.state.offsetMax }}
ref={e => (this.maxHead = e)}
{...mouseOpts}
/>
</div>
<label className="creepy">creepy</label>
</div>
@ -364,7 +414,9 @@ export default class Filter extends React.Component {
<div className="likelihood">
<h3 className="h6-heading-uppercase">likelihood to buy</h3>
{ likelihoods.map(opts => <SelectableOption {...opts}/>) }
{likelihoods.map(opts => (
<SelectableOption {...opts} />
))}
</div>
</div>
);
@ -375,8 +427,13 @@ export default class Filter extends React.Component {
return [
<div className="filter-label">Filter</div>,
<div className={`filter-content` + (this.state.collapsed ? ` collapsed` : ``)} onClick={() => this.open()}>
{ content }
<div
className={
`filter-content` + (this.state.collapsed ? ` collapsed` : ``)
}
onClick={() => this.open()}
>
{content}
</div>
];
}

Просмотреть файл

@ -1,16 +1,16 @@
// TODO: Inject likely % in .bar and .likelyhood-words
import React from 'react';
import React from "react";
export default class LikelyhoodChart extends React.Component {
constructor(props) {
super(props);
}
render(){
render() {
let values = this.props.values;
let total = values[0] + values[1];
let perc = Math.round(100 * values[0]/total, 10);
let perc = Math.round((100 * values[0]) / total, 10);
return (
<div>
@ -21,8 +21,10 @@ export default class LikelyhoodChart extends React.Component {
<span className="likely-label">likely</span>
</th>
<td className="likelyhood">
<span className="bar" style={{width: `${100 - perc}%`,}}></span>
<span className="likelyhood-words">{100 - perc}% likely to buy it</span>
<span className="bar" style={{ width: `${100 - perc}%` }} />
<span className="likelyhood-words">
{100 - perc}% likely to buy it
</span>
</td>
</tr>
<tr className="unlikely">
@ -30,8 +32,10 @@ export default class LikelyhoodChart extends React.Component {
<span className="likely-label">not likely</span>
</th>
<td className="likelyhood">
<span className="bar" style={{width: `${perc}%`,}}></span>
<span className="likelyhood-words">{perc}% not likely to buy it</span>
<span className="bar" style={{ width: `${perc}%` }} />
<span className="likelyhood-words">
{perc}% not likely to buy it
</span>
</td>
</tr>
</tbody>

Просмотреть файл

@ -1,6 +1,6 @@
/*
* This is a modified version of source/js/primary-nav.js
*/
* This is a modified version of source/js/primary-nav.js
*/
let primaryNav = {
init: function() {
@ -39,7 +39,7 @@ let primaryNav = {
setContentUnderneathState(openMenu);
}
document.addEventListener(`keyup`, (e) => {
document.addEventListener(`keyup`, e => {
if (e.keyCode === 27) {
menuOpen = false;
setMenuState(menuOpen);

Просмотреть файл

@ -1,12 +1,14 @@
import React from 'react';
import ReactGA from '../../../react-ga-proxy';
import copyToClipboard from '../../copy-to-clipboard.js';
import React from "react";
import ReactGA from "../../../react-ga-proxy";
import copyToClipboard from "../../copy-to-clipboard.js";
const SocialShareLink = (props) => {
const SocialShareLink = props => {
let classes = `social-icon`;
let srLabel = ``;
let link = `PrivacyNotIncluded.org`;
let shareText = `I think this ${props.productName} is ${props.creepType.toUpperCase()}. What do you think? Check out the Creep-O-Meter over on @mozillas ${link} buyers guide.`;
let shareText = `I think this ${
props.productName
} is ${props.creepType.toUpperCase()}. What do you think? Check out the Creep-O-Meter over on @mozillas ${link} buyers guide.`;
let shareEvent = {
category: `product`,
action: `share tap`,
@ -25,7 +27,9 @@ const SocialShareLink = (props) => {
classes += ` social-button-twitter`;
srLabel = `Twitter`;
shareEvent.label += `to twitter`;
link = `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}`;
link = `https://twitter.com/intent/tweet?text=${encodeURIComponent(
shareText
)}`;
}
if (props.type === `email`) {
@ -51,7 +55,7 @@ const SocialShareLink = (props) => {
if (props.type === `link`) {
let _trackShareAction = trackShareAction;
trackShareAction = (evt) => {
trackShareAction = evt => {
evt.preventDefault();
copyToClipboard(evt.target, window.location.href);
evt.target.innerHTML = evt.target.innerHTML.replace(srLabel, `Copied`);
@ -59,10 +63,19 @@ const SocialShareLink = (props) => {
};
}
return <a target="_blank" className="pni-s-link" href={link} onClick={trackShareAction}><span className={classes}/> {srLabel}</a>;
return (
<a
target="_blank"
className="pni-s-link"
href={link}
onClick={trackShareAction}
>
<span className={classes} /> {srLabel}
</a>
);
};
const SocialShare = (props) => {
const SocialShare = props => {
return (
<div class="social pni-share-buttons d-flex justify-content-center flex-wrap flex-md-nowrap mt-3">
<SocialShareLink type="facebook" {...props} />

Просмотреть файл

@ -1,5 +1,4 @@
const copyToClipboard = (linkElement, textToCopy) => {
let textArea = document.createElement(`textarea`);
textArea.setAttribute(`contenteditable`, true);

Просмотреть файл

@ -29,14 +29,15 @@ function isElementInViewport(element) {
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// map a value from one range to another
function map(v, s1,e1, s2,e2) {
return s2 + (v-s1) * (e2-s2) / (e1-s1);
function map(v, s1, e1, s2, e2) {
return s2 + ((v - s1) * (e2 - s2)) / (e1 - s1);
}
// cap a value to a range
@ -53,48 +54,65 @@ export default {
let bubbleText = bubble.querySelector(`.text`);
let products = document.querySelectorAll(`.product-box`);
window.addEventListener(`scroll`, () => {
window.addEventListener(
`scroll`,
() => {
// Figure out which face to show while scrolling:
let visible = Array.from(products).filter(v => {
return isElementInViewport(v) && !v.classList.contains(`d-none`);
});
// Figure out which face to show while scrolling:
let visible = Array.from(products).filter(v => {
return isElementInViewport(v) && !v.classList.contains(`d-none`);
});
let n = visible.length;
let n = visible.length;
// Shortcut this scroll update if there are no products
if (n === 0) {
return;
}
// Shortcut this scroll update if there are no products
if (n===0) { return; }
// Reduce the creepiness, treating any product without votes
// as a "neutral" product with creepiness = 50
let averageCreepiness = visible.reduce((tally, v) => {
let value = parseFloat(v.dataset.creepiness) || 50;
// Reduce the creepiness, treating any product without votes
// as a "neutral" product with creepiness = 50
let averageCreepiness = visible.reduce( (tally, v) => {
let value = parseFloat(v.dataset.creepiness) || 50;
return tally + value / n;
}, 0);
return tally + value/n;
}, 0);
// Compress the value so that we show a smiley face even for products with a lowish creepiness score.
let mappedAverageCreepiness = cap(
map(
averageCreepiness,
MINIMUM_HAPPINESS_RATING,
MAXIMUM_CREEPINESS_RATING,
0,
100
),
1,
100
);
// Compress the value so that we show a smiley face even for products with a lowish creepiness score.
let mappedAverageCreepiness = cap(map(averageCreepiness, MINIMUM_HAPPINESS_RATING, MAXIMUM_CREEPINESS_RATING, 0, 100), 1, 100);
// The averageCreepiness will be in range [1,100] so we can dec1 the
// valueto make sure we're in frame range [0,frames.length-1]:
let frame = Math.round(
((SPRITE_FRAME_COUNT - 1) * (mappedAverageCreepiness - 1)) / 100
);
// The averageCreepiness will be in range [1,100] so we can dec1 the
// valueto make sure we're in frame range [0,frames.length-1]:
let frame = Math.round((SPRITE_FRAME_COUNT-1) * (mappedAverageCreepiness-1)/100);
face.style.backgroundPositionY = `${-frame * EMOJI_FRAME_HEIGHT}px`;
face.style.backgroundPositionY = `${-frame * EMOJI_FRAME_HEIGHT}px`;
// Figure out what the corresponding creepiness label should be:
let len = CREEPINESS_LABELS.length;
let bin = Math.floor((len * (mappedAverageCreepiness - 1)) / 100);
// Figure out what the corresponding creepiness label should be:
let len = CREEPINESS_LABELS.length;
let bin = Math.floor(len * (mappedAverageCreepiness-1)/100);
if (bin === -1) {
bubbleText.textContent = ``;
bubble.classList.add(`d-none`);
} else {
bubbleText.textContent = `${CREEPINESS_LABELS[bin]}!`;
bubble.classList.remove(`d-none`);
if (bin === -1) {
bubbleText.textContent = ``;
bubble.classList.add(`d-none`);
} else {
bubbleText.textContent = `${CREEPINESS_LABELS[bin]}!`;
bubble.classList.remove(`d-none`);
}
},
{
passive: true // remember not to bog down the UI thread.
}
}, {
passive: true // remember not to bog down the UI thread.
});
);
}
};

Просмотреть файл

@ -1,55 +1,55 @@
import ReactGA from '../react-ga-proxy';
import DNT from '../dnt.js';
import ReactGA from "../react-ga-proxy";
import DNT from "../dnt.js";
function getQuerySelectorEvents(pageTitle, productName) {
return {
// "site-wide" events
'#donate-button': {
"#donate-button": {
category: `buyersguide`,
action: `donate tap`,
label: `${pageTitle} donate header`
},
'#donate-button-main': {
"#donate-button-main": {
category: `buyersguide`,
action: `donate tap`,
label: `${pageTitle} donate header`
},
'#donate-button-footer': {
"#donate-button-footer": {
category: `buyersguide`,
action: `donate tap`,
label: `${pageTitle} donate footer`
},
'#nav-social-button-fb': {
"#nav-social-button-fb": {
category: `buyersguide`,
action: `share tap`,
label: `share site to Facebook`,
transport: `beacon`
},
'#nav-social-button-twitter': {
"#nav-social-button-twitter": {
category: `buyersguide`,
action: `share tap`,
label: `share site to Twitter`,
transport: `beacon`
},
'#nav-social-button-email': {
"#nav-social-button-email": {
category: `buyersguide`,
action: `share tap`,
label: `share site to Email`,
transport: `beacon`
},
'#nav-social-button-fb-mb': {
"#nav-social-button-fb-mb": {
category: `buyersguide`,
action: `share tap`,
label: `share site to Facebook`,
transport: `beacon`
},
'#nav-social-button-twitter-mb': {
"#nav-social-button-twitter-mb": {
category: `buyersguide`,
action: `share tap`,
label: `share site to Twitter`,
transport: `beacon`
},
'#nav-social-button-email-mb': {
"#nav-social-button-email-mb": {
category: `buyersguide`,
action: `share tap`,
label: `share site to Email`,
@ -57,52 +57,52 @@ function getQuerySelectorEvents(pageTitle, productName) {
},
// product events
'#product-company-url': {
"#product-company-url": {
category: `product`,
action: `company link tap`,
label: `company link for ${productName}`,
transport: `beacon`
},
'#product-copy-link-button': {
"#product-copy-link-button": {
category: `product`,
action: `copy link tap`,
label: `copy link ${productName}`
},
'#product-live-chat': {
"#product-live-chat": {
category: `product`,
action: `customer support link tap`,
label: `support link for ${productName}`
},
'#creep-vote-btn': {
"#creep-vote-btn": {
category: `product`,
action: `opinion submitted`,
label: `opinion on ${productName}`
},
'#product-social-button-fb': {
"#product-social-button-fb": {
category: `product`,
action: `share tap`,
label: `share ${productName} to Facebook`,
transport: `beacon`
},
'#product-social-button-twitter': {
"#product-social-button-twitter": {
category: `product`,
action: `share tap`,
label: `share ${productName} to Twitter`,
transport: `beacon`
},
'#product-social-button-email': {
"#product-social-button-email": {
category: `product`,
action: `share tap`,
label: `share ${productName} to Email`,
transport: `beacon`
},
'#privacy-policy-link': {
"#privacy-policy-link": {
category: `product`,
action: `privacy policy link tap`,
label: `policy link for ${productName}`,
transport: `beacon`
},
'#reading-level-link': {
"#reading-level-link": {
category: `product`,
action: `carnegie mellon reading level links`,
label: `reading level link for ${productName}`,
@ -110,7 +110,7 @@ function getQuerySelectorEvents(pageTitle, productName) {
},
// product updates
'.product-update-link': {
".product-update-link": {
category: `product`,
action: `update article link tap`,
label: `update article link for ${productName}`,
@ -135,10 +135,12 @@ const ProductGA = {
let productBox = document.querySelector(`.product-detail .h1-heading`);
let productName = productBox ? productBox.textContent : `unknown product`;
let pageTitle = document.querySelector(`meta[property='og:title']`).getAttribute(`content`);
let pageTitle = document
.querySelector(`meta[property='og:title']`)
.getAttribute(`content`);
let querySelectorEvents = getQuerySelectorEvents(pageTitle, productName);
Object.keys(querySelectorEvents).forEach( querySelector => {
Object.keys(querySelectorEvents).forEach(querySelector => {
let elements = document.querySelectorAll(querySelector);
if (elements.length > 0) {

Просмотреть файл

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import Person from '../../components/people/person.jsx';
import LoadingIndicator from '../../components/loading-indicator/loading-indicator.jsx';
import React from "react";
import PropTypes from "prop-types";
import Person from "../../components/people/person.jsx";
import LoadingIndicator from "../../components/loading-indicator/loading-indicator.jsx";
export default class FellowList extends React.Component {
constructor(props) {
@ -14,11 +14,13 @@ export default class FellowList extends React.Component {
fetchFellows(query) {
Object.assign(query, {
'profile_type': `fellow`,
'ordering': `custom_name`
profile_type: `fellow`,
ordering: `custom_name`
});
let queryString = Object.entries(query).map(pair => pair.map(encodeURIComponent).join(`=`)).join(`&`);
let queryString = Object.entries(query)
.map(pair => pair.map(encodeURIComponent).join(`=`))
.join(`&`);
let xhr = new XMLHttpRequest();
xhr.addEventListener(`load`, () => {
@ -27,7 +29,10 @@ export default class FellowList extends React.Component {
});
});
xhr.open(`GET`, `${this.props.env.PULSE_API_DOMAIN}/api/pulse/v2/profiles/?${queryString}`);
xhr.open(
`GET`,
`${this.props.env.PULSE_API_DOMAIN}/api/pulse/v2/profiles/?${queryString}`
);
xhr.send();
}
@ -57,7 +62,8 @@ export default class FellowList extends React.Component {
newQKeys.splice(newQKeys.indexOf(key), 1);
});
if (newQKeys.length) { // newQuery has more key-value pairs than oldQuery
if (newQKeys.length) {
// newQuery has more key-value pairs than oldQuery
hasChanged = true;
}
@ -68,28 +74,37 @@ export default class FellowList extends React.Component {
// massage fellow's Pulse profile data and pass it to <Person> to render
let links = {};
let programType = fellow.program_type && fellow.program_type.toLowerCase() === `in residence` ? `Fellow in Residence` : fellow.program_type;
let programType =
fellow.program_type &&
fellow.program_type.toLowerCase() === `in residence`
? `Fellow in Residence`
: fellow.program_type;
let issues = fellow.issues;
issues.unshift(programType);
if ( fellow.twitter ) {
if (fellow.twitter) {
links.twitter = fellow.twitter;
}
if ( fellow.linkedin ) {
if (fellow.linkedin) {
links.linkedIn = fellow.linkedin;
}
let metadata = {
'internet_health_issues': issues,
internet_health_issues: issues,
links: links,
name: fellow.name,
role: `${fellow.profile_type}, ${fellow.program_year}`,
image: fellow.thumbnail,
location: fellow.location,
affiliations: [], // don't show affiliations meta for now
'custom_link': { text: `See work`, link: `https://${this.props.env.PULSE_DOMAIN}/profile/${fellow.profile_id}` }
custom_link: {
text: `See work`,
link: `https://${this.props.env.PULSE_DOMAIN}/profile/${
fellow.profile_id
}`
}
};
return <Person metadata={metadata} key={fellow.name} />;
@ -97,10 +112,18 @@ export default class FellowList extends React.Component {
renderFellows() {
if (!this.state.fellows) {
return <div className="col-12 mx-auto my-5 text-center"><LoadingIndicator /></div>;
return (
<div className="col-12 mx-auto my-5 text-center">
<LoadingIndicator />
</div>
);
}
return this.state.fellows.length ? this.state.fellows.map(fellow => this.renderFellowCard(fellow)) : <div className="col-12 mb-5">No fellow profile found.</div>;
return this.state.fellows.length ? (
this.state.fellows.map(fellow => this.renderFellowCard(fellow))
) : (
<div className="col-12 mb-5">No fellow profile found.</div>
);
}
render() {

Просмотреть файл

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import FellowList from './fellow-list.jsx';
import React from "react";
import PropTypes from "prop-types";
import FellowList from "./fellow-list.jsx";
export default class SingleFilterFellowList extends React.Component {
constructor(props) {
@ -28,23 +28,30 @@ export default class SingleFilterFellowList extends React.Component {
}
renderFilters() {
return <div className="fellowships-directory-filter px-2">
{ this.props.filterOptions.map(option => {
let id = `fellow-list-${this.props.filterType}-${option.replace(` `, `-`)}`;
return (
<div className="fellowships-directory-filter px-2">
{this.props.filterOptions.map(option => {
let id = `fellow-list-${this.props.filterType}-${option.replace(
` `,
`-`
)}`;
return <div key={option} className="filter-option">
<input type="radio"
name={this.props.filterType}
value={option}
id={id}
onClick={() => this.handleFilterClick(option)}
defaultChecked={this.state.selectedOption === option}
/>
<label htmlFor={id}>{option}</label>
</div>;
})
}
</div>;
return (
<div key={option} className="filter-option">
<input
type="radio"
name={this.props.filterType}
value={option}
id={id}
onClick={() => this.handleFilterClick(option)}
defaultChecked={this.state.selectedOption === option}
/>
<label htmlFor={id}>{option}</label>
</div>
);
})}
</div>
);
}
componentDidMount() {
@ -57,9 +64,7 @@ export default class SingleFilterFellowList extends React.Component {
return (
<div>
<div className="row">
<div className="col-12 mb-5">
{this.renderFilters()}
</div>
<div className="col-12 mb-5">{this.renderFilters()}</div>
</div>
<FellowList env={this.props.env} query={this.generateQuery()} />
</div>

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import React from "react";
import PropTypes from "prop-types";
export default class Highlights extends React.Component {
constructor(props) {
@ -13,13 +13,13 @@ export default class Highlights extends React.Component {
return (
<div className="col-md-6" key={`super-highlight-${index}`}>
<div className={`${item.image ? `pb-5` : `py-5`}`}>
{ item.image &&
<img src={item.image} />
}
{item.image && <img src={item.image} />}
<div className="key-item mx-2 mx-md-4 p-3">
<h5 className="h4-heading mb-2">{item.title}</h5>
<p>{item.description}</p>
<a className="cta-link mb-2" href={item.link_url}>{item.link_label}</a>
<a className="cta-link mb-2" href={item.link_url}>
{item.link_label}
</a>
</div>
</div>
</div>
@ -30,11 +30,17 @@ export default class Highlights extends React.Component {
return (
<div className="row my-3" key={`highlight-${index}`}>
<div className="col-sm-12 col-md-4 pt-3 hidden-sm-down">
{ item.image ? <img src={item.image}/> : <div className="placeholder"></div> }
{item.image ? (
<img src={item.image} />
) : (
<div className="placeholder" />
)}
</div>
<div className="col-sm-12 col-md-8 pt-3">
{ index !== 0 && <hr className="mt-0 mb-4" /> }
<h5 className="h5-heading mb-3"><a href={item.link_url}>{item.title}</a></h5>
{index !== 0 && <hr className="mt-0 mb-4" />}
<h5 className="h5-heading mb-3">
<a href={item.link_url}>{item.title}</a>
</h5>
<p>{item.description}</p>
</div>
</div>
@ -43,9 +49,7 @@ export default class Highlights extends React.Component {
return (
<div className="highlights mb-5">
<div className="row">
{superHighlights}
</div>
<div className="row">{superHighlights}</div>
{highlights}
</div>
);

Просмотреть файл

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import React from "react";
import PropTypes from "prop-types";
import moment from "moment";
export default class HomeNews extends React.Component {
constructor(props) {
@ -10,22 +10,29 @@ export default class HomeNews extends React.Component {
}
render() {
let newsItem = (item, featured=false, index=null, hr=false) => {
let newsItem = (item, featured = false, index = null, hr = false) => {
return (
<div className="news-item" key={index}>
<div className="d-flex align-items-center mb-2">
{ item.glyph && <img src={item.glyph} className="mr-2 glyph"/> }
{item.glyph && <img src={item.glyph} className="mr-2 glyph" />}
<p className="h6-heading-uppercase mb-0">{item.outlet}</p>
</div>
<h5 className="mb-2">
<a href={item.link} className={featured ? `h4-heading` : `h5-heading`}>{item.headline}</a>
<a
href={item.link}
className={featured ? `h4-heading` : `h5-heading`}
>
{item.headline}
</a>
</h5>
<p className="h6-heading mb-2">{ item.author && `by ${item.author} on ` }{ moment(item.date, `YYYY-MM-DD`).format(`MMMM YYYY`) }</p>
{ item.excerpt && <p>{item.excerpt}</p> }
{ hr && <hr/> }
<p className="h6-heading mb-2">
{item.author && `by ${item.author} on `}
{moment(item.date, `YYYY-MM-DD`).format(`MMMM YYYY`)}
</p>
{item.excerpt && <p>{item.excerpt}</p>}
{hr && <hr />}
</div>
);
};
let unfeaturedNews = this.props.data.slice(1).map((item, index, array) => {
@ -38,18 +45,20 @@ export default class HomeNews extends React.Component {
<div className="row mb-3">
<div className="col-md-6 mb-0 pb-4">
<div className="play-button-wrapper">
<img src={featuredNews.thumbnail}/>
{ featuredNews.is_video && <a href={featuredNews.link} className="play-button-overlay"></a> }
<img src={featuredNews.thumbnail} />
{featuredNews.is_video && (
<a href={featuredNews.link} className="play-button-overlay" />
)}
</div>
<div className="row">
<div className="col-12">
<div className="key-item mx-2 mx-md-4 p-3">
{ newsItem(featuredNews, true) }
{newsItem(featuredNews, true)}
</div>
</div>
</div>
</div>
<div className="col-md-6 mb-0 pb-4">{ unfeaturedNews }</div>
<div className="col-md-6 mb-0 pb-4">{unfeaturedNews}</div>
</div>
);
}

Просмотреть файл

@ -1,7 +1,7 @@
import React from 'react';
import ReactGA from 'react-ga';
import classNames from 'classnames';
import basketSignup from '../../basket-signup.js';
import React from "react";
import ReactGA from "react-ga";
import classNames from "classnames";
import basketSignup from "../../basket-signup.js";
export default class JoinUs extends React.Component {
constructor(props) {
@ -20,16 +20,20 @@ export default class JoinUs extends React.Component {
}
submitForm(event) {
this.setState({userSubmitted: true});
this.setState({ userSubmitted: true });
event.preventDefault();
if(this.refs.email.value && this.refs.privacy.checked){
basketSignup({
email: this.refs.email.value,
privacy: this.refs.privacy.checked,
newsletter: this.props.newsletter
}, this.formSubmissionSuccessful, this.formSubmissionFailure);
if (this.refs.email.value && this.refs.privacy.checked) {
basketSignup(
{
email: this.refs.email.value,
privacy: this.refs.privacy.checked,
newsletter: this.props.newsletter
},
this.formSubmissionSuccessful,
this.formSubmissionFailure
);
}
ReactGA.event({
@ -48,12 +52,12 @@ export default class JoinUs extends React.Component {
}
formSubmissionSuccessful() {
this.setState({signupSuccess: true});
this.setState({ signupSuccess: true });
}
formSubmissionFailure(e) {
console.error(e);
this.setState({signupFailed: true});
this.setState({ signupFailed: true });
}
componentDidMount() {
@ -64,59 +68,115 @@ export default class JoinUs extends React.Component {
render() {
let inputGroupClass = classNames({
'has-danger': !this.state.signupSuccess && this.state.userSubmitted && !this.refs.email.value || this.state.signupFailed
"has-danger":
(!this.state.signupSuccess &&
this.state.userSubmitted &&
!this.refs.email.value) ||
this.state.signupFailed
});
let privacyClass = classNames({
'form-check': true,
'has-danger': !this.state.signupSuccess && this.state.userSubmitted && !this.refs.privacy.checked
"form-check": true,
"has-danger":
!this.state.signupSuccess &&
this.state.userSubmitted &&
!this.refs.privacy.checked
});
let signupState = classNames({
'py-5': true,
'signup-success': this.state.signupSuccess && this.state.userSubmitted,
'signup-fail': !this.state.signupSuccess && this.state.userSubmitted
"py-5": true,
"signup-success": this.state.signupSuccess && this.state.userSubmitted,
"signup-fail": !this.state.signupSuccess && this.state.userSubmitted
});
return (
<div className={ `container my-default ${signupState}` }>
<div className={`container my-default ${signupState}`}>
<div className="col join-main-content">
<div className="row">
<div className="col-12 col-md-6 d-flex justify-content-center flex-column join-content">
<div className="mb-5 join-page-title">
<h2 className="h1-heading">{!this.state.signupSuccess ? `${this.props.ctaHeader}` : `Thanks!`}</h2>
<h2 className="h1-heading">
{!this.state.signupSuccess
? `${this.props.ctaHeader}`
: `Thanks!`}
</h2>
</div>
<div className="join-heading">
{ this.state.signupSuccess && <h3 className="h3-black">Thanks!</h3> }
{this.state.signupSuccess && (
<h3 className="h3-black">Thanks!</h3>
)}
</div>
{!this.state.signupSuccess ?
<p className="body-large" dangerouslySetInnerHTML={{__html:this.props.ctaDescription}}></p>
: <p dangerouslySetInnerHTML={{__html:this.props.thankYouMessage}}></p>
}
{!this.state.signupSuccess ? (
<p
className="body-large"
dangerouslySetInnerHTML={{
__html: this.props.ctaDescription
}}
/>
) : (
<p
dangerouslySetInnerHTML={{
__html: this.props.thankYouMessage
}}
/>
)}
</div>
{ !this.state.signupSuccess &&
<div className="col-12 col-md-6 join-form">
<form onSubmit={this.submitForm}>
<div className={inputGroupClass}>
<div className="mb-2">
<input type="email" className="form-control" placeholder="EMAIL ADDRESS" ref="email" onFocus={this.onInputFocus}/>
{!this.state.signupSuccess && (
<div className="col-12 col-md-6 join-form">
<form onSubmit={this.submitForm}>
<div className={inputGroupClass}>
<div className="mb-2">
<input
type="email"
className="form-control"
placeholder="EMAIL ADDRESS"
ref="email"
onFocus={this.onInputFocus}
/>
</div>
{this.state.userSubmitted && !this.refs.email.value && (
<small className="form-check form-control-feedback">
Please enter your email
</small>
)}
{this.state.signupFailed && (
<small className="form-check form-control-feedback">
Something went wrong. Please check your email address
and try again
</small>
)}
</div>
{this.state.userSubmitted && !this.refs.email.value && <small className="form-check form-control-feedback">Please enter your email</small>}
{this.state.signupFailed && <small className="form-check form-control-feedback">Something went wrong. Please check your email address and try again</small>}
</div>
<div className={privacyClass}>
<label className="form-check-label mb-4">
<input type="checkbox" className="form-check-input" id="PrivacyCheckbox" ref="privacy" />
<span className="form-text">I'm okay with Mozilla handling my info as explained in this <a href="https://www.mozilla.org/privacy/websites/">Privacy Notice</a></span>
{this.state.userSubmitted && !this.refs.privacy.checked && <small className="has-danger">Please check this box if you want to proceed</small>}
</label>
<div>
<button className="btn btn-normal join-btn">Sign Up</button>
<div className={privacyClass}>
<label className="form-check-label mb-4">
<input
type="checkbox"
className="form-check-input"
id="PrivacyCheckbox"
ref="privacy"
/>
<span className="form-text">
I'm okay with Mozilla handling my info as explained in
this{" "}
<a href="https://www.mozilla.org/privacy/websites/">
Privacy Notice
</a>
</span>
{this.state.userSubmitted &&
!this.refs.privacy.checked && (
<small className="has-danger">
Please check this box if you want to proceed
</small>
)}
</label>
<div>
<button className="btn btn-normal join-btn">
Sign Up
</button>
</div>
</div>
</div>
</form>
</div>
}
</form>
</div>
)}
</div>
</div>
</div>

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import React from "react";
import PropTypes from "prop-types";
export default class Leaders extends React.Component {
constructor(props) {
@ -12,23 +12,32 @@ export default class Leaders extends React.Component {
// Limit to 4 leaders
let leaders = this.props.data.slice(0, 4).map((item, index) => {
return (
<div className="featured-person col-6 col-sm-4 col-md-3 mb-4 mb-sm-0" key={index}>
<img className="img-fluid d-block" src={item.image} alt="Headshot"/>
<div
className="featured-person col-6 col-sm-4 col-md-3 mb-4 mb-sm-0"
key={index}
>
<img className="img-fluid d-block" src={item.image} alt="Headshot" />
<h2 className="h5-heading my-2">{item.name}</h2>
<p className="h6-heading">{item.affiliations.join(`, `)}</p>
<div className="person-social-links mt-3">
{ item.links.twitter && <a href={item.links.twitter} className="twitter gray small mr-4"></a> }
{ item.links.linkedIn && <a href={item.links.linkedIn} className="linkedIn gray small mr-4"></a> }
{item.links.twitter && (
<a
href={item.links.twitter}
className="twitter gray small mr-4"
/>
)}
{item.links.linkedIn && (
<a
href={item.links.linkedIn}
className="linkedIn gray small mr-4"
/>
)}
</div>
</div>
);
});
return (
<div className="row">
{ leaders }
</div>
);
return <div className="row">{leaders}</div>;
}
}

Просмотреть файл

@ -1,9 +1,11 @@
import React from 'react';
import React from "react";
export default () => {
return <div className="loading-indicator d-inline-block">
<div className="dot"></div>
<div className="dot"></div>
<div className="dot"></div>
</div>;
return (
<div className="loading-indicator d-inline-block">
<div className="dot" />
<div className="dot" />
<div className="dot" />
</div>
);
};

Просмотреть файл

@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
export default class MemberNotice extends React.Component {
constructor(props) {
@ -52,10 +52,14 @@ export default class MemberNotice extends React.Component {
this.refs.pane2.style.width = `${wrapperWidth}px`;
// Set "start" height (which is actually just the current height, but not explicitly set)
this.refs.wrapper.style.height = this.state.isExpanded ? `${this.refs.pane2.clientHeight}px` : `${this.refs.pane1.clientHeight}px`;
this.refs.wrapper.style.height = this.state.isExpanded
? `${this.refs.pane2.clientHeight}px`
: `${this.refs.pane1.clientHeight}px`;
// Set "end" height (this triggers the transition to start)
this.refs.wrapper.style.height = this.state.isExpanded ? `${this.refs.pane1.clientHeight}px` : `${this.refs.pane2.clientHeight}px`;
this.refs.wrapper.style.height = this.state.isExpanded
? `${this.refs.pane1.clientHeight}px`
: `${this.refs.pane2.clientHeight}px`;
}
componentDidMount() {
@ -69,23 +73,52 @@ export default class MemberNotice extends React.Component {
<div className="container d-flex py-3 justify-content-between align-items-top">
<div ref="wrapper" className="wrapper align-self-center mr-3">
<div ref="pane1" className="pane">
<p className="mb-0">Welcome to our member preview. Its a work in progress and we invite your <a href="https://mzl.la/2ohCL8O">feedback</a>.</p>
<p className="mb-0">
Welcome to our member preview. Its a work in progress and we
invite your <a href="https://mzl.la/2ohCL8O">feedback</a>.
</p>
</div>
<div ref="pane2" className="pane pane-hidden">
<div className="hidden-sm-down">
<h3 className="h5-heading">This is a Barn Raising</h3>
<p>There is a movement to keep the Internet healthy taking root around the world. Mozilla is a part of this movement and wants to help it grow.</p>
<p>We're building a home for people who care about the health of the Internet, hand in hand with the community that emerged from MozFest and events around the world. Its a space for us to learn, find resources, and connect to new people and ideas.</p>
<p className="mb-0">If you are a part of this movement for Internet Health then we want your <a href="https://mzl.la/2ohCL8O">feedback</a> to help it grow. This is a barn raising.</p>
<p>
There is a movement to keep the Internet healthy taking root
around the world. Mozilla is a part of this movement and wants
to help it grow.
</p>
<p>
We're building a home for people who care about the health of
the Internet, hand in hand with the community that emerged from
MozFest and events around the world. Its a space for us to
learn, find resources, and connect to new people and ideas.
</p>
<p className="mb-0">
If you are a part of this movement for Internet Health then we
want your <a href="https://mzl.la/2ohCL8O">feedback</a> to help
it grow. This is a barn raising.
</p>
</div>
<div className="hidden-md-up">
<h3 className="h5-heading">This is a Movement</h3>
<p className="mb-0">We're building a home for people who care about the health of the Internet. This is a space for us to learn, find resources, and connect. If you're a part of the movement for Internet Health, we want your <a href="https://mzl.la/2ohCL8O">feedback</a> and help.</p>
<p className="mb-0">
We're building a home for people who care about the health of
the Internet. This is a space for us to learn, find resources,
and connect. If you're a part of the movement for Internet
Health, we want your{" "}
<a href="https://mzl.la/2ohCL8O">feedback</a> and help.
</p>
</div>
</div>
</div>
<div>
<button className={`btn btn-${this.state.isExpanded ? `expanded` : `collapsed`}`} onClick={this.toggle}>+</button>
<button
className={`btn btn-${
this.state.isExpanded ? `expanded` : `collapsed`
}`}
onClick={this.toggle}
>
+
</button>
</div>
</div>
);

Просмотреть файл

@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
export default class MultipageNavMobile extends React.Component {
constructor(props) {
@ -26,34 +26,48 @@ export default class MultipageNavMobile extends React.Component {
render() {
let activeLinkLabel;
let links = this.props.links.map((link, index) => {
let className = `multipage-link${ link.isActive ? ` active` : `` }`;
let className = `multipage-link${link.isActive ? ` active` : ``}`;
if (link.isActive) {
activeLinkLabel = <a className={`active-link-label ${className}`}>{link.label}</a>;
activeLinkLabel = (
<a className={`active-link-label ${className}`}>{link.label}</a>
);
}
if (link.isActive) {
activeLinkLabel = <a className={`active-link-label d-inline-block ${className}`}>{link.label}</a>;
activeLinkLabel = (
<a className={`active-link-label d-inline-block ${className}`}>
{link.label}
</a>
);
}
return (
<div key={`link-${index}`}>
<a href={link.href} className={className}>{link.label}</a>
<a href={link.href} className={className}>
{link.label}
</a>
</div>
);
});
links.unshift(<div key="current-active-link">
<button className="expander" onClick={this.toggle}>
<div className="d-flex justify-content-between">
<div>{activeLinkLabel}</div>
<div className="d-inline-block align-self-center control"></div>
</div>
</button>
</div>);
links.unshift(
<div key="current-active-link">
<button className="expander" onClick={this.toggle}>
<div className="d-flex justify-content-between">
<div>{activeLinkLabel}</div>
<div className="d-inline-block align-self-center control" />
</div>
</button>
</div>
);
return (
<div className={`dropdown-nav${this.state.isOpen ? ` dropdown-nav-open` : ``}`}>
<div
className={`dropdown-nav${
this.state.isOpen ? ` dropdown-nav-open` : ``
}`}
>
{links}
</div>
);

Просмотреть файл

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import React from "react";
import PropTypes from "prop-types";
import moment from "moment";
export default class News extends React.Component {
constructor(props) {
@ -16,47 +16,63 @@ export default class News extends React.Component {
// HEROKU_APP_DOMAIN is used by review apps
if (!networkSiteUrl && this.props.env.HEROKU_APP_NAME) {
networkSiteUrl = `https://${this.props.env.HEROKU_APP_NAME}.herokuapp.com`;
networkSiteUrl = `https://${
this.props.env.HEROKU_APP_NAME
}.herokuapp.com`;
}
let xhr = new XMLHttpRequest();
xhr.addEventListener(`load`, () => {
this.setState({
news: JSON.parse(xhr.response)
}, () => {
if (this.props.whenLoaded) {
this.props.whenLoaded();
this.setState(
{
news: JSON.parse(xhr.response)
},
() => {
if (this.props.whenLoaded) {
this.props.whenLoaded();
}
}
});
);
});
xhr.open(`GET`, `${networkSiteUrl}/api/news/?format=json&featured=True&page=1`);
xhr.open(
`GET`,
`${networkSiteUrl}/api/news/?format=json&featured=True&page=1`
);
xhr.send();
}
render() {
let blurb = (newsItem, hasHR=true) => {
let blurb = (newsItem, hasHR = true) => {
return (
<div key={newsItem.headline}>
<div className="mb-3 news-item">
<div className="d-flex align-items-center mb-3">
{ newsItem.glyph && <img src={newsItem.glyph} className="mr-2 glyph"/> }
{newsItem.glyph && (
<img src={newsItem.glyph} className="mr-2 glyph" />
)}
<p className="h6-heading-uppercase mb-0">{newsItem.outlet}</p>
</div>
<h3 className="h4-heading mb-2">
<a href={newsItem.link} className="newsItem headline">{newsItem.headline}</a>
<a href={newsItem.link} className="newsItem headline">
{newsItem.headline}
</a>
</h3>
{ newsItem.author && <p className="h6-heading">by {newsItem.author}</p> }
<p className="h6-heading">{moment(newsItem.date, `YYYY-MM-DD`).format(`MMMM YYYY`)}</p>
{newsItem.author && (
<p className="h6-heading">by {newsItem.author}</p>
)}
<p className="h6-heading">
{moment(newsItem.date, `YYYY-MM-DD`).format(`MMMM YYYY`)}
</p>
</div>
{ hasHR && <hr/> }
{hasHR && <hr />}
</div>
);
};
let newsForYear = (year) => {
let filteredNews = this.state.news.filter((item) => {
let newsForYear = year => {
let filteredNews = this.state.news.filter(item => {
return item.date.match(year);
});
@ -66,7 +82,9 @@ export default class News extends React.Component {
<h2 className="h2-typeaccent">{year}</h2>
</div>
<div className="col-md-8 col-lg-7">
{ filteredNews.map((item, index, array) => { return blurb(item, index < array.length - 1); }) }
{filteredNews.map((item, index, array) => {
return blurb(item, index < array.length - 1);
})}
</div>
</div>
);
@ -83,11 +101,7 @@ export default class News extends React.Component {
year++;
}
return (
<div className="container py-5">
{ newsByYear }
</div>
);
return <div className="container py-5">{newsByYear}</div>;
}
}

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import Person from './person.jsx';
import React from "react";
import Person from "./person.jsx";
export default class People extends React.Component {
constructor(props) {
@ -15,36 +15,38 @@ export default class People extends React.Component {
// HEROKU_APP_DOMAIN is used by review apps
if (!networkSiteUrl && this.props.env.HEROKU_APP_NAME) {
networkSiteUrl = `https://${this.props.env.HEROKU_APP_NAME}.herokuapp.com`;
networkSiteUrl = `https://${
this.props.env.HEROKU_APP_NAME
}.herokuapp.com`;
}
let xhr = new XMLHttpRequest();
xhr.addEventListener(`load`, () => {
this.setState({
people: JSON.parse(xhr.response)
}, () => {
if (this.props.whenLoaded) {
this.props.whenLoaded();
this.setState(
{
people: JSON.parse(xhr.response)
},
() => {
if (this.props.whenLoaded) {
this.props.whenLoaded();
}
}
});
);
});
xhr.open(`GET`, `${networkSiteUrl}/api/people/?format=json&featured=True&page=1`);
xhr.open(
`GET`,
`${networkSiteUrl}/api/people/?format=json&featured=True&page=1`
);
xhr.send();
}
render() {
let people = this.state.people.map((person, index) => {
return (
<Person key={index} metadata={person}/>
);
return <Person key={index} metadata={person} />;
});
return (
<div className="row">
{people}
</div>
);
return <div className="row">{people}</div>;
}
}

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import React from "react";
import PropTypes from "prop-types";
class Person extends React.Component {
constructor(props) {
@ -14,17 +14,23 @@ class Person extends React.Component {
}
render() {
let issues = this.props.metadata.internet_health_issues.map((issue, index) => {
return (
<span key={index} className="issue-link small d-inline-block mr-1">{issue}</span>
);
});
let issues = this.props.metadata.internet_health_issues.map(
(issue, index) => {
return (
<span key={index} className="issue-link small d-inline-block mr-1">
{issue}
</span>
);
}
);
let filteredLinks = {};
// Django returns empty strings instead of null for empty links, so let's filter empty links out:
for(let key in this.props.metadata.links) {
if (Object.prototype.hasOwnProperty.call(this.props.metadata.links, key)) {
for (let key in this.props.metadata.links) {
if (
Object.prototype.hasOwnProperty.call(this.props.metadata.links, key)
) {
if (this.props.metadata.links[key] !== `` && key !== `interview`) {
filteredLinks[key] = this.props.metadata.links[key];
}
@ -35,15 +41,23 @@ class Person extends React.Component {
let classes = `${linkKey} gray mr-4`;
return (
<a href={this.props.metadata.links[linkKey]} className={classes} key={index}></a>
<a
href={this.props.metadata.links[linkKey]}
className={classes}
key={index}
/>
);
});
let metaBlock = (
<div className="meta-block mb-2">
<div className="h5-heading mb-1">{this.props.metadata.name}</div>
<div className="meta-block-item meta-block-item-role">{this.props.metadata.role}</div>
<div className="meta-block-item meta-block-item-location">{this.props.metadata.location}</div>
<div className="meta-block-item meta-block-item-role">
{this.props.metadata.role}
</div>
<div className="meta-block-item meta-block-item-location">
{this.props.metadata.location}
</div>
</div>
);
@ -54,43 +68,66 @@ class Person extends React.Component {
<div className="col-md-4 col-12 mr-3">
<div className="row">
<div className="col">
<img src={this.props.metadata.image} className="headshot" alt="Headshot" />
<img
src={this.props.metadata.image}
className="headshot"
alt="Headshot"
/>
</div>
</div>
</div>
<div className="col">
<div className="row">
<div ref="quoteContent" className="col-12 quote-content d-flex pt-3">
<div
ref="quoteContent"
className="col-12 quote-content d-flex pt-3"
>
<div className="col">
<div className="row my-5">
<div className="person-quote quote-small">{this.props.metadata.quote}</div>
<div className="person-quote quote-small">
{this.props.metadata.quote}
</div>
</div>
<div className="row">
<div className="quote-attribution">
{metaBlock}
<div className="person-issues mb-2">{issues}</div>
<div className="h6-heading">{this.props.metadata.affiliations[0]}</div>
{this.props.metadata.partnership_logo &&
<div className="h6-heading">
{this.props.metadata.affiliations[0]}
</div>
{this.props.metadata.partnership_logo && (
<div className="my-1">
<img className="partnership_logo" src={this.props.metadata.partnership_logo} alt="Logo of partnered organization"/>
<img
className="partnership_logo"
src={this.props.metadata.partnership_logo}
alt="Logo of partnered organization"
/>
</div>
}
)}
</div>
</div>
<div className="row person-social-links mt-3 justify-content-between">
{socialLinks.length > 0 &&
<div>{socialLinks}</div>
}
{this.props.metadata.links.interview &&
<div>
<a className="cta-link" href={this.props.metadata.links.interview}>Read Interview</a>
</div>
}
{this.props.metadata.custom_link &&
<div>
<a href={this.props.metadata.custom_link.link} className="cta-link">{this.props.metadata.custom_link.text}</a>
</div>
}
{socialLinks.length > 0 && <div>{socialLinks}</div>}
{this.props.metadata.links.interview && (
<div>
<a
className="cta-link"
href={this.props.metadata.links.interview}
>
Read Interview
</a>
</div>
)}
{this.props.metadata.custom_link && (
<div>
<a
href={this.props.metadata.custom_link.link}
className="cta-link"
>
{this.props.metadata.custom_link.text}
</a>
</div>
)}
</div>
</div>
</div>
@ -106,36 +143,56 @@ class Person extends React.Component {
<div className="col-4 mr-3">
<div className="row">
<div className="col">
<img src={this.props.metadata.image} className="headshot" alt="Headshot" />
<img
src={this.props.metadata.image}
className="headshot"
alt="Headshot"
/>
</div>
</div>
</div>
<div className="col bio-content pt-3">
{metaBlock}
<div className="person-issues">{issues}</div>
<div className="person-affiliations h6-heading mt-2">{this.props.metadata.affiliations.join(`; `)}</div>
{this.props.metadata.partnership_logo &&
<div className="person-affiliations h6-heading mt-2">
{this.props.metadata.affiliations.join(`; `)}
</div>
{this.props.metadata.partnership_logo && (
<div className="row mt-3">
<div className="col">
<img className="partnership_logo" src={this.props.metadata.partnership_logo} alt="Logo of partnered organization"/>
<img
className="partnership_logo"
src={this.props.metadata.partnership_logo}
alt="Logo of partnered organization"
/>
</div>
</div>
}
)}
<div className="person-social-links mt-3">
<div className="row flex-column-reverse flex-md-row justify-content-between">
{socialLinks.length > 0 &&
<div className="col-12 col-md my-1 my-0">{socialLinks}</div>
}
{this.props.metadata.links.interview &&
<div className="col-12 col-md my-1 my-0">
<a className="cta-link" href={this.props.metadata.links.interview}>Read Interview</a>
</div>
}
{this.props.metadata.custom_link &&
<div className="col-12 col-md my-1 my-0 text-md-right">
<a href={this.props.metadata.custom_link.link} className="cta-link">{this.props.metadata.custom_link.text}</a>
</div>
}
{socialLinks.length > 0 && (
<div className="col-12 col-md my-1 my-0">{socialLinks}</div>
)}
{this.props.metadata.links.interview && (
<div className="col-12 col-md my-1 my-0">
<a
className="cta-link"
href={this.props.metadata.links.interview}
>
Read Interview
</a>
</div>
)}
{this.props.metadata.custom_link && (
<div className="col-12 col-md my-1 my-0 text-md-right">
<a
href={this.props.metadata.custom_link.link}
className="cta-link"
>
{this.props.metadata.custom_link.text}
</a>
</div>
)}
</div>
</div>
</div>
@ -155,12 +212,12 @@ Person.propTypes = {
quote: PropTypes.string,
featured: PropTypes.bool,
affiliations: PropTypes.array,
'internet_health_issues': PropTypes.array,
internet_health_issues: PropTypes.array,
image: PropTypes.string,
'partnership_logo': PropTypes.string,
partnership_logo: PropTypes.string,
link: PropTypes.object,
'custom_link': PropTypes.object,
}).isRequired,
custom_link: PropTypes.object
}).isRequired
};
export default Person;

Просмотреть файл

@ -1,25 +1,36 @@
import React from 'react';
import classNames from 'classnames';
import SALESFORCE_COUNTRY_LIST from './salesforce-country-list.js';
import React from "react";
import classNames from "classnames";
import SALESFORCE_COUNTRY_LIST from "./salesforce-country-list.js";
export default class CountrySelect extends React.Component {
render() {
let className = classNames(`form-label-group`, `country-picker`, this.props.className);
let className = classNames(
`form-label-group`,
`country-picker`,
this.props.className
);
let codes = Object.keys(SALESFORCE_COUNTRY_LIST);
let options = codes.map( code => {
return <option key={code} value={code}>{SALESFORCE_COUNTRY_LIST[code]}</option>;
let options = codes.map(code => {
return (
<option key={code} value={code}>
{SALESFORCE_COUNTRY_LIST[code]}
</option>
);
});
return (
<div className={className}>
<select className="form-control"
<select
className="form-control"
disabled={this.props.disabled}
ref={(element) => { this.element = element; }}
ref={element => {
this.element = element;
}}
onFocus={this.props.onFocus}
defaultValue={``}
>
<option value="">{this.props.label}</option>
{ options }
{options}
</select>
</div>
);

Просмотреть файл

@ -23,8 +23,8 @@ class DonationModal extends React.Component {
// CSS position property, we need to relocate the DOM
// node so that we get the full-viewport effect.
let body = document.body,
c1 = body.children[0],
n = this.fragment;
c1 = body.children[0],
n = this.fragment;
if (!c1) {
body.appendChild(n);
@ -56,12 +56,12 @@ class DonationModal extends React.Component {
getModalContent() {
if (!this.donateURL) {
let base = `https://donate.mozilla.org/?`,
query = [
`utm_source=foundation.mozilla.org`,
`utm_medium=petitionmodal`,
`utm_campaign=${this.props.slug}`,
`utm_content=${this.props.name}`
].join(`&`);
query = [
`utm_source=foundation.mozilla.org`,
`utm_medium=petitionmodal`,
`utm_campaign=${this.props.slug}`,
`utm_content=${this.props.name}`
].join(`&`);
this.donateURL = `${base}${query}`;
}

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import classNames from 'classnames';
import React from "react";
import classNames from "classnames";
export default class FloatingLabelInput extends React.Component {
render() {
@ -7,9 +7,12 @@ export default class FloatingLabelInput extends React.Component {
return (
<div className={className}>
<input className="form-control"
<input
className="form-control"
disabled={this.props.disabled}
ref={(element) => { this.element = element; }}
ref={element => {
this.element = element;
}}
id={this.props.id}
type={this.props.type}
placeholder={this.props.label}

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import classNames from 'classnames';
import React from "react";
import classNames from "classnames";
export default class FloatingLabelInput extends React.Component {
render() {
@ -7,9 +7,12 @@ export default class FloatingLabelInput extends React.Component {
return (
<div className={className}>
<textarea className="form-control"
<textarea
className="form-control"
disabled={this.props.disabled}
ref={(element) => { this.element = element; }}
ref={element => {
this.element = element;
}}
id={this.props.id}
type={this.props.type}
placeholder={this.props.label}

Просмотреть файл

@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
/**
* In order to move fast and not break things, we're doing petition localisation
@ -8,9 +8,8 @@ import React from 'react';
* a link element.
*/
export default {
// English
"en": {
en: {
"First name": `First name`,
"Please enter your given name(s)": `Please enter your given name(s)`,
"Last name": `Last name`,
@ -21,17 +20,22 @@ export default {
"Please enter your country": `Please enter your country`,
"Postal code": `Postal code`,
"Please enter your postal code": `Please enter your postal code`,
"Comment": `Comment`,
Comment: `Comment`,
"Please include a comment": `Please include a comment`,
"Something went wrong. Please check your email address and try again": `Something went wrong. Please check your email address and try again`,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": <span>I'm okay with Mozilla handling my info as explained in this <a href="https://www.mozilla.org/privacy/websites/">Privacy Notice</a></span>,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": (
<span>
I'm okay with Mozilla handling my info as explained in this{" "}
<a href="https://www.mozilla.org/privacy/websites/">Privacy Notice</a>
</span>
),
"Please check this box if you want to proceed": `Please check this box if you want to proceed`,
"Yes, I want to receive email updates about Mozilla's campaigns.": `Yes, I want to receive email updates about Mozillas campaigns.`,
"Add my name": `Add my name`
},
// German
"de": {
de: {
"First name": `Vorname`,
"Please enter your given name(s)": `Bitte geben Sie Ihre(n) Vornamen ein`,
"Last name": `Nachname`,
@ -42,17 +46,26 @@ export default {
"Please enter your country": `Bitte geben Sie Ihr Land ein`,
"Postal code": `Postleitzahl`,
"Please enter your postal code": `Bitte geben Sie Ihre Postleitzahl ein`,
"Comment": `Kommentar`,
Comment: `Kommentar`,
"Please include a comment": `Bitte geben Sie einen Kommentar ein`,
"Something went wrong. Please check your email address and try again": `Etwas ist schiefgegangen. Bitte überprüfen Sie Ihre E-Mail-Adresse und versuchen Sie es erneut`,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": <span>Ich bin einverstanden mit dem Umgang Mozillas mit meinen Informationen wie im <a href="https://www.mozilla.org/privacy/websites/">Datenschutzhinweis</a> beschrieben</span>,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": (
<span>
Ich bin einverstanden mit dem Umgang Mozillas mit meinen Informationen
wie im{" "}
<a href="https://www.mozilla.org/privacy/websites/">
Datenschutzhinweis
</a>{" "}
beschrieben
</span>
),
"Please check this box if you want to proceed": `Aktivieren Sie bitte dieses Kontrollkästchen, um fortzufahren`,
"Yes, I want to receive email updates about Mozilla's campaigns.": `Ja, ich möchte per E-Mail Neuigkeiten über Mozillas Kampagnen erfahren.`,
"Add my name": `Meinen Namen hinzufügen`
},
// Spanish
"es": {
es: {
"First name": `Nombre`,
"Please enter your given name(s)": `Introduce tu nombre`,
"Last name": `Apellido`,
@ -63,17 +76,25 @@ export default {
"Please enter your country": `Introduce tu país`,
"Postal code": `Código postal`,
"Please enter your postal code": `Introduce tu código postal`,
"Comment": `Comentario`,
Comment: `Comentario`,
"Please include a comment": `Por favor, escribe un comentario`,
"Something went wrong. Please check your email address and try again": `Algo fue mal. Comprueba tu dirección de correo y vuelve a intentarlo`,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": <span>Estoy de acuerdo con la gestión de mi información, tal y como se explica en este <a href="https://www.mozilla.org/privacy/websites/">Aviso de privacidad</a></span>,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": (
<span>
Estoy de acuerdo con la gestión de mi información, tal y como se explica
en este{" "}
<a href="https://www.mozilla.org/privacy/websites/">
Aviso de privacidad
</a>
</span>
),
"Please check this box if you want to proceed": `Marca esta casilla si quieres continuar`,
"Yes, I want to receive email updates about Mozilla's campaigns.": `Sí, quiero recibir actualizaciones sobre las campañas de Mozilla.`,
"Add my name": `Añadir mi nombre`
},
// French
"fr": {
fr: {
"First name": `Prénom`,
"Please enter your given name(s)": `Veuillez saisir votre prénom`,
"Last name": `Nom`,
@ -84,17 +105,24 @@ export default {
"Please enter your country": `Veuillez sélectionner votre pays`,
"Postal code": `Code postal`,
"Please enter your postal code": `Veuillez saisir votre code postal`,
"Comment": `Commentaire`,
Comment: `Commentaire`,
"Please include a comment": `Veuillez ajouter un commentaire`,
"Something went wrong. Please check your email address and try again": `Une erreur sest produite. Veuillez vérifier votre adresse électronique et réessayer`,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": <span>Jaccepte que Mozilla utilise mes informations conformément à <a href="https://www.mozilla.org/privacy/websites/">cette politique de confidentialité</a></span>,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": (
<span>
Jaccepte que Mozilla utilise mes informations conformément à{" "}
<a href="https://www.mozilla.org/privacy/websites/">
cette politique de confidentialité
</a>
</span>
),
"Please check this box if you want to proceed": `Veuillez cocher cette case si vous désirez poursuivre`,
"Yes, I want to receive email updates about Mozilla's campaigns.": `Jaccepte de recevoir des informations par courriel au sujet des campagnes de Mozilla.`,
"Add my name": `Ajouter mon nom`
},
// Polish
"pl": {
pl: {
"First name": `Imię`,
"Please enter your given name(s)": `Proszę podać imię (lub imiona)`,
"Last name": `Nazwisko`,
@ -105,17 +133,24 @@ export default {
"Please enter your country": `Proszę podać kraj`,
"Postal code": `Kod pocztowy`,
"Please enter your postal code": `Proszę podać kod pocztowy`,
"Comment": `Komentarz`,
Comment: `Komentarz`,
"Please include a comment": `Proszę dołączyć komentarz`,
"Something went wrong. Please check your email address and try again": `Coś się nie powiodło. Proszę sprawdzić poprawność adresu e-mail i spróbować ponownie`,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": <span>Pozwalam Mozilli wykorzystywać te informacje w sposób opisany w <a href="https://www.mozilla.org/privacy/websites/">zasadach ochrony prywatności</a></span>,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": (
<span>
Pozwalam Mozilli wykorzystywać te informacje w sposób opisany w{" "}
<a href="https://www.mozilla.org/privacy/websites/">
zasadach ochrony prywatności
</a>
</span>
),
"Please check this box if you want to proceed": `Proszę zaznaczyć to pole, aby kontynuować`,
"Yes, I want to receive email updates about Mozilla's campaigns.": `Tak, chcę otrzymywać wiadomości o kampaniach Mozilli.`,
"Add my name": `Podpisz`
},
// Portuguese
"pt": {
pt: {
"First name": `Nome`,
"Please enter your given name(s)": `Insira seu nome`,
"Last name": `Sobrenome`,
@ -126,13 +161,20 @@ export default {
"Please enter your country": `Insira seu país`,
"Postal code": `CEP`,
"Please enter your postal code": `Insira seu CEP`,
"Comment": `Comentário`,
Comment: `Comentário`,
"Please include a comment": `Inclua um comentário`,
"Something went wrong. Please check your email address and try again": `Algo deu errado. Verifique seu endereço de e-mail e tente novamente`,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": <span>Concordo com a Mozilla lidar com minhas informações, como explicado neste <a href="https://www.mozilla.org/privacy/websites/">Aviso de Privacidade</a></span>,
"I'm okay with Mozilla handling my info as explained in this Privacy Notice": (
<span>
Concordo com a Mozilla lidar com minhas informações, como explicado
neste{" "}
<a href="https://www.mozilla.org/privacy/websites/">
Aviso de Privacidade
</a>
</span>
),
"Please check this box if you want to proceed": `Marque esta opção se deseja continuar`,
"Yes, I want to receive email updates about Mozilla's campaigns.": `Quero receber atualizações por e-mail sobre campanhas da Mozilla.`,
"Add my name": `Adicionar meu nome`
}
};

Просмотреть файл

@ -1,4 +1,4 @@
import data from './locale-strings.jsx';
import data from "./locale-strings.jsx";
const DEFAULT_LOCALE = `en`;
let currentLocale = false;
@ -17,7 +17,7 @@ function getCurrentLocale() {
if (!currentLocale) {
var pathsegments = window.location.pathname.split(`/`).filter(v => v);
currentLocale = (pathsegments.length > 0) ? pathsegments[0] : DEFAULT_LOCALE;
currentLocale = pathsegments.length > 0 ? pathsegments[0] : DEFAULT_LOCALE;
}
return currentLocale || DEFAULT_LOCALE;

Просмотреть файл

@ -1,12 +1,12 @@
import React from 'react';
import ReactGA from 'react-ga';
import classNames from 'classnames';
import DonationModal from './donation-modal.jsx';
import FloatingLabelInput from './floating-label-input.jsx';
import FloatingLabelTextarea from './floating-label-textarea.jsx';
import CountrySelect from './country-select.jsx';
import basketSignup from '../../basket-signup.js';
import get from './locales';
import React from "react";
import ReactGA from "react-ga";
import classNames from "classnames";
import DonationModal from "./donation-modal.jsx";
import FloatingLabelInput from "./floating-label-input.jsx";
import FloatingLabelTextarea from "./floating-label-textarea.jsx";
import CountrySelect from "./country-select.jsx";
import basketSignup from "../../basket-signup.js";
import get from "./locales";
export default class Petition extends React.Component {
constructor(props) {
@ -54,7 +54,7 @@ export default class Petition extends React.Component {
basketSuccess: false,
basketFailed: false,
userTriedSubmitting: false,
showDonationModal: false,
showDonationModal: false
};
}
@ -69,19 +69,31 @@ export default class Petition extends React.Component {
// helper function for auto-generating checkboxes off of the passed props.
generateCheckboxes(disabled) {
return [`checkbox1`, `checkbox2`].map(name => {
let label = this[name];
return [`checkbox1`, `checkbox2`]
.map(name => {
let label = this[name];
if (!label) { return null; }
return (
<div key={name}>
<label className="form-check-label">
<input className="form-check-input" disabled={disabled} type="checkbox" ref={name} />
<span className="h6-heading form-text" dangerouslySetInnerHTML={{__html: label}}/>
</label>
</div>
);
}).filter(v => v);
if (!label) {
return null;
}
return (
<div key={name}>
<label className="form-check-label">
<input
className="form-check-input"
disabled={disabled}
type="checkbox"
ref={name}
/>
<span
className="h6-heading form-text"
dangerouslySetInnerHTML={{ __html: label }}
/>
</label>
</div>
);
})
.filter(v => v);
}
// state update function
@ -90,7 +102,7 @@ export default class Petition extends React.Component {
apiSuccess: true
};
if (this.props.modals && this.props.modals.length>0) {
if (this.props.modals && this.props.modals.length > 0) {
update.showDonationModal = true;
}
@ -99,7 +111,7 @@ export default class Petition extends React.Component {
// state update function
apiSubmissionFailure(e) {
if(e && e instanceof Error) {
if (e && e instanceof Error) {
this.setState({ apiFailed: true });
}
}
@ -111,19 +123,24 @@ export default class Petition extends React.Component {
// state update function
basketSubmissionFailure(e) {
if(e && e instanceof Error) {
if (e && e instanceof Error) {
this.setState({ basketFailed: true });
}
}
// state check function
apiIsDone() {
return this.state.apiSubmitted && (this.state.apiSuccess || this.state.apiFailed);
return (
this.state.apiSubmitted && (this.state.apiSuccess || this.state.apiFailed)
);
}
// state check function
basketIsDone() {
return this.state.basketSubmitted && (this.state.basketSuccess || this.state.basketFailed);
return (
this.state.basketSubmitted &&
(this.state.basketSuccess || this.state.basketFailed)
);
}
// state check function
@ -171,47 +188,47 @@ export default class Petition extends React.Component {
// These should not be possible due to the fact that we validate
// their content prior to submission. TODO: remove these rejections?
if(!givenNames || !surname) {
if (!givenNames || !surname) {
return reject(new Error(`missing name/surname`));
}
if(this.props.requiresCountryCode === `True` && !country) {
if (this.props.requiresCountryCode === `True` && !country) {
return reject(new Error(`missing country`));
}
if(this.props.requiresPostalCode === `True` && !postalCode) {
if (this.props.requiresPostalCode === `True` && !postalCode) {
return reject(new Error(`missing postal code`));
}
if(this.props.commentRequirements === `required` && !comment) {
if (this.props.commentRequirements === `required` && !comment) {
return reject(new Error(`missing required comment`));
}
if (this.refs.newsletterSignup) {
newsletterSignup = !!(this.refs.newsletterSignup.checked);
newsletterSignup = !!this.refs.newsletterSignup.checked;
}
let payload = {
givenNames,
surname,
email: this.email.element.value,
checkbox1: this.props.checkbox1 ? !!(this.refs.checkbox1.checked) : null,
checkbox2: this.props.checkbox2 ? !!(this.refs.checkbox2.checked) : null,
checkbox1: this.props.checkbox1 ? !!this.refs.checkbox1.checked : null,
checkbox2: this.props.checkbox2 ? !!this.refs.checkbox2.checked : null,
newsletterSignup,
country,
postalCode,
comment,
source: window.location.toString(),
source: window.location.toString()
};
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState !== XMLHttpRequest.DONE) {
if (xhr.readyState !== XMLHttpRequest.DONE) {
return;
}
if(xhr.status !== 201) {
if (xhr.status !== 201) {
reject(new Error(xhr.responseText));
}
@ -220,7 +237,7 @@ export default class Petition extends React.Component {
xhr.open(`POST`, this.props.apiUrl, true);
xhr.setRequestHeader(`Content-Type`, `application/json`);
xhr.setRequestHeader(`X-Requested-With`,`XMLHttpRequest`);
xhr.setRequestHeader(`X-Requested-With`, `XMLHttpRequest`);
xhr.setRequestHeader(`X-CSRFToken`, this.props.csrfToken);
xhr.timeout = 5000;
xhr.ontimeout = () => reject(new Error(`xhr timed out`));
@ -247,13 +264,17 @@ export default class Petition extends React.Component {
}
return new Promise((resolve, reject) => {
if(this.email.element.value && this.refs.privacy.checked) {
if(this.refs.newsletterSignup.checked) {
basketSignup({
email: this.email.element.value,
privacy: this.refs.privacy.checked,
newsletter: this.props.newsletter
}, resolve, reject);
if (this.email.element.value && this.refs.privacy.checked) {
if (this.refs.newsletterSignup.checked) {
basketSignup(
{
email: this.email.element.value,
privacy: this.refs.privacy.checked,
newsletter: this.props.newsletter
},
resolve,
reject
);
} else {
resolve();
}
@ -291,7 +312,6 @@ export default class Petition extends React.Component {
postalCode = !!this.postalCode.element.value;
}
if (this.props.commentRequirements === `required`) {
comment = !!this.comment.element.value;
}
@ -374,12 +394,14 @@ export default class Petition extends React.Component {
let success = signingIsDone && this.state.apiSuccess; // we don't actually care about basket succeeding, see (5)
let signupState = classNames({
'row': true,
'sign-success': success,
'sign-failure': signingIsDone && (this.state.apiFailed || this.state.basketFailed)
row: true,
"sign-success": success,
"sign-failure":
signingIsDone && (this.state.apiFailed || this.state.basketFailed)
});
let unrecoverableError = this.state.apiSubmitted && !this.state.apiSuccess && this.state.apiFailed;
let unrecoverableError =
this.state.apiSubmitted && !this.state.apiSuccess && this.state.apiFailed;
let petitionContent, formContent;
@ -389,18 +411,21 @@ export default class Petition extends React.Component {
petitionContent = this.renderUnrecoverableError();
} else {
petitionContent = this.renderStandardHeading();
formContent = !unrecoverableError && !this.state.basketSuccess && this.renderSubmissionForm();
formContent =
!unrecoverableError &&
!this.state.basketSuccess &&
this.renderSubmissionForm();
}
return (
<div className={signupState}>
<div className="col">
<div className="row">
<div className="col-12 petition-content">{ petitionContent }</div>
{ formContent }
<div className="col-12 petition-content">{petitionContent}</div>
{formContent}
</div>
</div>
{ this.state.showDonationModal ? this.renderDonationModal() : null }
{this.state.showDonationModal ? this.renderDonationModal() : null}
</div>
);
}
@ -414,23 +439,25 @@ export default class Petition extends React.Component {
// This is where can do client-side A/B testing
let modals = this.modals;
if (modals.length===0) {
if (modals.length === 0) {
return null;
}
let modal = modals[0];
return <DonationModal
slug={this.props.ctaSlug}
name={modal.name}
heading={modal.header}
bodyText={modal.body}
donateText={modal.donate_text}
shareText={modal.dismiss_text}
onDonate={() => this.userElectedToDonate()}
onShare={() => this.userElectedToShare()}
onClose={() => this.setState({ showDonationModal: false })}
/>;
return (
<DonationModal
slug={this.props.ctaSlug}
name={modal.name}
heading={modal.header}
bodyText={modal.body}
donateText={modal.donate_text}
shareText={modal.dismiss_text}
onDonate={() => this.userElectedToDonate()}
onShare={() => this.userElectedToShare()}
onClose={() => this.setState({ showDonationModal: false })}
/>
);
}
/**
@ -441,7 +468,9 @@ export default class Petition extends React.Component {
return (
<div>
<p>{this.props.thankYou}</p>
<a href={this.props.shareLink} className="btn btn-info">{this.props.shareText}</a>
<a href={this.props.shareLink} className="btn btn-info">
{this.props.shareText}
</a>
</div>
);
} else {
@ -449,21 +478,51 @@ export default class Petition extends React.Component {
<div>
<p>{this.props.thankYou}</p>
<div className="share-btn-container">
<button className="share-progress-btn share-progress-fb" onClick={e => this.fbButtonClicked(e)}>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 264 512">
<path fill="currentColor" d="M76.7 512V283H0v-91h76.7v-71.7C76.7 42.4 124.3 0 193.8 0c33.3 0 61.9 2.5 70.2 3.6V85h-48.2c-37.8 0-45.1 18-45.1 44.3V192H256l-11.7 91h-73.6v229"></path>
<button
className="share-progress-btn share-progress-fb"
onClick={e => this.fbButtonClicked(e)}
>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 264 512"
>
<path
fill="currentColor"
d="M76.7 512V283H0v-91h76.7v-71.7C76.7 42.4 124.3 0 193.8 0c33.3 0 61.9 2.5 70.2 3.6V85h-48.2c-37.8 0-45.1 18-45.1 44.3V192H256l-11.7 91h-73.6v229"
/>
</svg>
SHARE
</button>
<button className="share-progress-btn share-progress-tw" onClick={e => this.twButtonClicked(e)}>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path>
<button
className="share-progress-btn share-progress-tw"
onClick={e => this.twButtonClicked(e)}
>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
/>
</svg>
TWEET
</button>
<button className="share-progress-btn share-progress-em" onClick={e => this.emButtonClicked(e)}>
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M502.3 190.8c3.9-3.1 9.7-.2 9.7 4.7V400c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V195.6c0-5 5.7-7.8 9.7-4.7 22.4 17.4 52.1 39.5 154.1 113.6 21.1 15.4 56.7 47.8 92.2 47.6 35.7.3 72-32.8 92.3-47.6 102-74.1 131.6-96.3 154-113.7zM256 320c23.2.4 56.6-29.2 73.4-41.4 132.7-96.3 142.8-104.7 173.4-128.7 5.8-4.5 9.2-11.5 9.2-18.9v-19c0-26.5-21.5-48-48-48H48C21.5 64 0 85.5 0 112v19c0 7.4 3.4 14.3 9.2 18.9 30.6 23.9 40.7 32.4 173.4 128.7 16.8 12.2 50.2 41.8 73.4 41.4z"></path>
<button
className="share-progress-btn share-progress-em"
onClick={e => this.emButtonClicked(e)}
>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M502.3 190.8c3.9-3.1 9.7-.2 9.7 4.7V400c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V195.6c0-5 5.7-7.8 9.7-4.7 22.4 17.4 52.1 39.5 154.1 113.6 21.1 15.4 56.7 47.8 92.2 47.6 35.7.3 72-32.8 92.3-47.6 102-74.1 131.6-96.3 154-113.7zM256 320c23.2.4 56.6-29.2 73.4-41.4 132.7-96.3 142.8-104.7 173.4-128.7 5.8-4.5 9.2-11.5 9.2-18.9v-19c0-26.5-21.5-48-48-48H48C21.5 64 0 85.5 0 112v19c0 7.4 3.4 14.3 9.2 18.9 30.6 23.9 40.7 32.4 173.4 128.7 16.8 12.2 50.2 41.8 73.4 41.4z"
/>
</svg>
EMAIL
</button>
@ -479,7 +538,10 @@ export default class Petition extends React.Component {
renderUnrecoverableError() {
return (
<div>
<p>Something went wrong while trying to sign the petition. Please try again later.</p>
<p>
Something went wrong while trying to sign the petition. Please try
again later.
</p>
</div>
);
}
@ -496,14 +558,13 @@ export default class Petition extends React.Component {
);
}
/**
* @returns {jsx} Petition cta description if one exists.
*/
renderCtaDescription() {
if (this.props.ctaDescription) {
return (
<p dangerouslySetInnerHTML={{__html:this.props.ctaDescription}}></p>
<p dangerouslySetInnerHTML={{ __html: this.props.ctaDescription }} />
);
}
return null;
@ -517,35 +578,52 @@ export default class Petition extends React.Component {
* @returns {jsx} the petition form
*/
renderSubmissionForm() {
let disableFields = (this.state.userTriedSubmitting && this.state.apiSubmitted) ? `disabled` : null;
let disableFields =
this.state.userTriedSubmitting && this.state.apiSubmitted
? `disabled`
: null;
let givenGroupClass = classNames({
'has-danger': this.state.userTriedSubmitting && !this.givenNames.element.value
"has-danger":
this.state.userTriedSubmitting && !this.givenNames.element.value
});
let surGroupClass = classNames({
'has-danger': this.state.userTriedSubmitting && !this.surname.element.value
"has-danger":
this.state.userTriedSubmitting && !this.surname.element.value
});
let emailGroupClass = classNames({
'has-danger': this.state.userTriedSubmitting && (!this.email.element.value || !this.validatesAsEmail(this.email.element.value))
"has-danger":
this.state.userTriedSubmitting &&
(!this.email.element.value ||
!this.validatesAsEmail(this.email.element.value))
});
let countryGroupClass = classNames({
'has-danger': this.props.requiresCountryCode === `True` && this.state.userTriedSubmitting && !this.country.element.value
"has-danger":
this.props.requiresCountryCode === `True` &&
this.state.userTriedSubmitting &&
!this.country.element.value
});
let postalCodeGroupClass = classNames({
'has-danger': this.props.requiresPostalCode === `True` && this.state.userTriedSubmitting && !this.postalCode.element.value
"has-danger":
this.props.requiresPostalCode === `True` &&
this.state.userTriedSubmitting &&
!this.postalCode.element.value
});
let commentGroupClass = classNames({
'has-danger': this.props.commentRequirements === `required` && this.state.userTriedSubmitting && !this.comment.element.value
"has-danger":
this.props.commentRequirements === `required` &&
this.state.userTriedSubmitting &&
!this.comment.element.value
});
let privacyClass = classNames(`my-3`, {
'form-check': true,
'has-danger': this.state.userTriedSubmitting && !this.refs.privacy.checked
"form-check": true,
"has-danger": this.state.userTriedSubmitting && !this.refs.privacy.checked
});
let checkboxes = this.generateCheckboxes(disableFields);
@ -557,97 +635,188 @@ export default class Petition extends React.Component {
<div className={givenGroupClass}>
<FloatingLabelInput
className="mb-1 w-100"
ref={(element) => { this.givenNames = element; }} id="givenNames"
type="text" label={ get(`First name`) }
ref={element => {
this.givenNames = element;
}}
id="givenNames"
type="text"
label={get(`First name`)}
disabled={disableFields}
onFocus={this.onInputFocus}
/>
{this.state.userTriedSubmitting && !this.givenNames.element.value && <small className="form-check form-control-feedback">{ get(`Please enter your given name(s)`) }</small>}
{this.state.userTriedSubmitting &&
!this.givenNames.element.value && (
<small className="form-check form-control-feedback">
{get(`Please enter your given name(s)`)}
</small>
)}
</div>
<div className={surGroupClass}>
<FloatingLabelInput
className="mb-1 w-100"
ref={(element) => { this.surname = element; }} id="surname"
type="text" label={ get(`Last name`) }
ref={element => {
this.surname = element;
}}
id="surname"
type="text"
label={get(`Last name`)}
disabled={disableFields}
onFocus={this.onInputFocus}
/>
{this.state.userTriedSubmitting && !this.surname.element.value && <small className="form-check form-control-feedback">{ get(`Please enter your surname`) }</small>}
{this.state.userTriedSubmitting &&
!this.surname.element.value && (
<small className="form-check form-control-feedback">
{get(`Please enter your surname`)}
</small>
)}
</div>
<div className={emailGroupClass}>
<FloatingLabelInput
className="mb-1 w-100"
ref={(element) => { this.email = element; }} id="emailInput"
type="email" label={ get(`Email address`) }
ref={element => {
this.email = element;
}}
id="emailInput"
type="email"
label={get(`Email address`)}
disabled={disableFields}
onFocus={this.onInputFocus}
/>
{this.state.userTriedSubmitting && (!this.email.element.value || !this.validatesAsEmail(this.email.element.value)) && <small className="form-check form-control-feedback">{ get(`Please enter your email`) }</small>}
{this.state.userTriedSubmitting &&
(!this.email.element.value ||
!this.validatesAsEmail(this.email.element.value)) && (
<small className="form-check form-control-feedback">
{get(`Please enter your email`)}
</small>
)}
</div>
{ this.legacy === `True` ? null :
{this.legacy === `True` ? null : (
<div className={countryGroupClass}>
<CountrySelect
className="mb-1 w-100"
ref={(element) => { this.country = element; }}
label={ get(`Your country`) }
ref={element => {
this.country = element;
}}
label={get(`Your country`)}
disabled={disableFields}
onFocus={this.onInputFocus}
/>
{ this.props.requiresCountryCode === `True` && this.state.userTriedSubmitting && !this.country.element.value && <small className="form-check form-control-feedback">{ get(`Please enter your country`) }</small>}
{this.props.requiresCountryCode === `True` &&
this.state.userTriedSubmitting &&
!this.country.element.value && (
<small className="form-check form-control-feedback">
{get(`Please enter your country`)}
</small>
)}
</div>
}
)}
{ this.props.requiresPostalCode === `False` ? null :
{this.props.requiresPostalCode === `False` ? null : (
<div className={postalCodeGroupClass}>
<FloatingLabelInput
className="mb-1 w-100"
ref={(element) => { this.postalCode = element; }} id="postalCodeInput"
type="text" label={ get(`Postal code`) }
ref={element => {
this.postalCode = element;
}}
id="postalCodeInput"
type="text"
label={get(`Postal code`)}
disabled={disableFields}
onFocus={this.onInputFocus}
/>
{this.state.userTriedSubmitting && !this.postalCode.element.value && <small className="form-check form-control-feedback">{ get(`Please enter your postal code`) }</small>}
{this.state.userTriedSubmitting &&
!this.postalCode.element.value && (
<small className="form-check form-control-feedback">
{get(`Please enter your postal code`)}
</small>
)}
</div>
}
)}
{ this.props.commentRequirements === `none` ? null :
{this.props.commentRequirements === `none` ? null : (
<div className={commentGroupClass}>
<FloatingLabelTextarea
className="mb-1 w-100"
ref={(element) => { this.comment = element; }} id="commentInput"
type="text" label="Comment"
ref={element => {
this.comment = element;
}}
id="commentInput"
type="text"
label="Comment"
disabled={disableFields}
onFocus={this.onInputFocus}
/>
{this.props.commentRequirements === `required` && this.state.userTriedSubmitting && !this.comment.element.value && <small className="form-check form-control-feedback">Please include a comment</small>}
{this.props.commentRequirements === `required` &&
this.state.userTriedSubmitting &&
!this.comment.element.value && (
<small className="form-check form-control-feedback">
Please include a comment
</small>
)}
</div>
}
)}
</div>
{this.state.basketFailed && <small className="form-check form-control-feedback">{ get(`Something went wrong. Please check your email address and try again`) }</small>}
{this.state.basketFailed && (
<small className="form-check form-control-feedback">
{get(
`Something went wrong. Please check your email address and try again`
)}
</small>
)}
<div className={privacyClass}>
<div className="my-2">
<label className="form-check-label">
<input disabled={disableFields} type="checkbox" className="form-check-input" id="PrivacyCheckbox" ref="privacy" />
<span className="h6-heading form-text">{ get(`I'm okay with Mozilla handling my info as explained in this Privacy Notice`) }</span>
{this.state.userTriedSubmitting && !this.refs.privacy.checked && <small className="has-danger">{ get(`Please check this box if you want to proceed`) }</small>}
<input
disabled={disableFields}
type="checkbox"
className="form-check-input"
id="PrivacyCheckbox"
ref="privacy"
/>
<span className="h6-heading form-text">
{get(
`I'm okay with Mozilla handling my info as explained in this Privacy Notice`
)}
</span>
{this.state.userTriedSubmitting &&
!this.refs.privacy.checked && (
<small className="has-danger">
{get(`Please check this box if you want to proceed`)}
</small>
)}
</label>
</div>
{ this.props.subscribed ? null :
{this.props.subscribed ? null : (
<div className="my-2">
<label className="form-check-label">
<input disabled={disableFields} type="checkbox" className="form-check-input" id="NewsletterSignup" ref="newsletterSignup" />
<span className="h6-heading form-text">{ get(`Yes, I want to receive email updates about Mozilla's campaigns.`) }</span>
<input
disabled={disableFields}
type="checkbox"
className="form-check-input"
id="NewsletterSignup"
ref="newsletterSignup"
/>
<span className="h6-heading form-text">
{get(
`Yes, I want to receive email updates about Mozilla's campaigns.`
)}
</span>
</label>
</div>
}
{ checkboxes.length > 0 ? (<div className="my-2">{checkboxes}</div>) : null }
)}
{checkboxes.length > 0 ? (
<div className="my-2">{checkboxes}</div>
) : null}
<div className="mt-3">
<button disabled={disableFields} className="col-12 btn btn-normal petition-btn">{ get(`Add my name`) }</button>
<button
disabled={disableFields}
className="col-12 btn btn-normal petition-btn"
>
{get(`Add my name`)}
</button>
</div>
</div>
</form>

Просмотреть файл

@ -1,254 +1,254 @@
const SALESFORCE_COUNTRY_LIST = {
"AF": `Afghanistan`,
"AX": `Aland Islands`,
"AL": `Albania`,
"DZ": `Algeria`,
"AS": `American Samoa`,
"AD": `Andorra`,
"AO": `Angola`,
"AI": `Anguilla`,
"AQ": `Antarctica`,
"AG": `Antigua and Barbuda`,
"AR": `Argentina`,
"AM": `Armenia`,
"AW": `Aruba`,
"AU": `Australia`,
"AT": `Austria`,
"AZ": `Azerbaijan`,
"BS": `Bahamas`,
"BH": `Bahrain`,
"BD": `Bangladesh`,
"BB": `Barbados`,
"BY": `Belarus`,
"BE": `Belgium`,
"BZ": `Belize`,
"BJ": `Benin`,
"BM": `Bermuda`,
"BT": `Bhutan`,
"BO": `Bolivia, Plurinational State of`,
"BQ": `Bonaire, Sint Eustatius and Saba`,
"BA": `Bosnia and Herzegovina`,
"BW": `Botswana`,
"BV": `Bouvet Island`,
"BR": `Brazil`,
"IO": `British Indian Ocean Territory`,
"BN": `Brunei Darussalam`,
"BG": `Bulgaria`,
"BF": `Burkina Faso`,
"BI": `Burundi`,
"KH": `Cambodia`,
"CM": `Cameroon`,
"CA": `Canada`,
"CV": `Cape Verde`,
"KY": `Cayman Islands`,
"CF": `Central African Republic`,
"TD": `Chad`,
"CL": `Chile`,
"CN": `China`,
"CX": `Christmas Island`,
"CC": `Cocos (Keeling) Islands`,
"CO": `Colombia`,
"KM": `Comoros`,
"CG": `Congo`,
"CD": `Congo, the Democratic Republic of the`,
"CK": `Cook Islands`,
"CR": `Costa Rica`,
"CI": `Cote d'Ivoire`,
"HR": `Croatia`,
"CU": `Cuba`,
"CW": `Curaçao`,
"CY": `Cyprus`,
"CZ": `Czech Republic`,
"DK": `Denmark`,
"DJ": `Djibouti`,
"DM": `Dominica`,
"DO": `Dominican Republic`,
"EC": `Ecuador`,
"EG": `Egypt`,
"SV": `El Salvador`,
"GQ": `Equatorial Guinea`,
"ER": `Eritrea`,
"EE": `Estonia`,
"ET": `Ethiopia`,
"FK": `Falkland Islands (Malvinas)`,
"FO": `Faroe Islands`,
"FJ": `Fiji`,
"FI": `Finland`,
"FR": `France`,
"GF": `French Guiana`,
"PF": `French Polynesia`,
"TF": `French Southern Territories`,
"GA": `Gabon`,
"GM": `Gambia`,
"GE": `Georgia`,
"DE": `Germany`,
"GH": `Ghana`,
"GI": `Gibraltar`,
"GR": `Greece`,
"GL": `Greenland`,
"GD": `Grenada`,
"GP": `Guadeloupe`,
"GU": `Guam`,
"GT": `Guatemala`,
"GG": `Guernsey`,
"GN": `Guinea`,
"GW": `Guinea-Bissau`,
"GY": `Guyana`,
"HT": `Haiti`,
"HM": `Heard Island and McDonald Islands`,
"VA": `Holy See (Vatican City State)`,
"HN": `Honduras`,
"HK": `Hong Kong`,
"HU": `Hungary`,
"IS": `Iceland`,
"IN": `India`,
"ID": `Indonesia`,
"IR": `Iran, Islamic Republic of`,
"IQ": `Iraq`,
"IE": `Ireland`,
"IM": `Isle of Man`,
"IL": `Israel`,
"IT": `Italy`,
"JM": `Jamaica`,
"JP": `Japan`,
"JE": `Jersey`,
"JO": `Jordan`,
"KZ": `Kazakhstan`,
"KE": `Kenya`,
"KI": `Kiribati`,
"KP": `Korea, Democratic People's Republic of`,
"KR": `Korea, Republic of`,
"KW": `Kuwait`,
"KG": `Kyrgyzstan`,
"LA": `Lao People's Democratic Republic`,
"LV": `Latvia`,
"LB": `Lebanon`,
"LS": `Lesotho`,
"LR": `Liberia`,
"LY": `Libya`,
"LI": `Liechtenstein`,
"LT": `Lithuania`,
"LU": `Luxembourg`,
"MO": `Macao`,
"MK": `Macedonia, the former Yugoslav Republic of`,
"MG": `Madagascar`,
"MW": `Malawi`,
"MY": `Malaysia`,
"MV": `Maldives`,
"ML": `Mali`,
"MT": `Malta`,
"MH": `Marshall Islands`,
"MQ": `Martinique`,
"MR": `Mauritania`,
"MU": `Mauritius`,
"YT": `Mayotte`,
"MX": `Mexico`,
"FM": `Micronesia`,
"MD": `Moldova, Republic of`,
"MC": `Monaco`,
"MN": `Mongolia`,
"ME": `Montenegro`,
"MS": `Montserrat`,
"MA": `Morocco`,
"MZ": `Mozambique`,
"MM": `Myanmar`,
"NA": `Namibia`,
"NR": `Nauru`,
"NP": `Nepal`,
"NL": `Netherlands`,
"AN": `Netherlands Antilles`,
"NC": `New Caledonia`,
"NZ": `New Zealand`,
"NI": `Nicaragua`,
"NE": `Niger`,
"NG": `Nigeria`,
"NU": `Niue`,
"NF": `Norfolk Island`,
"MP": `Northern Mariana Islands`,
"NO": `Norway`,
"OM": `Oman`,
"PK": `Pakistan`,
"PW": `Palau`,
"PS": `Palestine`,
"PA": `Panama`,
"PG": `Papua New Guinea`,
"PY": `Paraguay`,
"PE": `Peru`,
"PH": `Philippines`,
"PN": `Pitcairn`,
"PL": `Poland`,
"PT": `Portugal`,
"PR": `Puerto Rico`,
"QA": `Qatar`,
"RE": `Reunion`,
"RO": `Romania`,
"RU": `Russian Federation`,
"RW": `Rwanda`,
"BL": `Saint Barthélemy`,
"SH": `Saint Helena, Ascension and Tristan da Cunha`,
"KN": `Saint Kitts and Nevis`,
"LC": `Saint Lucia`,
"MF": `Saint Martin (French part)`,
"PM": `Saint Pierre and Miquelon`,
"VC": `Saint Vincent and the Grenadines`,
"WS": `Samoa`,
"SM": `San Marino`,
"ST": `Sao Tome and Principe`,
"SA": `Saudi Arabia`,
"SN": `Senegal`,
"RS": `Serbia`,
"SC": `Seychelles`,
"SL": `Sierra Leone`,
"SG": `Singapore`,
"SX": `Sint Maarten (Dutch part)`,
"SK": `Slovakia`,
"SI": `Slovenia`,
"SB": `Solomon Islands`,
"SO": `Somalia`,
"ZA": `South Africa`,
"GS": `South Georgia and the South Sandwich Islands`,
"SS": `South Sudan`,
"ES": `Spain`,
"LK": `Sri Lanka`,
"SD": `Sudan`,
"SR": `Suriname`,
"SJ": `Svalbard and Jan Mayen`,
"SZ": `Swaziland`,
"SE": `Sweden`,
"CH": `Switzerland`,
"SY": `Syrian Arab Republic`,
"TW": `Taiwan`,
"TJ": `Tajikistan`,
"TZ": `Tanzania, United Republic of`,
"TH": `Thailand`,
"TL": `Timor-Leste`,
"TG": `Togo`,
"TK": `Tokelau`,
"TO": `Tonga`,
"TT": `Trinidad and Tobago`,
"TN": `Tunisia`,
"TR": `Turkey`,
"TM": `Turkmenistan`,
"TC": `Turks and Caicos Islands`,
"TV": `Tuvalu`,
"VI": `U.S. Virgin Islands`,
"UG": `Uganda`,
"UA": `Ukraine`,
"AE": `United Arab Emirates`,
"GB": `United Kingdom`,
"US": `United States`,
"UM": `United States Minor Outlying Islands`,
"UY": `Uruguay`,
"UZ": `Uzbekistan`,
"VU": `Vanuatu`,
"VE": `Venezuela, Bolivarian Republic of`,
"VN": `Viet Nam`,
"VG": `Virgin Islands, British`,
"WF": `Wallis and Futuna`,
"EH": `Western Sahara`,
"YE": `Yemen`,
"ZM": `Zambia`,
"ZW": `Zimbabwe`
AF: `Afghanistan`,
AX: `Aland Islands`,
AL: `Albania`,
DZ: `Algeria`,
AS: `American Samoa`,
AD: `Andorra`,
AO: `Angola`,
AI: `Anguilla`,
AQ: `Antarctica`,
AG: `Antigua and Barbuda`,
AR: `Argentina`,
AM: `Armenia`,
AW: `Aruba`,
AU: `Australia`,
AT: `Austria`,
AZ: `Azerbaijan`,
BS: `Bahamas`,
BH: `Bahrain`,
BD: `Bangladesh`,
BB: `Barbados`,
BY: `Belarus`,
BE: `Belgium`,
BZ: `Belize`,
BJ: `Benin`,
BM: `Bermuda`,
BT: `Bhutan`,
BO: `Bolivia, Plurinational State of`,
BQ: `Bonaire, Sint Eustatius and Saba`,
BA: `Bosnia and Herzegovina`,
BW: `Botswana`,
BV: `Bouvet Island`,
BR: `Brazil`,
IO: `British Indian Ocean Territory`,
BN: `Brunei Darussalam`,
BG: `Bulgaria`,
BF: `Burkina Faso`,
BI: `Burundi`,
KH: `Cambodia`,
CM: `Cameroon`,
CA: `Canada`,
CV: `Cape Verde`,
KY: `Cayman Islands`,
CF: `Central African Republic`,
TD: `Chad`,
CL: `Chile`,
CN: `China`,
CX: `Christmas Island`,
CC: `Cocos (Keeling) Islands`,
CO: `Colombia`,
KM: `Comoros`,
CG: `Congo`,
CD: `Congo, the Democratic Republic of the`,
CK: `Cook Islands`,
CR: `Costa Rica`,
CI: `Cote d'Ivoire`,
HR: `Croatia`,
CU: `Cuba`,
CW: `Curaçao`,
CY: `Cyprus`,
CZ: `Czech Republic`,
DK: `Denmark`,
DJ: `Djibouti`,
DM: `Dominica`,
DO: `Dominican Republic`,
EC: `Ecuador`,
EG: `Egypt`,
SV: `El Salvador`,
GQ: `Equatorial Guinea`,
ER: `Eritrea`,
EE: `Estonia`,
ET: `Ethiopia`,
FK: `Falkland Islands (Malvinas)`,
FO: `Faroe Islands`,
FJ: `Fiji`,
FI: `Finland`,
FR: `France`,
GF: `French Guiana`,
PF: `French Polynesia`,
TF: `French Southern Territories`,
GA: `Gabon`,
GM: `Gambia`,
GE: `Georgia`,
DE: `Germany`,
GH: `Ghana`,
GI: `Gibraltar`,
GR: `Greece`,
GL: `Greenland`,
GD: `Grenada`,
GP: `Guadeloupe`,
GU: `Guam`,
GT: `Guatemala`,
GG: `Guernsey`,
GN: `Guinea`,
GW: `Guinea-Bissau`,
GY: `Guyana`,
HT: `Haiti`,
HM: `Heard Island and McDonald Islands`,
VA: `Holy See (Vatican City State)`,
HN: `Honduras`,
HK: `Hong Kong`,
HU: `Hungary`,
IS: `Iceland`,
IN: `India`,
ID: `Indonesia`,
IR: `Iran, Islamic Republic of`,
IQ: `Iraq`,
IE: `Ireland`,
IM: `Isle of Man`,
IL: `Israel`,
IT: `Italy`,
JM: `Jamaica`,
JP: `Japan`,
JE: `Jersey`,
JO: `Jordan`,
KZ: `Kazakhstan`,
KE: `Kenya`,
KI: `Kiribati`,
KP: `Korea, Democratic People's Republic of`,
KR: `Korea, Republic of`,
KW: `Kuwait`,
KG: `Kyrgyzstan`,
LA: `Lao People's Democratic Republic`,
LV: `Latvia`,
LB: `Lebanon`,
LS: `Lesotho`,
LR: `Liberia`,
LY: `Libya`,
LI: `Liechtenstein`,
LT: `Lithuania`,
LU: `Luxembourg`,
MO: `Macao`,
MK: `Macedonia, the former Yugoslav Republic of`,
MG: `Madagascar`,
MW: `Malawi`,
MY: `Malaysia`,
MV: `Maldives`,
ML: `Mali`,
MT: `Malta`,
MH: `Marshall Islands`,
MQ: `Martinique`,
MR: `Mauritania`,
MU: `Mauritius`,
YT: `Mayotte`,
MX: `Mexico`,
FM: `Micronesia`,
MD: `Moldova, Republic of`,
MC: `Monaco`,
MN: `Mongolia`,
ME: `Montenegro`,
MS: `Montserrat`,
MA: `Morocco`,
MZ: `Mozambique`,
MM: `Myanmar`,
NA: `Namibia`,
NR: `Nauru`,
NP: `Nepal`,
NL: `Netherlands`,
AN: `Netherlands Antilles`,
NC: `New Caledonia`,
NZ: `New Zealand`,
NI: `Nicaragua`,
NE: `Niger`,
NG: `Nigeria`,
NU: `Niue`,
NF: `Norfolk Island`,
MP: `Northern Mariana Islands`,
NO: `Norway`,
OM: `Oman`,
PK: `Pakistan`,
PW: `Palau`,
PS: `Palestine`,
PA: `Panama`,
PG: `Papua New Guinea`,
PY: `Paraguay`,
PE: `Peru`,
PH: `Philippines`,
PN: `Pitcairn`,
PL: `Poland`,
PT: `Portugal`,
PR: `Puerto Rico`,
QA: `Qatar`,
RE: `Reunion`,
RO: `Romania`,
RU: `Russian Federation`,
RW: `Rwanda`,
BL: `Saint Barthélemy`,
SH: `Saint Helena, Ascension and Tristan da Cunha`,
KN: `Saint Kitts and Nevis`,
LC: `Saint Lucia`,
MF: `Saint Martin (French part)`,
PM: `Saint Pierre and Miquelon`,
VC: `Saint Vincent and the Grenadines`,
WS: `Samoa`,
SM: `San Marino`,
ST: `Sao Tome and Principe`,
SA: `Saudi Arabia`,
SN: `Senegal`,
RS: `Serbia`,
SC: `Seychelles`,
SL: `Sierra Leone`,
SG: `Singapore`,
SX: `Sint Maarten (Dutch part)`,
SK: `Slovakia`,
SI: `Slovenia`,
SB: `Solomon Islands`,
SO: `Somalia`,
ZA: `South Africa`,
GS: `South Georgia and the South Sandwich Islands`,
SS: `South Sudan`,
ES: `Spain`,
LK: `Sri Lanka`,
SD: `Sudan`,
SR: `Suriname`,
SJ: `Svalbard and Jan Mayen`,
SZ: `Swaziland`,
SE: `Sweden`,
CH: `Switzerland`,
SY: `Syrian Arab Republic`,
TW: `Taiwan`,
TJ: `Tajikistan`,
TZ: `Tanzania, United Republic of`,
TH: `Thailand`,
TL: `Timor-Leste`,
TG: `Togo`,
TK: `Tokelau`,
TO: `Tonga`,
TT: `Trinidad and Tobago`,
TN: `Tunisia`,
TR: `Turkey`,
TM: `Turkmenistan`,
TC: `Turks and Caicos Islands`,
TV: `Tuvalu`,
VI: `U.S. Virgin Islands`,
UG: `Uganda`,
UA: `Ukraine`,
AE: `United Arab Emirates`,
GB: `United Kingdom`,
US: `United States`,
UM: `United States Minor Outlying Islands`,
UY: `Uruguay`,
UZ: `Uzbekistan`,
VU: `Vanuatu`,
VE: `Venezuela, Bolivarian Republic of`,
VN: `Viet Nam`,
VG: `Virgin Islands, British`,
WF: `Wallis and Futuna`,
EH: `Western Sahara`,
YE: `Yemen`,
ZM: `Zambia`,
ZW: `Zimbabwe`
};
export default SALESFORCE_COUNTRY_LIST;

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import React from "react";
import PropTypes from "prop-types";
export default class PulseProjectList extends React.Component {
constructor(props) {
@ -16,7 +16,7 @@ export default class PulseProjectList extends React.Component {
projectXHR.addEventListener(`load`, () => {
let projects = JSON.parse(projectXHR.response);
projects.results = projects.results.sort((a,b) => {
projects.results = projects.results.sort((a, b) => {
if (this.props.reverseChronological) {
return Date.parse(a.created) < Date.parse(b.created) ? 1 : -1;
} else {
@ -24,30 +24,41 @@ export default class PulseProjectList extends React.Component {
}
});
this.setState({
projects: this.props.max ? projects.results.slice(0, this.props.max) : projects.results
}, () => {
if (this.props.whenLoaded) {
this.props.whenLoaded();
this.setState(
{
projects: this.props.max
? projects.results.slice(0, this.props.max)
: projects.results
},
() => {
if (this.props.whenLoaded) {
this.props.whenLoaded();
}
}
});
);
});
const apiURL = `${this.props.env.PULSE_API_DOMAIN}/api/pulse/v2/entries/`;
const params = {
"format": `json`,
"help_type": this.props.help && this.props.help !== `all` ? this.props.help : null,
"issue": this.props.issues && this.props.issues !== `all` ? this.props.issues : null,
"page_size": this.props.max ? this.props.max : 12,
"search": this.props.query,
"featured": this.props.featured && `True`
format: `json`,
help_type:
this.props.help && this.props.help !== `all` ? this.props.help : null,
issue:
this.props.issues && this.props.issues !== `all`
? this.props.issues
: null,
page_size: this.props.max ? this.props.max : 12,
search: this.props.query,
featured: this.props.featured && `True`
};
// Serialize parameters into a query string
const serializedParams = Object.keys(params).filter((key) => params[key]).map((key) => {
return `${key}=${encodeURIComponent(params[key])}`;
});
const serializedParams = Object.keys(params)
.filter(key => params[key])
.map(key => {
return `${key}=${encodeURIComponent(params[key])}`;
});
const apiURLwithQuery = `${apiURL}?${serializedParams.join(`&`)}`;
@ -68,7 +79,7 @@ export default class PulseProjectList extends React.Component {
byline = `By ${project.related_creators.map(rc => rc.name).join(`, `)}`;
}
if (this.props.directLink){
if (this.props.directLink) {
url = project.content_url;
} else {
url = `https://${this.props.env.PULSE_DOMAIN}/entry/${project.id}`;
@ -76,22 +87,34 @@ export default class PulseProjectList extends React.Component {
return (
<div className="col-6 col-md-4 my-4" key={`pulse-project-${index}`}>
<a className="pulse-project" href={url} target="_blank" rel="noopener noreferrer">
<a
className="pulse-project"
href={url}
target="_blank"
rel="noopener noreferrer"
>
<div className="thumbnail">
<div className="img-container">
<img className={`project-image${ project.thumbnail ? `` : ` placeholder` }`} src={ project.thumbnail ? project.thumbnail : `/_images/proportional-spacer.png` }/>
<img
className={`project-image${
project.thumbnail ? `` : ` placeholder`
}`}
src={
project.thumbnail
? project.thumbnail
: `/_images/proportional-spacer.png`
}
/>
</div>
</div>
<h5 className="project-title h5-heading my-2">{project.title}</h5>
</a>
{ byline && <p className="h6-heading my-1">{byline}</p> }
{byline && <p className="h6-heading my-1">{byline}</p>}
</div>
);
});
return (
<div className="row">{projectList}</div>
);
return <div className="row">{projectList}</div>;
}
}

Просмотреть файл

@ -1,18 +1,18 @@
let _dntStatus = navigator.doNotTrack || navigator.msDoNotTrack,
fxMatch = navigator.userAgent.match(/Firefox\/(\d+)/),
ie10Match = navigator.userAgent.match(/MSIE 10/i),
w8Match = navigator.appVersion.match(/Windows NT 6.2/);
fxMatch = navigator.userAgent.match(/Firefox\/(\d+)/),
ie10Match = navigator.userAgent.match(/MSIE 10/i),
w8Match = navigator.appVersion.match(/Windows NT 6.2/);
if (fxMatch && Number(fxMatch[1]) < 32) {
_dntStatus = `Unspecified`;
} else if (ie10Match && w8Match) {
_dntStatus = `Unspecified`;
} else {
_dntStatus = { '0': `Disabled`, '1': `Enabled` }[_dntStatus] || `Unspecified`;
_dntStatus = { "0": `Disabled`, "1": `Enabled` }[_dntStatus] || `Unspecified`;
}
const DNT = {
allowTracking: (_dntStatus !== `Enabled`)
allowTracking: _dntStatus !== `Enabled`
};
export default DNT;

Просмотреть файл

@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ReactGA from '../react-ga-proxy.js';
import React from "react";
import ReactDOM from "react-dom";
import ReactGA from "../react-ga-proxy.js";
const KEY_STATE = `donate modal state`;
const KEY_TIMER = `donate modal timer`;
@ -39,7 +39,10 @@ class DonateModal extends React.Component {
this.startTimer();
// show modal after delay. If delay is a negative value, show modal immediately
setTimeout(() => this.setState({ visible: true }), Math.max(this.state.delay, 0));
setTimeout(
() => this.setState({ visible: true }),
Math.max(this.state.delay, 0)
);
}
}
@ -63,11 +66,14 @@ class DonateModal extends React.Component {
sessionStorage.setItem(KEY_STATE, `dismissed`);
sessionStorage.removeItem(KEY_TIMER);
this.setState({
dismissed: true
}, () => {
clearInterval(this.runTimer);
});
this.setState(
{
dismissed: true
},
() => {
clearInterval(this.runTimer);
}
);
}
render() {
@ -76,9 +82,9 @@ class DonateModal extends React.Component {
}
let title = this.props.title,
subheading = this.props.subheading,
cta = this.props.cta,
utm = this.props.utm;
subheading = this.props.subheading,
cta = this.props.cta,
utm = this.props.utm;
return (
<div className={`donate-modal ${this.state.visible ? `show` : ``}`}>
@ -88,18 +94,22 @@ class DonateModal extends React.Component {
<div className="container">
<div className="row align-items-center text-center text-md-left">
<div className="col-md-6">
<h1 className="h3-heading">{ title }</h1>
<p className="normal">{ subheading } </p>
<h1 className="h3-heading">{title}</h1>
<p className="normal">{subheading} </p>
</div>
<div className="col-md-4 offset-md-2">
<h2 className="h5-heading">{ cta.title }</h2>
<h2 className="h5-heading">{cta.title}</h2>
<div>
<a
className="d-block d-md-inline-block text-center btn btn-donate ml-0"
onClick={evt => this.handleBtnClick(evt)}
href={`https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=${utm.medium}&utm_campaign=${utm.campaign}&utm_content=${utm.content}`}
href={`https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=${
utm.medium
}&utm_campaign=${utm.campaign}&utm_content=${utm.content}`}
target="_blank"
>{ cta.text }</a>
>
{cta.text}
</a>
</div>
</div>
</div>
@ -111,6 +121,6 @@ class DonateModal extends React.Component {
// Export a manual injection function.
export default function injectDonateModal(element, props={}) {
export default function injectDonateModal(element, props = {}) {
ReactDOM.render(<DonateModal {...props} />, element);
}

Просмотреть файл

@ -1,20 +1,20 @@
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "React" }] */
import React from 'react';
import ReactGA from 'react-ga';
import ReactDOM from 'react-dom';
import Analytics from './analytics.js';
import React from "react";
import ReactGA from "react-ga";
import ReactDOM from "react-dom";
import Analytics from "./analytics.js";
import JoinUs from './components/join/join.jsx';
import Petition from './components/petition/petition.jsx';
import People from './components/people/people.jsx';
import MemberNotice from './components/member-notice/member-notice.jsx';
import MultipageNavMobile from './components/multipage-nav-mobile/multipage-nav-mobile.jsx';
import News from './components/news/news.jsx';
import SingleFilterFellowList from './components/fellow-list/single-filter-fellow-list.jsx';
import PulseProjectList from './components/pulse-project-list/pulse-project-list.jsx';
import JoinUs from "./components/join/join.jsx";
import Petition from "./components/petition/petition.jsx";
import People from "./components/people/people.jsx";
import MemberNotice from "./components/member-notice/member-notice.jsx";
import MultipageNavMobile from "./components/multipage-nav-mobile/multipage-nav-mobile.jsx";
import News from "./components/news/news.jsx";
import SingleFilterFellowList from "./components/fellow-list/single-filter-fellow-list.jsx";
import PulseProjectList from "./components/pulse-project-list/pulse-project-list.jsx";
import primaryNav from './primary-nav.js';
import primaryNav from "./primary-nav.js";
const SHOW_MEMBER_NOTICE = false;
@ -28,7 +28,7 @@ const apps = [];
let main = {
init() {
this.fetchEnv((envData) => {
this.fetchEnv(envData => {
env = envData;
networkSiteURL = env.NETWORK_SITE_URL;
@ -52,11 +52,15 @@ let main = {
},
decorateExternalLinks() {
Array.from( document.querySelectorAll(`a`) ).forEach((link) => {
Array.from(document.querySelectorAll(`a`)).forEach(link => {
let href = link.getAttribute(`href`);
// Define an external link as any URL with `//` in it
if (href && href.match(/\/\//) && env.TARGET_DOMAINS.some(domain => !href.match(`//${domain}`))) {
if (
href &&
href.match(/\/\//) &&
env.TARGET_DOMAINS.some(domain => !href.match(`//${domain}`))
) {
link.setAttribute(`target`, `_blank`);
// https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/
@ -99,7 +103,7 @@ let main = {
let ticking = false;
let elBurgerWrapper = document.querySelector(`.wrapper-burger`);
let adjustNavbar = (scrollPosition) => {
let adjustNavbar = scrollPosition => {
if (scrollPosition > 0) {
elBurgerWrapper.classList.add(`scrolled`);
} else {
@ -107,15 +111,20 @@ let main = {
}
};
let elCtaAnchor = document.querySelector(`#cta-anchor`);
let elStickyButton = document.querySelector(`.narrow-sticky-button-container`);
let elStickyButton = document.querySelector(
`.narrow-sticky-button-container`
);
let noopCtaButton = () => {};
let adjustCtaButton = noopCtaButton;
if (elCtaAnchor && elStickyButton) {
let getAnchorPosition = () => {
return elCtaAnchor.getBoundingClientRect().top + window.scrollY - window.innerHeight;
return (
elCtaAnchor.getBoundingClientRect().top +
window.scrollY -
window.innerHeight
);
};
let ctaAnchorPosition = getAnchorPosition();
@ -124,14 +133,14 @@ let main = {
ctaAnchorPosition = getAnchorPosition();
});
let scrollCtaButton = (scrollPosition) => {
let scrollCtaButton = scrollPosition => {
if (scrollPosition > ctaAnchorPosition) {
elStickyButton.classList.add(`hidden`);
adjustCtaButton = noopCtaButton;
}
};
let initCtaButton = (scrollPosition) => {
let initCtaButton = scrollPosition => {
if (scrollPosition <= ctaAnchorPosition) {
elStickyButton.classList.remove(`hidden`);
adjustCtaButton = scrollCtaButton;
@ -164,21 +173,25 @@ let main = {
// Extra tracking
document.getElementById(`donate-header-btn`).addEventListener(`click`, () => {
ReactGA.event({
category: `donate`,
action: `donate button tap`,
label: `${document.title} header`
document
.getElementById(`donate-header-btn`)
.addEventListener(`click`, () => {
ReactGA.event({
category: `donate`,
action: `donate button tap`,
label: `${document.title} header`
});
});
});
document.getElementById(`donate-footer-btn`).addEventListener(`click`, () => {
ReactGA.event({
category: `donate`,
action: `donate button tap`,
label: `${document.title} footer`
document
.getElementById(`donate-footer-btn`)
.addEventListener(`click`, () => {
ReactGA.event({
category: `donate`,
action: `donate button tap`,
label: `${document.title} footer`
});
});
});
},
bindGAEventTrackers() {
@ -197,7 +210,9 @@ let main = {
});
}
let participateDonateBtn = document.querySelector(`#view-participate .card-cta .btn[href*="donate.mozilla.org"]`);
let participateDonateBtn = document.querySelector(
`#view-participate .card-cta .btn[href*="donate.mozilla.org"]`
);
if (participateDonateBtn) {
participateDonateBtn.addEventListener(`click`, () => {
@ -213,28 +228,46 @@ let main = {
// Embed various React components based on the existence of containers within the current page
injectReactComponents() {
if (SHOW_MEMBER_NOTICE && document.getElementById(`member-notice`)) {
apps.push(new Promise(resolve => {
ReactDOM.render(<MemberNotice whenLoaded={() => resolve()}/>, document.getElementById(`member-notice`));
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<MemberNotice whenLoaded={() => resolve()} />,
document.getElementById(`member-notice`)
);
})
);
}
// Embed additional instances of the Join Us box that don't need an API exposed (eg: Homepage)
if (document.querySelectorAll(`.join-us:not(#join-us)`)) {
var elements = Array.from(document.querySelectorAll(`.join-us:not(#join-us)`));
var elements = Array.from(
document.querySelectorAll(`.join-us:not(#join-us)`)
);
if (elements.length) {
elements.forEach(element => {
var props = element.dataset;
apps.push(new Promise(resolve => {
ReactDOM.render(<JoinUs {...props} isHidden={false} whenLoaded={() => resolve()} />, element);
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<JoinUs
{...props}
isHidden={false}
whenLoaded={() => resolve()}
/>,
element
);
})
);
});
}
}
// petition elements
var petitionElements = Array.from(document.querySelectorAll(`.sign-petition`));
var petitionElements = Array.from(
document.querySelectorAll(`.sign-petition`)
);
var subscribed = false;
if (window.location.search.indexOf(`subscribed=1`) !== -1) {
@ -244,41 +277,73 @@ let main = {
petitionElements.forEach(element => {
var props = element.dataset;
props.apiUrl = `${networkSiteURL}/api/campaign/petitions/${props.petitionId}/`;
props.apiUrl = `${networkSiteURL}/api/campaign/petitions/${
props.petitionId
}/`;
apps.push(new Promise(resolve => {
ReactDOM.render(<Petition {...props} isHidden={false} subscribed={subscribed} whenLoaded={() => resolve()}/>, element);
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<Petition
{...props}
isHidden={false}
subscribed={subscribed}
whenLoaded={() => resolve()}
/>,
element
);
})
);
});
if (document.getElementById(`people`)) {
apps.push(new Promise(resolve => {
ReactDOM.render(<People env={env} whenLoaded={() => resolve()} />, document.getElementById(`people`));
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<People env={env} whenLoaded={() => resolve()} />,
document.getElementById(`people`)
);
})
);
}
// Multipage nav used in landing pages
if (document.querySelector(`#multipage-nav`)) {
let links = [];
links = [].map.call(document.querySelectorAll(`#multipage-nav a`), (child) => {
return {
label: child.textContent.trim(),
href: child.getAttribute(`href`),
isActive: !!child.getAttribute(`class`).match(/active/)
};
});
links = [].map.call(
document.querySelectorAll(`#multipage-nav a`),
child => {
return {
label: child.textContent.trim(),
href: child.getAttribute(`href`),
isActive: !!child.getAttribute(`class`).match(/active/)
};
}
);
apps.push(new Promise(resolve => {
ReactDOM.render(<MultipageNavMobile links={links} whenLoaded={() => resolve()} />, document.querySelector(`#multipage-nav-mobile .container .row .col-12`));
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<MultipageNavMobile links={links} whenLoaded={() => resolve()} />,
document.querySelector(
`#multipage-nav-mobile .container .row .col-12`
)
);
})
);
}
// News
if (document.querySelector(`#news`)) {
apps.push(new Promise(resolve => {
ReactDOM.render(<News env={env} whenLoaded={() => resolve()} />, document.querySelector(`#news`));
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<News env={env} whenLoaded={() => resolve()} />,
document.querySelector(`#news`)
);
})
);
}
// Fellowships single filter fellow list
@ -287,17 +352,20 @@ let main = {
);
singleFilterFellowList.forEach(target => {
apps.push(new Promise(resolve => {
ReactDOM.render(
<SingleFilterFellowList
env={env}
filterType={target.dataset.filterType}
filterOptions={target.dataset.filterOptions.split(`,`)}
selectedOption={target.dataset.selectedOption}
whenLoaded={() => resolve()}
/>, target
);
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<SingleFilterFellowList
env={env}
filterType={target.dataset.filterType}
filterOptions={target.dataset.filterOptions.split(`,`)}
selectedOption={target.dataset.selectedOption}
whenLoaded={() => resolve()}
/>,
target
);
})
);
});
// Pulse project lists
@ -306,21 +374,24 @@ let main = {
);
pulseProjectList.forEach(target => {
apps.push(new Promise(resolve => {
ReactDOM.render(
<PulseProjectList
env={ env }
featured={ target.dataset.featured === `True` }
help={ target.dataset.help }
issues={ target.dataset.issues }
max={ parseInt(target.dataset.max, 10) }
query={ target.dataset.query || `` }
reverseChronological={ target.dataset.reversed === `True` }
whenLoaded={() => resolve()}
directLink={ target.dataset.directLink === `True` }
/>, target
);
}));
apps.push(
new Promise(resolve => {
ReactDOM.render(
<PulseProjectList
env={env}
featured={target.dataset.featured === `True`}
help={target.dataset.help}
issues={target.dataset.issues}
max={parseInt(target.dataset.max, 10)}
query={target.dataset.query || ``}
reverseChronological={target.dataset.reversed === `True`}
whenLoaded={() => resolve()}
directLink={target.dataset.directLink === `True`}
/>,
target
);
})
);
});
/*

Просмотреть файл

@ -1 +1 @@
import 'whatwg-fetch';
import "whatwg-fetch";

Просмотреть файл

@ -1,4 +1,4 @@
import ReactGA from 'react-ga';
import ReactGA from "react-ga";
let primaryNav = {
init: function() {
@ -58,7 +58,7 @@ let primaryNav = {
trackMenuState(openMenu);
}
document.addEventListener(`keyup`, (e) => {
document.addEventListener(`keyup`, e => {
if (e.keyCode === 27) {
menuOpen = false;
setMenuState(menuOpen);

4
source/js/react-ga-proxy.js поставляемый
Просмотреть файл

@ -1,5 +1,5 @@
import DNT from './dnt.js';
import ReactGA from 'react-ga';
import DNT from "./dnt.js";
import ReactGA from "react-ga";
// A no-operation object with the same API surface
// as ReactGA, for when tracking is not appreciated:

Просмотреть файл

@ -1,5 +1,11 @@
let path = require(`path`);
let frontendPath = path.resolve(__dirname, `network-api`,`networkapi`,`frontend`,`_js`);
let frontendPath = path.resolve(
__dirname,
`network-api`,
`networkapi`,
`frontend`,
`_js`
);
let rules = [
{
@ -15,7 +21,6 @@ let rules = [
}
];
let main = {
entry: `./source/js/main.js`,
output: {