Adds edit functionality

This commit is contained in:
Alicia Sykes 2024-03-07 16:52:56 +00:00
parent 282e3bc060
commit 7fef7d3edd
14 changed files with 467 additions and 13 deletions

View file

@ -5,7 +5,7 @@ import FontAwesome from "@components/form/FontAwesome.svelte"
<div class="hero">
<h1>Awesome Privacy</h1>
<p class="intro">
Your guide to finding privacy-respecting alternatives to popular software and services.
Your guide to finding and comparing privacy-respecting alternatives to popular software and services.
</p>
<div class="github-link-wrap">
<a href="https://github.com/lissy93/awesome-privacy">

View file

@ -4,11 +4,12 @@ const {
text,
url,
className,
title,
} = Astro.props;
---
<div class={`button ${className || ''}`}>
<div class={`button ${className || ''}`} title={title}>
<a href={url}>{text}<slot /></a>
</div>

View file

@ -26,6 +26,7 @@
// Service actions
delete: solidIcons.faTrash,
edit: solidIcons.faPenToSquare,
add: solidIcons.faPlusHexagon,
// Website Detailed Stats
blacklistFound: solidIcons.faDoNotEnter,

View file

@ -0,0 +1,300 @@
<script lang="ts">
import yaml from 'js-yaml';
import { writable } from 'svelte/store';
import { makeAdditionRequest } from '../../utils/data-src-delete-n-edit';
// Defining writable stores for each form field
const listingCategory = writable('');
const serviceName = writable('');
const serviceUrl = writable('');
const serviceIcon = writable('');
const serviceDescription = writable('');
const serviceGithub = writable('');
const serviceTosdrId = writable('');
const serviceOpenSource = writable(false);
const serviceSecurityAudited = writable(false);
const serviceCrypto = writable(false);
const additionalInfo = writable('');
$: yamlText = yaml.dump([{
name: $serviceName,
url: $serviceUrl,
icon: $serviceIcon,
description: $serviceDescription,
github: $serviceGithub,
tosdrId: $serviceTosdrId,
openSource: $serviceOpenSource,
securityAudited: $serviceSecurityAudited,
acceptsCrypto: $serviceCrypto,
}]);
$: issueUrl = makeAdditionRequest({
listingCategory: $listingCategory,
serviceName: $serviceName,
serviceUrl: $serviceUrl,
serviceIcon: $serviceIcon,
serviceDescription: $serviceDescription,
serviceGithub: $serviceGithub,
serviceTosdrId: $serviceTosdrId,
serviceOpenSource: $serviceOpenSource,
serviceSecurityAudited: $serviceSecurityAudited,
serviceCrypto: $serviceCrypto,
additionalInfo: $additionalInfo,
}, yamlText);
// Form submission handler
function handleSubmit() {
const formData = {
listingCategory: $listingCategory,
serviceName: $serviceName,
serviceUrl: $serviceUrl,
serviceIcon: $serviceIcon,
serviceDescription: $serviceDescription,
serviceGithub: $serviceGithub,
serviceTosdrId: $serviceTosdrId,
serviceOpenSource: $serviceOpenSource,
serviceSecurityAudited: $serviceSecurityAudited,
serviceCrypto: $serviceCrypto,
additionalInfo: $additionalInfo,
};
const issueCreationUrl = makeAdditionRequest(formData, yamlText);
window.open(issueCreationUrl, '_blank');
}
</script>
<p>
Before completing this form, you must ensure that the service you are adding aligns
with the <a href="/about#creteria">Requirements</a> for Awesome Privacy.
<br />
You'll need a GitHub account in order to submit this form.
</p>
<form on:submit|preventDefault={handleSubmit}>
<!-- Category Dropdown -->
<div class="form-row">
<label for="listing-category">Category</label>
<select bind:value={$listingCategory} id="listing-category" required autocomplete="off">
<option value="">--Please choose an option--</option>
<option value="Essentials">Essentials</option>
<option value="Communication">Communication</option>
<option value="Security Tools">Security Tools</option>
<option value="Networking">Networking</option>
<option value="Productivity">Productivity</option>
<option value="Utilities">Utilities</option>
<option value="Operating Systems">Operating Systems</option>
<option value="Development">Development</option>
<option value="Home and IoT">Home and IoT</option>
<option value="Finance">Finance</option>
<option value="Social">Social</option>
<option value="Media">Media</option>
<option value="Creativity">Creativity</option>
</select>
<p>
Choose the top-level category, which should align with
the <a href="/browse">one of these</a>.
</p>
</div>
<!-- Listing Name -->
<div class="form-row">
<label for="service-name">Listing Name</label>
<input type="text" bind:value={$serviceName} id="service-name" required autocomplete="off">
<p>Enter the name of the app, software or service</p>
</div>
<!-- Listing URL -->
<div class="form-row">
<label for="service-url">Listing URL</label>
<input type="url" bind:value={$serviceUrl} id="service-url" required autocomplete="off">
<p>Enter the fully-qualified domain name of the homepage for this listing</p>
</div>
<!-- Listing Icon -->
<div class="form-row">
<label for="service-icon">Listing Icon</label>
<input type="url" bind:value={$serviceIcon} id="service-icon" required autocomplete="off">
<p>Paste a URL to a square logo for the service. Dimensions must be no less than 64x64, and no more than 512x512 pixels</p>
</div>
<!-- Listing Description -->
<div class="form-row">
<label for="service-description">Listing Description</label>
<textarea bind:value={$serviceDescription} id="service-description" required autocomplete="off"></textarea>
<p>Please provide a description for this listing. Keep it factual and objective. Markdown is supported.</p>
</div>
<!-- GitHub Repository -->
<div class="form-row">
<label for="service-github">GitHub Repository</label>
<input type="text" bind:value={$serviceGithub} id="service-github" required autocomplete="off">
<p>Share a link to where the project's source is located</p>
</div>
<!-- ToS;DR ID -->
<div class="form-row">
<label for="service-tosdr-id">ToS;DR ID</label>
<input type="number" bind:value={$serviceTosdrId} id="service-tosdr-id" autocomplete="off">
<p>
Has the Privacy policy been documented by <a href="https://tosdr.org/">tosdr.org</a>?
If so, please include the report reference below (this is a 3 or 4-digit numerical ID).
Skip section if not applicable.
</p>
</div>
<!-- Open Source Checkbox -->
<div class="form-row">
<label for="service-open-source">Is Open Source?</label>
<input type="checkbox" bind:checked={$serviceOpenSource} id="service-open-source">
<p>Is this service fully open source? Aka, can it be compiled from source by the user, or self-hosted?</p>
</div>
<!-- Security Audited Checkbox -->
<div class="form-row">
<label for="service-security-audited">Security Audited?</label>
<input type="checkbox" bind:checked={$serviceSecurityAudited} id="service-security-audited">
<p>Has this service been independently security audited by an accredited auditor?</p>
</div>
<!-- Accepts Crypto Checkbox -->
<div class="form-row">
<label for="service-crypto">Accepts Anon Payment?</label>
<input type="checkbox" bind:checked={$serviceCrypto} id="service-crypto">
<p>If this is a hosted and paid for service, does it accept anonymous payment methods, including crypto (e.g., Monero)?</p>
</div>
<div class="final-info">
<p>
Finally, please provide any supporting material, including:
</p>
<ul>
<li>
A justification of why this app/service should be included in the list
</li>
<li>
Links to any published security audit, if they exist
</li>
<li>
Links to the services privacy policy, terms of service and other relevant
documents where applicable
</li>
<li>
Your affiliation with the service.
For transparency, you must disclose if you are associated
with them or any similar items in any way
</li>
<li>Links to relevant discussions, past issues/PRs related to this service</li>
</ul>
<textarea bind:value={$additionalInfo} id="additional-info" rows="5"></textarea>
</div>
<button type="submit">Submit</button>
<a href={issueUrl} target="_blank" class="open-in-gh">Open in GitHub Issues</a>
</form>
<div class="output-yaml">
<p>Below is the YAML content, which will be appended to the appropriate section
within <a href="github.com/lissy93/awesome-privacy/blob/main/awesome-privacy.yml">awesome-privacy.yml</a>
upon approval.
</p>
<pre>{@html yamlText}</pre>
<p>Your submission will need to be reviewed by a maintainer and the community before it can be merged.</p>
</div>
<style lang="scss">
.form-row {
display: grid;
grid-template-columns: 1fr 3fr 2fr;
gap: 1rem;
padding: 0.5rem 0;
&:not(:last-child) {
border-bottom: 1px solid var(--transparent-accent);
}
p {
margin: 0;
font-size: 0.8rem;
opacity: 0.6;
}
}
.final-info {
display: flex;
flex-direction: column;
padding: 1rem 0;
gap: 1rem;
p {
margin: 0.5rem 0;
}
ul {
padding-left: 0.25rem;
margin: 0 0 0.5rem 0;
font-size: 0.8rem;
opacity: 0.6;
list-style: circle;
}
}
input, textarea {
width: 100%;
border: 1px solid var(--accent-3);
border-radius: var(--curve-md);
font-size: 1.2rem;
padding: 0.5rem 0;
&:focus {
outline: none;
border: 2px solid var(--accent);
}
}
input {
height: fit-content;
&[type="number"]::-webkit-outer-spin-button,
&[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
&[type="number"] {
-moz-appearance: textfield;
}
&[type="checkbox"] {
width: 2rem;
height: 2rem;
}
}
textarea {
resize: vertical;
}
.open-in-gh {
margin: 0 auto;
font-size: 0.8rem;
opacity: 0.6;
display: block;
text-align: center;
}
button {
cursor: pointer;
background: var(--accent-3);
color: var(--accent-fg);
padding: 0.5rem 2rem;
border: 1px solid var(--foreground);
box-shadow: 3px 3px 0 var(--foreground);
border-radius: var(--curve-lg);
font-size: 1.8rem;
font-family: "Lekton", sans-serif;
margin: 1rem auto;
display: flex;
transition: all 0.2s ease-in-out;
&:hover {
background: var(--accent);
}
}
.output-yaml {
pre {
font-family: 'Courier New', Courier, monospace;
background: #cecbf780;
padding: 0.2rem 0.4rem;
border-radius: var(--curve-sm);
font-size: 0.9rem;
}
}
</style>

View file

@ -64,6 +64,9 @@
href={makeEditRequest(categoryName, sectionName, serviceName, yamlContent)}>
<FontAwesome iconName="edit" /> Submit Edit to {serviceName}
</a>
<a class="button-link" href="/edit">
<FontAwesome iconName="add" /> Add alternative
</a>
</div>
{/if}

View file

@ -1,7 +1,7 @@
<script lang="ts">
import FontAwesome from '@components/form/FontAwesome.svelte';
import { fetchSrcData, makeRemovalRequest, makeEditRequest } from '@utils/data-src-delete-n-edit';
import { fetchSrcData, makeRemovalRequest } from '@utils/data-src-delete-n-edit';
import { onMount } from 'svelte';
@ -9,18 +9,26 @@
export let sectionName: string;
export let serviceName: string;
const apYaml = 'https://github.com/lissy93/awesome-privacy/blob/main/awesome-privacy.yml';
let yamlContent = '';
let editLink = apYaml;
onMount(async () => {
const results = await fetchSrcData(categoryName, sectionName, serviceName);
yamlContent = results.yamlContent;
const lineNumbers = results.lineNumbers || null;
const numberRange = lineNumbers ? `#L${lineNumbers.start}-L${lineNumbers.end}` : '';
const yamlLink = 'https://github.com/lissy93/awesome-privacy/blob/main/awesome-privacy.yml';
editLink = `${yamlLink}${numberRange}`;
});
</script>
<div class="actions">
<a title="Edit" target="_blank"
href={makeEditRequest(categoryName, sectionName, serviceName, yamlContent)}>
href={editLink}>
<FontAwesome iconName="edit" />
</a>
<a title="Delete" target="_blank"

View file

@ -57,6 +57,7 @@ function generateChartData(languages: Record<string, number>): string {
'cpp': '#00599C',
'C++': '#00599C',
'Dockerfile': '#2496ED',
'SCSS': '#CC6699',
};
// Arrays to hold the data, labels, and colors

View file

@ -49,7 +49,7 @@ const {
<section>
{services ? (
{services && services.length > 0 ? (
<ul>
{services.map((service: Service) => (
<li id={slugify(service.name)}>
@ -126,7 +126,9 @@ const {
</p>
)}
{buttonLink && ( <Button className="view-all" text="View More..." url={buttonLink} /> )}
{buttonLink && (
<Button title={`View all ${categoryName}`} className="view-all" text="View More..." url={buttonLink} />
)}
</section>

View file

@ -26,7 +26,7 @@ const categoryLabels: {[key: string]: string} = {
};
const siteCategories = Object.entries(websiteInfo.site_category)
.filter(([key, value]) => value)
.filter(([_key, value]) => value)
.map(([key]) => categoryLabels[key]);
const securityChecks = analyzeSecurityChecks(websiteInfo.security_checks);
@ -311,6 +311,10 @@ h4 {
width: 180px;
float: right;
font-size: 1rem;
transform: scale(0.8);
opacity: 0.85;
transition: all 0.2s ease-in-out;
&:hover { transform: scale(0.85); opacity: 1; }
:global(.button) {
background: var(--accent-3);
}

View file

@ -183,7 +183,7 @@ const getApiEndpoint = () => {
{ url && (
<li>
<b>Web info:</b>
<a href={`https://web-check.xyz/results/${formatLink(url)}`}>{`web-check.xyz/results/${formatLink(url)}`}</a>
<a href={`https://web-check.xyz/results/${formatLink(url)}`}>{`web-check.xyz/results/${formatLink(url).split('/')[0]}`}</a>
</li>
)}
</ul>

View file

@ -43,7 +43,7 @@ as Git makes it possible to maintain a full log of what was changed, when, by wh
### 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.
The aim of this is to give you a broader picture of each listing, 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

View file

@ -1,7 +1,7 @@
import type { APIRoute } from 'astro';
import yaml from 'js-yaml';
import type { AwesomePrivacy, Service } from '../../types/Service';
import type { AwesomePrivacy, Service, Category } from '../../types/Service';
interface LineNumberRange {
start: number;
@ -25,13 +25,15 @@ const awesomePrivacyYamlPath = 'https://raw.githubusercontent.com/Lissy93/awesom
* Given a service object and an array of string lines from the raw YAML
* Find the starting and ending line number for that service
*/
const calculateServiceRange = (service: Service, yamlLines: string[]): LineNumberRange | null => {
const calculateServiceRange = (service: Service, category: Category, yamlLines: string[]): LineNumberRange | null => {
const lookFor = `- name: ${service.name}`;
const start = yamlLines.findIndex(line => line.includes(lookFor)) + 1;
const categoryStart = yamlLines.findIndex(line => line.includes(category.name));
const start = yamlLines.slice(categoryStart).findIndex(line => line.includes(lookFor)) + categoryStart + 1;
if (start === -1) return null;
const detectEnd = (line: string) => {
return line.trim().length === 0
|| line.startsWith(' - ')
|| line.includes('- name:')
|| line.includes('notableMentions:')
|| line.includes('furtherInfo:')
|| line.includes('wordOfWarning:')
@ -61,7 +63,7 @@ const makeResults = (yamlObject: AwesomePrivacy, yamlLines: string[]): LineNumbe
organizedData[category.name][section.name] = {};
section.services.forEach((service) => {
organizedData[category.name][section.name][service.name] = {
lineNumbers: calculateServiceRange(service, yamlLines),
lineNumbers: calculateServiceRange(service, category, yamlLines),
yaml: convertJsonIntoYaml(service),
};
});

80
web/src/pages/edit.astro Normal file
View file

@ -0,0 +1,80 @@
---
import Layout from '@layouts/Layout.astro';
import AddNewService from '@components/things/AddNewService.svelte';
import type { AwesomePrivacy, Section } from '../types/Service';
import { fetchData, slugify } from '@utils/fetch-data';
import FontAwesome from '@components/form/FontAwesome.svelte';
const categories = (await fetchData() as AwesomePrivacy)?.categories || [];
---
<Layout title="Awesome Privacy">
<section>
<h2>About our Data</h2>
<p>
All data on Awesome Privacy is community maintained via Git.
<br />
You can make edits/additions/removals by editing the
<a href="github.com/lissy93/awesome-privacy/blob/main/awesome-privacy.yml">awesome-privacy.yml</a> file.
To learn more, see the <a href="/about#our-data">about page</a>.
</p>
</section>
<section>
<h2>Submit an Addition</h2>
<AddNewService client:load />
</section>
<section>
<h2>Submit a Removal Request</h2>
<p>
You can submit a removal request by browsing to a given service's page,
and clicking the "Request Removal" button.
This will open a form where you can justify your reasoning, to get it
deleted from the <a href="github.com/lissy93/awesome-privacy/blob/main/awesome-privacy.yml">awesome-privacy.yml</a> file.
</p>
</section>
<section>
<h2>Edit a Listing</h2>
<p>
Edits are welcome! All data is located in
<a href="github.com/lissy93/awesome-privacy/blob/main/awesome-privacy.yml">awesome-privacy.yml</a>.
<br>
To modify an entry, navigate to it's page, scroll to the bottom, and click "Edit".
This will take you to directly to the relevant lines in the file, where you can make your changes.
</p>
</section>
</Layout>
<style lang="scss">
section {
margin: 2rem auto;
padding: 1rem;
width: 1000px;
max-width: calc(100% - 5rem);
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0 2rem;
border: 2px solid var(--foreground);
box-shadow: 6px 6px 0 var(--foreground);
background: var(--accent-fg);
@media(max-width: 768px) {
max-width: 95%;
padding: 0.5rem;
margin: 0 auto;
}
p {
margin-top: 0;
}
h2 {
margin-bottom: 0.5rem;
}
}
</style>

View file

@ -24,6 +24,58 @@ export const makeEditRequest = (categoryName: string, sectionName: string, servi
return `${issueCreate}${baseOptions}${removalData}`;
};
export const makeAdditionRequest = (formData: {
listingCategory: string;
serviceName: string;
serviceUrl: string;
serviceIcon: string;
serviceDescription: string;
serviceGithub: string;
serviceTosdrId: string;
serviceOpenSource: boolean;
serviceSecurityAudited: boolean;
serviceCrypto: boolean;
additionalInfo: string;
}, yamlText?: string) => {
const userInfo = formData.additionalInfo.split('\n').map(line => `> ${line}`).join('\n');
const additionalInfoText: string = `\n${userInfo}`
+ `\n\n**YAML Content for Addition**\n\n\`\`\`yaml\n${yamlText || '# nothing yet'}\n\`\`\`\n`
+ `\n\n<sup>This ticket was submitted via `
+ `<a href="https://awesome-privacy.xyz/edit">awesome-privacy.xyz/edit</a></sup>`;
const issueTitle = `[ADDITION] ${formData.serviceName} (Complete)`;
const queryParams = new URLSearchParams({
'assignees': 'lissy93,liss-bot',
'labels': '',
'projects': '',
'template': 'complete-addition.yml',
'title': issueTitle,
'listing-category': formData.listingCategory,
'service-name': formData.serviceName,
'service-url': formData.serviceUrl,
'service-icon': formData.serviceIcon,
'service-description': formData.serviceDescription,
'service-github': formData.serviceGithub,
'service-tosdr-id': formData.serviceTosdrId,
'service-opensource': formData.serviceOpenSource ? 'true' : 'false',
'service-security-audited': formData.serviceSecurityAudited ? 'true' : 'false',
'service-crypto': formData.serviceCrypto ? 'true' : 'false',
'additional-info': additionalInfoText,
});
const issueCreateUrl = 'https://github.com/Lissy93/awesome-privacy/issues/new';
return `${issueCreateUrl}?${queryParams.toString()}`;
};
export const makeSourceYamlLink = async (categoryName: string, sectionName: string, serviceName: string) => {
const sourceData = await fetchSrcData(categoryName, sectionName, serviceName);
const lineNumbers = sourceData.lineNumbers || null;
const numberRange = lineNumbers ? `L${lineNumbers.start}-L${lineNumbers.end}` : '';
const yamlLink = 'https://github.com/lissy93/awesome-privacy/blob/main/awesome-privacy.yml';
return `${yamlLink}${numberRange}`;
};
export const fetchSrcData = async (categoryName: string, sectionName: string, serviceName: string) => {
const lineNumberData = await fetch('/api/line-numbers.json')
.then((res) => res.json());