337 lines
9.4 KiB
Plaintext
337 lines
9.4 KiB
Plaintext
---
|
|
|
|
import Button from '@components/form/Button.astro';
|
|
import { parseMarkdown, formatLink } from '@utils/parse-markdown';
|
|
import type { Service } from 'src/types/Service';
|
|
import FontAwesome from '@components/form/FontAwesome.svelte';
|
|
import DeleteListing from '@components/things/DeleteListing.svelte';
|
|
import { slugify } from '@utils/fetch-data';
|
|
|
|
import GitHubMetrics from '@components/things/ItemGitHubMetrics.astro';
|
|
import SaveListing from '@components/things/SaveListing.svelte';
|
|
|
|
interface Props {
|
|
services: Service[];
|
|
subHeading?: boolean;
|
|
buttonLink?: string;
|
|
noGitHubMetrics?: boolean;
|
|
sectionName: string;
|
|
categoryName: string;
|
|
}
|
|
|
|
const {
|
|
services,
|
|
subHeading,
|
|
buttonLink,
|
|
noGitHubMetrics,
|
|
sectionName,
|
|
categoryName,
|
|
} = Astro.props;
|
|
|
|
---
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const serviceIcons = document.querySelectorAll<HTMLImageElement>('.service-icon');
|
|
const broke = '/broken-image.png';
|
|
|
|
serviceIcons.forEach(function(icon) {
|
|
icon.onerror = function() {
|
|
const imgElement = this as HTMLImageElement;
|
|
const serviceUrl = imgElement.getAttribute('data-service-url');
|
|
const newSrcAttribute = (imgElement.src.includes('on.ho') ? broke : `https://icon.horse/icon/${serviceUrl}`);
|
|
imgElement.src = imgElement.src !== newSrcAttribute ? newSrcAttribute : broke;
|
|
imgElement.onerror = null;
|
|
};
|
|
});
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
<section>
|
|
{services && services.length > 0 ? (
|
|
<ul>
|
|
{services.map((service: Service) => (
|
|
<li id={slugify(service.name)}>
|
|
<DeleteListing client:load categoryName={categoryName} sectionName={sectionName} serviceName={service.name} />
|
|
<div class="save-listing">
|
|
<SaveListing client:visible
|
|
categoryName={categoryName}
|
|
sectionName={sectionName}
|
|
serviceName={service.name}
|
|
/>
|
|
</div>
|
|
<div class="service-head">
|
|
<img
|
|
width="40"
|
|
height="40"
|
|
loading="lazy"
|
|
decoding="async"
|
|
class="service-icon"
|
|
alt={`${service.name} Icon`}
|
|
data-service-url={formatLink(service.url)}
|
|
src={service.icon || `https://icon.horse/icon/${formatLink(service.url)}`}
|
|
/>
|
|
|
|
<a class="service-title" href={`/${slugify(categoryName)}/${slugify(sectionName)}/${slugify(service.name)}`}>
|
|
{subHeading ? <h4>{service.name}</h4> : <h3>{service.name}</h3>}
|
|
</a>
|
|
{service.followWith && <p class="follow-with">({service.followWith})</p> }
|
|
<a class="service-link" href={service.url}>{formatLink(service.url)}</a>
|
|
</div>
|
|
<div class="service-body">
|
|
<p set:html={parseMarkdown(service.description)}></p>
|
|
<div class="service-stats">
|
|
<div class="left">
|
|
{ service.securityAudited && (
|
|
<span class="meta-item great" title={`${service.name} has been security audited by an accredited auditor, with results published publicly`}>
|
|
<FontAwesome iconName="securityAudited" /> Security Audited
|
|
</span>
|
|
)}
|
|
{ service.acceptsCrypto && (
|
|
<span class="meta-item great" title={`${service.name} accepts anonymous payment methods`}>
|
|
<FontAwesome iconName="cryptoAccepted" /> Crypto Payments Accepted
|
|
</span>
|
|
)}
|
|
{ service.securityAudited === false && (
|
|
<span class="meta-item warning" title={`${service.name} has not been audited`}>
|
|
<FontAwesome iconName="notSecurityAudited" /> No Security Audit
|
|
</span>
|
|
)}
|
|
{ (service.openSource === false) && (
|
|
<span class="warning">
|
|
<FontAwesome iconName="closedSource" />
|
|
Not Open Source
|
|
</span>
|
|
)}
|
|
{ service.openSource || (service.github && service.openSource !== false) ? (
|
|
<span class="meta-item great" title={`${service.name} is open source`}>
|
|
<FontAwesome iconName="openSource" /> Open Source
|
|
</span>
|
|
) : null }
|
|
{ service.github && !noGitHubMetrics && <GitHubMetrics github={service.github} /> }
|
|
{ service.github && noGitHubMetrics && (
|
|
<span class="meta-item" title={`View ${service.name} on GitHub`}>
|
|
<a href={`https://github.com/${service.github}`} target="_blank">
|
|
<FontAwesome iconName="github" /> {service.github}
|
|
</a>
|
|
</span>
|
|
) }
|
|
</div>
|
|
<div class="view-service">
|
|
<a href={`/${slugify(categoryName)}/${slugify(sectionName)}/${slugify(service.name)}`}>View {service.name} Report</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p class="nothing-yet">
|
|
<strong>⚠️ This section is still a work in progress ⚠️</strong><br />
|
|
Check back soon, or help us complete it by submiting a pull request on GitHub.
|
|
<br />
|
|
<span class="quick-submit">Or submit an entry <a href="/submit">here</a></span>
|
|
</p>
|
|
)}
|
|
|
|
{buttonLink && (
|
|
<Button title={`View all ${categoryName}`} className="view-all" text="View More..." url={buttonLink} />
|
|
)}
|
|
</section>
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
section {
|
|
padding: 1rem 0;
|
|
position: relative;
|
|
&:not(:last-child) {
|
|
border-bottom: 2px solid var(--accent-3);
|
|
}
|
|
}
|
|
|
|
.nothing-yet {
|
|
font-size: 1.4rem;
|
|
opacity: 0.8;
|
|
font-style: italic;
|
|
text-align: center;
|
|
margin-bottom: 3rem;
|
|
.quick-submit {
|
|
margin-top: 1rem;
|
|
font-size: 0.8rem;
|
|
opacity: 0.8;
|
|
}
|
|
}
|
|
|
|
ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0 0 3rem 0;
|
|
li {
|
|
margin-bottom: 1rem;
|
|
position: relative;
|
|
.save-listing {
|
|
position: absolute;
|
|
right: 1rem;
|
|
top: 1rem;
|
|
}
|
|
.service-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
.service-title {
|
|
text-decoration: none;
|
|
color: var(--foreground);
|
|
h3, h4 {
|
|
margin: 0;
|
|
font-size: 1.6rem;
|
|
}
|
|
|
|
position: relative;
|
|
&:after {
|
|
background: none repeat scroll 0 0 transparent;
|
|
bottom: 0;
|
|
content: "";
|
|
display: block;
|
|
height: 3px;
|
|
left: 50%;
|
|
position: absolute;
|
|
background: var(--accent-3);
|
|
transition: width 0.3s ease 0s, left 0.3s ease 0s;
|
|
width: 0;
|
|
}
|
|
&:hover:after {
|
|
width: 100%;
|
|
left: 0;
|
|
}
|
|
}
|
|
|
|
.service-icon {
|
|
width: 2.5rem;
|
|
height: 2.5rem;
|
|
border-radius: var(--curve-sm);
|
|
|
|
font-size: 10px;
|
|
overflow: hidden;
|
|
color: var(--accent);
|
|
}
|
|
.follow-with {
|
|
opacity: 0.7;
|
|
font-style: italic;
|
|
margin: 0;
|
|
}
|
|
.service-link {
|
|
max-width: 300px;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
.service-body {
|
|
margin: 0.5rem 0 2rem;
|
|
opacity: 0.8;
|
|
:global(p) {
|
|
margin: 0;
|
|
font-size: 1.2rem;
|
|
:global(a) {
|
|
color: var(--foregorund);
|
|
}
|
|
}
|
|
.service-stats {
|
|
.left {
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
.view-service {
|
|
transition: all 0.2s ease-in-out;
|
|
opacity: 0.95;
|
|
a {
|
|
padding: 0.25rem 0.6rem;
|
|
width: fit-content;
|
|
right: 1rem;
|
|
font-size: 0.9rem;
|
|
background: var(--accent-3);
|
|
color: var(--accent-fg);
|
|
text-decoration: none;
|
|
border-radius: var(--curve-md);
|
|
}
|
|
&:hover {
|
|
opacity: 1;
|
|
transform: scale(1.05);
|
|
}
|
|
}
|
|
|
|
.meta-item, .warning {
|
|
display: flex;
|
|
align-items: center;
|
|
// justify-content: center;
|
|
gap: 0.25rem;
|
|
// opacity: 0.6;
|
|
font-size: 0.9rem;
|
|
padding: 0.5rem 0;
|
|
:global(svg) {
|
|
color: var(--foreground);
|
|
width: 1.2rem;
|
|
height: 1.2rem;
|
|
}
|
|
a {
|
|
text-decoration: none;
|
|
color: var(--foreground);
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
&:hover {
|
|
color: var(--accent-3);
|
|
:global(svg) {
|
|
color: var(--accent-3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.warning {
|
|
color: var(--danger);
|
|
:global(svg) {
|
|
color: var(--danger);
|
|
}
|
|
}
|
|
.great {
|
|
color: #0fb953; // var(--success);
|
|
:global(svg) {
|
|
color: #0fb953; // var(--success);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
section :global(.view-all) {
|
|
width: fit-content;
|
|
position: absolute;
|
|
right: 1rem;
|
|
margin-top: -2.5rem;
|
|
background: var(--accent-3);
|
|
}
|
|
|
|
li:hover :global(.actions) {
|
|
opacity: 0.6;
|
|
:global(a):hover {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
:global(.actions a):hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
</style>
|