kopia lustrzana https://github.com/Stopka/fedisearch
Properly applied code quality tools
rodzic
8958ab363a
commit
2dda770993
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"eslint:recommended",
|
||||
"plugin:@next/next/recommended",
|
||||
"standard"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint",
|
||||
"react",
|
||||
"react-hooks",
|
||||
"jsx-a11y",
|
||||
"import",
|
||||
"@next/next"
|
||||
],
|
||||
"rules": {
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": ["error"]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true
|
||||
}
|
|
@ -15,5 +15,8 @@ module.exports = {
|
|||
permanent: true
|
||||
}
|
||||
]
|
||||
},
|
||||
typescript: {
|
||||
tsconfigPath: '../tsconfig.json'
|
||||
}
|
||||
}
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -16,17 +16,20 @@
|
|||
"@apollo/client": "^3.6.9",
|
||||
"@datapunt/matomo-tracker-js": "^0.5.1",
|
||||
"@elastic/elasticsearch": "^8.2.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.17",
|
||||
"@hookform/resolvers": "^2.8.5",
|
||||
"@fortawesome/fontawesome-common-types": "^6.2.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@hookform/resolvers": "^2.9.10",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"apollo-server-micro": "^3.10.1",
|
||||
"axios": "^0.21.1",
|
||||
"bootstrap": "^5.1.3",
|
||||
"graphql": "^16.5.0",
|
||||
"micro": "^9.4.1",
|
||||
"micro-cors": "^0.1.1",
|
||||
"next": "^12.2.5",
|
||||
"nexus": "^1.3.0",
|
||||
|
@ -40,25 +43,48 @@
|
|||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^12.0.7",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@next/eslint-plugin-next": "^13.0.0",
|
||||
"@types/jest": "^29.2.0",
|
||||
"@types/micro-cors": "^0.1.2",
|
||||
"@types/node": "^18.7.18",
|
||||
"@types/npmlog": "^4.1.3",
|
||||
"@types/react": "^17.0.14",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.1",
|
||||
"eslint-plugin-react": "^7.27.1",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"jest": "^27.3.0",
|
||||
"standard": "*",
|
||||
"ts-jest": "^27.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||
"@typescript-eslint/parser": "^5.37.0",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-standard-react": "^12.0.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.2.5",
|
||||
"eslint-plugin-promise": "^6.0.1",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"jest": "^29.2.2",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"standard-with-typescript",
|
||||
"standard-react"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"tsconfig.json"
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/no-misused-promises": [
|
||||
"error",
|
||||
{
|
||||
"checksVoidReturn": {
|
||||
"attributes": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React from 'react'
|
||||
import FallbackImage from './FallbackImage'
|
||||
|
||||
const Avatar:React.FC<{url:string|null|undefined}> = ({ url }) => {
|
||||
const Avatar: React.FC<{ url: string | null | undefined }> = ({ url }) => {
|
||||
return (
|
||||
<FallbackImage
|
||||
className={'avatar'}
|
||||
src={url}
|
||||
fallbackSrc={'/avatar.svg'}
|
||||
alt={'Avatar'}
|
||||
/>
|
||||
<FallbackImage
|
||||
className={'avatar'}
|
||||
src={url ?? undefined}
|
||||
fallbackSrc={'/avatar.svg'}
|
||||
alt={'Avatar'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default Avatar
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
import React, { ImgHTMLAttributes, useEffect, useState } from 'react'
|
||||
export default function FallbackImage ({ fallbackSrc, src, alt, ...props }: ImgHTMLAttributes<HTMLImageElement>&{fallbackSrc?:string}) {
|
||||
import React, { ImgHTMLAttributes, ReactElement, useEffect, useState } from 'react'
|
||||
|
||||
export default function FallbackImage ({
|
||||
fallbackSrc,
|
||||
src,
|
||||
alt,
|
||||
...props
|
||||
}: ImgHTMLAttributes<HTMLImageElement> & { fallbackSrc?: string }): ReactElement {
|
||||
const [showFallback, setShowFallback] = useState<boolean>(false)
|
||||
useEffect(() => {
|
||||
setShowFallback(!src)
|
||||
setShowFallback(src === undefined || src === null || src === '')
|
||||
}, [src])
|
||||
const handleError = (event): void => {
|
||||
if (props.onError) {
|
||||
if (props.onError != null) {
|
||||
props.onError(event)
|
||||
}
|
||||
if (!fallbackSrc) {
|
||||
if (fallbackSrc === undefined || fallbackSrc === '') {
|
||||
return
|
||||
}
|
||||
setShowFallback(true)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import striptags from 'striptags'
|
||||
import Avatar from './Avatar'
|
||||
import SoftwareBadge from './badges/SoftwareBadge'
|
||||
|
@ -14,16 +14,16 @@ import { FeedResultItem } from '../graphql/client/queries/ListFeedsQuery'
|
|||
|
||||
const FeedResult = ({
|
||||
feed
|
||||
}:{ feed: FeedResultItem }) => {
|
||||
}: { feed: FeedResultItem }): ReactElement => {
|
||||
const fallbackEmojiImage = '/emoji.svg'
|
||||
|
||||
const handleEmojiImageError = (event) => {
|
||||
const handleEmojiImageError = (event): void => {
|
||||
event.target.src = fallbackEmojiImage
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.querySelectorAll('.with-emoji img').forEach(element => {
|
||||
if (element.attributes['data-error-handler']) {
|
||||
if (element.attributes['data-error-handler'] === 'attached') {
|
||||
return
|
||||
}
|
||||
element.addEventListener('error', handleEmojiImageError)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import FeedResult from './FeedResult'
|
||||
import { FeedResultItem } from '../graphql/client/queries/ListFeedsQuery'
|
||||
|
||||
const FeedResults = ({
|
||||
feeds
|
||||
}:{ feeds: FeedResultItem[] }) => {
|
||||
}: { feeds: FeedResultItem[] }): ReactElement => {
|
||||
if (feeds.length === 0) {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
|
||||
const Footer:React.FC = () => {
|
||||
const Footer: React.FC = () => {
|
||||
return (
|
||||
<footer className={'text-center mt-5'}>
|
||||
©{(new Date()).getFullYear()} <a href={'https://skorpil.cz'}>Štěpán Škorpil</a>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
|
||||
export function getFlagEmoji (countryCode:string):string {
|
||||
export function getFlagEmoji (countryCode: string): string {
|
||||
const codePoints = countryCode
|
||||
.toUpperCase()
|
||||
.split('')
|
||||
|
@ -8,18 +8,28 @@ export function getFlagEmoji (countryCode:string):string {
|
|||
return String.fromCodePoint(...codePoints)
|
||||
}
|
||||
|
||||
export interface GeoParams{
|
||||
countryCode?: string,
|
||||
countryName?:string,
|
||||
city?: string
|
||||
export interface GeoParams {
|
||||
countryCode?: string
|
||||
countryName?: string
|
||||
city?: string
|
||||
}
|
||||
|
||||
export default function Geo ({ countryCode, countryName, city }:GeoParams):React.ReactElement|null {
|
||||
if (!countryCode && !city) {
|
||||
export default function Geo ({ countryCode, countryName, city }: GeoParams): React.ReactElement | null {
|
||||
const alignedCountryCode = countryCode ?? ''
|
||||
const alignedCity = city ?? ''
|
||||
if (alignedCountryCode === '' && alignedCity === '') {
|
||||
return null
|
||||
}
|
||||
return <div className={'geo'}>
|
||||
{city ? <span className={'city'}>{city}</span> : ''}
|
||||
{countryCode ? <span className={'country'} title={`${countryName ?? countryCode}`}>{getFlagEmoji(countryCode)}</span> : ''}
|
||||
{
|
||||
alignedCity !== ''
|
||||
? <span className={'city'}>{alignedCity}</span>
|
||||
: ''
|
||||
}
|
||||
{
|
||||
alignedCountryCode !== ''
|
||||
? <span className={'country'} title={`${countryName ?? alignedCountryCode}`}>{getFlagEmoji(alignedCountryCode)}</span>
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -17,10 +17,10 @@ const Loader: React.FC<{ children: ReactNode, loading: boolean, hideContent?: bo
|
|||
</div>
|
||||
)
|
||||
|
||||
if (table) {
|
||||
if (table !== undefined || table !== 0) {
|
||||
return (
|
||||
<>
|
||||
{showTop && loading
|
||||
{(showTop ?? false) && loading
|
||||
? (
|
||||
<tbody>
|
||||
<tr className={className}>
|
||||
|
@ -31,8 +31,8 @@ const Loader: React.FC<{ children: ReactNode, loading: boolean, hideContent?: bo
|
|||
</tbody>
|
||||
)
|
||||
: ''}
|
||||
{hideContent && loading ? '' : children}
|
||||
{showBottom && loading
|
||||
{(hideContent ?? false) && loading ? '' : children}
|
||||
{(showBottom ?? false) && loading
|
||||
? (
|
||||
<tbody>
|
||||
<tr className={className}>
|
||||
|
@ -50,9 +50,9 @@ const Loader: React.FC<{ children: ReactNode, loading: boolean, hideContent?: bo
|
|||
}
|
||||
return (
|
||||
<>
|
||||
{showTop && loading ? spinner : ''}
|
||||
{hideContent && loading ? '' : children}
|
||||
{showBottom && loading ? spinner : ''}
|
||||
{(showTop ?? false) && loading ? spinner : ''}
|
||||
{(hideContent ?? false) && loading ? '' : children}
|
||||
{(showBottom ?? false) && loading ? spinner : ''}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import NavItem from './NavItem'
|
|||
import { faUser, faServer, faChartPie } from '@fortawesome/free-solid-svg-icons'
|
||||
import FallbackImage from './FallbackImage'
|
||||
|
||||
const NavBar:React.FC = () => {
|
||||
const NavBar: React.FC = () => {
|
||||
const [showMenu, setShowMenu] = useState<boolean>(false)
|
||||
return (
|
||||
<nav className="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useRouter } from 'next/router'
|
|||
import { IconProp } from '@fortawesome/fontawesome-svg-core'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
const NavItem:FC<{path:string, label:string, icon:IconProp}> = ({ path, label, icon }) => {
|
||||
const NavItem: FC<{ path: string, label: string, icon: IconProp }> = ({ path, label, icon }) => {
|
||||
const router = useRouter()
|
||||
const active = router.pathname === path
|
||||
return (
|
||||
|
|
|
@ -2,8 +2,8 @@ import React from 'react'
|
|||
import Avatar from './Avatar'
|
||||
import { ParentFeedItem } from '../graphql/client/queries/ListFeedsQuery'
|
||||
|
||||
const ParentFeed: React.FC<{feed:ParentFeedItem|null}> = ({ feed }) => {
|
||||
if (!feed) {
|
||||
const ParentFeed: React.FC<{ feed: ParentFeedItem | null }> = ({ feed }) => {
|
||||
if (feed == null) {
|
||||
return (<></>)
|
||||
}
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
|
||||
const ProgressBar: React.FC<{ percents: number, way?: 'left' | 'right' | 'top' | 'bottom', color?:string }> = ({ percents, way, color }) => {
|
||||
const ProgressBar: React.FC<{ percents: number, way?: 'left' | 'right' | 'top' | 'bottom', color?: string }> = ({ percents, way, color }) => {
|
||||
way = way ?? 'right'
|
||||
percents = Math.round(percents)
|
||||
color = color ?? 'var(--accent-color)'
|
||||
|
|
|
@ -4,8 +4,8 @@ import { faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons'
|
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
const SortToggle: React.FC<{
|
||||
onToggle:(StatsRequestSortBy)=>void,
|
||||
field:string,
|
||||
onToggle: (StatsRequestSortBy) => void
|
||||
field: string
|
||||
sort: Sort
|
||||
}> = ({ onToggle, field, sort, children }) => {
|
||||
return (
|
||||
|
|
|
@ -2,12 +2,12 @@ import React from 'react'
|
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core'
|
||||
|
||||
const Badge:React.FC<{ faIcon:IconProp, label:string, value:string|number|null, className?:string, showUnknown?:boolean }> = ({ faIcon, label, value, className, showUnknown }) => {
|
||||
const Badge: React.FC<{ faIcon: IconProp, label: string, value: string | number | null, className?: string, showUnknown?: boolean }> = ({ faIcon, label, value, className, showUnknown }) => {
|
||||
if (value === null && showUnknown !== true) {
|
||||
return (<></>)
|
||||
}
|
||||
return (
|
||||
<div className={'badge bg-secondary ' + className} title={label}>
|
||||
<div className={`badge bg-secondary ${className ?? ''}`} title={label}>
|
||||
<FontAwesomeIcon icon={faIcon} className={'margin-right'}/>
|
||||
<span className="visually-hidden">{label}:</span>
|
||||
<span className={'value'}>{value === null ? '?' : value}</span>
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { faRobot } from '@fortawesome/free-solid-svg-icons'
|
||||
import Badge from './Badge'
|
||||
|
||||
const BotBadge:React.FC<{ bot: boolean | null}> = ({ bot }) => {
|
||||
const BotBadge: React.FC<{ bot: boolean | null }> = ({ bot }) => {
|
||||
return (
|
||||
<Badge faIcon={faRobot}
|
||||
label={'Bot'}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { faCalendarPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import Badge from './Badge'
|
||||
|
||||
const CreatedAtBadge:React.FC<{ createdAt: string | null }> = ({ createdAt }) => {
|
||||
const CreatedAtBadge: React.FC<{ createdAt: string | null }> = ({ createdAt }) => {
|
||||
return (
|
||||
<Badge faIcon={faCalendarPlus}
|
||||
label={'Created at'}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { faRss, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import Badge from './Badge'
|
||||
|
||||
const FeedTypeBadge:React.FC<{ type: 'account' | 'channel' }> = ({ type }) => {
|
||||
const FeedTypeBadge: React.FC<{ type: 'account' | 'channel' }> = ({ type }) => {
|
||||
return (
|
||||
<Badge faIcon={type === 'channel' ? faRss : faUser}
|
||||
label={'Feed type'}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { faUserFriends } from '@fortawesome/free-solid-svg-icons'
|
|||
import React from 'react'
|
||||
import Badge from './Badge'
|
||||
|
||||
const FollowersBadge:React.FC<{ followers: number|null}> = ({ followers }) => {
|
||||
const FollowersBadge: React.FC<{ followers: number | null }> = ({ followers }) => {
|
||||
return (
|
||||
<Badge faIcon={faUserFriends}
|
||||
label={'Followers'}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { faEye } from '@fortawesome/free-solid-svg-icons'
|
|||
import React from 'react'
|
||||
import Badge from './Badge'
|
||||
|
||||
const FollowingBadge:React.FC<{ following: number|null}> = ({ following }) => {
|
||||
const FollowingBadge: React.FC<{ following: number | null }> = ({ following }) => {
|
||||
return (
|
||||
<Badge faIcon={faEye}
|
||||
label={'Following'}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import Badge from './Badge'
|
||||
import { faCalendarCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
const LastPostAtBadge:React.FC<{ lastStatusAt: string | null }> = ({ lastStatusAt }) => {
|
||||
const LastPostAtBadge: React.FC<{ lastStatusAt: string | null }> = ({ lastStatusAt }) => {
|
||||
return (
|
||||
<Badge faIcon={faCalendarCheck}
|
||||
label={'Last status at'}
|
||||
|
|
|
@ -8,8 +8,8 @@ const SoftwareBadge: React.FC<{ softwareName: string | null }> = ({ softwareName
|
|||
<FallbackImage className={'icon'}
|
||||
src={softwareName !== null ? `/software/${softwareName}.svg` : fallbackImage}
|
||||
fallbackSrc={fallbackImage}
|
||||
alt={softwareName}
|
||||
title={softwareName}
|
||||
alt={softwareName ?? undefined}
|
||||
title={softwareName ?? undefined}
|
||||
/>
|
||||
<span className={'value'}>{softwareName}</span>
|
||||
</div>)
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { faCommentAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import Badge from './Badge'
|
||||
|
||||
const StatusesCountBadge:React.FC<{ statusesCount: number | null }> = ({ statusesCount }) => {
|
||||
const StatusesCountBadge: React.FC<{ statusesCount: number | null }> = ({ statusesCount }) => {
|
||||
return (
|
||||
<Badge faIcon={faCommentAlt}
|
||||
label={'Status count'}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ApolloClient, InMemoryCache } from '@apollo/client'
|
||||
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client'
|
||||
|
||||
export default function createGraphqlClient () {
|
||||
export default function createGraphqlClient (): ApolloClient<NormalizedCacheObject> {
|
||||
return new ApolloClient({
|
||||
uri: '/api/graphql',
|
||||
cache: new InMemoryCache()
|
||||
|
|
|
@ -57,56 +57,57 @@ export const ListFeedsQuery = gql`
|
|||
}
|
||||
`
|
||||
|
||||
export type ParentFeedItem = {
|
||||
id: string,
|
||||
avatar: string,
|
||||
displayName: string
|
||||
export interface ParentFeedItem {
|
||||
id: string
|
||||
avatar: string
|
||||
displayName: string
|
||||
name: string
|
||||
domain: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface FeedResultItem {
|
||||
id: string
|
||||
avatar: string
|
||||
displayName: string
|
||||
foundAt: string
|
||||
bot: boolean
|
||||
createdAt: string
|
||||
description: string
|
||||
followersCount: number
|
||||
followingCount: number
|
||||
lastStatusAt: string
|
||||
locked: boolean
|
||||
name: string
|
||||
refreshedAt: string
|
||||
statusesCount: number
|
||||
type: 'account' | 'channel'
|
||||
url: string
|
||||
fields: Array<{
|
||||
name: string
|
||||
value: string
|
||||
}>
|
||||
node: {
|
||||
domain: string
|
||||
url:string
|
||||
foundAt: string
|
||||
geoip: {
|
||||
// eslint-disable-next-line camelcase
|
||||
city_name: string
|
||||
// eslint-disable-next-line camelcase
|
||||
country_iso_code: string
|
||||
}
|
||||
halfYearActiveUserCount: number
|
||||
id: string
|
||||
monthActiveUserCount: number
|
||||
name: string
|
||||
openRegistrations: boolean
|
||||
refreshAttemptedAt: string
|
||||
refreshedAt: string
|
||||
softwareName: string
|
||||
}
|
||||
parent: ParentFeedItem | null
|
||||
}
|
||||
|
||||
export type FeedResultItem = {
|
||||
id: string,
|
||||
avatar: string,
|
||||
displayName: string,
|
||||
foundAt: string,
|
||||
bot: boolean,
|
||||
createdAt: string,
|
||||
description: string,
|
||||
followersCount: number,
|
||||
followingCount: number,
|
||||
lastStatusAt: string,
|
||||
locked: boolean,
|
||||
name: string,
|
||||
refreshedAt: string,
|
||||
statusesCount: number,
|
||||
type: 'account' | 'channel'
|
||||
url: string,
|
||||
fields: {
|
||||
name: string, value: string
|
||||
}[],
|
||||
node: {
|
||||
domain: string,
|
||||
foundAt: string,
|
||||
geoip: {
|
||||
// eslint-disable-next-line camelcase
|
||||
city_name: string,
|
||||
// eslint-disable-next-line camelcase
|
||||
country_iso_code: string,
|
||||
},
|
||||
halfYearActiveUserCount: number,
|
||||
id: string,
|
||||
monthActiveUserCount: number,
|
||||
name: string,
|
||||
openRegistrations: boolean,
|
||||
refreshAttemptedAt: string,
|
||||
refreshedAt: string,
|
||||
softwareName: string
|
||||
},
|
||||
parent: ParentFeedItem|null
|
||||
}
|
||||
|
||||
export type ListFeedsResult = {
|
||||
listFeeds: List<FeedResultItem>
|
||||
export interface ListFeedsResult {
|
||||
listFeeds: List<FeedResultItem>
|
||||
}
|
||||
|
|
|
@ -32,30 +32,30 @@ export const ListNodesQuery = gql`
|
|||
}
|
||||
`
|
||||
|
||||
export type NodeResultItem = {
|
||||
domain: string,
|
||||
foundAt: string,
|
||||
geoip: {
|
||||
// eslint-disable-next-line camelcase
|
||||
city_name: string,
|
||||
// eslint-disable-next-line camelcase
|
||||
country_iso_code: string,
|
||||
},
|
||||
halfYearActiveUserCount: number,
|
||||
id: string,
|
||||
monthActiveUserCount: number,
|
||||
name: string,
|
||||
openRegistrations: boolean,
|
||||
refreshAttemptedAt: string,
|
||||
refreshedAt: string,
|
||||
softwareName: string,
|
||||
softwareVersion: string,
|
||||
standardizedSoftwareVersion:string,
|
||||
totalUserCount: number,
|
||||
statusesCount: number,
|
||||
accountFeedCount: number
|
||||
export interface NodeResultItem {
|
||||
domain: string
|
||||
foundAt: string
|
||||
geoip: {
|
||||
// eslint-disable-next-line camelcase
|
||||
city_name: string
|
||||
// eslint-disable-next-line camelcase
|
||||
country_iso_code: string
|
||||
}
|
||||
halfYearActiveUserCount: number
|
||||
id: string
|
||||
monthActiveUserCount: number
|
||||
name: string
|
||||
openRegistrations: boolean
|
||||
refreshAttemptedAt: string
|
||||
refreshedAt: string
|
||||
softwareName: string
|
||||
softwareVersion: string
|
||||
standardizedSoftwareVersion: string
|
||||
totalUserCount: number
|
||||
statusesCount: number
|
||||
accountFeedCount: number
|
||||
}
|
||||
|
||||
export type ListNodesResult = {
|
||||
listNodes: List<NodeResultItem>;
|
||||
export interface ListNodesResult {
|
||||
listNodes: List<NodeResultItem>
|
||||
}
|
||||
|
|
|
@ -14,13 +14,13 @@ export const ListStatsQuery = gql`
|
|||
}
|
||||
`
|
||||
|
||||
export type StatsResultItem = {
|
||||
softwareName: string,
|
||||
nodeCount: number,
|
||||
accountFeedCount: number,
|
||||
channelFeedCount: number
|
||||
export interface StatsResultItem {
|
||||
softwareName: string
|
||||
nodeCount: number
|
||||
accountFeedCount: number
|
||||
channelFeedCount: number
|
||||
}
|
||||
|
||||
export type ListStatsResult = {
|
||||
listStats: List<StatsResultItem>;
|
||||
export interface ListStatsResult {
|
||||
listStats: List<StatsResultItem>
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { PagingType } from '../../server/schema/types'
|
||||
|
||||
export type List<TItem> = {
|
||||
paging: PagingType,
|
||||
items: TItem[]
|
||||
export interface List<TItem> {
|
||||
paging: PagingType
|
||||
items: TItem[]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { FeedQueryInputType } from '../types/FeedQueryInput'
|
||||
import { PagingInputType } from '../types/PagingInput'
|
||||
|
||||
export type ListFeedsVariables = {
|
||||
paging: PagingInputType;
|
||||
query: FeedQueryInputType
|
||||
export interface ListFeedsVariables {
|
||||
paging: PagingInputType
|
||||
query: FeedQueryInputType
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { PagingInputType } from '../types/PagingInput'
|
||||
import { NodeQueryInputType } from '../types/NodeQueryInput'
|
||||
|
||||
export type ListNodesVariables = {
|
||||
paging: PagingInputType;
|
||||
query: NodeQueryInputType
|
||||
export interface ListNodesVariables {
|
||||
paging: PagingInputType
|
||||
query: NodeQueryInputType
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { StatsQueryInputType } from '../types/StatsQueryInput'
|
||||
|
||||
export type ListStatsVariables = {
|
||||
query: StatsQueryInputType
|
||||
export interface ListStatsVariables {
|
||||
query: StatsQueryInputType
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
export const NodeSortingByValues:readonly [string, ...string[]] = [
|
||||
export const NodeSortingByValues: readonly [string, ...string[]] = [
|
||||
'domain',
|
||||
'softwareName',
|
||||
'totalUserCount',
|
||||
|
@ -14,4 +14,4 @@ export const NodeSortingByValues:readonly [string, ...string[]] = [
|
|||
|
||||
export const nodeSortingBySchema = z.enum(NodeSortingByValues)
|
||||
|
||||
export type NodeSoringByEnumType = z.infer<typeof nodeSortingBySchema>;
|
||||
export type NodeSoringByEnumType = z.infer<typeof nodeSortingBySchema>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export type PagingInputType = {
|
||||
page: number
|
||||
export interface PagingInputType {
|
||||
page: number
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
export const createSortingInputSchema = (members:z.ZodEnum<[string, ...string[]]>) => {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export const createSortingInputSchema = (members: z.ZodEnum<[string, ...string[]]>) => {
|
||||
return z.object({
|
||||
sortBy: members,
|
||||
sortWay: z.enum(['asc', 'desc'])
|
||||
})
|
||||
}
|
||||
|
||||
export type SortingInputType<TMembers> = {
|
||||
sortBy: TMembers
|
||||
sortWay: 'asc'|'desc'
|
||||
export interface SortingInputType<TMembers> {
|
||||
sortBy: TMembers
|
||||
sortWay: 'asc' | 'desc'
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
export const StatsSortingByValues:readonly [string, ...string[]] = [
|
||||
export const StatsSortingByValues: readonly [string, ...string[]] = [
|
||||
'softwareName',
|
||||
'nodeCount',
|
||||
'accountFeedCount',
|
||||
|
@ -9,4 +9,4 @@ export const StatsSortingByValues:readonly [string, ...string[]] = [
|
|||
|
||||
export const statsSortingBySchema = z.enum(StatsSortingByValues)
|
||||
|
||||
export type StatsSoringByEnumType = z.infer<typeof statsSortingBySchema>;
|
||||
export type StatsSoringByEnumType = z.infer<typeof statsSortingBySchema>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { ElasticClient } from '../../../lib/storage/ElasticClient'
|
||||
|
||||
type Context = {
|
||||
elasticClient: ElasticClient
|
||||
defaultPaging: {
|
||||
limit: 20
|
||||
}
|
||||
interface Context {
|
||||
elasticClient: ElasticClient
|
||||
defaultPaging: {
|
||||
limit: 20
|
||||
}
|
||||
}
|
||||
|
||||
export default Context
|
||||
|
|
|
@ -3,7 +3,7 @@ import resolvers from './resolvers'
|
|||
import schema from './schema'
|
||||
import { createContext } from './context'
|
||||
|
||||
export default function createGraphqlServer () {
|
||||
export default function createGraphqlServer (): ApolloServer {
|
||||
return new ApolloServer({
|
||||
schema,
|
||||
resolvers,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { join } from 'path'
|
|||
import * as types from './types'
|
||||
import * as queries from './queries'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import * as elastic from './sources/elastic'
|
||||
|
||||
const schema = makeSchema({
|
||||
types: {
|
||||
|
|
|
@ -21,7 +21,7 @@ export const listNodes = extendType({
|
|||
default: { default: '', sortBy: 'refreshedAt', sortWay: 'desc' }
|
||||
})
|
||||
},
|
||||
resolve: async (event, { paging, query }:ListNodesVariables, { elasticClient, defaultPaging }: Context) => {
|
||||
resolve: async (event, { paging, query }: ListNodesVariables, { elasticClient, defaultPaging }: Context) => {
|
||||
console.info('Searching nodes', { paging, query })
|
||||
|
||||
const results = await elasticClient.search<Node>({
|
||||
|
|
|
@ -5,6 +5,7 @@ import { ListStatsVariables } from '../../../common/queries/listStats'
|
|||
import nodeIndex from '../../../../lib/storage/Definitions/nodeIndex'
|
||||
import { StatsQueryInputType } from '../../../common/types/StatsQueryInput'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
const getSort = (query: StatsQueryInputType) => {
|
||||
switch (query.sortBy) {
|
||||
case 'nodeCount':
|
||||
|
@ -30,7 +31,7 @@ export const listStats = extendType({
|
|||
default: { sortBy: 'nodeCount', sortWay: 'desc' }
|
||||
})
|
||||
},
|
||||
resolve: async (event, { query }:ListStatsVariables, { elasticClient }: Context) => {
|
||||
resolve: async (event, { query }: ListStatsVariables, { elasticClient }: Context) => {
|
||||
console.info('Searching stats', { query })
|
||||
|
||||
const results = await elasticClient.search({
|
||||
|
@ -59,7 +60,7 @@ export const listStats = extendType({
|
|||
sort: {
|
||||
bucket_sort: {
|
||||
sort: [
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
getSort(query)
|
||||
]
|
||||
}
|
||||
|
@ -68,16 +69,16 @@ export const listStats = extendType({
|
|||
}
|
||||
}
|
||||
})
|
||||
type Aggregation = {
|
||||
buckets:{
|
||||
key:string,
|
||||
interface Aggregation {
|
||||
buckets: Array<{
|
||||
key: string
|
||||
// eslint-disable-next-line camelcase
|
||||
doc_count:number
|
||||
accountFeedCount: {value:number}
|
||||
channelFeedCount: {value:number}
|
||||
}[]
|
||||
doc_count: number
|
||||
accountFeedCount: { value: number }
|
||||
channelFeedCount: { value: number }
|
||||
}>
|
||||
}
|
||||
const software = results.aggregations.software as Aggregation
|
||||
const software = results?.aggregations?.software as Aggregation
|
||||
return {
|
||||
items: software.buckets.map(bucket => {
|
||||
return {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default function getFlagEmoji (countryCode:string):string {
|
||||
export default function getFlagEmoji (countryCode: string): string {
|
||||
const codePoints = countryCode
|
||||
.toUpperCase()
|
||||
.split('')
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { UserOptions } from '@datapunt/matomo-tracker-js/es/types'
|
||||
import MatomoTracker from '@datapunt/matomo-tracker-js'
|
||||
|
||||
let matomo:MatomoTracker|undefined
|
||||
let matomo: MatomoTracker | undefined
|
||||
|
||||
const getMatomo = (config:UserOptions):MatomoTracker => {
|
||||
if (!matomo) {
|
||||
const getMatomo = (config: UserOptions): MatomoTracker => {
|
||||
if (matomo == null) {
|
||||
console.info('Starting Matomo', config)
|
||||
matomo = new MatomoTracker(config)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default function getNodeId (name:string, domain:string):string {
|
||||
export default function getNodeId (name: string, domain: string): string {
|
||||
return `${name}@${domain}`
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
export const matomoConfig = {
|
||||
urlBase: typeof process.env.MATOMO_URL === 'string' && process.env.MATOMO_URL !== ''
|
||||
? process.env.MATOMO_URL
|
||||
: 'https://domain.tld',
|
||||
siteId: parseInt(typeof process.env.MATOMO_SITE_ID === 'string' && process.env.MATOMO_SITE_ID !== ''
|
||||
? process.env.MATOMO_SITE_ID
|
||||
: '1'
|
||||
urlBase:
|
||||
process.env.MATOMO_URL !== undefined && process.env.MATOMO_URL !== ''
|
||||
? process.env.MATOMO_URL
|
||||
: 'https://domain.tld',
|
||||
siteId: parseInt(
|
||||
process.env.MATOMO_SITE_ID !== undefined && process.env.MATOMO_SITE_ID !== ''
|
||||
? process.env.MATOMO_SITE_ID
|
||||
: '1'
|
||||
),
|
||||
disabled: !process.env.MATOMO_URL || !process.env.MATOMO_SITE_ID
|
||||
disabled: (process.env.MATOMO_URL ?? '') === '' || (process.env.MATOMO_SITE_ID ?? '') === ''
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default function prepareSimpleQuery (search:string):string {
|
||||
export default function prepareSimpleQuery (search: string): string {
|
||||
const tokens = search.split(/\s+/)
|
||||
const searchContainsWildcard = tokens.filter(token => token.length > 0 && token.slice(-1) === '*').length > 0
|
||||
return tokens.map(token => searchContainsWildcard ? token : token + '*').join(' ')
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
import Field from './Field'
|
||||
|
||||
interface Feed {
|
||||
domain: string
|
||||
foundAt: number,
|
||||
refreshedAt?: number,
|
||||
name: string,
|
||||
fullName: string,
|
||||
displayName: string,
|
||||
description: string,
|
||||
strippedDescription?: string,
|
||||
followersCount?: number,
|
||||
followingCount?: number,
|
||||
statusesCount?: number,
|
||||
lastStatusAt?: number,
|
||||
createdAt?: number,
|
||||
bot?: boolean,
|
||||
locked: boolean,
|
||||
url: string,
|
||||
avatar?: string,
|
||||
type: 'account' | 'channel',
|
||||
parentFeedName?: string,
|
||||
parentFeedDomain?: string
|
||||
fields: Field[],
|
||||
extractedEmails: string[],
|
||||
extractedTags: string[]
|
||||
domain: string
|
||||
foundAt: number
|
||||
refreshedAt?: number
|
||||
name: string
|
||||
fullName: string
|
||||
displayName: string
|
||||
description: string
|
||||
strippedDescription?: string
|
||||
followersCount?: number
|
||||
followingCount?: number
|
||||
statusesCount?: number
|
||||
lastStatusAt?: number
|
||||
createdAt?: number
|
||||
bot?: boolean
|
||||
locked: boolean
|
||||
url: string
|
||||
avatar?: string
|
||||
type: 'account' | 'channel'
|
||||
parentFeedName?: string
|
||||
parentFeedDomain?: string
|
||||
fields: Field[]
|
||||
extractedEmails: string[]
|
||||
extractedTags: string[]
|
||||
}
|
||||
|
||||
export default Feed
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
interface Field {
|
||||
name: string,
|
||||
value: string
|
||||
strippedName?: string
|
||||
strippedValue?: string
|
||||
name: string
|
||||
value: string
|
||||
strippedName?: string
|
||||
strippedValue?: string
|
||||
}
|
||||
|
||||
export default Field
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
interface Geo {
|
||||
cityName?: string,
|
||||
continentName?: string,
|
||||
countryIsoCode?: string,
|
||||
countryName?: string,
|
||||
latitude: number
|
||||
longitude: number
|
||||
location?: string,
|
||||
regionIsoCode?: string,
|
||||
regionName?: string
|
||||
cityName?: string
|
||||
continentName?: string
|
||||
countryIsoCode?: string
|
||||
countryName?: string
|
||||
latitude: number
|
||||
longitude: number
|
||||
location?: string
|
||||
regionIsoCode?: string
|
||||
regionName?: string
|
||||
}
|
||||
|
||||
export default Geo
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import Geo from './Geo'
|
||||
|
||||
interface Node {
|
||||
name?:string,
|
||||
strippedName?:string,
|
||||
foundAt: number,
|
||||
refreshAttemptedAt?: number
|
||||
refreshedAt?: number
|
||||
openRegistrations?: boolean
|
||||
domain: string,
|
||||
serverIps?: string[],
|
||||
geoip?: Geo[],
|
||||
softwareName?: string;
|
||||
softwareVersion?: string
|
||||
standardizedSoftwareVersion?: string
|
||||
halfYearActiveUserCount?: number,
|
||||
monthActiveUserCount?: number,
|
||||
statusesCount?: number,
|
||||
totalUserCount?: number,
|
||||
discoveredByDomain?:string,
|
||||
accountFeedCount?: number,
|
||||
channelFeedCount?: number,
|
||||
name?: string
|
||||
strippedName?: string
|
||||
foundAt: number
|
||||
refreshAttemptedAt?: number
|
||||
refreshedAt?: number
|
||||
openRegistrations?: boolean
|
||||
domain: string
|
||||
serverIps?: string[]
|
||||
geoip?: Geo[]
|
||||
softwareName?: string
|
||||
softwareVersion?: string
|
||||
standardizedSoftwareVersion?: string
|
||||
halfYearActiveUserCount?: number
|
||||
monthActiveUserCount?: number
|
||||
statusesCount?: number
|
||||
totalUserCount?: number
|
||||
discoveredByDomain?: string
|
||||
accountFeedCount?: number
|
||||
channelFeedCount?: number
|
||||
}
|
||||
|
||||
export default Node
|
||||
|
|
|
@ -6,7 +6,7 @@ const elasticClient = new Client({
|
|||
},
|
||||
auth: {
|
||||
username: process.env.ELASTIC_USER ?? 'elastic',
|
||||
password: process.env.ELASTIC_PASSWORD
|
||||
password: process.env.ELASTIC_PASSWORD ?? ''
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ZodSchema } from 'zod'
|
||||
|
||||
export function preserveUndefined<Source, Target> (cast: (value:Source)=>Target) {
|
||||
return (value:Source|undefined):Target|undefined => {
|
||||
export function preserveUndefined<Source, Target> (cast: (value: Source) => Target) {
|
||||
return (value: Source | undefined): Target | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ export function preserveUndefined<Source, Target> (cast: (value:Source)=>Target)
|
|||
}
|
||||
}
|
||||
|
||||
export function preserveNull<Source, Target> (cast: (value:Source)=>Target) {
|
||||
return (value:Source|null):Target|null => {
|
||||
export function preserveNull<Source, Target> (cast: (value: Source) => Target) {
|
||||
return (value: Source | null): Target | null => {
|
||||
if (value === null) {
|
||||
return null
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ export function preserveNull<Source, Target> (cast: (value:Source)=>Target) {
|
|||
}
|
||||
}
|
||||
|
||||
export function undefinedToDefault<Type> (defaultValue:Type): (value:Type|undefined)=>Type {
|
||||
export function undefinedToDefault<Type> (defaultValue: Type): (value: Type | undefined) => Type {
|
||||
return (value) => typeof value === 'undefined' ? defaultValue : value
|
||||
}
|
||||
|
||||
export function stringTrimmed (value: string|undefined): string {
|
||||
export function stringTrimmed (value: string | undefined): string {
|
||||
return (value ?? '').trim().replace(/^\++|\++$/g, '')
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ export function stringToInt (value: string): number {
|
|||
return parseInt(value)
|
||||
}
|
||||
|
||||
export function stringToBool (value:string):boolean {
|
||||
export function stringToBool (value: string): boolean {
|
||||
switch (value) {
|
||||
case 'true':
|
||||
case '1':
|
||||
|
@ -42,7 +42,8 @@ export function stringToBool (value:string):boolean {
|
|||
}
|
||||
}
|
||||
|
||||
export function transform<Target> (originalSchema:ZodSchema<any>, cast:(value:unknown)=>Target, newSchema:ZodSchema<any>) {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export function transform<Target> (originalSchema: ZodSchema<any>, cast: (value: unknown) => Target, newSchema: ZodSchema<any>) {
|
||||
return originalSchema.refine(
|
||||
value => {
|
||||
try {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import '../styles/global.scss'
|
||||
import { AppProps } from 'next/app'
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ApolloProvider } from '@apollo/client'
|
||||
import createGraphqlClient from '../graphql/client/createGraphqlClient'
|
||||
|
||||
const graphqlClient = createGraphqlClient()
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
const App = ({ Component, pageProps }: AppProps): ReactElement => {
|
||||
return <ApolloProvider client={graphqlClient}>
|
||||
<Component {...pageProps} />
|
||||
</ApolloProvider>
|
||||
|
|
|
@ -8,7 +8,7 @@ const graphqlServer = createGraphqlServer()
|
|||
|
||||
const startedServer = graphqlServer.start()
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) :Promise<void> => {
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.end()
|
||||
return
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Head from 'next/head'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Loader from '../components/Loader'
|
||||
import FeedResults from '../components/FeedResults'
|
||||
import Layout, { siteTitle } from '../components/Layout'
|
||||
|
@ -16,7 +16,7 @@ import getMatomo from '../lib/getMatomo'
|
|||
import { feedQueryInputSchema, FeedQueryInputType } from '../graphql/common/types/FeedQueryInput'
|
||||
import { ListFeedsVariables } from '../graphql/common/queries/listFeeds'
|
||||
|
||||
const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ matomoConfig }) => {
|
||||
const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ matomoConfig }): ReactElement => {
|
||||
const router = useRouter()
|
||||
const routerQuery = feedQueryInputSchema.parse(router.query)
|
||||
const [page, setPage] = useState<number>(0)
|
||||
|
@ -28,8 +28,9 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
query
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
useEffect((): void => {
|
||||
router.push({ query })
|
||||
.catch((error) => console.error(error))
|
||||
getMatomo(matomoConfig).trackEvent({
|
||||
category: 'feeds',
|
||||
action: 'new-search'
|
||||
|
@ -48,7 +49,7 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
})
|
||||
}, [page])
|
||||
|
||||
const handleQueryChange = (event) => {
|
||||
const handleQueryChange = (event): void => {
|
||||
const inputElement = event.target
|
||||
const value = inputElement.value
|
||||
const name = inputElement.name
|
||||
|
@ -60,7 +61,7 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
setPage(0)
|
||||
}
|
||||
|
||||
const handleSearchSubmit = async (event) => {
|
||||
const handleSearchSubmit = async (event): Promise<void> => {
|
||||
event.preventDefault()
|
||||
setPageLoading(true)
|
||||
setPage(0)
|
||||
|
@ -68,7 +69,7 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
setPageLoading(false)
|
||||
}
|
||||
|
||||
const handleLoadMore = async (event) => {
|
||||
const handleLoadMore = async (event): Promise<void> => {
|
||||
event.preventDefault()
|
||||
setPageLoading(true)
|
||||
await fetchMore({
|
||||
|
@ -117,12 +118,12 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
|
||||
<Loader loading={loading || pageLoading} showBottom={true}>
|
||||
{
|
||||
data && query.search
|
||||
? <FeedResults feeds={data.listFeeds.items} />
|
||||
: ''
|
||||
(data != null) && query.search.length > 0
|
||||
? <FeedResults feeds={data.listFeeds.items}/>
|
||||
: ''
|
||||
}
|
||||
</Loader>
|
||||
{!loading && !pageLoading && data?.listFeeds?.paging?.hasNext
|
||||
{!loading && !pageLoading && data?.listFeeds?.paging?.hasNext !== undefined && data?.listFeeds?.paging?.hasNext
|
||||
? (
|
||||
<div className={'d-flex justify-content-center'}>
|
||||
<button className={'btn btn-secondary'} onClick={handleLoadMore}>
|
||||
|
@ -132,10 +133,10 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
</div>
|
||||
)
|
||||
: ''}
|
||||
{error
|
||||
{(error != null)
|
||||
? (<div className={'d-flex justify-content-center'}>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/>
|
||||
<span>{error.message}</span>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/>
|
||||
<span>{error.message}</span>
|
||||
</div>)
|
||||
: ''}
|
||||
</Layout>
|
||||
|
|
|
@ -21,7 +21,7 @@ import { NodeSoringByEnumType } from '../graphql/common/types/NodeSortingByEnum'
|
|||
|
||||
const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ matomoConfig }) => {
|
||||
const router = useRouter()
|
||||
let routerQuery:NodeQueryInputType
|
||||
let routerQuery: NodeQueryInputType
|
||||
try {
|
||||
routerQuery = nodeQueryInputSchema.parse(router.query)
|
||||
} catch (e) {
|
||||
|
@ -56,26 +56,26 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
]
|
||||
})
|
||||
}, [page])
|
||||
useEffect(() => {
|
||||
router.push({ query })
|
||||
useEffect((): void => {
|
||||
void router.push({ query })
|
||||
getMatomo(matomoConfig).trackEvent({
|
||||
category: 'nodes',
|
||||
action: 'new-search'
|
||||
})
|
||||
}, [query])
|
||||
|
||||
const handleQueryChange = (event) => {
|
||||
const handleQueryChange = (event): void => {
|
||||
const targetInput = event.target
|
||||
const value = targetInput.value
|
||||
const name = targetInput.name
|
||||
const newQuery:NodeQueryInputType = { ...query }
|
||||
const newQuery: NodeQueryInputType = { ...query }
|
||||
newQuery[name] = value
|
||||
console.info('Query changed', { name, value })
|
||||
setQuery(newQuery)
|
||||
setPage(0)
|
||||
}
|
||||
|
||||
const handleSearchSubmit = async (event) => {
|
||||
const handleSearchSubmit = async (event): Promise<void> => {
|
||||
setPageLoading(true)
|
||||
event.preventDefault()
|
||||
setQuery(query)
|
||||
|
@ -84,7 +84,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
setPageLoading(false)
|
||||
}
|
||||
|
||||
const handleLoadMore = async (event) => {
|
||||
const handleLoadMore = async (event): Promise<void> => {
|
||||
event.preventDefault()
|
||||
setPage(page + 1)
|
||||
console.info('Loading next page', { query, page })
|
||||
|
@ -107,7 +107,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
setPageLoading(false)
|
||||
}
|
||||
|
||||
const toggleSort = (sortBy: NodeSoringByEnumType) => {
|
||||
const toggleSort = (sortBy: NodeSoringByEnumType): void => {
|
||||
const sortWay = query.sortBy === sortBy && query.sortWay === 'asc' ? 'desc' : 'asc'
|
||||
getMatomo(matomoConfig).trackEvent({
|
||||
category: 'nodes',
|
||||
|
@ -155,7 +155,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
</form>
|
||||
<Loader loading={loading || pageLoading} showBottom={true}>
|
||||
{
|
||||
data
|
||||
(data != null)
|
||||
? (
|
||||
<div className="table-responsive">
|
||||
<table className={'table table-dark table-striped table-bordered nodes'}>
|
||||
|
@ -212,7 +212,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.listNodes.items.length
|
||||
{(data.listNodes.items.length > 0)
|
||||
? data.listNodes.items.map((node, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
|
@ -228,7 +228,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
<td className={'text-end'}>{node.halfYearActiveUserCount ?? '?'}</td>
|
||||
<td className={'text-end'}>{node.statusesCount ?? '?'}</td>
|
||||
<td>{node.openRegistrations === null ? '?' : (node.openRegistrations ? 'Opened' : 'Closed')}</td>
|
||||
<td>{node.refreshedAt ? (new Date(node.refreshedAt)).toLocaleDateString() : 'Never'}</td>
|
||||
<td>{node.refreshedAt !== '' ? (new Date(node.refreshedAt)).toLocaleDateString() : 'Never'}</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
|
@ -244,7 +244,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
: ''
|
||||
}
|
||||
</Loader>
|
||||
{data?.listNodes?.paging?.hasNext && !loading && !pageLoading
|
||||
{!loading && !pageLoading && data?.listNodes?.paging?.hasNext !== undefined && data?.listNodes?.paging?.hasNext
|
||||
? (
|
||||
<div className={'d-flex justify-content-center'}>
|
||||
<button className={'btn btn-secondary'} onClick={handleLoadMore}>
|
||||
|
@ -254,7 +254,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
</div>
|
||||
)
|
||||
: ''}
|
||||
{error
|
||||
{(error != null)
|
||||
? (<div className={'d-flex justify-content-center'}>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/>
|
||||
<span>{error.message}</span>
|
||||
|
|
|
@ -37,14 +37,14 @@ const Stats: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
})
|
||||
|
||||
useEffect(() => {
|
||||
router.push({ query })
|
||||
void router.push({ query })
|
||||
getMatomo(matomoConfig).trackEvent({
|
||||
category: 'stats',
|
||||
action: 'new-search'
|
||||
})
|
||||
}, [query])
|
||||
|
||||
const toggleSort = (sortBy: StatsSoringByEnumType) => {
|
||||
const toggleSort = (sortBy: StatsSoringByEnumType): void => {
|
||||
const sortWay = query.sortBy === sortBy && query.sortWay === 'asc' ? 'desc' : 'asc'
|
||||
getMatomo(matomoConfig).trackEvent({
|
||||
category: 'stats',
|
||||
|
@ -73,7 +73,7 @@ const Stats: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
accountFeedCount: 0,
|
||||
channelFeedCount: 0
|
||||
}
|
||||
if (data) {
|
||||
if (data != null) {
|
||||
data.listStats.items.forEach(item => {
|
||||
if (item.softwareName === null) {
|
||||
return
|
||||
|
@ -119,7 +119,7 @@ const Stats: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
</tr>
|
||||
</thead>
|
||||
<Loader loading={loading} table={4} showTop={true} hideContent={true}>
|
||||
{!data
|
||||
{(data == null)
|
||||
? (
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -183,7 +183,7 @@ const Stats: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
|
|||
}
|
||||
</Loader>
|
||||
</table>
|
||||
{error
|
||||
{(error != null)
|
||||
? (<div className={'d-flex justify-content-center'}>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/>
|
||||
<span>{error.message}</span>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export type Sort = {
|
||||
sortBy?: string,
|
||||
sortWay?: 'asc' | 'desc'
|
||||
export interface Sort {
|
||||
sortBy?: string
|
||||
sortWay?: 'asc' | 'desc'
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": [
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"no-console": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
Plik diff jest za duży
Load Diff
Ładowanie…
Reference in New Issue