1
0
mirror of https://github.com/uetchy/namae.git synced 2025-03-17 12:30:32 +09:00

feat: search for github repositories

This commit is contained in:
uetchy 2019-08-06 00:45:18 +09:00
parent ee6ceead02
commit a32685ac59
17 changed files with 369 additions and 295 deletions

View File

@ -5,6 +5,7 @@
"providers": { "providers": {
"domains": "Domains", "domains": "Domains",
"github": "Github Organization", "github": "Github Organization",
"githubSearch": "Github Repository",
"npm": "npm", "npm": "npm",
"pypi": "PyPI", "pypi": "PyPI",
"rubygems": "RubyGems", "rubygems": "RubyGems",

View File

@ -5,6 +5,7 @@
"providers": { "providers": {
"domains": "ドメイン", "domains": "ドメイン",
"github": "Github Organization", "github": "Github Organization",
"githubSearch": "Github リポジトリ",
"npm": "npm", "npm": "npm",
"pypi": "PyPI", "pypi": "PyPI",
"rubygems": "RubyGems", "rubygems": "RubyGems",

View File

@ -3,18 +3,19 @@ import styled, { createGlobalStyle } from 'styled-components'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import GithubCard from './components/cards/GithubCard'
import DomainCard from './components/cards/DomainCard' import DomainCard from './components/cards/DomainCard'
import GithubCard from './components/cards/GithubCard'
import NpmCard from './components/cards/NpmCard'
import PypiCard from './components/cards/PypiCard'
import RubyGemsCard from './components/cards/RubyGemsCard'
import CratesioCard from './components/cards/CratesioCard'
import HomebrewCard from './components/cards/HomebrewCard' import HomebrewCard from './components/cards/HomebrewCard'
import LinuxCard from './components/cards/LinuxCard'
import GithubSearchCard from './components/cards/GithubSearchCard'
import TwitterCard from './components/cards/TwitterCard' import TwitterCard from './components/cards/TwitterCard'
import SlackCard from './components/cards/SlackCard' import SlackCard from './components/cards/SlackCard'
import NpmCard from './components/cards/NpmCard'
import JsOrgCard from './components/cards/JsOrgCard'
import PypiCard from './components/cards/PypiCard'
import S3Card from './components/cards/S3Card' import S3Card from './components/cards/S3Card'
import CratesioCard from './components/cards/CratesioCard' import JsOrgCard from './components/cards/JsOrgCard'
import RubyGemsCard from './components/cards/RubyGemsCard'
import LinuxCard from './components/cards/LinuxCard'
import { EventReporter } from './components/Analytics' import { EventReporter } from './components/Analytics'
import Welcome from './components/Welcome' import Welcome from './components/Welcome'
@ -93,15 +94,16 @@ export default function App() {
<DomainCard name={query} /> <DomainCard name={query} />
<GithubCard name={query} /> <GithubCard name={query} />
<NpmCard name={query} /> <NpmCard name={query} />
<JsOrgCard name={query} />
<PypiCard name={query} /> <PypiCard name={query} />
<CratesioCard name={query} />
<RubyGemsCard name={query} /> <RubyGemsCard name={query} />
<CratesioCard name={query} />
<HomebrewCard name={query} /> <HomebrewCard name={query} />
<LinuxCard name={query} />
<GithubSearchCard query={query} />
<TwitterCard name={query} /> <TwitterCard name={query} />
<SlackCard name={query} /> <SlackCard name={query} />
<S3Card name={query} /> <S3Card name={query} />
<LinuxCard name={query} /> <JsOrgCard name={query} />
</Cards> </Cards>
<EventReporter query={query} /> <EventReporter query={query} />
</SearchResult> </SearchResult>

View File

@ -10,8 +10,54 @@ import { Tooltip } from 'react-tippy'
import { ExternalLink } from './Links' import { ExternalLink } from './Links'
import 'react-tippy/dist/tippy.css' import 'react-tippy/dist/tippy.css'
export function Card({ title, children }) {
return (
<CardContainer>
<CardTitle>{title}</CardTitle>
<CardContent>
<ErrorBoundary>
<Suspense
fallback={
<ResultContainer>
<BarLoader />
</ResultContainer>
}>
{children}
</Suspense>
</ErrorBoundary>
</CardContent>
</CardContainer>
)
}
export function Repeater({ items = [], moreItems = [], children }) {
const [revealAlternatives, setRevealAlternatives] = useState(false)
function onClick() {
setRevealAlternatives(true)
}
return (
<>
{items.map((name) => (
<CellError key={name}>{children(name)}</CellError>
))}
{revealAlternatives
? moreItems.map((name) => (
<CellError key={name}>{children(name)}</CellError>
))
: null}
{moreItems.length > 0 && !revealAlternatives ? (
<Button onClick={onClick}>show more</Button>
) : null}
</>
)
}
export function DedicatedAvailability({ export function DedicatedAvailability({
name, name,
message = '',
service, service,
link, link,
prefix = '', prefix = '',
@ -25,19 +71,21 @@ export function DedicatedAvailability({
} }
return ( return (
<AvailabilityResult <Result
availability={response.availability} title={name}
name={name} message={message}
link={link} link={link}
icon={icon}
color={response.availability ? 'green' : 'red'}
prefix={prefix} prefix={prefix}
suffix={suffix} suffix={suffix}
icon={icon}
/> />
) )
} }
export function ExistentialAvailability({ export function ExistentialAvailability({
name, name,
message = '',
target, target,
link, link,
prefix = '', prefix = '',
@ -53,59 +101,54 @@ export function ExistentialAvailability({
const availability = response.status === 404 const availability = response.status === 404
return ( return (
<AvailabilityResult <Result
name={name} title={name}
availability={availability} message={message}
link={link} link={link}
icon={icon}
color={availability ? 'green' : 'red'}
prefix={prefix} prefix={prefix}
suffix={suffix} suffix={suffix}
icon={icon}
/> />
) )
} }
export function CustomSearchCard({ export const Result = ({
title, title,
query, message = '',
link, link,
icon,
color = 'inherit',
prefix = '', prefix = '',
suffix = '', suffix = '',
icon, }) => {
children, const content = (
}) { <>
return ( {prefix}
<CardContainer> {title}
<CardTitle>{title}</CardTitle> {suffix}
<CardList> </>
<ErrorHandler key={query}>{children(query)}</ErrorHandler>
</CardList>
</CardContainer>
) )
}
export function Card({ title, nameList = [], alternativeList = [], children }) {
const [revealAlternatives, setRevealAlternatives] = useState(false)
function onClick() {
setRevealAlternatives(true)
}
return ( return (
<CardContainer> <ResultContainer>
<CardTitle>{title}</CardTitle> <Tooltip
<CardList> title={message}
{nameList.map((name) => ( position="bottom"
<ErrorHandler key={name}>{children(name)}</ErrorHandler> arrow={true}
))} animation="shift"
{revealAlternatives && duration="200">
alternativeList.map((name) => ( <ResultItem color={color}>
<ErrorHandler key={name}>{children(name)}</ErrorHandler> {icon}
))} <ResultName>
{alternativeList.length > 0 && !revealAlternatives ? ( {link ? (
<Button onClick={onClick}>show more</Button> <ExternalLink href={link}>{content}</ExternalLink>
) : null} ) : (
</CardList> content
</CardContainer> )}
</ResultName>
</ResultItem>
</Tooltip>
</ResultContainer>
) )
} }
@ -141,7 +184,7 @@ class ErrorBoundary extends React.Component {
} }
} }
const ErrorHandler = ({ children }) => ( const CellError = ({ children }) => (
<ErrorBoundary> <ErrorBoundary>
<Suspense <Suspense
fallback={ fallback={
@ -154,28 +197,6 @@ const ErrorHandler = ({ children }) => (
</ErrorBoundary> </ErrorBoundary>
) )
const AvailabilityResult = ({
name,
availability,
link,
prefix = '',
suffix = '',
icon,
}) => (
<ResultContainer>
<ResultItem color={availability ? 'green' : 'red'}>
{icon}
<ResultName>
<ExternalLink href={link}>
{prefix}
{name}
{suffix}
</ExternalLink>
</ResultName>
</ResultItem>
</ResultContainer>
)
const CardContainer = styled.div` const CardContainer = styled.div`
padding: 40px; padding: 40px;
@ -195,7 +216,7 @@ const CardTitle = styled.div`
} }
` `
const CardList = styled.div` const CardContent = styled.div`
border-radius: 2px; border-radius: 2px;
${mobile} { ${mobile} {

View File

@ -1,15 +1,18 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { DiRust } from 'react-icons/di' import { DiRust } from 'react-icons/di'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, Repeater, DedicatedAvailability } from '../Cards'
export default function CratesioCard({ name }) { export default function CratesioCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
const names = [lowerCase]
return ( return (
<Card title={t('providers.rust')} nameList={[lowerCase]}> <Card title={t('providers.rust')}>
<Repeater items={names}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={name} name={name}
@ -18,6 +21,7 @@ export default function CratesioCard({ name }) {
icon={<DiRust />} icon={<DiRust />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,31 +1,34 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaMapSigns } from 'react-icons/fa' import { FaMapSigns } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, Repeater, DedicatedAvailability } from '../Cards'
export default function DomainCard({ name }) { export default function DomainCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
return ( const names = [`${lowerCase}.com`, `${lowerCase}app.com`, `${lowerCase}.app`]
<Card const moreNames = [
title={t('providers.domains')}
nameList={[`${lowerCase}.com`, `${lowerCase}app.com`, `${lowerCase}.app`]}
alternativeList={[
`${lowerCase}.dev`, `${lowerCase}.dev`,
`${lowerCase}.io`, `${lowerCase}.io`,
`${lowerCase}.tools`, `${lowerCase}.tools`,
`get${lowerCase}.com`, `get${lowerCase}.com`,
]}> ]
return (
<Card title={t('providers.domains')}>
<Repeater items={names} moreItems={moreNames}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={name} name={name}
message="Go Domainr.com"
service="domain" service="domain"
link={`https://domainr.com/?q=${name}`} link={`https://domainr.com/?q=${name}`}
icon={<FaMapSigns />} icon={<FaMapSigns />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,23 +1,24 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaGithub } from 'react-icons/fa' import { FaGithub } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, Repeater, DedicatedAvailability } from '../Cards'
export default function GithubCard({ name }) { export default function GithubCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
return ( const names = [name]
<Card const moreNames = [
title={t('providers.github')}
nameList={[name]}
alternativeList={[
`${lowerCase}hq`, `${lowerCase}hq`,
`${lowerCase}-team`, `${lowerCase}-team`,
`${lowerCase}-org`, `${lowerCase}-org`,
`${lowerCase}-js`, `${lowerCase}-js`,
]}> ]
return (
<Card title={t('providers.github')}>
<Repeater items={names} moreItems={moreNames}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={name} name={name}
@ -27,6 +28,7 @@ export default function GithubCard({ name }) {
icon={<FaGithub />} icon={<FaGithub />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,26 +1,38 @@
import React from 'react' import React from 'react'
import useFetch from 'fetch-suspense'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaGithub } from 'react-icons/fa' import { FaGithub } from 'react-icons/fa'
import fetch from 'isomorphic-unfetch'
import { CustomSearchCard } from '../Cards'
export default function GithubSearchCard({ name }) { import { Card, Result } from '../Cards'
function Search({ query }) {
const searchQuery = encodeURIComponent(`${query} in:name`)
const response = useFetch(
`https://api.github.com/search/repositories?q=${searchQuery}&per_page=3`
)
const repos = response.items
return (
<div>
{repos.map((repo) => (
<Result
title={repo.full_name}
message={`Star: ${repo.stargazers_count}`}
link={repo.html_url}
icon={<FaGithub />}
key={repo.id}
/>
))}
</div>
)
}
export default function GithubSearchCard({ query }) {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<CustomSearchCard <Card title={t('providers.githubSearch')}>
title={t('providers.githubSearch')} <Search query={query} />
query={name} </Card>
link={`https://github.com/search/${name}`}
prefix="github.com/"
icon={<FaGithub />}>
{async (query) => {
const response = await fetch(
`https://api.github.com/repos/search?q=${query}`
)
const data = await response.json()
console.log(data)
}}
</CustomSearchCard>
) )
} }

View File

@ -1,15 +1,18 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { IoIosBeer } from 'react-icons/io' import { IoIosBeer } from 'react-icons/io'
import { Card } from '../Cards'
import { ExistentialAvailability } from '../Cards' import { Card, Repeater, ExistentialAvailability } from '../Cards'
export default function HomebrewCard({ name }) { export default function HomebrewCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
const names = [lowerCase]
return ( return (
<Card title={t('providers.homebrew')} nameList={[lowerCase]}> <Card title={t('providers.homebrew')}>
<Repeater items={names}>
{(name) => ( {(name) => (
<> <>
<ExistentialAvailability <ExistentialAvailability
@ -27,6 +30,7 @@ export default function HomebrewCard({ name }) {
/> />
</> </>
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,15 +1,18 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaJsSquare } from 'react-icons/fa' import { FaJsSquare } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, Repeater, DedicatedAvailability } from '../Cards'
export default function JsOrgCard({ name }) { export default function JsOrgCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
const names = [lowerCase]
return ( return (
<Card title={t('providers.jsorg')} nameList={[lowerCase]}> <Card title={t('providers.jsorg')}>
<Repeater items={names}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={`${name}.js.org`} name={`${name}.js.org`}
@ -18,6 +21,7 @@ export default function JsOrgCard({ name }) {
icon={<FaJsSquare />} icon={<FaJsSquare />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -2,15 +2,18 @@ import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { DiUbuntu } from 'react-icons/di' import { DiUbuntu } from 'react-icons/di'
import { DiDebian } from 'react-icons/di' import { DiDebian } from 'react-icons/di'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, Repeater, DedicatedAvailability } from '../Cards'
export default function LinuxCard({ name }) { export default function LinuxCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
const names = [lowerCase]
return ( return (
<Card title={t('providers.linux')} nameList={[lowerCase]}> <Card title={t('providers.linux')}>
<Repeater items={names}>
{(name) => ( {(name) => (
<> <>
<DedicatedAvailability <DedicatedAvailability
@ -29,6 +32,7 @@ export default function LinuxCard({ name }) {
/> />
</> </>
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,18 +1,19 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaNpm } from 'react-icons/fa' import { FaNpm } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, Repeater, DedicatedAvailability } from '../Cards'
export default function NpmCard({ name }) { export default function NpmCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
const names = [lowerCase]
const moreNames = [`${lowerCase}-js`]
return ( return (
<Card <Card title={t('providers.npm')}>
title={t('providers.npm')} <Repeater items={names} moreItems={moreNames}>
nameList={[lowerCase]}
alternativeList={[`${lowerCase}-js`]}>
{(name) => ( {(name) => (
<> <>
<DedicatedAvailability <DedicatedAvailability
@ -31,6 +32,7 @@ export default function NpmCard({ name }) {
/> />
</> </>
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,18 +1,19 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaPython } from 'react-icons/fa' import { FaPython } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards'
import { capitalize } from '../../util/text' import { capitalize } from '../../util/text'
import { Card, DedicatedAvailability, Repeater } from '../Cards'
export default function PypiCard({ name }) { export default function PypiCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const names = [name]
const moreNames = [`Py${capitalize(name)}`]
return ( return (
<Card <Card title={t('providers.pypi')}>
title={t('providers.pypi')} <Repeater items={names} moreItems={moreNames}>
nameList={[name]}
alternativeList={[`Py${capitalize(name)}`]}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={name} name={name}
@ -21,6 +22,7 @@ export default function PypiCard({ name }) {
icon={<FaPython />} icon={<FaPython />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,17 +1,18 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaGem } from 'react-icons/fa' import { FaGem } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, Repeater, DedicatedAvailability } from '../Cards'
export default function RubyGemsCard({ name }) { export default function RubyGemsCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const names = [name]
const moreNames = [`${name.toLowerCase()}-rb`]
return ( return (
<Card <Card title={t('providers.rubygems')}>
title={t('providers.rubygems')} <Repeater items={names} moreItems={moreNames}>
nameList={[name]}
alternativeList={[`${name.toLowerCase()}-rb`]}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={name} name={name}
@ -20,6 +21,7 @@ export default function RubyGemsCard({ name }) {
icon={<FaGem />} icon={<FaGem />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,15 +1,18 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaAws } from 'react-icons/fa' import { FaAws } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, DedicatedAvailability, Repeater } from '../Cards'
export default function S3Card({ name }) { export default function S3Card({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
const names = [lowerCase]
return ( return (
<Card title={t('providers.s3')} nameList={[lowerCase]}> <Card title={t('providers.s3')}>
<Repeater items={names}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={name} name={name}
@ -19,6 +22,7 @@ export default function S3Card({ name }) {
icon={<FaAws />} icon={<FaAws />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,15 +1,18 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaSlack } from 'react-icons/fa' import { FaSlack } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards' import { Card, DedicatedAvailability, Repeater } from '../Cards'
export default function SlackCard({ name }) { export default function SlackCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
const lowerCase = name.toLowerCase() const lowerCase = name.toLowerCase()
const names = [lowerCase]
return ( return (
<Card title={t('providers.slack')} nameList={[lowerCase]}> <Card title={t('providers.slack')}>
<Repeater items={names}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={name} name={name}
@ -19,6 +22,7 @@ export default function SlackCard({ name }) {
icon={<FaSlack />} icon={<FaSlack />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }

View File

@ -1,25 +1,26 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FaTwitter } from 'react-icons/fa' import { FaTwitter } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards'
import { capitalize } from '../../util/text' import { capitalize } from '../../util/text'
import { Card, Repeater, DedicatedAvailability } from '../Cards'
export default function TwitterCard({ name }) { export default function TwitterCard({ name }) {
const { t } = useTranslation() const { t } = useTranslation()
return ( const names = [name]
<Card const moreNames = [
title={t('providers.twitter')}
nameList={[name]}
alternativeList={[
`${name.toLowerCase()}app`, `${name.toLowerCase()}app`,
`hey${name.toLowerCase()}`, `hey${name.toLowerCase()}`,
`${capitalize(name)}Team`, `${capitalize(name)}Team`,
`${capitalize(name)}HQ`, `${capitalize(name)}HQ`,
`${name.toLowerCase()}_official`, `${name.toLowerCase()}_official`,
`${name.toLowerCase()}-support`, `${name.toLowerCase()}-support`,
]}> ]
return (
<Card title={t('providers.twitter')}>
<Repeater items={names} moreItems={moreNames}>
{(name) => ( {(name) => (
<DedicatedAvailability <DedicatedAvailability
name={name} name={name}
@ -29,6 +30,7 @@ export default function TwitterCard({ name }) {
icon={<FaTwitter />} icon={<FaTwitter />}
/> />
)} )}
</Repeater>
</Card> </Card>
) )
} }