1
0
mirror of https://github.com/uetchy/namae.git synced 2025-03-17 04:30:31 +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": {
"domains": "Domains",
"github": "Github Organization",
"githubSearch": "Github Repository",
"npm": "npm",
"pypi": "PyPI",
"rubygems": "RubyGems",

View File

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

View File

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

View File

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

View File

@ -1,23 +1,27 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
const lowerCase = name.toLowerCase()
const names = [lowerCase]
return (
<Card title={t('providers.rust')} nameList={[lowerCase]}>
{(name) => (
<DedicatedAvailability
name={name}
service="cratesio"
link={`https://crates.io/crates/${name}`}
icon={<DiRust />}
/>
)}
<Card title={t('providers.rust')}>
<Repeater items={names}>
{(name) => (
<DedicatedAvailability
name={name}
service="cratesio"
link={`https://crates.io/crates/${name}`}
icon={<DiRust />}
/>
)}
</Repeater>
</Card>
)
}

View File

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

View File

@ -1,32 +1,34 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
const lowerCase = name.toLowerCase()
const names = [name]
const moreNames = [
`${lowerCase}hq`,
`${lowerCase}-team`,
`${lowerCase}-org`,
`${lowerCase}-js`,
]
return (
<Card
title={t('providers.github')}
nameList={[name]}
alternativeList={[
`${lowerCase}hq`,
`${lowerCase}-team`,
`${lowerCase}-org`,
`${lowerCase}-js`,
]}>
{(name) => (
<DedicatedAvailability
name={name}
service="github"
link={`https://github.com/${name}`}
prefix="github.com/"
icon={<FaGithub />}
/>
)}
<Card title={t('providers.github')}>
<Repeater items={names} moreItems={moreNames}>
{(name) => (
<DedicatedAvailability
name={name}
service="github"
link={`https://github.com/${name}`}
prefix="github.com/"
icon={<FaGithub />}
/>
)}
</Repeater>
</Card>
)
}

View File

@ -1,26 +1,38 @@
import React from 'react'
import useFetch from 'fetch-suspense'
import { useTranslation } from 'react-i18next'
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()
return (
<CustomSearchCard
title={t('providers.githubSearch')}
query={name}
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>
<Card title={t('providers.githubSearch')}>
<Search query={query} />
</Card>
)
}

View File

@ -1,32 +1,36 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
const lowerCase = name.toLowerCase()
const names = [lowerCase]
return (
<Card title={t('providers.homebrew')} nameList={[lowerCase]}>
{(name) => (
<>
<ExistentialAvailability
name={name}
target={`https://formulae.brew.sh/api/formula/${name}.json`}
link={`https://formulae.brew.sh/formula/${name}`}
icon={<IoIosBeer />}
/>
<ExistentialAvailability
name={name}
target={`https://formulae.brew.sh/api/cask/${name}.json`}
link={`https://formulae.brew.sh/cask/${name}`}
suffix=" (Cask)"
icon={<IoIosBeer />}
/>
</>
)}
<Card title={t('providers.homebrew')}>
<Repeater items={names}>
{(name) => (
<>
<ExistentialAvailability
name={name}
target={`https://formulae.brew.sh/api/formula/${name}.json`}
link={`https://formulae.brew.sh/formula/${name}`}
icon={<IoIosBeer />}
/>
<ExistentialAvailability
name={name}
target={`https://formulae.brew.sh/api/cask/${name}.json`}
link={`https://formulae.brew.sh/cask/${name}`}
suffix=" (Cask)"
icon={<IoIosBeer />}
/>
</>
)}
</Repeater>
</Card>
)
}

View File

@ -1,23 +1,27 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
const lowerCase = name.toLowerCase()
const names = [lowerCase]
return (
<Card title={t('providers.jsorg')} nameList={[lowerCase]}>
{(name) => (
<DedicatedAvailability
name={`${name}.js.org`}
service="dns"
link={`https://${name}.js.org`}
icon={<FaJsSquare />}
/>
)}
<Card title={t('providers.jsorg')}>
<Repeater items={names}>
{(name) => (
<DedicatedAvailability
name={`${name}.js.org`}
service="dns"
link={`https://${name}.js.org`}
icon={<FaJsSquare />}
/>
)}
</Repeater>
</Card>
)
}

View File

@ -2,33 +2,37 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { DiUbuntu } 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 }) {
const { t } = useTranslation()
const lowerCase = name.toLowerCase()
const names = [lowerCase]
return (
<Card title={t('providers.linux')} nameList={[lowerCase]}>
{(name) => (
<>
<DedicatedAvailability
name={name}
service="launchpad"
link={`https://launchpad.net/ubuntu/+source/${name}`}
prefix="launchpad:"
icon={<DiUbuntu />}
/>
<DedicatedAvailability
name={name}
service="debian"
link={`https://packages.debian.org/buster/${name}`}
prefix="debian:"
icon={<DiDebian />}
/>
</>
)}
<Card title={t('providers.linux')}>
<Repeater items={names}>
{(name) => (
<>
<DedicatedAvailability
name={name}
service="launchpad"
link={`https://launchpad.net/ubuntu/+source/${name}`}
prefix="launchpad:"
icon={<DiUbuntu />}
/>
<DedicatedAvailability
name={name}
service="debian"
link={`https://packages.debian.org/buster/${name}`}
prefix="debian:"
icon={<DiDebian />}
/>
</>
)}
</Repeater>
</Card>
)
}

