Adds edit/remove capabilities, improves responsivness, makes service name hoverable, implements new screenshot service

This commit is contained in:
Alicia Sykes 2024-03-05 23:15:33 +00:00
parent 043dbc4f27
commit 282e3bc060
8 changed files with 203 additions and 42 deletions

View file

@ -23,6 +23,10 @@
faceMeh: solidIcons.faMeh,
faceBad: solidIcons.faFrown,
// Service actions
delete: solidIcons.faTrash,
edit: solidIcons.faPenToSquare,
// Website Detailed Stats
blacklistFound: solidIcons.faDoNotEnter,
blacklistNotFound: solidIcons.faShieldCheck,

View file

@ -13,7 +13,6 @@ const { gitHubData, repo } = Astro.props;
function generateChartData(languages: Record<string, number>): string {
const colorMap: { [key: string]: string } = {
'TypeScript': '#2b7489',
'Objective-C++': '#6866fb',
@ -21,26 +20,26 @@ function generateChartData(languages: Record<string, number>): string {
'Dart': '#00B4AB',
'Shell': '#89e051',
'Elixir': '#6e4a7e',
'Kotlin': '#F18E33',
'Kotlin': '#7F52FF',
'Objective-C': '#438eff',
'Ruby': '#701516',
'Go': '#375eab',
'Ruby': '#CC342D',
'Go': '#00ADD8',
'PHP': '#4F5D95',
'Java': '#b07219',
'Scala': '#DC322F',
'Makefile': '#427819',
'Perl': '#0298c3',
'Lua': '#000080',
'Vue': '#2c3e50',
'HTML': '#e44b23',
'Swift': '#ffac45',
'C': '#555555',
'Perl': '#39457E',
'Lua': '#2C2D72',
'Vue': '#4FC08D',
'HTML': '#E34F26',
'Swift': '#F05138',
'C': '#A8B9CC',
'C#': '#097c01',
'Clojure': '#db5855',
'Rust': '#dea584',
'Assembly': '#6E4C13',
'Arduino': '#bd79d1',
'C Sharp': '#178600',
'C Sharp': '#512BD4',
'CSS': '#563d7c',
'TeX': '#3D6117',
'OCaml': '#3be133',
@ -55,7 +54,9 @@ function generateChartData(languages: Record<string, number>): string {
'Common Lisp': '#3fb68b',
'Groovy': '#e69f56',
'Julia': '#a270ba',
'cpp': '#f34b7d',
'cpp': '#00599C',
'C++': '#00599C',
'Dockerfile': '#2496ED',
};
// Arrays to hold the data, labels, and colors
@ -131,7 +132,7 @@ const languageChart = `https://quickchart.io/chart?c=${generateChartData(gitHubD
<h4>Author</h4>
<p>
<img src={gitHubData.info.ownerAvatar} width="20" height="20" />
<a href={`https://github.com/${gitHubData.info.ownerUsername}`}>
<a target="_blank" href={`https://github.com/${gitHubData.info.ownerUsername}`}>
{gitHubData.info.ownerUsername}
</a>
</p>
@ -184,7 +185,9 @@ const languageChart = `https://quickchart.io/chart?c=${generateChartData(gitHubD
{gitHubData.contributors.map((contributor) => (
<li>
<img src={contributor.avatar} width="20" height="20" />
<a href={`https://github.com/${contributor.username}`}>@{contributor.username}</a> ({contributor.contributions})
<a href={`https://github.com/${contributor.username}`} target="_blank">
@{contributor.username}
</a> ({contributor.contributions})
</li>
))}
</ul>
@ -195,7 +198,9 @@ const languageChart = `https://quickchart.io/chart?c=${generateChartData(gitHubD
{gitHubData.commits.map((commit) => (
<li>
<img src={commit.authorAvatar} width="20" height="20" />
<a href={`https://github.com/${commit.authorUsername}`}>{commit.authorName}</a>
<a href={`https://github.com/${commit.authorUsername}`} target="_blank">
{commit.authorName}
</a>
<span class="date" title={timeAgo(commit.authorDate)}>({formatDate(commit.authorDate)})</span>
<p class="commit-msg">{commit.message}</p>
</li>

