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

@ -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;
})
data = [...data, ...page];
nextPage = getNextPage(response.headers.get('Link'));
entry.acct = `${entry.acct}@${domain}`
}
return data;
return entry
})
data = [...data, ...page]
nextPage = getNextPage(response.headers.get('Link'))
}
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(
indirectFollows
.filter(
// exclude direct follows
({ acct }) => !directFollowIds.has(acct)
).map(account => {
const acct = account.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]);
const otherAccount = indirectFollowMap.get(acct)
account.followed_by = new Set([
...Array.from(account.followed_by.values()),
...otherAccount.followed_by,
])
}
indirectFollowMap.set(acct, account);
});
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>>([]);
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">
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;
}}>
<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
<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">
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}
{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>
{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.
<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}
) : null}
</div>
</form>
{isDone || follows.length > 0 ?
{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
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>;
</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";
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>
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">
<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.
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.
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.
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>
<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.
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.
Well, maybe it should be. In the meantime, you can use this
website.
</FAQItem>
</div>
</div>
</div>
</section>;
</section>
)
}
function FAQItem({ defaultSelected, title, children }: { defaultSelected?: boolean, title: string, children: React.ReactNode }) {
const [selected, setSelected] = useState(defaultSelected);
return (<>
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">
<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>
<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">
{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> : null}
</>);
</div>
) : null}
</>
)
}

Wyświetl plik

@ -1,16 +1,41 @@
import React from "react";
import React from 'react'
export default function Footer({}) {
return <footer className="bg-white dark:bg-gray-800">
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>.
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 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>
</div>
</footer>;
</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">
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">
<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="#" selected>
Home
</MenuItem>
<MenuItem link="#faq">FAQ</MenuItem>
<MenuItem link="https://github.com/gabipurcaru/followgraph">Fork me on GitHub</MenuItem>
<MenuItem link="https://github.com/gabipurcaru/followgraph">
Fork me on GitHub
</MenuItem>
</ul>
</div>
</div>
</nav>
</header>);
</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>);
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">
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">
) : (
<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>);
)}
</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">
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/>
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
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="
<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,9 +45,16 @@ 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>
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>
@ -49,5 +69,6 @@ export default function Hero({ }) {
/>
</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 (<>
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,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: {},