1. Remove the "airbnb" eslint plugin since it conflicts with prettier (#1374)

and so was just annoying
2. Get rid of all existing ESLint errors.
2a. Turned off `react/display-name` because I couldn't figure out how to
make that wrapped component have a display name. If anyone can figure it
out, that'd be great, because that makes things nicer when using the
React debugger.
2b. The part where it says `plausible()` is undefined in `app.js` is
bothering me. I disabled the check because I can't figure out where that
actually comes from to put in the proper import.
2c. Told ESLint we're using Babel.
3. Added `npm run format` and `npm run lint` commands.
This commit is contained in:
Mackenzie 2021-10-11 08:48:19 -04:00 committed by GitHub
parent cd68761928
commit 9c2fd9aca5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 109 additions and 100 deletions

View File

@ -21,7 +21,6 @@ repos:
files: "assets/js"
additional_dependencies:
- eslint@7.2.0
- eslint-config-airbnb@18.2.0
- eslint-plugin-import@2.22.1
- eslint-plugin-jsx-a11y@6.4.1
- eslint-plugin-react@7.21.5

View File

@ -2,7 +2,11 @@
"env": {
"browser": true
},
"extends": ["airbnb", "prettier"],
"extends": ["eslint:recommended",
"plugin:react/recommended",
"prettier"
],
"parser": "babel-eslint",
"plugins": ["prettier"],
"rules": {
"max-len": [0, {"code": 120}],
@ -11,6 +15,7 @@
"react/destructuring-assignment": [0],
"react/prop-types": [0],
"max-classes-per-file": [0],
"react/display-name": [0],
"react/jsx-one-expression-per-line": [0],
"react/self-closing-comp": [0],
"no-unused-expressions": [1, { "allowShortCircuit": true }],
@ -18,5 +23,14 @@
"jsx-a11y/click-events-have-key-events": [0],
"jsx-a11y/no-static-element-interactions": [0],
"react/no-did-update-set-state": [0]
},
"settings": {
"react": {
"createClass": "createReactClass", // Regex for Component Factory to use,
// default to "createReactClass"
"pragma": "React", // Pragma to use, default to "React"
"fragment": "Fragment", // Fragment to use (may be a property of <pragma>), default to "Fragment"
"version": "detect"
}
}
}

6
assets/.prettierignore Normal file
View File

@ -0,0 +1,6 @@
node_modules/
static/images/
.*.json
.*rc
*.json
*.config.js

View File