View File

@ -1,36 +1,38 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
const lowerCase = name.toLowerCase()
const names = [lowerCase]
const moreNames = [`${lowerCase}-js`]
return (
<Card
title={t('providers.npm')}
nameList={[lowerCase]}
alternativeList={[`${lowerCase}-js`]}>
{(name) => (
<>
<DedicatedAvailability
name={name}
service="npm"
link={`https://www.npmjs.com/package/${name}`}
icon={<FaNpm />}
/>
<DedicatedAvailability
name={name}
service="npm-org"
link={`https://www.npmjs.com/org/${name}`}
prefix="@"
suffix=" (Organization)"
icon={<FaNpm />}
/>
</>
)}
<Card title={t('providers.npm')}>
<Repeater items={names} moreItems={moreNames}>
{(name) => (
<>
<DedicatedAvailability
name={name}
service="npm"
link={`https://www.npmjs.com/package/${name}`}
icon={<FaNpm />}
/>
<DedicatedAvailability
name={name}
service="npm-org"
link={`https://www.npmjs.com/org/${name}`}
prefix="@"
suffix=" (Organization)"
icon={<FaNpm />}
/>
</>
)}
</Repeater>
</Card>
)
}

View File

@ -1,26 +1,28 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { FaPython } from 'react-icons/fa'
import { Card } from '../Cards'
import { DedicatedAvailability } from '../Cards'
import { capitalize } from '../../util/text'
import { Card, DedicatedAvailability, Repeater } from '../Cards'
export default function PypiCard({ name }) {
const { t } = useTranslation()
const names = [name]
const moreNames = [`Py${capitalize(name)}`]
return (
<Card
title={t('providers.pypi')}
nameList={[name]}
alternativeList={[`Py${capitalize(name)}`]}>
{(name) => (
<DedicatedAvailability
name={name}
service="pypi"
link={`https://pypi.org/project/${name}`}
icon={<FaPython />}
/>
)}
<Card title={t('providers.pypi')}>
<Repeater items={names} moreItems={moreNames}>
{(name) => (
<DedicatedAvailability
name={name}
service="pypi"
link={`https://pypi.org/project/${name}`}
icon={<FaPython />}
/>
)}
</Repeater>
</Card>
)
}

View File

@ -1,25 +1,27 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
const names = [name]
const moreNames = [`${name.toLowerCase()}-rb`]
return (
<Card
title={t('providers.rubygems')}
nameList={[name]}
alternativeList={[`${name.toLowerCase()}-rb`]}>
{(name) => (
<DedicatedAvailability
name={name}
service="rubygems"
link={`https://rubygems.org/gems/${name}`}
icon={<FaGem />}
/>
)}
<Card title={t('providers.rubygems')}>
<Repeater items={names} moreItems={moreNames}>
{(name) => (
<DedicatedAvailability
name={name}
service="rubygems"
link={`https://rubygems.org/gems/${name}`}
icon={<FaGem />}
/>
)}
</Repeater>
</Card>
)
}

View File

@ -1,24 +1,28 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
const lowerCase = name.toLowerCase()
const names = [lowerCase]
return (
<Card title={t('providers.s3')} nameList={[lowerCase]}>
{(name) => (
<DedicatedAvailability
name={name}
service="s3"
link={`https://${name}.s3.amazonaws.com`}
suffix=".s3.amazonaws.com"
icon={<FaAws />}
/>
)}
<Card title={t('providers.s3')}>
<Repeater items={names}>
{(name) => (
<DedicatedAvailability
name={name}
service="s3"
link={`https://${name}.s3.amazonaws.com`}
suffix=".s3.amazonaws.com"
icon={<FaAws />}
/>
)}
</Repeater>
</Card>
)
}

View File

@ -1,24 +1,28 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
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 }) {
const { t } = useTranslation()
const lowerCase = name.toLowerCase()
const names = [lowerCase]
return (
<Card title={t('providers.slack')} nameList={[lowerCase]}>
{(name) => (
<DedicatedAvailability
name={name}
service="slack"
link={`https://${name}.slack.com`}
suffix=".slack.com"
icon={<FaSlack />}
/>
)}
<Card title={t('providers.slack')}>
<Repeater items={names}>
{(name) => (
<DedicatedAvailability
name={name}
service="slack"
link={`https://${name}.slack.com`}
suffix=".slack.com"
icon={<FaSlack />}
/>
)}
</Repeater>
</Card>
)
}

View File

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