Add prettier package and apply config to all files (#11)

pull/12/head^2
stffffn 2022-12-25 18:13:49 +01:00 zatwierdzone przez GitHub
rodzic 1a368e963f
commit 8141375a2b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
15 zmienionych plików z 654 dodań i 328 usunięć

Wyświetl plik

@ -1,6 +1,3 @@
{
"extends": [
"next/core-web-vitals",
"prettier"
]
}
"extends": ["next/core-web-vitals", "prettier"]
}

Wyświetl plik

@ -3,4 +3,4 @@
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
}

Wyświetl plik

@ -1,78 +1,88 @@
import React, { useState } from "react";
import sanitizeHtml from 'sanitize-html';
import React, { useState } from 'react'
import sanitizeHtml from 'sanitize-html'
import debounce from 'debounce'
type AccountDetails = {
id: string, // IMPORTANT: this is int64 so will overflow Javascript's number type
acct: string,
followed_by: Set<string>, // list of handles
};
id: string // IMPORTANT: this is int64 so will overflow Javascript's number type
acct: string
followed_by: Set<string> // list of handles
}
async function usernameToId(handle: string): Promise<{ id: number, domain: string }> {
const match = handle.match(/^(.+)@(.+)$/);
async function usernameToId(
handle: string
): Promise<{ id: number; domain: string }> {
const match = handle.match(/^(.+)@(.+)$/)
if (!match || match.length < 2) {
throw new Error(`Incorrect handle: ${handle}`);
throw new Error(`Incorrect handle: ${handle}`)
}
const domain = match[2];
const username = match[1];
let response = await fetch(`https://${domain}/api/v1/accounts/lookup?acct=${username}`);
const domain = match[2]
const username = match[1]
let response = await fetch(
`https://${domain}/api/v1/accounts/lookup?acct=${username}`
)
if (response.status !== 200) {
throw new Error('HTTP request failed');
throw new Error('HTTP request failed')
}
const { id } = await response.json();
return { id, domain };
const { id } = await response.json()
return { id, domain }
}
function getDomain(handle: string) {
const match = handle.match(/^(.+)@(.+)$/);
const match = handle.match(/^(.+)@(.+)$/)
if (!match || match.length < 2) {
throw new Error(`Incorrect handle: ${handle}`);
throw new Error(`Incorrect handle: ${handle}`)
}
const domain = match[2];
return domain;
const domain = match[2]
return domain
}
async function accountFollows(handle: string, limit: number, logError: (x: string) => void): Promise<Array<AccountDetails>> {
let id, domain: string;
async function accountFollows(
handle: string,
limit: number,
logError: (x: string) => void
): Promise<Array<AccountDetails>> {
let id, domain: string
try {
({ id, domain } = await usernameToId(handle));
;({ id, domain } = await usernameToId(handle))
} catch (e) {
logError(`Cannot find handle ${handle}.`);
return [];
logError(`Cannot find handle ${handle}.`)
return []
}
let nextPage: string | null = `https://${domain}/api/v1/accounts/${id}/following`;
let data: Array<AccountDetails> = [];
let nextPage:
| string
| null = `https://${domain}/api/v1/accounts/${id}/following`
let data: Array<AccountDetails> = []
while (nextPage && data.length <= limit) {
console.log(`Get page: ${nextPage}`);
let response;
let page;
console.log(`Get page: ${nextPage}`)
let response
let page
try {
response = await fetch(nextPage);
response = await fetch(nextPage)
if (response.status !== 200) {
throw new Error('HTTP request failed');
throw new Error('HTTP request failed')
}
page = await response.json();
console.log(response.statusText);
page = await response.json()
console.log(response.statusText)
} catch (e) {
logError(`Error while retrieving followers for ${handle}.`)
console.log('eeeee', e);
break;
console.log('eeeee', e)
break
}
if (!page.map) {
break;
break
}
page = page.map((entry: AccountDetails) => {
if (entry.acct && !/@/.test(entry.acct)) {
// make sure the domain is always there
entry.acct = `${entry.acct}@${domain}`;
};
return entry;
entry.acct = `${entry.acct}@${domain}`
}
return entry
})
data = [...data, ...page];
nextPage = getNextPage(response.headers.get('Link'));
data = [...data, ...page]
nextPage = getNextPage(response.headers.get('Link'))
}
return data;
return data
}
async function accountFofs(
@ -81,103 +91,127 @@ async function accountFofs(
setFollows: (x: Array<AccountDetails>) => void,
logError: (x: string) => void
): Promise<void> {
const directFollows = await accountFollows(handle, 2000, logError);
setProgress([0, directFollows.length]);
let progress = 0;
const directFollows = await accountFollows(handle, 2000, logError)
setProgress([0, directFollows.length])
let progress = 0
const directFollowIds = new Set(directFollows.map(({ acct }) => acct));
directFollowIds.add(handle.replace(/^@/, ''));
const directFollowIds = new Set(directFollows.map(({ acct }) => acct))
directFollowIds.add(handle.replace(/^@/, ''))
const indirectFollowLists: Array<Array<AccountDetails>> = [];
const indirectFollowLists: Array<Array<AccountDetails>> = []
const updateList = debounce(() => {
let indirectFollows: Array<AccountDetails> = [].concat([], ...indirectFollowLists);
const indirectFollowMap = new Map();
let indirectFollows: Array<AccountDetails> = [].concat(
[],
...indirectFollowLists
)
const indirectFollowMap = new Map()
indirectFollows.filter(
// exclude direct follows
({ acct }) => !directFollowIds.has(acct)
).map(account => {
const acct = account.acct;
if (indirectFollowMap.has(acct)) {
const otherAccount = indirectFollowMap.get(acct);
account.followed_by = new Set([...Array.from(account.followed_by.values()), ...otherAccount.followed_by]);
}
indirectFollowMap.set(acct, account);
});
indirectFollows
.filter(
// exclude direct follows
({ acct }) => !directFollowIds.has(acct)
)
.map((account) => {
const acct = account.acct
if (indirectFollowMap.has(acct)) {
const otherAccount = indirectFollowMap.get(acct)
account.followed_by = new Set([
...Array.from(account.followed_by.values()),
...otherAccount.followed_by,
])
}
indirectFollowMap.set(acct, account)
})
const list = Array.from(indirectFollowMap.values()).sort((a, b) => {
if (a.followed_by.size != b.followed_by.size) {
return b.followed_by.size - a.followed_by.size;
return b.followed_by.size - a.followed_by.size
}
return b.followers_count - a.followers_count;
});
return b.followers_count - a.followers_count
})
setFollows(list);
}, 2000);
setFollows(list)
}, 2000)
await Promise.all(
directFollows.map(
async ({ acct }) => {
const follows = await accountFollows(acct, 200, logError);
progress++;
setProgress([progress, directFollows.length]);
indirectFollowLists.push(follows.map(account => ({ ...account, followed_by: new Set([acct]) })));
updateList();
}
),
);
directFollows.map(async ({ acct }) => {
const follows = await accountFollows(acct, 200, logError)
progress++
setProgress([progress, directFollows.length])
indirectFollowLists.push(
follows.map((account) => ({ ...account, followed_by: new Set([acct]) }))
)
updateList()
})
)
updateList.flush();
updateList.flush()
}
function getNextPage(linkHeader: string | null): string | null {
if (!linkHeader) {
return null;
return null
}
// Example header:
// Link: <https://mastodon.example/api/v1/accounts/1/follows?limit=2&max_id=7628164>; rel="next", <https://mastodon.example/api/v1/accounts/1/follows?limit=2&since_id=7628165>; rel="prev"
const match = linkHeader.match(/<(.+)>; rel="next"/);
const match = linkHeader.match(/<(.+)>; rel="next"/)
if (match && match.length > 0) {
return match[1];
return match[1]
}
return null;
return null
}
export function Content({ }) {
const [handle, setHandle] = useState("");
const [follows, setFollows] = useState<Array<AccountDetails>>([]);
const [isLoading, setLoading] = useState(false);
const [isDone, setDone] = useState(false);
const [domain, setDomain] = useState<string>("");
const [[numLoaded, totalToLoad], setProgress] = useState<Array<number>>([0, 0]);
const [errors, setErrors] = useState<Array<string>>([]);
export function Content({}) {
const [handle, setHandle] = useState('')
const [follows, setFollows] = useState<Array<AccountDetails>>([])
const [isLoading, setLoading] = useState(false)
const [isDone, setDone] = useState(false)
const [domain, setDomain] = useState<string>('')
const [[numLoaded, totalToLoad], setProgress] = useState<Array<number>>([
0, 0,
])
const [errors, setErrors] = useState<Array<string>>([])
async function search(handle: string) {
if (!/@/.test(handle)) {
return;
return
}
setErrors([]);
setLoading(true);
setDone(false);
setFollows([]);
setProgress([0, 0]);
setDomain(getDomain(handle));
await accountFofs(handle, setProgress, setFollows, error => setErrors(e => [...e, error]));
setLoading(false);
setDone(true);
setErrors([])
setLoading(true)
setDone(false)
setFollows([])
setProgress([0, 0])
setDomain(getDomain(handle))
await accountFofs(handle, setProgress, setFollows, (error) =>
setErrors((e) => [...e, error])
)
setLoading(false)
setDone(true)
}
return <section className="bg-gray-50 dark:bg-gray-800" id="searchForm">
<div className="px-4 py-8 mx-auto space-y-12 lg:space-y-20 lg:py-24 lg:px-6">
<form onSubmit={e => {
search(handle);
e.preventDefault();
return false;
}}>
<div className="form-group mb-6 text-4xl lg:ml-16">
<label htmlFor="mastodonHandle" className="form-label inline-block mb-2 text-gray-700 dark:text-gray-200">Your Mastodon handle:</label>
<input type="text" value={handle} onChange={e => setHandle(e.target.value)} className="form-control
return (
<section className="bg-gray-50 dark:bg-gray-800" id="searchForm">
<div className="px-4 py-8 mx-auto space-y-12 lg:space-y-20 lg:py-24 lg:px-6">
<form
onSubmit={(e) => {
search(handle)
e.preventDefault()
return false
}}
>
<div className="form-group mb-6 text-4xl lg:ml-16">
<label
htmlFor="mastodonHandle"
className="form-label inline-block mb-2 text-gray-700 dark:text-gray-200"
>
Your Mastodon handle:
</label>
<input
type="text"
value={handle}
onChange={(e) => setHandle(e.target.value)}
className="form-control
block
w-80
px-3
@ -193,11 +227,21 @@ export function Content({ }) {
m-0
focus:text-gray-900 focus:bg-white focus:border-green-600 focus:outline-none
dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-gray-200 dark:focus:bg-gray-900 dark:focus:text-gray-200
" id="mastodonHandle"
aria-describedby="mastodonHandleHelp" placeholder="johnmastodon@mas.to" />
<small id="mastodonHandleHelp" className="block mt-1 text-xs text-gray-600 dark:text-gray-300">Be sure to include the full handle, including the domain.</small>
"
id="mastodonHandle"
aria-describedby="mastodonHandleHelp"
placeholder="johnmastodon@mas.to"
/>
<small
id="mastodonHandleHelp"
className="block mt-1 text-xs text-gray-600 dark:text-gray-300"
>
Be sure to include the full handle, including the domain.
</small>
<button type="submit" className="
<button
type="submit"
className="
px-6
py-2.5
bg-green-600
@ -213,61 +257,107 @@ export function Content({ }) {
active:bg-green-800 active:shadow-lg
transition
duration-150
ease-in-out">
Search
{isLoading ?
<svg className="w-4 h-4 ml-2 fill-white animate-spin inline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">{/*! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}<path d="M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z" /></svg>
: null}
</button>
ease-in-out"
>
Search
{isLoading ? (
<svg
className="w-4 h-4 ml-2 fill-white animate-spin inline"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
{/*! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z" />
</svg>
) : null}
</button>
{isLoading ?
<p className="text-sm dark:text-gray-400">Loaded {numLoaded} of {totalToLoad}...</p>
: null}
{isLoading ? (
<p className="text-sm dark:text-gray-400">
Loaded {numLoaded} of {totalToLoad}...
</p>
) : null}
{isDone && follows.length === 0 ?
<div className="flex p-4 mt-4 max-w-full sm:max-w-xl text-sm text-gray-700 bg-gray-100 rounded-lg dark:bg-gray-700 dark:text-gray-300" role="alert">
<svg aria-hidden="true" className="flex-shrink-0 inline w-5 h-5 mr-3" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<span className="sr-only">Info</span>
<div>
<span className="font-medium">No results found.</span> Please double check for typos in the handle, and ensure that you follow at least a few people
to seed the search. Otherwise, try again later as Mastodon may throttle requests.
{isDone && follows.length === 0 ? (
<div
className="flex p-4 mt-4 max-w-full sm:max-w-xl text-sm text-gray-700 bg-gray-100 rounded-lg dark:bg-gray-700 dark:text-gray-300"
role="alert"
>
<svg
aria-hidden="true"
className="flex-shrink-0 inline w-5 h-5 mr-3"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clip-rule="evenodd"
></path>
</svg>
<span className="sr-only">Info</span>
<div>
<span className="font-medium">No results found.</span> Please
double check for typos in the handle, and ensure that you
follow at least a few people to seed the search. Otherwise,
try again later as Mastodon may throttle requests.
</div>
</div>
) : null}
</div>
</form>
{isDone || follows.length > 0 ? (
<div className="flex-col lg:flex items-center justify-center">
<div className="max-w-4xl content-center px-2 sm:px-8 py-4 bg-white border rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
<div className="flow-root">
<ul
role="list"
className="divide-y divide-gray-200 dark:divide-gray-700"
>
{follows.slice(0, 500).map((account) => (
<AccountDetails
key={account.acct}
account={account}
mainDomain={domain}
/>
))}
</ul>
</div>
</div>
: null}
</div>
</form>
{isDone || follows.length > 0 ?
<div className="flex-col lg:flex items-center justify-center">
<div className="max-w-4xl content-center px-2 sm:px-8 py-4 bg-white border rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700">
<div className="flow-root">
<ul role="list" className="divide-y divide-gray-200 dark:divide-gray-700">
{follows.slice(0, 500).map(account => <AccountDetails key={account.acct} account={account} mainDomain={domain} />)}
</ul>
</div>
</div>
</div>
: null}
) : null}
<ErrorLog errors={errors} />
</div>
</section>;
<ErrorLog errors={errors} />
</div>
</section>
)
}
function AccountDetails({ account, mainDomain }) {
const { avatar_static, display_name, acct, note, followers_count, followed_by } = account;
let formatter = Intl.NumberFormat('en', { notation: 'compact' });
let numFollowers = formatter.format(followers_count);
const {
avatar_static,
display_name,
acct,
note,
followers_count,
followed_by,
} = account
let formatter = Intl.NumberFormat('en', { notation: 'compact' })
let numFollowers = formatter.format(followers_count)
const [expandedFollowers, setExpandedFollowers] = useState(false);
const [expandedFollowers, setExpandedFollowers] = useState(false)
return (
<li className="py-3 sm:py-4">
<div className="flex flex-col sm:flex-row items-center space-x-4">
<div className="flex-shrink-0">
<img className="w-16 h-16 sm:w-8 sm:h-8 rounded-full" src={avatar_static} alt={display_name} />
<img
className="w-16 h-16 sm:w-8 sm:h-8 rounded-full"
src={avatar_static}
alt={display_name}
/>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate dark:text-white">
@ -277,36 +367,73 @@ function AccountDetails({ account, mainDomain }) {
{acct} | {numFollowers} followers
</p>
<br />
<small className="text-sm dark:text-gray-200" dangerouslySetInnerHTML={{ __html: sanitizeHtml(note) }}></small>
<small
className="text-sm dark:text-gray-200"
dangerouslySetInnerHTML={{ __html: sanitizeHtml(note) }}
></small>
<br />
<small className="text-xs text-gray-800 dark:text-gray-400">
Followed by{' '}
{followed_by.size < 9 || expandedFollowers ?
{followed_by.size < 9 || expandedFollowers ? (
Array.from<string>(followed_by.values()).map((handle, idx) => (
<><span className="font-semibold">{handle.replace(/@.+/, '')}</span>{idx === followed_by.size - 1 ? '.' : ', '}</>
<>
<span className="font-semibold">
{handle.replace(/@.+/, '')}
</span>
{idx === followed_by.size - 1 ? '.' : ', '}
</>
))
: <>
<button onClick={() => setExpandedFollowers(true)} className="font-semibold">{followed_by.size} of your contacts</button>.
</>}
) : (
<>
<button
onClick={() => setExpandedFollowers(true)}
className="font-semibold"
>
{followed_by.size} of your contacts
</button>
.
</>
)}
</small>
</div>
<div className="inline-flex items-center text-base font-semibold text-gray-900 dark:text-white my-4 sm:my-0">
<a href={`https://${mainDomain}/@${acct.replace('@'+mainDomain, '')}`} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" target="_blank" rel="noreferrer">
<a
href={`https://${mainDomain}/@${acct.replace(
'@' + mainDomain,
''
)}`}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
target="_blank"
rel="noreferrer"
>
Follow
</a>
</div>
</div>
</li>
);
)
}
function ErrorLog({ errors }: { errors: Array<string> }) {
const [expanded, setExpanded] = useState(false);
return (<>
{errors.length > 0 ? <div className="text-sm text-gray-600 dark:text-gray-200 lg:ml-12 border border-solid border-gray-200 dark:border-gray-700 rounded p-4">
Found <button className="font-bold" onClick={() => setExpanded(!expanded)}>{errors.length} warnings</button>{expanded ? ':' : '.'}
{expanded ? errors.map(err => <p key={err} className="text-xs">{err}</p>) : null}
</div> : null}
</>);
}
const [expanded, setExpanded] = useState(false)
return (
<>
{errors.length > 0 ? (
<div className="text-sm text-gray-600 dark:text-gray-200 lg:ml-12 border border-solid border-gray-200 dark:border-gray-700 rounded p-4">
Found{' '}
<button className="font-bold" onClick={() => setExpanded(!expanded)}>
{errors.length} warnings
</button>
{expanded ? ':' : '.'}
{expanded
? errors.map((err) => (
<p key={err} className="text-xs">
{err}
</p>
))
: null}
</div>
) : null}
</>
)
}

Wyświetl plik

@ -1,62 +1,127 @@
import React, { useState } from "react";
export function FAQ({ }) {
return <section className="bg-white dark:bg-gray-900 pt-12">
<div className="max-w-screen-xl px-4 pb-8 mx-auto lg:pb-24 lg:px-6" id="faq">
<h2 className="mb-6 text-3xl font-extrabold tracking-tight text-center text-gray-900 lg:mb-8 lg:text-3xl dark:text-white">Frequently asked questions</h2>
<div className="max-w-screen-md mx-auto">
<div id="accordion-flush" data-accordion="collapse" data-active-classes="bg-white dark:bg-gray-900 text-gray-900 dark:text-white" data-inactive-classes="text-gray-500 dark:text-gray-400">
<FAQItem defaultSelected title="How does this work?">
The tool looks up all the people you follow, and then the people <em>they</em> follow. Then
it sorts them by the number of mutuals, or otherwise by how popular those accounts are.
</FAQItem>
import React, { useState } from 'react'
export function FAQ({}) {
return (
<section className="bg-white dark:bg-gray-900 pt-12">
<div
className="max-w-screen-xl px-4 pb-8 mx-auto lg:pb-24 lg:px-6"
id="faq"
>
<h2 className="mb-6 text-3xl font-extrabold tracking-tight text-center text-gray-900 lg:mb-8 lg:text-3xl dark:text-white">
Frequently asked questions
</h2>
<div className="max-w-screen-md mx-auto">
<div
id="accordion-flush"
data-accordion="collapse"
data-active-classes="bg-white dark:bg-gray-900 text-gray-900 dark:text-white"
data-inactive-classes="text-gray-500 dark:text-gray-400"
>
<FAQItem defaultSelected title="How does this work?">
The tool looks up all the people you follow, and then the people{' '}
<em>they</em> follow. Then it sorts them by the number of mutuals,
or otherwise by how popular those accounts are.
</FAQItem>
<FAQItem title="Do I need to grant the app any permissions?">
Not at all! This app uses public APIs to fetch potential people you can follow on Mastodon. In fact, it only does inauthenticated network requests to various
Mastodon instances.
</FAQItem>
<FAQItem title="Do I need to grant the app any permissions?">
Not at all! This app uses public APIs to fetch potential people
you can follow on Mastodon. In fact, it only does inauthenticated
network requests to various Mastodon instances.
</FAQItem>
<FAQItem title="Help! The search got stuck.">
Don&apos;t worry. The list of suggestions will load in 30 seconds or so. Sometimes it gets stuck because one or more of the queries
made to Mastodon time out. This is not a problem, because the rest of the queries will work as expected.
</FAQItem>
<FAQItem title="Help! The search got stuck.">
Don&apos;t worry. The list of suggestions will load in 30 seconds
or so. Sometimes it gets stuck because one or more of the queries
made to Mastodon time out. This is not a problem, because the rest
of the queries will work as expected.
</FAQItem>
<FAQItem title="Why don't I see any results?">
There could be a few reasons:
<ul className="list-disc ml-4">
<li>This tool only works if your list of follows is public. If you&apos;ve opted to hide your social graph, you will not see
any results here.</li>
<li>Due to the high volume of requests, sometimes Mastodon throttles this tool. If that&apos;s the case, try again a bit later.</li>
<li>Make sure you have no typos in the Mastodon handle, and make sure you follow at least a few people to seed the search.</li>
</ul>
</FAQItem>
<FAQItem title="Why don't I see any results?">
There could be a few reasons:
<ul className="list-disc ml-4">
<li>
This tool only works if your list of follows is public. If
you&apos;ve opted to hide your social graph, you will not see
any results here.
</li>
<li>
Due to the high volume of requests, sometimes Mastodon
throttles this tool. If that&apos;s the case, try again a bit
later.
</li>
<li>
Make sure you have no typos in the Mastodon handle, and make
sure you follow at least a few people to seed the search.
</li>
</ul>
</FAQItem>
<FAQItem title="How can I contribute with suggestions?">
Click the &quot;Fork me on Github&quot; link on the top right, and open up an issue.
</FAQItem>
<FAQItem title="How can I contribute with suggestions?">
Click the &quot;Fork me on Github&quot; link on the top right, and
open up an issue.
</FAQItem>
<FAQItem title="Why is this not a core Mastodon feature?">
Well, maybe it should be. In the meantime, you can use this website.
</FAQItem>
<FAQItem title="Why is this not a core Mastodon feature?">
Well, maybe it should be. In the meantime, you can use this
website.
</FAQItem>
</div>
</div>
</div>
</div>
</section>;
</section>
)
}
function FAQItem({ defaultSelected, title, children }: { defaultSelected?: boolean, title: string, children: React.ReactNode }) {
const [selected, setSelected] = useState(defaultSelected);
return (<>
<h3 id="accordion-flush-heading-1">
<button type="button" onClick={() => setSelected(!selected)} className={`flex items-center justify-between w-full py-5 font-medium text-left text-gray-${selected ? 900 : 500} bg-white border-b border-gray-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-${selected ? 200 : 400}`} data-accordion-target="#accordion-flush-body-1" aria-expanded="true" aria-controls="accordion-flush-body-1">
<span>{title}</span>
<svg data-accordion-icon className="w-6 h-6 rotate-180 shrink-0" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" /></svg>
</button>
</h3>
{selected ?
<div id="accordion-flush-body-1" aria-labelledby="accordion-flush-heading-1">
<div className="py-5 border-b border-gray-200 dark:border-gray-700 dark:text-gray-300">
{children}
function FAQItem({
defaultSelected,
title,
children,
}: {
defaultSelected?: boolean
title: string
children: React.ReactNode
}) {
const [selected, setSelected] = useState(defaultSelected)
return (
<>
<h3 id="accordion-flush-heading-1">
<button
type="button"
onClick={() => setSelected(!selected)}
className={`flex items-center justify-between w-full py-5 font-medium text-left text-gray-${
selected ? 900 : 500
} bg-white border-b border-gray-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-${
selected ? 200 : 400
}`}
data-accordion-target="#accordion-flush-body-1"
aria-expanded="true"
aria-controls="accordion-flush-body-1"
>
<span>{title}</span>
<svg
data-accordion-icon
className="w-6 h-6 rotate-180 shrink-0"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</h3>
{selected ? (
<div
id="accordion-flush-body-1"
aria-labelledby="accordion-flush-heading-1"
>
<div className="py-5 border-b border-gray-200 dark:border-gray-700 dark:text-gray-300">
{children}
</div>
</div>
</div> : null}
</>);
) : null}
</>
)
}

Wyświetl plik

@ -1,16 +1,41 @@
import React from "react";
export default function Footer({ }) {
return <footer className="bg-white dark:bg-gray-800">
<div className="max-w-screen-xl p-4 py-6 mx-auto lg:py-16 md:p-8 lg:p-10">
<hr className="my-6 border-gray-200 sm:mx-auto dark:border-gray-700 lg:my-8" />
<div className="text-center">
<div className="mb-5 lg:text-2xl font-semibold text-gray-700 dark:text-white text-lg">
Followgraph for Mastodon, built by&nbsp; <a href="https://mastodon.online/@gabipurcaru" target="_blank" rel="noreferrer" className="font-bold text-gray-900 dark:text-gray-400">@gabipurcaru@mastodon.online</a>.
</div>
<span className="block text-sm text-center text-gray-500 dark:text-gray-400">Built with <a href="https://flowbite.com" className="text-purple-600 hover:underline dark:text-purple-500">Flowbite</a> and <a href="https://tailwindcss.com" className="text-purple-600 hover:underline dark:text-purple-500">Tailwind CSS</a>.
</span>
</div>
import React from 'react'
export default function Footer({}) {
return (
<footer className="bg-white dark:bg-gray-800">
<div className="max-w-screen-xl p-4 py-6 mx-auto lg:py-16 md:p-8 lg:p-10">
<hr className="my-6 border-gray-200 sm:mx-auto dark:border-gray-700 lg:my-8" />
<div className="text-center">
<div className="mb-5 lg:text-2xl font-semibold text-gray-700 dark:text-white text-lg">
Followgraph for Mastodon, built by&nbsp;{' '}
<a
href="https://mastodon.online/@gabipurcaru"
target="_blank"
rel="noreferrer"
className="font-bold text-gray-900 dark:text-gray-400"
>
@gabipurcaru@mastodon.online
</a>
.
</div>
<span className="block text-sm text-center text-gray-500 dark:text-gray-400">
Built with{' '}
<a
href="https://flowbite.com"
className="text-purple-600 hover:underline dark:text-purple-500"
>
Flowbite
</a>{' '}
and{' '}
<a
href="https://tailwindcss.com"
className="text-purple-600 hover:underline dark:text-purple-500"
>
Tailwind CSS
</a>
.
</span>
</div>
</footer>;
</div>
</footer>
)
}

Wyświetl plik

@ -1,39 +1,80 @@
import React from "react";
import React from 'react'
export default function Header() {
return (<header className="fixed w-full">
<nav className="bg-white border-gray-200 py-2.5 dark:bg-gray-900">
<div className="flex flex-wrap items-center justify-between max-w-screen-xl px-4 mx-auto">
<Logo />
return (
<header className="fixed w-full">
<nav className="bg-white border-gray-200 py-2.5 dark:bg-gray-900">
<div className="flex flex-wrap items-center justify-between max-w-screen-xl px-4 mx-auto">
<Logo />
<div className="items-center justify-between hidden w-full lg:flex lg:w-auto lg:order-1" id="mobile-menu-2">
<ul className="flex flex-col mt-4 font-medium lg:flex-row lg:space-x-8 lg:mt-0">
<MenuItem link="#" selected>Home</MenuItem>
<MenuItem link="#faq">FAQ</MenuItem>
<MenuItem link="https://github.com/gabipurcaru/followgraph">Fork me on GitHub</MenuItem>
</ul>
</div>
</div>
</nav>
</header>);
<div
className="items-center justify-between hidden w-full lg:flex lg:w-auto lg:order-1"
id="mobile-menu-2"
>
<ul className="flex flex-col mt-4 font-medium lg:flex-row lg:space-x-8 lg:mt-0">
<MenuItem link="#" selected>
Home
</MenuItem>
<MenuItem link="#faq">FAQ</MenuItem>
<MenuItem link="https://github.com/gabipurcaru/followgraph">
Fork me on GitHub
</MenuItem>
</ul>
</div>
</div>
</nav>
</header>
)
function Logo({ }) {
return (<a href="#" className="flex items-center">
<svg className="w-12 h-12 mr-4 dark:fill-white" xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 342.68"><path d="M3.59 300.55a3.59 3.59 0 0 1-3.59-3.6c0-1.02.14-2.03.38-3.03 5.77-45.65 41.51-57.84 66.87-64.42 12.69-3.29 44.26-15.82 31.68-33.04-7.05-9.67-13.44-16.47-19.83-26.68-4.61-6.81-7.04-12.88-7.04-17.75 0-5.19 2.75-11.27 8.26-12.64-.73-10.45-.97-24.2-.48-35.39 1.75-19.2 15.52-33.35 33.31-39.62 7.05-2.68 3.64-13.38 11.42-13.62 18.24-.49 48.14 15.07 59.82 27.71 6.81 7.3 11.18 17.02 11.91 29.91l-.73 32.22c3.4.98 5.59 3.16 6.57 6.56.97 3.89 0 9.25-3.41 16.79 0 .24-.24.24-.24.48-7.51 12.37-15.33 20.56-23.92 32.03-3.84 5.11-3.09 10.01.1 14.41-4.48 2.62-8.85 5.62-13.06 9.16-16.76 14.07-29.68 35.08-34.13 68.61l-.5 2.9c-.24 1.9-.38 3.48-.38 4.66 0 1.48.13 2.93.37 4.35H3.59zM428 174.68c46.41 0 84 37.62 84 84 0 46.4-37.62 84-84 84-46.4 0-84-37.62-84-84 0-46.41 37.61-84 84-84zm-13.25 49.44c-.03-3.91-.39-6.71 4.46-6.64l15.7.19c5.07-.03 6.42 1.58 6.36 6.33v21.43h21.3c3.91-.04 6.7-.4 6.63 4.45l-.19 15.71c.03 5.07-1.58 6.42-6.32 6.36h-21.42v21.41c.06 4.75-1.29 6.36-6.36 6.33l-15.7.18c-4.85.08-4.49-2.72-4.46-6.63v-21.29h-21.43c-4.75.06-6.35-1.29-6.32-6.36l-.19-15.71c-.08-4.85 2.72-4.49 6.62-4.45h21.32v-21.31zm-261.98 76.47c-2.43 0-4.39-1.96-4.39-4.39 0-1.25.17-2.48.47-3.7 7.03-55.71 40.42-67.83 71.33-75.78 14.84-3.82 44.44-18.71 40.85-37.91-7.49-6.94-14.92-16.53-16.21-30.83l-.9.02c-2.07-.03-4.08-.5-5.95-1.56-4.13-2.35-6.4-6.85-7.49-11.99-2.29-15.67-2.86-23.67 5.49-27.17l.07-.02c-1.04-19.34 2.23-47.79-17.63-53.8 39.21-48.44 84.41-74.8 118.34-31.7 37.81 1.98 54.67 55.54 31.19 85.52h-.99c8.36 3.5 7.1 12.49 5.49 27.17-1.09 5.14-3.36 9.64-7.49 11.99-1.87 1.06-3.87 1.53-5.95 1.56l-.9-.02c-1.29 14.3-8.74 23.89-16.23 30.83-1.01 5.43.63 10.52 3.84 15.11-14.05 17.81-22.44 40.31-22.44 64.76 0 14.89 3.11 29.07 8.73 41.91H152.77z" /></svg>
<span className="self-center text-xl font-semibold whitespace-nowrap dark:text-white">Followgraph for Mastodon</span>
</a>);
}
function Logo({}) {
return (
<a href="#" className="flex items-center">
<svg
className="w-12 h-12 mr-4 dark:fill-white"
xmlns="http://www.w3.org/2000/svg"
shape-rendering="geometricPrecision"
text-rendering="geometricPrecision"
image-rendering="optimizeQuality"
fill-rule="evenodd"
clip-rule="evenodd"
viewBox="0 0 512 342.68"
>
<path d="M3.59 300.55a3.59 3.59 0 0 1-3.59-3.6c0-1.02.14-2.03.38-3.03 5.77-45.65 41.51-57.84 66.87-64.42 12.69-3.29 44.26-15.82 31.68-33.04-7.05-9.67-13.44-16.47-19.83-26.68-4.61-6.81-7.04-12.88-7.04-17.75 0-5.19 2.75-11.27 8.26-12.64-.73-10.45-.97-24.2-.48-35.39 1.75-19.2 15.52-33.35 33.31-39.62 7.05-2.68 3.64-13.38 11.42-13.62 18.24-.49 48.14 15.07 59.82 27.71 6.81 7.3 11.18 17.02 11.91 29.91l-.73 32.22c3.4.98 5.59 3.16 6.57 6.56.97 3.89 0 9.25-3.41 16.79 0 .24-.24.24-.24.48-7.51 12.37-15.33 20.56-23.92 32.03-3.84 5.11-3.09 10.01.1 14.41-4.48 2.62-8.85 5.62-13.06 9.16-16.76 14.07-29.68 35.08-34.13 68.61l-.5 2.9c-.24 1.9-.38 3.48-.38 4.66 0 1.48.13 2.93.37 4.35H3.59zM428 174.68c46.41 0 84 37.62 84 84 0 46.4-37.62 84-84 84-46.4 0-84-37.62-84-84 0-46.41 37.61-84 84-84zm-13.25 49.44c-.03-3.91-.39-6.71 4.46-6.64l15.7.19c5.07-.03 6.42 1.58 6.36 6.33v21.43h21.3c3.91-.04 6.7-.4 6.63 4.45l-.19 15.71c.03 5.07-1.58 6.42-6.32 6.36h-21.42v21.41c.06 4.75-1.29 6.36-6.36 6.33l-15.7.18c-4.85.08-4.49-2.72-4.46-6.63v-21.29h-21.43c-4.75.06-6.35-1.29-6.32-6.36l-.19-15.71c-.08-4.85 2.72-4.49 6.62-4.45h21.32v-21.31zm-261.98 76.47c-2.43 0-4.39-1.96-4.39-4.39 0-1.25.17-2.48.47-3.7 7.03-55.71 40.42-67.83 71.33-75.78 14.84-3.82 44.44-18.71 40.85-37.91-7.49-6.94-14.92-16.53-16.21-30.83l-.9.02c-2.07-.03-4.08-.5-5.95-1.56-4.13-2.35-6.4-6.85-7.49-11.99-2.29-15.67-2.86-23.67 5.49-27.17l.07-.02c-1.04-19.34 2.23-47.79-17.63-53.8 39.21-48.44 84.41-74.8 118.34-31.7 37.81 1.98 54.67 55.54 31.19 85.52h-.99c8.36 3.5 7.1 12.49 5.49 27.17-1.09 5.14-3.36 9.64-7.49 11.99-1.87 1.06-3.87 1.53-5.95 1.56l-.9-.02c-1.29 14.3-8.74 23.89-16.23 30.83-1.01 5.43.63 10.52 3.84 15.11-14.05 17.81-22.44 40.31-22.44 64.76 0 14.89 3.11 29.07 8.73 41.91H152.77z" />
</svg>
<span className="self-center text-xl font-semibold whitespace-nowrap dark:text-white">
Followgraph for Mastodon
</span>
</a>
)
}
}
function MenuItem({ link, children, selected }: { link: string, children: string | React.ReactElement, selected?: boolean }) {
return (<li>
{selected ?
<a href={link} className="block py-2 pl-3 pr-4 text-white bg-purple-700 rounded lg:bg-transparent lg:text-purple-700 lg:p-0 dark:text-white" aria-current="page">
{children}
</a>
:
<a href={link} className="block py-2 pl-3 pr-4 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-purple-700 lg:p-0 dark:text-gray-300 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
{children}
</a>
}
</li>);
function MenuItem({
link,
children,
selected,
}: {
link: string
children: string | React.ReactElement
selected?: boolean
}) {
return (
<li>
{selected ? (
<a
href={link}
className="block py-2 pl-3 pr-4 text-white bg-purple-700 rounded lg:bg-transparent lg:text-purple-700 lg:p-0 dark:text-white"
aria-current="page"
>
{children}
</a>
) : (
<a
href={link}
className="block py-2 pl-3 pr-4 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-purple-700 lg:p-0 dark:text-gray-300 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700"
>
{children}
</a>
)}
</li>
)
}

Wyświetl plik

@ -1,27 +1,40 @@
import Image from 'next/image'
import React from "react";
import React from 'react'
export default function Hero({ }) {
return <section className="bg-white dark:bg-gray-900">
<div className="grid max-w-screen-xl px-4 pt-20 pb-8 mx-auto lg:gap-8 xl:gap-0 lg:py-16 lg:grid-cols-12 lg:pt-28 lg:px-20">
<div className="mr-auto place-self-center lg:col-span-7">
<h1 className="max-w-2xl mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl xl:text-6xl dark:text-white">
Find awesome people <br /> on Mastodon.
</h1>
<p className="max-w-2xl mb-6 font-light text-gray-500 lg:mb-8 md:text-lg lg:text-xl dark:text-gray-400">
This tool allows you to expand your connection graph and find new people to follow. It works by
looking up your &quot;follows&apos; follows&quot;. <br /> <br/>
Your extended network is a treasure trove!
</p>
<div className="space-y-4 sm:flex sm:space-y-0 sm:space-x-4 ">
<a href="https://github.com/gabipurcaru/followgraph" className="inline-flex items-center justify-center w-full px-5 py-3 text-sm font-medium text-center text-gray-900 border border-gray-200 rounded-lg sm:w-auto hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 dark:text-white dark:border-gray-700 dark:hover:bg-gray-700 dark:focus:ring-gray-800">
<svg className="w-4 h-4 mr-2 text-gray-500 dark:fill-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">{
/* Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) */
}<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" /></svg> View on GitHub
</a>
export default function Hero({}) {
return (
<section className="bg-white dark:bg-gray-900">
<div className="grid max-w-screen-xl px-4 pt-20 pb-8 mx-auto lg:gap-8 xl:gap-0 lg:py-16 lg:grid-cols-12 lg:pt-28 lg:px-20">
<div className="mr-auto place-self-center lg:col-span-7">
<h1 className="max-w-2xl mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl xl:text-6xl dark:text-white">
Find awesome people <br /> on Mastodon.
</h1>
<p className="max-w-2xl mb-6 font-light text-gray-500 lg:mb-8 md:text-lg lg:text-xl dark:text-gray-400">
This tool allows you to expand your connection graph and find new
people to follow. It works by looking up your &quot;follows&apos;
follows&quot;. <br /> <br />
Your extended network is a treasure trove!
</p>
<a href="#searchForm" className="
<div className="space-y-4 sm:flex sm:space-y-0 sm:space-x-4 ">
<a
href="https://github.com/gabipurcaru/followgraph"
className="inline-flex items-center justify-center w-full px-5 py-3 text-sm font-medium text-center text-gray-900 border border-gray-200 rounded-lg sm:w-auto hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 dark:text-white dark:border-gray-700 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
>
<svg
className="w-4 h-4 mr-2 text-gray-500 dark:fill-gray-300"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512"
>
{/* Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) */}
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
</svg>{' '}
View on GitHub
</a>
<a
href="#searchForm"
className="
inline-flex items-center justify-center w-full
px-5 py-3 text-sm font-medium text-center
text-gray-900 border
@ -32,22 +45,30 @@ export default function Hero({ }) {
dark:bg-green-700 dark:hover:bg-green-600
dark:focus:ring-gray-800
dark:border-gray-700 ">
<svg className="w-4 h-4 mr-2 dark:fill-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">{/* Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}<path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z" /></svg>
Use now
</a>
dark:border-gray-700 "
>
<svg
className="w-4 h-4 mr-2 dark:fill-gray-300"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
{/* Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z" />
</svg>
Use now
</a>
</div>
</div>
<div className="hidden lg:mt-0 lg:col-span-5 lg:flex">
<Image
src="/hero.png"
alt="Picture of people at a party"
width={500}
height={500}
priority
/>
</div>
</div>
<div className="hidden lg:mt-0 lg:col-span-5 lg:flex">
<Image
src="/hero.png"
alt="Picture of people at a party"
width={500}
height={500}
priority
/>
</div>
</div>
</section>;
</section>
)
}

22
package-lock.json wygenerowano
Wyświetl plik

@ -31,6 +31,7 @@
"autoprefixer": "^10.4.13",
"eslint-config-prettier": "^8.5.0",
"postcss": "^8.4.20",
"prettier": "2.8.1",
"tailwindcss": "^3.2.4"
}
},
@ -3440,6 +3441,21 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz",
"integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@ -6608,6 +6624,12 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
"prettier": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz",
"integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==",
"dev": true
},
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",

Wyświetl plik

@ -6,7 +6,9 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"prettier": "prettier --check . --config .prettierrc",
"prettier:fix": "prettier --write . --config .prettierrc"
},
"dependencies": {
"@next/font": "13.0.7",
@ -32,6 +34,7 @@
"autoprefixer": "^10.4.13",
"eslint-config-prettier": "^8.5.0",
"postcss": "^8.4.20",
"prettier": "2.8.1",
"tailwindcss": "^3.2.4"
}
}

Wyświetl plik

@ -1,10 +1,12 @@
import '../styles/globals.css'
import { Analytics } from '@vercel/analytics/react';
import { Analytics } from '@vercel/analytics/react'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return (<>
<Component {...pageProps} />
<Analytics />
</>);
return (
<>
<Component {...pageProps} />
<Analytics />
</>
)
}

Wyświetl plik

@ -1,8 +1,8 @@
import { Content } from './../components/Content';
import { FAQ } from './../components/FAQ';
import Footer from './../components/Footer';
import Hero from './../components/Hero';
import Header from './../components/Header';
import { Content } from './../components/Content'
import { FAQ } from './../components/FAQ'
import Footer from './../components/Footer'
import Hero from './../components/Hero'
import Header from './../components/Header'
import Head from 'next/head'
export default function Home() {
@ -10,7 +10,10 @@ export default function Home() {
<>
<Head>
<title>Followgraph for Mastodon</title>
<meta name="description" content="Find people to follow on Mastodon by expanding your follow graph." />
<meta
name="description"
content="Find people to follow on Mastodon by expanding your follow graph."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>

Wyświetl plik

@ -1 +1 @@
google-site-verification: googledfe269244d136c58.html
google-site-verification: googledfe269244d136c58.html

Wyświetl plik

@ -1 +1,19 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Wyświetl plik

@ -2,4 +2,6 @@
@tailwind components;
@tailwind utilities;
html { scroll-behavior: smooth; }
html {
scroll-behavior: smooth;
}

Wyświetl plik

@ -1,8 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},