409 lines
14 KiB
Svelte
409 lines
14 KiB
Svelte
<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 serviceIosApp = writable('');
|
|
const serviceAndroidApp = writable('');
|
|
const serviceDiscordInvite = writable('');
|
|
const serviceSubreddit = writable('');
|
|
const serviceOpenSource = writable(false);
|
|
const serviceSecurityAudited = writable(false);
|
|
const serviceCrypto = writable(false);
|
|
const additionalInfo = writable('');
|
|
|
|
let codeBlock: any;
|
|
let interactiveActivated = false;
|
|
|
|
$: yamlText, updateHighlighting();
|
|
|
|
function updateHighlighting() {
|
|
if (codeBlock) {
|
|
codeBlock.textContent = yamlText
|
|
codeBlock.dataset.highlighted && delete codeBlock.dataset.highlighted;
|
|
if (window && (window as any).hljs) {
|
|
(window as any).hljs.highlightElement(codeBlock);
|
|
interactiveActivated = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const filterEmptyValues = (obj: Record<string, any>) => {
|
|
const filteredObj: Record<string, any> = {};
|
|
Object.keys(obj).forEach(key => {
|
|
if (obj[key] || ['name', 'url', 'icon', 'description'].includes(key)) {
|
|
filteredObj[key] = obj[key];
|
|
}
|
|
});
|
|
return filteredObj;
|
|
}
|
|
|
|
$: yamlText = yaml.dump([{
|
|
name: $serviceName,
|
|
url: $serviceUrl,
|
|
icon: $serviceIcon,
|
|
description: $serviceDescription,
|
|
github: $serviceGithub,
|
|
tosdrId: $serviceTosdrId,
|
|
iosApp: $serviceIosApp,
|
|
androidApp: $serviceAndroidApp,
|
|
discordInvite: $serviceDiscordInvite,
|
|
subreddit: $serviceSubreddit,
|
|
openSource: $serviceOpenSource,
|
|
securityAudited: $serviceSecurityAudited,
|
|
acceptsCrypto: $serviceCrypto,
|
|
}].map(obj => filterEmptyValues(obj)));
|
|
|
|
$: issueUrl = makeAdditionRequest({
|
|
listingCategory: $listingCategory,
|
|
serviceName: $serviceName,
|
|
serviceUrl: $serviceUrl,
|
|
serviceIcon: $serviceIcon,
|
|
serviceDescription: $serviceDescription,
|
|
serviceGithub: $serviceGithub,
|
|
serviceTosdrId: $serviceTosdrId,
|
|
serviceIosApp: $serviceIosApp,
|
|
serviceAndroidApp: $serviceAndroidApp,
|
|
serviceDiscordInvite: $serviceDiscordInvite,
|
|
serviceSubreddit: $serviceSubreddit,
|
|
serviceOpenSource: $serviceOpenSource,
|
|
serviceSecurityAudited: $serviceSecurityAudited,
|
|
serviceCrypto: $serviceCrypto,
|
|
additionalInfo: $additionalInfo,
|
|
}, yamlText);
|
|
|
|
// Form submission handler
|
|
function handleSubmit() {
|
|
window.open(issueUrl, '_blank');
|
|
}
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/an-old-hope.min.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/yaml.min.js"></script>
|
|
</svelte:head>
|
|
|
|
<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}>
|
|
|
|
<h3>Basics</h3>
|
|
<p class="sub-title-description">
|
|
All fields here are required.
|
|
</p>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- Section 2 -->
|
|
<h3>Third-Party Referencing</h3>
|
|
<p class="sub-title-description">
|
|
In order to create a comprehensive listing, we combine the data inputted above with other sources,
|
|
to give additional context and help users make informed decisions.
|
|
Metrics from these services are fetched automatically at build-time from our API.
|
|
<br />
|
|
All fields are optional, but the more information you provide, the better!
|
|
</p>
|
|
|
|
<!-- 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.<br />
|
|
Use the format [user]/[repo] e.g, lissy93/dashy
|
|
</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>
|
|
|
|
<!-- Apple App Store URL -->
|
|
<div class="form-row">
|
|
<label for="service-tosdr-id">iOS App</label>
|
|
<input type="url" bind:value={$serviceIosApp} id="service-ios-app" autocomplete="off">
|
|
<p>
|
|
Paste the link to the mobile app on the Apple App Store.<br />
|
|
E.g. https://apps.apple.com/us/app/bitwarden-password-manager/id1137397744
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Google Play App Store URL -->
|
|
<div class="form-row">
|
|
<label for="service-tosdr-id">Android App</label>
|
|
<input type="url" bind:value={$serviceAndroidApp} id="service-android-app" autocomplete="off">
|
|
<p>
|
|
Paste the link to the mobile app on the Google Play Store.<br />
|
|
E.g. https://play.google.com/store/apps/details?id=com.x8bit.bitwarden
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Discord Server Invite Code -->
|
|
<div class="form-row">
|
|
<label for="service-tosdr-id">Discord Invite</label>
|
|
<input type="text" bind:value={$serviceDiscordInvite} id="service-discord-invite" autocomplete="off">
|
|
<p>
|
|
Paste the invite code to the Discord server for this service.<br />
|
|
E.g. If the invite URL is https://discord.com/invite/4JMAauFZBq the code is 4JMAauFZBq
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Reddit sub name -->
|
|
<div class="form-row">
|
|
<label for="service-tosdr-id">Subreddit</label>
|
|
<input type="text" bind:value={$serviceSubreddit} id="service-subreddit" autocomplete="off">
|
|
<p>
|
|
If the service has a subreddit, please provide the name here.<br />
|
|
Don't include `r/` in the name, nor the full URL - just the sub name.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Section 3 - Checklist and details -->
|
|
<h3>Privacy Checklist</h3>
|
|
<p class="sub-title-description">
|
|
Finally, check the boxes that apply to the service you are submitting,
|
|
and then provide any additional information to back this up in the text area below.
|
|
</p>
|
|
|
|
<!-- 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>
|
|
{#if !interactiveActivated || !codeBlock}
|
|
<pre><code class="language-yaml">{@html yamlText}</code></pre>
|
|
{/if}
|
|
<pre><code bind:this={codeBlock} class="language-yaml"></code></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, select {
|
|
width: 100%;
|
|
border: 1px solid var(--accent-3);
|
|
border-radius: var(--curve-md);
|
|
font-size: 1.2rem;
|
|
padding: 0.5rem 0.25rem;
|
|
background: var(--background-form);
|
|
color: var(--foreground);
|
|
&:focus {
|
|
outline: none;
|
|
border: 1px 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;
|
|
background: var(--background-form);
|
|
}
|
|
}
|
|
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(--box-outline);
|
|
box-shadow: 3px 3px 0 var(--box-outline);
|
|
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);
|
|
}
|
|
}
|
|
|
|
.sub-title-description {
|
|
margin-top: 0;
|
|
font-size: 0.8rem;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.output-yaml {
|
|
pre {
|
|
font-family: 'Courier New', Courier, monospace;
|
|
background: var(--background-form);
|
|
padding: 0.2rem 0.4rem;
|
|
border-radius: var(--curve-sm);
|
|
font-size: 0.9rem;
|
|
}
|
|
}
|
|
</style>
|