View file

@ -90,7 +90,7 @@ const {
<FontAwesome iconName="openSource" /> Open Source
</span> -->
<span title={`GitHub repository: ${github}`}>
<a href={`https://github.com/${github}`}><FontAwesome iconName="github" /> {github}</a>
<a href={`https://github.com/${github}`} target="_blank"><FontAwesome iconName="github" /> {github}</a>
</span>
{ stats.statusCode === 200 && stats.data && (
<span title={`GitHub Star Count: ${stargazers_count}`}>

View file

@ -4,6 +4,7 @@ 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';
@ -52,9 +53,7 @@ const {
<ul>
{services.map((service: Service) => (
<li id={slugify(service.name)}>
<div class="actions">
</div>
<DeleteListing client:load categoryName={categoryName} sectionName={sectionName} serviceName={service.name} />
<div class="service-head">
<img
width="40"
@ -106,7 +105,7 @@ const {
{ 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}`}>
<a href={`https://github.com/${service.github}`} target="_blank">
<FontAwesome iconName="github" /> {service.github}
</a>
</span>
@ -155,6 +154,7 @@ const {
margin: 0 0 3rem 0;
li {
margin-bottom: 1rem;
position: relative;
.service-head {
display: flex;
align-items: center;
@ -167,6 +167,24 @@ const {
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 {
@ -275,14 +293,16 @@ const {
}
}
section :global(.view-all) {
width: fit-content;
position: absolute;
right: 1rem;
margin-top: -2.5rem;
background: var(--accent-3);
}
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.5;
}
</style>

View file

@ -39,11 +39,7 @@ else if (risk < 50) riskText = 'risky';
else if (risk < 75) riskText = 'dangerous';
else if (risk >= 75) riskText = 'very dangerous';
const clientSideKey = 'cd02a8520c90410bbc32440950bd3b7e';
const screenshotOptions = 'format=png&width=960&height=540&ttl=2419200&response_type=image&no_cookie_banners=true&no_ads=true';
const screenshotEndpoint = 'https://api.apiflash.com/v1';
const screenshotUrl = `${screenshotEndpoint}/urltoimage?access_key=${clientSideKey}&url=${encodeURIComponent(url)}&${screenshotOptions}`;
const screenshotUrl = `https://screenshot-cache.as93.workers.dev/${url}`;
---

View file

@ -6,6 +6,7 @@ import Comments from '@components/things/Comments.svelte';
import GitHubDetailedInfo from '@components/things/GitHubDetailedInfo.astro';
import PrivacyPolicyDetails from '@components/things/PrivacyPolicyDetails.astro';
import WebsiteDetailedInfo from '@components/things/WebsiteDetailedInfo.astro';
import DataActions from '@components/things/DataActions.svelte';
import type { AwesomePrivacy, Section, Service } from '../types/Service';
import { fetchData, slugify } from '@utils/fetch-data';
import { fetchGitHubStats } from '@utils/fetch-repo-info';
@ -123,6 +124,10 @@ const githubData = github ? await fetchGitHubStats(github) : null;
const privacyData = tosdrId ? await fetchTosdrPrivacy(tosdrId) : null;
const websiteData = (url && !ignoredSites.some(ignoredSite => url.includes(ignoredSite))) ? await fetchWebsiteInfo(url) : null;
const getApiEndpoint = () => {
return `https://api.awesome-privacy.xyz/${slugify(categoryName)}/${slugify(parentSection.name)}/${slugify(name)}`;
}
---
<Layout title={makePageTitle()} keywords={makeKeyWordTag()} description={makeDescriptionTag()} >
@ -171,7 +176,7 @@ const websiteData = (url && !ignoredSites.some(ignoredSite => url.includes(ignor
)}
{ tosdrId && (
<li>
<b>ToS;DR:</b>
<b>Privacy:</b>
<a href={`https://tosdr.org/en/service/${tosdrId}`}>{`tosdr.org/en/service/${tosdrId}`}</a>
</li>
)}
@ -247,6 +252,29 @@ const websiteData = (url && !ignoredSites.some(ignoredSite => url.includes(ignor
sectionName={parentSection.name}
/>
</section>
<section>
<h3>About the Data: {name}</h3>
<DataActions client:load categoryName={categoryName} sectionName={parentSection.name} serviceName={name} />
<h4>API</h4>
<p>
You can access {name}'s data programmatically via our API.
Simply make a <code>GET</code> request to:
</p>
<pre><a href={getApiEndpoint()}><code>{getApiEndpoint()}</code></a></pre>
<p>
The REST API is free, no-auth and CORS-enabled.
To learn more, view the <a href="/api/docs">Swagger Docs</a> or read the <a href="/about#our-api">API Usage Guide</a>.
</p>
<h4>About the Data</h4>
<p>
Beyond the user-submitted YAML you see above, we also augment each listing with
additional data dynamically fetched from several sources.
To learn more about where the rest of data included in this page comes from,
and how it is computed,
see the <a href="/about#our-data">About the Data</a> section of our About page.
</p>
</section>
<div class="pagination-navigation">
{ previous ? (
@ -276,6 +304,11 @@ main {
width: 1000px;
max-width: calc(100% - 5rem);
position: relative;
@media(max-width: 768px) {
max-width: 95%;
padding: 0.5rem;
margin: 0 auto;
}
}
section {
@ -289,6 +322,10 @@ section {
}
p {
margin: 0.25rem 0 0.5rem 0;
}
h3 {
font-size: 2rem;
margin: -2rem 0 1rem -2rem;
@ -297,12 +334,18 @@ h3 {
color: var(--accent-fg);
width: fit-content;
padding: 0.25rem 0.5rem;
@media(max-width: 768px) {
margin: -2rem auto 2rem auto;
}
}
.service-head {
h2 {
font-size: 3rem;
margin: 0;
@media(max-width: 768px) {
text-align: center;
}
}
.url-wrap {
display: flex;
@ -315,6 +358,9 @@ h3 {
font-style: italic;
opacity: 0.6;
}
@media(max-width: 768px) {
flex-direction: column;
}
}
}
@ -333,6 +379,14 @@ h3 {
color: var(--accent);
}
}
@media(max-width: 768px) {
display: none;
}
}
h4 {
font-size: 1.4rem;
margin-bottom: 0;
}
.intro {
@ -341,12 +395,15 @@ h3 {
opacity: 0.9;
display: flex;
gap: 0.5rem;
@media(max-width: 768px) {
flex-direction: column;
}
.service-icon {
border-radius: var(--curve-md);
margin-top: 1rem;
}
h4 {
margin-bottom: 0;
@media(max-width: 768px) {
margin: 1rem auto 0 auto;
}
}
ul {
padding-left: 1rem;
@ -359,6 +416,10 @@ h3 {
font-weight: 400;
min-width: 85px;
display: inline-block;
@media(max-width: 768px) {
width: auto;
display: block;
}
}
}
}
@ -439,6 +500,23 @@ h3 {
}
}
pre {
a {
text-decoration: none;
color: var(--foreground);
code {
background: #acabb782;
padding: 0.2rem 0.4rem;
border-radius: var(--curve-sm);
font-family: Courier New;
font-size: 0.9rem;
@media(max-width: 768px) {
white-space: break-spaces;
}
}
}
}
.highlights {
display: flex;
gap: 1rem;

View file

@ -32,6 +32,41 @@ const licenseContent = async () => {
return await response.text();
};
const aboutOurData = `
All data is stored in
[\`awesome-privacy.yml\`](https://github.com/lissy93/awesome-privacy/blob/main/awesome-privacy.yml).
This file is then pulled into the website at build-time, and also used to generate
the repositories README file.
We use this method in the interest of transparency and data integrity,
as Git makes it possible to maintain a full log of what was changed, when, by who and why.
### Augmention
The data is augmented with some extra info, to add additional context to each service.
The aim of this is to give you a broader picture of each service, to help you make a more informed decision.
Currently, this extra data is pulled from:
- **GitHub API** - To fetch info about each project's repository
- Including author & contributors, creation & last updated date, languages, license and star count
- **Web Check** - To fetch info about each project's website
- Including SSL chain, WHOIS, server location, blacklist check and a screenshot
- **ToS;DR** - To fetch a summerized version of each services Privacy Policy
- Including policy and terms of service links, postives + negatives and privacy score
- **User Reviews** - User-submitted comments + feedback on a given service
- Implemented using self-hosted instance of Remark42
- **Portainer Templates** - To fetch info about each project's Docker image (if it's self-hosted)
- Including supported architectures, run command, pull count, last updated and config options
### API
We also have a free, no-auth, CORS-enabled RESR API,
which you can use to access Awesome Privacy's data programmatically,
or to build your own apps on top of it.
The [Swagger Spec](#) outlines all endpoints, usage and examples.
The host is: \`https://api.awesome-privacy.xyz\`
`;
const projectRequrements = `
For software to be included in this list, it must meet the following requirements:
@ -43,13 +78,16 @@ For software to be included in this list, it must meet the following requirement
- The software must be secure by default, without requiring additional configuration
- There should be no current, critical security issues
- The handling of past issues must have been prompt, transparent and effective
- Ideally hosted services should have been security audited, with the report published publicly
- **Open Source**
- The full source code should be released under an open source license
- Ideally it should be possible for the user to build and run/deploy the software themselves from source
- **Actively Maintained**
- The developers should address dependency updates and security patches in a timely manner
- Ideally the source should have been updated within the last 12 months
- **Transparent**
- It should be clear who is behind the project, what their motives are, and what (if any) the funding model is
- For hosted solutions, the privacy policy should clearly state what data is collected, how it's used and how long it's stored
- **Ethical**
- Must not suppress free speech, discriminate or disregard any human rights
- **Relevant**
@ -176,6 +214,10 @@ const description = 'Privacy is a fundamental human right; '
<div class="software-requirements"><p set:html={parseMarkdown(projectRequrements)}></p></div>
<hr />
<h2 id="our-data">Our Data</h2>
<div class="about-the-data"><p set:html={parseMarkdown(aboutOurData)}></p></div>
<hr />
<h2 id="contributing">Contributing</h2>
<p>
Awesome Privacy (including all data and code) is fully open source,
@ -305,7 +347,7 @@ h2 {
margin: 2rem 0 1rem 0;
}
p {
font-size: 1.4rem;
font-size: 1.2rem;
}
hr {
margin: 2rem 0;
@ -323,14 +365,15 @@ h3 {
font-size: 1.6rem;
}
.software-requirements {
.software-requirements, .about-the-data {
:global(p) {
font-size: 1.4rem;
font-size: 1.2rem;
margin: 0 0 0.5rem 0;
}
:global(ul) {
padding-left: 0.5rem;
list-style: none;
font-size: 1.1rem;
font-size: 1.2rem;
:global(li ul) {
padding: 0 0 0.5rem 1rem;
list-style: circle;
@ -341,6 +384,20 @@ h3 {
transition: color 0.2s ease-in-out;
color: var(--accent-3);
}
:global(h3) {
font-size: 1.6rem;
margin-bottom: 0;
}
:global(strong) {
font-weight: 500;
}
:global(code) {
font-family: 'Courier New', Courier, monospace;
background: #acabb782;
padding: 0.2rem 0.4rem;
border-radius: var(--curve-sm);
font-size: 0.9rem;
}
}
.user-list {
@ -401,6 +458,7 @@ h3 {
max-width: 550px;
margin: 0 auto;
}
small {
font-size: 1rem;
margin: 1rem 0;

View file

@ -84,7 +84,7 @@ code {
font-style: normal;
}
html, body, p, a, ul, ol, li, blockquote, pre, code, strong, i {
html, body, p, a, ul, ol, li, blockquote, pre, strong, i {
font-family: "Rubik", sans-serif;
}