mirror of
https://github.com/uetchy/namae.git
synced 2025-07-01 22:10:04 +09:00
feat: location based domain suggestion
This commit is contained in:
parent
7fbc430949
commit
73de4dd7ca
30
api/list/domain/[query].ts
Normal file
30
api/list/domain/[query].ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
|
import fetch from 'cross-fetch';
|
||||||
|
import { send, sendError } from '../../../util/http';
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NowRequest,
|
||||||
|
res: NowResponse
|
||||||
|
): Promise<void> {
|
||||||
|
const { query } = req.query;
|
||||||
|
|
||||||
|
if (!query || typeof query !== 'string') {
|
||||||
|
return sendError(res, new Error('No query given'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://domainr.p.rapidapi.com/v2/search?query=${encodeURIComponent(
|
||||||
|
query
|
||||||
|
)}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'X-RapidAPI-Key': process.env.DOMAINR_API_KEY,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
send(res, response);
|
||||||
|
} catch (err) {
|
||||||
|
sendError(res, err);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
|
import 'cross-fetch';
|
||||||
import whois from 'whois-json';
|
import whois from 'whois-json';
|
||||||
import { send, sendError } from '../../../util/http';
|
import { send, sendError } from '../../../util/http';
|
||||||
import { NowRequest, NowResponse } from '@vercel/node';
|
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NowRequest,
|
req: NowRequest,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { send, sendError } from '../../../util/http';
|
|
||||||
import { NowRequest, NowResponse } from '@vercel/node';
|
import { NowRequest, NowResponse } from '@vercel/node';
|
||||||
import nodeFetch from 'isomorphic-unfetch';
|
import nodeFetch from 'cross-fetch';
|
||||||
|
import { send, sendError } from '../../../util/http';
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NowRequest,
|
req: NowRequest,
|
||||||
|
53
package.json
53
package.json
@ -10,58 +10,57 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/browser": "^5.21.1",
|
"@sentry/browser": "^5.21.1",
|
||||||
|
"cross-fetch": "^3.0.6",
|
||||||
"easy-peasy": "^3.3.1",
|
"easy-peasy": "^3.3.1",
|
||||||
"fetch-suspense": "^1.2.2",
|
"fetch-suspense": "^1.2.2",
|
||||||
"framer-motion": "^2.5.1",
|
"framer-motion": "^2.5.1",
|
||||||
"i18next": ">=19.7.0",
|
"i18next": ">=19.8.4",
|
||||||
"i18next-browser-languagedetector": "^6.0.1",
|
"i18next-browser-languagedetector": "^6.0.1",
|
||||||
"i18next-chained-backend": "^2.0.1",
|
"i18next-chained-backend": "^2.0.1",
|
||||||
"i18next-localstorage-backend": "^3.1.1",
|
"i18next-localstorage-backend": "^3.1.2",
|
||||||
"i18next-xhr-backend": "^3.2.2",
|
"i18next-xhr-backend": "^3.2.2",
|
||||||
"isomorphic-unfetch": "^3.0.0",
|
|
||||||
"npm-name": "^6.0.1",
|
"npm-name": "^6.0.1",
|
||||||
"prop-types": "^15.7.2",
|
"rc-tooltip": "^5.0.2",
|
||||||
"rc-tooltip": "^5.0.0",
|
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-ga": "^3.1.1",
|
"react-ga": "^3.3.0",
|
||||||
"react-helmet": "^6.0.0",
|
"react-helmet": "^6.0.0",
|
||||||
"react-i18next": "11.7.2",
|
"react-i18next": "11.8.5",
|
||||||
"react-icons": "^3.11.0",
|
"react-icons": "^3.11.0",
|
||||||
"react-router": "^5.1.2",
|
"react-router": "^5.1.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "3.4.3",
|
"react-scripts": "3.4.3",
|
||||||
"react-spinners": "^0.9.0",
|
"react-spinners": "^0.9.0",
|
||||||
"react-toastify": "^6.0.8",
|
"react-toastify": "^6.2.0",
|
||||||
"styled-components": "^5.1.1",
|
"styled-components": "^5.2.1",
|
||||||
"swr": "^0.3.0",
|
"swr": "^0.3.2",
|
||||||
"validator": "^13.1.0",
|
"validator": "^13.5.2",
|
||||||
"whois-json": "^2.0.4"
|
"whois-json": "^2.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/cli": "^1.55.1",
|
"@sentry/cli": "^1.61.0",
|
||||||
"@testing-library/jest-dom": "^5.11.3",
|
"@testing-library/jest-dom": "^5.11.9",
|
||||||
"@testing-library/react": "^11.0.2",
|
"@testing-library/react": "^11.2.3",
|
||||||
"@types/i18next-node-fs-backend": "^2.1.0",
|
"@types/i18next-node-fs-backend": "^2.1.0",
|
||||||
"@types/jest": "26.0.13",
|
"@types/jest": "26.0.20",
|
||||||
"@types/node": "^14.6.0",
|
"@types/node": "^14.14.22",
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
"@types/react-helmet": "^6.1.0",
|
"@types/react-helmet": "^6.1.0",
|
||||||
"@types/react-router-dom": "^5.1.3",
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"@types/styled-components": "^5.1.2",
|
"@types/styled-components": "^5.1.7",
|
||||||
"@types/validator": "^13.1.0",
|
"@types/validator": "^13.1.3",
|
||||||
"@vercel/build-utils": "^2.4.2",
|
"@vercel/build-utils": "^2.7.0",
|
||||||
"@vercel/node": "^1.7.4",
|
"@vercel/node": "^1.9.0",
|
||||||
"codacy-coverage": "^3.4.0",
|
"codacy-coverage": "^3.4.0",
|
||||||
"husky": "^4.3.0",
|
"husky": "^4.3.8",
|
||||||
"i18next-node-fs-backend": "^2.1.3",
|
"i18next-node-fs-backend": "^2.1.3",
|
||||||
"jest": "24.9.0",
|
"jest": "24.9.0",
|
||||||
"mutationobserver-shim": "^0.3.5",
|
"mutationobserver-shim": "^0.3.5",
|
||||||
"nock": "^13.0.4",
|
"nock": "^13.0.6",
|
||||||
"prettier": "^2.1.1",
|
"prettier": "^2.2.1",
|
||||||
"pretty-quick": "^3.0.0",
|
"pretty-quick": "^3.1.0",
|
||||||
"ts-jest": "26.3.0",
|
"ts-jest": "26.4.4",
|
||||||
"typescript": "^4.0.2"
|
"typescript": "^4.1.3"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import fetch from 'isomorphic-unfetch';
|
import fetch from 'cross-fetch';
|
||||||
import { TiArrowSync } from 'react-icons/ti';
|
import { TiArrowSync } from 'react-icons/ti';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
@ -7,9 +7,10 @@ import { Card, Result } from '../core';
|
|||||||
|
|
||||||
const Search: React.FC<{ query: string }> = ({ query }) => {
|
const Search: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const term = encodeURIComponent(query);
|
|
||||||
const response = useFetch(
|
const response = useFetch(
|
||||||
`/api/services/appstore/${term}?country=${t('countryCode')}`
|
`/api/services/appstore/${encodeURIComponent(query)}?country=${t(
|
||||||
|
'countryCode'
|
||||||
|
)}`
|
||||||
) as {
|
) as {
|
||||||
result: Array<{ name: string; viewURL: string; price: number; id: string }>;
|
result: Array<{ name: string; viewURL: string; price: number; id: string }>;
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SiRust } from 'react-icons/si';
|
import { SiRust } from 'react-icons/si';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const CratesioCard: React.FC<{ query: string }> = ({ query }) => {
|
const CratesioCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lowerCase = query.toLowerCase();
|
const normalizedQuery = normalize(query);
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -1,17 +1,35 @@
|
|||||||
|
import fetch from 'cross-fetch';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { MdDomain } from 'react-icons/md';
|
import { MdDomain } from 'react-icons/md';
|
||||||
|
import useSWR from 'swr';
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { normalize } from '../../../util/text';
|
||||||
import { zones } from '../../../util/zones';
|
import { zones } from '../../../util/zones';
|
||||||
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
|
export interface DomainrResponse {
|
||||||
|
results: {
|
||||||
|
domain: string;
|
||||||
|
host: string;
|
||||||
|
subdomain: string;
|
||||||
|
zone: string;
|
||||||
|
path: string;
|
||||||
|
registerURL: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetcher(url: string) {
|
||||||
|
return fetch(url, {}).then((res) => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
const DomainCard: React.FC<{ query: string }> = ({ query }) => {
|
const DomainCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const sanitizedQuery = query
|
const normalizedQuery = normalize(query, {
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
alphanumeric: false,
|
||||||
.replace(/_/g, '-');
|
allowUnderscore: false,
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
});
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const domainHackSuggestions = zones
|
const domainHackSuggestions = zones
|
||||||
.map((zone) => new RegExp(`${zone}$`).exec(lowerCase.slice(1)))
|
.map((zone) => new RegExp(`${zone}$`).exec(lowerCase.slice(1)))
|
||||||
@ -23,37 +41,54 @@ const DomainCard: React.FC<{ query: string }> = ({ query }) => {
|
|||||||
lowerCase.substring(m.index + 1)
|
lowerCase.substring(m.index + 1)
|
||||||
);
|
);
|
||||||
|
|
||||||
const names = [
|
const { data } = useSWR<DomainrResponse>(
|
||||||
`${lowerCase}.com`,
|
`/api/list/domain/${encodeURIComponent(query)}`,
|
||||||
`${lowerCase}.org`,
|
fetcher
|
||||||
`${lowerCase}.app`,
|
);
|
||||||
`${lowerCase}.dev`,
|
|
||||||
`${lowerCase}.io`,
|
const cctldSuggestions =
|
||||||
`${lowerCase}.sh`,
|
data?.results
|
||||||
|
?.filter((res) => res.subdomain !== '' && res.path === '')
|
||||||
|
?.map((res) => res.domain) ?? [];
|
||||||
|
|
||||||
|
const names =
|
||||||
|
// use Set() to eliminate dupes
|
||||||
|
new Set([
|
||||||
|
...['com', 'org', 'app', 'io'].map((tld) => lowerCase + '.' + tld),
|
||||||
...domainHackSuggestions,
|
...domainHackSuggestions,
|
||||||
];
|
...cctldSuggestions,
|
||||||
const moreNames = [
|
]);
|
||||||
|
|
||||||
|
const moreNames = new Set([
|
||||||
`${lowerCase}app.com`,
|
`${lowerCase}app.com`,
|
||||||
`get${lowerCase}.com`,
|
`get${lowerCase}.com`,
|
||||||
`${lowerCase}.co`,
|
...[
|
||||||
`${lowerCase}.tools`,
|
'co',
|
||||||
`${lowerCase}.build`,
|
'dev',
|
||||||
`${lowerCase}.run`,
|
'sh',
|
||||||
`${lowerCase}.ai`,
|
'cloud',
|
||||||
`${lowerCase}.design`,
|
'tools',
|
||||||
`${lowerCase}.directory`,
|
'build',
|
||||||
`${lowerCase}.guru`,
|
'run',
|
||||||
`${lowerCase}.ninja`,
|
'ai',
|
||||||
`${lowerCase}.net`,
|
'design',
|
||||||
`${lowerCase}.info`,
|
'directory',
|
||||||
`${lowerCase}.biz`,
|
'guru',
|
||||||
`${lowerCase}.website`,
|
'ninja',
|
||||||
`${lowerCase}.eu`,
|
'info',
|
||||||
];
|
'biz',
|
||||||
|
].map((tld) => lowerCase + '.' + tld),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const name of moreNames) {
|
||||||
|
if (names.has(name)) {
|
||||||
|
moreNames.delete(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={t('providers.domains')}>
|
<Card title={t('providers.domains')}>
|
||||||
<Repeater items={names} moreItems={moreNames}>
|
<Repeater items={Array.from(names)} moreItems={Array.from(moreNames)}>
|
||||||
{(name) => (
|
{(name) => (
|
||||||
<DedicatedAvailability
|
<DedicatedAvailability
|
||||||
name={name}
|
name={name}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SiFirebase } from 'react-icons/si';
|
import { SiFirebase } from 'react-icons/si';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const FirebaseCard: React.FC<{ query: string }> = ({ query }) => {
|
const FirebaseCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const sanitizedQuery = query
|
const normalizedQuery = normalize(query, {
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
allowUnderscore: false,
|
||||||
.replace(/_/g, '-');
|
});
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
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 { normalize } from '../../../util/text';
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const GithubCard: React.FC<{ query: string }> = ({ query }) => {
|
const GithubCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lowerCase = query.toLowerCase();
|
const normalizedQuery = normalize(query, {});
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [query, `${lowerCase}-dev`, `${lowerCase}-org`];
|
const names = [normalizedQuery, `${lowerCase}-dev`, `${lowerCase}-org`];
|
||||||
const moreNames = [
|
const moreNames = [
|
||||||
`${lowerCase}hq`,
|
`${lowerCase}hq`,
|
||||||
`${lowerCase}-team`,
|
`${lowerCase}-team`,
|
||||||
|
@ -7,10 +7,12 @@ import { Card, Result } from '../core';
|
|||||||
|
|
||||||
const Search: React.FC<{ query: string }> = ({ query }) => {
|
const Search: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const searchQuery = encodeURIComponent(`${query} in:name`);
|
const searchQuery = `${query} in:name`;
|
||||||
const limit = 10;
|
const limit = 10;
|
||||||
const response = useFetch(
|
const response = useFetch(
|
||||||
`https://api.github.com/search/repositories?q=${searchQuery}&per_page=${limit}`
|
`https://api.github.com/search/repositories?q=${encodeURIComponent(
|
||||||
|
searchQuery
|
||||||
|
)}&per_page=${limit}`
|
||||||
) as {
|
) as {
|
||||||
items: Array<{
|
items: Array<{
|
||||||
full_name: string;
|
full_name: string;
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaGitlab } from 'react-icons/fa';
|
import { FaGitlab } from 'react-icons/fa';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { Card, Repeater, DedicatedAvailability } from '../core';
|
||||||
|
|
||||||
const GitLabCard: React.FC<{ query: string }> = ({ query }) => {
|
const GitLabCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lowerCase = query.toLowerCase();
|
const normalizedQuery = normalize(query, {
|
||||||
|
allowUnderscore: false,
|
||||||
|
});
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DiHeroku } from 'react-icons/di';
|
import { DiHeroku } from 'react-icons/di';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { Card, Repeater, DedicatedAvailability } from '../core';
|
||||||
|
|
||||||
const HerokuCard: React.FC<{ query: string }> = ({ query }) => {
|
const HerokuCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const normalizedQuery = normalize(query, {
|
||||||
const sanitizedQuery = query
|
allowUnderscore: false,
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
});
|
||||||
.replace(/_/g, '-');
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
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 { normalize } from '../../../util/text';
|
||||||
|
|
||||||
import { Card, Repeater, ExistentialAvailability } from '../core';
|
import { Card, Repeater, ExistentialAvailability } from '../core';
|
||||||
|
|
||||||
const HomebrewCard: React.FC<{ query: string }> = ({ query }) => {
|
const HomebrewCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lowerCase = query.toLowerCase();
|
const normalizedQuery = normalize(query);
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaInstagram } from 'react-icons/fa';
|
import { FaInstagram } from 'react-icons/fa';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
|
|
||||||
import { Card, Repeater, ExistentialAvailability } from '../core';
|
import { Card, Repeater, ExistentialAvailability } from '../core';
|
||||||
|
|
||||||
const InstagramCard: React.FC<{ query: string }> = ({ query }) => {
|
const InstagramCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lowerCase = query.toLowerCase();
|
const normalizedQuery = normalize(query, { allowHyphens: false });
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [query];
|
const names = [normalizedQuery];
|
||||||
const moreNames = [`${lowerCase}app`, `${lowerCase}_hq`, `get.${lowerCase}`];
|
const moreNames = [`${lowerCase}app`, `${lowerCase}_hq`, `get.${lowerCase}`];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
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 { normalize } from '../../../util/text';
|
||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { Card, Repeater, DedicatedAvailability } from '../core';
|
||||||
|
|
||||||
const JsOrgCard: React.FC<{ query: string }> = ({ query }) => {
|
const JsOrgCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const sanitizedQuery = query
|
const normalizedQuery = normalize(query, {
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
allowUnderscore: false,
|
||||||
.replace(/_/g, '-');
|
});
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SiDebian, SiUbuntu } from 'react-icons/si';
|
import { SiDebian, SiUbuntu } from 'react-icons/si';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const LinuxCard: React.FC<{ query: string }> = ({ query }) => {
|
const LinuxCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lowerCase = query.toLowerCase();
|
const normalizedQuery = normalize(query);
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
import { NetlifyIcon } from '../../Icons';
|
import { NetlifyIcon } from '../../Icons';
|
||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { Card, Repeater, DedicatedAvailability } from '../core';
|
||||||
|
|
||||||
const NetlifyCard: React.FC<{ query: string }> = ({ query }) => {
|
const NetlifyCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const normalizedQuery = normalize(query, {
|
||||||
const sanitizedQuery = query
|
allowUnderscore: false,
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
});
|
||||||
.replace(/_/g, '-');
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RiNpmjsFill, RiNpmjsLine } from 'react-icons/ri';
|
import { RiNpmjsFill, RiNpmjsLine } from 'react-icons/ri';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const NpmCard: React.FC<{ query: string }> = ({ query }) => {
|
const NpmCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lowerCase = query.toLowerCase();
|
const normalizedQuery = normalize(query, {
|
||||||
|
allowUnderscore: false,
|
||||||
|
});
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase, `${lowerCase}-js`];
|
const names = [lowerCase, `${lowerCase}-js`];
|
||||||
const moreNames = [`${lowerCase}js`];
|
const moreNames = [`${lowerCase}js`];
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
import { OcamlIcon } from '../../Icons';
|
import { OcamlIcon } from '../../Icons';
|
||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { Card, Repeater, DedicatedAvailability } from '../core';
|
||||||
|
|
||||||
const OcamlCard: React.FC<{ query: string }> = ({ query }) => {
|
const OcamlCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lowerCase = query.toLowerCase();
|
const normalizedQuery = normalize(query, { allowUnderscore: false });
|
||||||
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -2,14 +2,15 @@ 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 { capitalize } from '../../../util/text';
|
import { capitalize, normalize } from '../../../util/text';
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const PypiCard: React.FC<{ query: string }> = ({ query }) => {
|
const PypiCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const normalizedQuery = normalize(query);
|
||||||
const names = [query];
|
const capitalCase = capitalize(normalizedQuery);
|
||||||
const moreNames = [`Py${capitalize(query)}`];
|
const names = [normalizedQuery];
|
||||||
|
const moreNames = [`Py${capitalCase}`];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={t('providers.pypi')}>
|
<Card title={t('providers.pypi')}>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SiRubygems } from 'react-icons/si';
|
import { SiRubygems } from 'react-icons/si';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const RubyGemsCard: React.FC<{ query: string }> = ({ query }) => {
|
const RubyGemsCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const normalizedQuery = normalize(query);
|
||||||
const names = [query];
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
const moreNames = [`${query.toLowerCase()}-rb`];
|
const names = [normalizedQuery];
|
||||||
|
const moreNames = [`${lowerCase}-rb`];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={t('providers.rubygems')}>
|
<Card title={t('providers.rubygems')}>
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
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 { normalize } from '../../../util/text';
|
||||||
|
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const S3Card: React.FC<{ query: string }> = ({ query }) => {
|
const S3Card: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const sanitizedQuery = query
|
const normalizedQuery = normalize(query, {
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
allowUnderscore: false,
|
||||||
.replace(/_/g, '-');
|
});
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
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 { normalize } from '../../../util/text';
|
||||||
|
|
||||||
import { Card, DedicatedAvailability, Repeater } from '../core';
|
import { Card, DedicatedAvailability, Repeater } from '../core';
|
||||||
|
|
||||||
const SlackCard: React.FC<{ query: string }> = ({ query }) => {
|
const SlackCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const sanitizedQuery = query
|
const normalizedQuery = normalize(query, { allowUnderscore: false });
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
.replace(/_/g, '-');
|
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -2,10 +2,12 @@ import React from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { Card, Repeater, DedicatedAvailability } from '../core';
|
||||||
import { SpectrumIcon } from '../../Icons';
|
import { SpectrumIcon } from '../../Icons';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
|
|
||||||
const SpectrumCard: React.FC<{ query: string }> = ({ query }) => {
|
const SpectrumCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const names = [query];
|
const normalizedQuery = normalize(query);
|
||||||
|
const names = [normalizedQuery];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={t('providers.spectrum')}>
|
<Card title={t('providers.spectrum')}>
|
||||||
|
@ -2,19 +2,17 @@ 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 { capitalize } from '../../../util/text';
|
import { capitalize, normalize } from '../../../util/text';
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { Card, Repeater, DedicatedAvailability } from '../core';
|
||||||
|
|
||||||
const TwitterCard: React.FC<{ query: string }> = ({ query }) => {
|
const TwitterCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const sanitizedQuery = query
|
const normalizedQuery = normalize(query, { allowHyphens: false });
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
.replace(/-/g, '_');
|
const capitalCase = capitalize(normalizedQuery);
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
|
||||||
const capitalCase = capitalize(sanitizedQuery);
|
|
||||||
|
|
||||||
const names = [sanitizedQuery, `${capitalCase}App`, `${lowerCase}hq`];
|
const names = [normalizedQuery, `${capitalCase}App`, `${lowerCase}hq`];
|
||||||
const moreNames = [
|
const moreNames = [
|
||||||
`hey${lowerCase}`,
|
`hey${lowerCase}`,
|
||||||
`${capitalCase}Team`,
|
`${capitalCase}Team`,
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { normalize } from '../../../util/text';
|
||||||
import { NowIcon } from '../../Icons';
|
import { NowIcon } from '../../Icons';
|
||||||
|
|
||||||
import { Card, Repeater, DedicatedAvailability } from '../core';
|
import { Card, Repeater, DedicatedAvailability } from '../core';
|
||||||
|
|
||||||
const VercelCard: React.FC<{ query: string }> = ({ query }) => {
|
const VercelCard: React.FC<{ query: string }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const normalizedQuery = normalize(query, {
|
||||||
const sanitizedQuery = query
|
allowUnderscore: false,
|
||||||
.replace(/[^0-9a-zA-Z_-]/g, '')
|
});
|
||||||
.replace(/_/g, '-');
|
const lowerCase = normalizedQuery.toLowerCase();
|
||||||
const lowerCase = sanitizedQuery.toLowerCase();
|
|
||||||
|
|
||||||
const names = [lowerCase];
|
const names = [lowerCase];
|
||||||
|
|
||||||
|
@ -5,11 +5,43 @@ export function capitalize(text: string): string {
|
|||||||
|
|
||||||
export function sanitize(text: string): string {
|
export function sanitize(text: string): string {
|
||||||
return text
|
return text
|
||||||
.replace(/[\s@+!#$%^&*()[\]./<>{}]/g, '')
|
.replace(/[@+!#$%^&*()[\]./<>{}]/g, '')
|
||||||
.normalize('NFD')
|
.normalize('NFD')
|
||||||
.replace(/[\u0300-\u036f]/g, '');
|
.replace(/[\u0300-\u036f]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NormalizeOptions {
|
||||||
|
alphanumeric?: boolean;
|
||||||
|
allowSpaces?: boolean;
|
||||||
|
allowUnderscore?: boolean;
|
||||||
|
allowHyphens?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalize(
|
||||||
|
text: string,
|
||||||
|
{
|
||||||
|
alphanumeric = true,
|
||||||
|
allowSpaces = false,
|
||||||
|
allowUnderscore = true,
|
||||||
|
allowHyphens = true,
|
||||||
|
}: NormalizeOptions = {}
|
||||||
|
): string {
|
||||||
|
if (alphanumeric) {
|
||||||
|
text = text.replace(/[^0-9a-zA-Z-_\s]/g, '');
|
||||||
|
}
|
||||||
|
if (!allowUnderscore) {
|
||||||
|
text = text.replace(/_/g, '-');
|
||||||
|
}
|
||||||
|
if (!allowHyphens) {
|
||||||
|
text = text.replace(/-/g, allowUnderscore ? '_' : ' ');
|
||||||
|
}
|
||||||
|
if (!allowSpaces) {
|
||||||
|
text = text.replace(/\s/g, '');
|
||||||
|
}
|
||||||
|
text = text.replace(/[-_\s]+$/, '');
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
export function upper(word: string): string {
|
export function upper(word: string): string {
|
||||||
return word.toUpperCase();
|
return word.toUpperCase();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es2015",
|
||||||
"lib": ["dom", "dom.iterable", "ESNext"],
|
"lib": ["dom", "dom.iterable", "ESNext"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
@ -14,7 +14,8 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"allowJs": true
|
"allowJs": true,
|
||||||
|
"downlevelIteration": true
|
||||||
},
|
},
|
||||||
"include": ["src", "types"]
|
"include": ["src", "types"]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { NowResponse } from '@vercel/node';
|
import { NowResponse } from '@vercel/node';
|
||||||
import nodeFetch from 'isomorphic-unfetch';
|
import nodeFetch from 'cross-fetch';
|
||||||
|
|
||||||
export type HttpMethod =
|
export type HttpMethod =
|
||||||
| 'GET'
|
| 'GET'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user