@ -48,7 +48,7 @@ if (registerForm) {
registerForm.submit();
}
}
/* eslint-disable-next-line no-undef */
plausible('Signup', {callback: submitForm});
})
}
@ -94,7 +94,7 @@ function showChangelogNotification(el) {
const embedButton = document.getElementById('generate-embed')
if (embedButton) {
embedButton.addEventListener('click', function(e) {
embedButton.addEventListener('click', function(_e) {
const baseUrl = document.getElementById('base-url').value
const embedCode = document.getElementById('embed-code')
const theme = document.getElementById('theme').value.toLowerCase()

View File

@ -13,6 +13,7 @@ class ApiError extends Error {
function serialize(obj) {
var str = [];
for (var p in obj)
/* eslint-disable-next-line no-prototype-builtins */
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}

View File

@ -47,7 +47,7 @@ export function parseQuery(querystring, site) {
export function appliedFilters(query) {
return Object.keys(query.filters)
.map((key) => [key, query.filters[key]])
.filter(([key, value]) => !!value);
.filter(([_key, value]) => !!value);
}
function generateQueryString(data) {
@ -89,7 +89,7 @@ class QueryLink extends React.Component {
}
render() {
const { history, query, to, ...props } = this.props
const { to, ...props } = this.props
return (
<Link
{...props}

View File

@ -46,7 +46,7 @@ export default class SiteSwitcher extends React.Component {
}
handleKeydown(e) {
const { query, history, site } = this.props;
const { site } = this.props;
const { sites } = this.state;
if (e.target.tagName === 'INPUT') return true;

View File

@ -74,7 +74,7 @@ export default class PropertyBreakdown extends React.Component {
renderUrl(value) {
if (isValidHttpUrl(value.name)) {
return (
<a target="_blank" href={value.name} className="hidden group-hover:block">
<a target="_blank" href={value.name} rel="noreferrer" className="hidden group-hover:block">
<svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg>
</a>
)

View File

@ -1,11 +1,11 @@
import React from 'react';
import Datamap from 'datamaps'
import { withRouter } from 'react-router-dom'
import * as d3 from "d3"
import numberFormatter from '../number-formatter'
import FadeIn from '../fade-in'
import LazyLoader from '../lazy-loader'
import Bar from './bar'
import MoreLink from './more-link'
import * as api from '../api'
import { navigateToQuery } from '../query'
@ -128,7 +128,7 @@ class Countries extends React.Component {
geolocationDbNotice() {
if (this.props.site.selfhosted) {
return (
<span className="text-xs text-gray-500 absolute bottom-4 right-3">IP Geolocation by <a target="_blank" href="https://db-ip.com" className="text-indigo-600">DB-IP</a></span>
<span className="text-xs text-gray-500 absolute bottom-4 right-3">IP Geolocation by <a target="_blank" href="https://db-ip.com" rel="noreferrer" className="text-indigo-600">DB-IP</a></span>
)
}
}

View File

@ -22,19 +22,19 @@ const EXPLANATION = {
function iconFor(screenSize) {
if (screenSize === 'Mobile') {
return (
<svg width="16px" height="16px" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="-mt-px feather"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12" y2="18"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="-mt-px feather"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12" y2="18"/></svg>
)
} else if (screenSize === 'Tablet') {
return (
<svg width="16px" height="16px" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="-mt-px feather"><rect x="4" y="2" width="16" height="20" rx="2" ry="2" transform="rotate(180 12 12)"/><line x1="12" y1="18" x2="12" y2="18"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="-mt-px feather"><rect x="4" y="2" width="16" height="20" rx="2" ry="2" transform="rotate(180 12 12)"/><line x1="12" y1="18" x2="12" y2="18"/></svg>
)
} else if (screenSize === 'Laptop') {
return (
<svg width="16px" height="16px" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="-mt-px feather"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="2" y1="20" x2="22" y2="20"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="-mt-px feather"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="2" y1="20" x2="22" y2="20"/></svg>
)
} else if (screenSize === 'Desktop') {
return (
<svg width="16px" height="16px" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="-mt-px feather"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="-mt-px feather"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
)
}
}

View File

@ -24,7 +24,7 @@ class EntryPagesModal extends React.Component {
}
loadPages() {
const {query, page, pages} = this.state;
const {query, page} = this.state;
api.get(
`/api/stats/${encodeURIComponent(this.props.site.domain)}/entry-pages`,

View File

@ -24,7 +24,7 @@ class ExitPagesModal extends React.Component {
}
loadPages() {
const {query, page, pages} = this.state;
const {query, page} = this.state;
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/exit-pages`, query, {limit: 100, page})
.then((res) => this.setState((state) => ({loading: false, pages: state.pages.concat(res), moreResultsAvailable: res.length === 100})))

View File

@ -4,7 +4,6 @@ import { Link, withRouter } from 'react-router-dom'
import Modal from './modal'
import * as api from '../../api'
import numberFormatter from '../../number-formatter'
import Bar from '../bar'
import {parseQuery, toHuman} from '../../query'
import RocketIcon from './rocket-icon'

View File

@ -25,7 +25,7 @@ class PagesModal extends React.Component {
loadPages() {
const detailed = this.showExtra()
const {query, page, pages} = this.state;
const {query, page} = this.state;
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/pages`, query, {limit: 100, page, detailed})
.then((res) => this.setState((state) => ({loading: false, pages: state.pages.concat(res), moreResultsAvailable: res.length === 100})))

View File

@ -62,7 +62,7 @@ class ReferrerDrilldownModal extends React.Component {
renderExternalLink(name) {
if (name !== 'Direct / None') {
return (
<a target="_blank" href={'//' + name} className="hidden group-hover:block">
<a target="_blank" href={'//' + name} rel="noreferrer" className="hidden group-hover:block">
<svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg>
</a>
)
@ -92,14 +92,14 @@ class ReferrerDrilldownModal extends React.Component {
return (
<div key={tweet.tweet_id}>
<div className={"flex items-center my-4" + border} >
<a className="flex items-center group" href={authorUrl} target="_blank">
<a className="flex items-center group" href={authorUrl} target="_blank" rel="noreferrer">
<img className="rounded-full w-8" src={tweet.author_image} />
<div className="ml-2 leading-tight">
<div className="font-bold group-hover:text-blue-500">{tweet.author_name}</div>
<div className="text-xs text-gray-500 dark:text-gray-400">@{tweet.author_handle}</div>
</div>
</a>
<a className="ml-auto twitter-icon" href={tweetUrl} target="_blank"></a>
<a className="ml-auto twitter-icon" href={tweetUrl} target="_blank" rel="noreferrer"></a>
</div>
<div className="my-2 cursor-text tweet-text whitespace-pre-wrap" dangerouslySetInnerHTML={{__html: tweet.text}}>
</div>

View File

@ -1,7 +1,6 @@
import React from "react";
import { Link, withRouter } from 'react-router-dom'
import FadeIn from '../../fade-in'
import Modal from './modal'
import * as api from '../../api'
import numberFormatter, {durationFormatter} from '../../number-formatter'

View File

@ -62,7 +62,7 @@ export default class Referrers extends React.Component {
renderExternalLink(referrer) {
if (this.props.query.filters.source && this.props.query.filters.source !== 'Google' && referrer.name !== 'Direct / None') {
return (
<a target="_blank" href={'//' + referrer.name} className="hidden group-hover:block">
<a target="_blank" href={'//' + referrer.name} rel="noreferrer" className="hidden group-hover:block">
<svg className="inline w-4 h-4 ml-1 -mt-1 text-gray-600 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg>
</a>
)

View File

@ -91,7 +91,7 @@ export default class SearchTerms extends React.Component {
<RocketIcon />
<div>Could not find any search terms for this period</div>
<div>Google Search Console data is sampled and delayed by 24-36h</div>
<div>Read more on <a href="https://docs.plausible.io/google-search-console-integration/#i-dont-see-google-search-query-data-in-my-dashboard" target="_blank" className="hover:underline text-indigo-700 dark:text-indigo-500">our documentation</a></div>
<div>Read more on <a href="https://docs.plausible.io/google-search-console-integration/#i-dont-see-google-search-query-data-in-my-dashboard" target="_blank" rel="noreferrer" className="hover:underline text-indigo-700 dark:text-indigo-500">our documentation</a></div>
</div>
)
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import { withRouter } from 'react-router-dom'
import Chart from 'chart.js/auto';
import { eventName, navigateToQuery } from '../query'
import { navigateToQuery } from '../query'
import numberFormatter, {durationFormatter} from '../number-formatter'
import * as api from '../api'
import {ThemeContext} from '../theme-context'
@ -67,7 +67,7 @@ const DAYS_ABBREV = [
]
function dateFormatter(interval, longForm) {
return function(isoDate, index, ticks) {
return function(isoDate, _index, _ticks) {
let date = new Date(isoDate)
if (interval === 'month') {
@ -147,7 +147,7 @@ class LineGraph extends React.Component {
return ` ${item.formattedValue} ${pluralizedLabel}`
}
},
footer: function(dataPoints) {
footer: function(_dataPoints) {
if (graphData.interval === 'month') {
return 'Click to view month'
} else if (graphData.interval === 'date') {
@ -178,7 +178,7 @@ class LineGraph extends React.Component {
grid: {display: false},
ticks: {
maxTicksLimit: 8,
callback: function(val, index, ticks) { return dateFormatter(graphData.interval)(this.getLabelForValue(val)) },
callback: function(val, _index, _ticks) { return dateFormatter(graphData.interval)(this.getLabelForValue(val)) },
color: this.props.darkTheme ? 'rgb(243, 244, 246)' : undefined
}
}
@ -345,7 +345,7 @@ class LineGraph extends React.Component {
}
}
LineGraph = withRouter(LineGraph)
const LineGraphWithRouter = withRouter(LineGraph)
export default class VisitorGraph extends React.Component {
constructor(props) {
@ -379,7 +379,7 @@ export default class VisitorGraph extends React.Component {
return (
<ThemeContext.Consumer>
{theme => (
<LineGraph graphData={this.state.graphData} site={this.props.site} query={this.props.query} darkTheme={theme}/>
<LineGraphWithRouter graphData={this.state.graphData} site={this.props.site} query={this.props.query} darkTheme={theme}/>
)}
</ThemeContext.Consumer>
)

View File

@ -9,7 +9,7 @@ export const withThemeProvider = (WrappedComponent) => {
dark: document.querySelector('html').classList.contains('dark') || false
};
this.mutationObserver = new MutationObserver((mutationsList, observer) => {
this.mutationObserver = new MutationObserver((mutationsList, _observer) => {
mutationsList.forEach(mutation => {
if (mutation.attributeName === 'class') {
this.setState({ dark: mutation.target.classList.contains('dark') });

View File

@ -6,6 +6,7 @@ if (window.Element && !Element.prototype.closest) {
el = this;
do {
i = matches.length;
// eslint-disable-next-line no-empty
while (--i >= 0 && matches.item(i) !== el) {};
} while ((i < 0) && (el = el.parentElement));
return el;

122
assets/package-lock.json generated
View File

@ -47,8 +47,8 @@
"webpack-cli": "^4.7.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.2.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
@ -62,11 +62,11 @@
}
},
"../deps/phoenix": {
"version": "1.5.8",
"version": "1.4.17",
"license": "MIT"
},
"../deps/phoenix_html": {
"version": "2.14.3"
"version": "2.14.2"
},
"node_modules/@babel/code-frame": {
"version": "7.12.13",
@ -2296,6 +2296,36 @@
"dev": true,
"license": "Apache-2.0"
},
"node_modules/babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
"integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
"deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.0.0",
"@babel/parser": "^7.7.0",
"@babel/traverse": "^7.7.0",
"@babel/types": "^7.7.0",
"eslint-visitor-keys": "^1.0.0",
"resolve": "^1.12.0"
},
"engines": {
"node": ">=6"
},
"peerDependencies": {
"eslint": ">= 4.12.1"
}
},
"node_modules/babel-eslint/node_modules/eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/babel-loader": {
"version": "8.2.2",
"license": "MIT",
@ -2737,11 +2767,6 @@
"typedarray": "^0.0.6"
}
},
"node_modules/confusing-browser-globals": {
"version": "1.0.10",
"dev": true,
"license": "MIT"
},
"node_modules/contains-path": {
"version": "0.1.0",
"dev": true,
@ -3646,43 +3671,6 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-airbnb": {
"version": "18.2.1",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-config-airbnb-base": "^14.2.1",
"object.assign": "^4.1.2",
"object.entries": "^1.1.2"
},
"engines": {
"node": ">= 6"
},
"peerDependencies": {
"eslint": "^5.16.0 || ^6.8.0 || ^7.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4 || ^3 || ^2.3.0 || ^1.7.0"
}
},
"node_modules/eslint-config-airbnb-base": {
"version": "14.2.1",
"dev": true,
"license": "MIT",
"dependencies": {
"confusing-browser-globals": "^1.0.10",
"object.assign": "^4.1.2",
"object.entries": "^1.1.2"
},
"engines": {
"node": ">= 6"
},
"peerDependencies": {
"eslint": "^5.16.0 || ^6.8.0 || ^7.2.0",
"eslint-plugin-import": "^2.22.1"
}
},
"node_modules/eslint-config-prettier": {
"version": "7.0.0",
"dev": true,
@ -11200,6 +11188,28 @@
"version": "2.2.0",
"dev": true
},
"babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
"integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/parser": "^7.7.0",
"@babel/traverse": "^7.7.0",
"@babel/types": "^7.7.0",
"eslint-visitor-keys": "^1.0.0",
"resolve": "^1.12.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true
}
}
},
"babel-loader": {
"version": "8.2.2",
"requires": {
@ -11480,10 +11490,6 @@
"typedarray": "^0.0.6"
}
},
"confusing-browser-globals": {
"version": "1.0.10",
"dev": true
},
"contains-path": {
"version": "0.1.0",
"dev": true
@ -12190,24 +12196,6 @@
}
}
},
"eslint-config-airbnb": {
"version": "18.2.1",
"dev": true,
"requires": {
"eslint-config-airbnb-base": "^14.2.1",
"object.assign": "^4.1.2",
"object.entries": "^1.1.2"
}
},
"eslint-config-airbnb-base": {
"version": "14.2.1",
"dev": true,
"requires": {
"confusing-browser-globals": "^1.0.10",
"object.assign": "^4.1.2",
"object.entries": "^1.1.2"
}
},
"eslint-config-prettier": {
"version": "7.0.0",
"dev": true,

View File

@ -3,7 +3,9 @@
"license": "MIT",
"scripts": {
"deploy": "$(npm bin)/webpack --mode production",
"watch": "$(npm bin)/webpack --mode development --watch"
"watch": "$(npm bin)/webpack --mode development --watch",
"format": "$(npm bin)/prettier --write {css,js}/**",
"lint": "$(npm bin)/eslint js/**"
},
"dependencies": {
"@babel/core": "^7.14.3",
@ -47,8 +49,8 @@
"webpack-cli": "^4.7.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.2.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",