Files
bitvid/components/iframe_forms/iframe-application-form.html
T

621 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>bitvid Whitelist Application Form</title>
<script>
(() => {
const VALID_THEMES = new Set(["light", "dark"]);
const STORAGE_KEY = "bitvid:theme";
const FALLBACK_THEME = "dark";
const ACCENT_CSS_VARIABLES = Object.freeze({
accent: "--color-accent",
accentStrong: "--color-accent-strong",
accentPressed: "--color-accent-pressed",
});
const { documentElement: root } = document;
if (!(root instanceof HTMLElement)) {
return;
}
const normalizeTheme = (value) => {
if (typeof value !== "string") {
return null;
}
const normalized = value.trim().toLowerCase();
if (normalized === "" || normalized === "default") {
return FALLBACK_THEME;
}
return VALID_THEMES.has(normalized) ? normalized : null;
};
const getParentRoot = () => {
try {
if (window.parent && window.parent !== window) {
const parentDocument = window.parent.document;
const parentRoot = parentDocument?.documentElement;
return parentRoot instanceof HTMLElement ? parentRoot : null;
}
} catch (error) {
return null;
}
return null;
};
const readParentTheme = () => {
const parentRoot = getParentRoot();
if (!parentRoot) {
return null;
}
return normalizeTheme(parentRoot.dataset?.theme);
};
const readStoredTheme = () => {
try {
return normalizeTheme(window.localStorage.getItem(STORAGE_KEY));
} catch (error) {
return null;
}
};
const applyTheme = (theme) => {
if (theme && VALID_THEMES.has(theme)) {
root.dataset.theme = theme;
} else {
root.removeAttribute("data-theme");
}
};
const applyAccentOverridesFromParent = () => {
const parentRoot = getParentRoot();
if (!parentRoot) {
Object.values(ACCENT_CSS_VARIABLES).forEach((cssVariable) => {
root.style.removeProperty(cssVariable);
});
return;
}
Object.values(ACCENT_CSS_VARIABLES).forEach((cssVariable) => {
try {
const value = parentRoot.style.getPropertyValue(cssVariable);
if (typeof value === "string" && value.trim() !== "") {
root.style.setProperty(cssVariable, value.trim());
} else {
root.style.removeProperty(cssVariable);
}
} catch (error) {
root.style.removeProperty(cssVariable);
}
});
};
const syncTheme = () => {
const resolvedTheme =
readParentTheme() ?? readStoredTheme() ?? FALLBACK_THEME;
applyTheme(resolvedTheme);
applyAccentOverridesFromParent();
};
syncTheme();
const parentRoot = getParentRoot();
let observer;
if (parentRoot && typeof MutationObserver !== "undefined") {
observer = new MutationObserver(syncTheme);
observer.observe(parentRoot, {
attributes: true,
attributeFilter: ["data-theme"],
});
}
window.addEventListener("storage", (event) => {
if (event?.key === STORAGE_KEY) {
syncTheme();
}
});
window.addEventListener(
"pageshow",
() => {
syncTheme();
},
{ once: true }
);
window.addEventListener(
"beforeunload",
() => {
if (observer) {
observer.disconnect();
}
},
{ once: true }
);
})();
</script>
<!-- Link to your main stylesheet -->
<link rel="stylesheet" href="../../css/tailwind.generated.css" />
<!-- Load nostr‑tools v2.10.4 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
</head>
<body class="iframe-form" data-ds="new">
<div class="container">
<div class="form-container">
<p>
bitvid is currently in early access. If you would like to request
access, please fill out this application. Applications will be
reviewed manually, and approvals will be based on alignment with
bitvid’s community values.
</p>
<form id="wl-form">
<!-- 1. Applicant Information -->
<h2>1. Applicant Information</h2>
<label for="applicantNpub">Nostr Public Key (npub):</label>
<input
class="form-control"
type="text"
id="applicantNpub"
placeholder="Enter your npub"
required
/>
<label for="contactMethod"
>Preferred Contact Method (if applicable):</label
>
<input
class="form-control"
type="text"
id="contactMethod"
placeholder="Nostr DM, email, or other"
/>
<label for="username">Username or Alias (if applicable):</label>
<input
class="form-control"
type="text"
id="username"
placeholder="Enter your preferred name"
/>
<!-- 2. Content Intent -->
<h2>2. Content Intent</h2>
<p>
What type of content do you plan to upload? (Check all that apply)
</p>
<div class="checkbox-group">
<label
><input
class="checkbox"
type="checkbox"
name="contentType"
value="Educational"
/>
Educational</label
>
<label
><input
class="checkbox"
type="checkbox"
name="contentType"
value="Entertainment"
/>
Entertainment</label
>
<label
><input
class="checkbox"
type="checkbox"
name="contentType"
value="News & Journalism"
/>
News & Journalism</label
>
<label
><input
class="checkbox"
type="checkbox"
name="contentType"
value="Creative Works"
/>
Creative Works</label
>
<label
><input
class="checkbox"
type="checkbox"
name="contentType"
value="Discussions & Opinions"
/>
Discussions & Opinions</label
>
<label
><input
class="checkbox"
type="checkbox"
name="contentType"
value="Other"
/>
Other</label
>
</div>
<label for="otherContent">Other (please specify):</label>
<input
class="form-control"
type="text"
id="otherContent"
placeholder="Describe if Other was selected"
/>
<label for="whyJoin">Why do you want to join bitvid?</label>
<textarea
class="textarea"
id="whyJoin"
rows="3"
placeholder="Explain your motivation"
></textarea>
<label for="priorExperience"
>Have you created content on other platforms before?</label
>
<select class="select" id="priorExperience">
<option value="">Select an option</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
<label for="experienceLinks"
>If yes, provide links or references to your previous work:</label
>
<textarea
class="textarea"
id="experienceLinks"
rows="2"
placeholder="Paste links or describe your work"
></textarea>
<!-- 3. Community Engagement -->
<h2>3. Community Engagement</h2>
<label for="familiarGuidelines"
>Are you familiar with bitvid’s Community Guidelines?</label
>
<select class="select" id="familiarGuidelines">
<option value="">Select an option</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
<label for="agreeGuidelines"
>Do you agree to follow the guidelines and respect decentralized
moderation?</label
>
<select class="select" id="agreeGuidelines">
<option value="">Select an option</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
<label for="communityContribution"
>How do you plan to contribute positively to the bitvid
community?</label
>
<textarea
class="textarea"
id="communityContribution"
rows="3"
placeholder="Explain your approach"
></textarea>
<!-- 4. Additional Information -->
<h2>4. Additional Information</h2>
<label for="specialSkills"
>Do you have any special skills or interests that could help improve
bitvid?</label
>
<textarea
class="textarea"
id="specialSkills"
rows="3"
placeholder="e.g., software development, moderation, design, advocacy"
></textarea>
<label for="testFeatures"
>Would you be interested in helping test new features?</label
>
<select class="select" id="testFeatures">
<option value="">Select an option</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
<!-- 5. Declaration -->
<h2>5. Declaration</h2>
<p>
By submitting this application, you confirm that:
<br />- The information provided is accurate. <br />- You understand
that approval is based on community alignment and available
capacity. <br />- You acknowledge that whitelist status may be
revoked if guidelines are violated.
</p>
<label for="signature">Signature (Digital or Written):</label>
<input
class="form-control"
type="text"
id="signature"
placeholder="Your signature"
/>
<label for="declarationDate">Date:</label>
<input class="form-control" type="date" id="declarationDate" />
<button class="btn" type="submit">Submit Application</button>
</form>
<div id="status"></div>
</div>
</div>
<script>
// Provide no-op fallback loggers so embeds stay functional if bitvidLogger is unavailable.
const noopLogger = {
log: () => {},
info: () => {},
warn: () => {},
error: () => {},
};
const dev = window.bitvidLogger?.dev || noopLogger;
const user = window.bitvidLogger?.user || noopLogger;
document.addEventListener("DOMContentLoaded", () => {
// Logging functions for both on-page and console output.
function log(msg, type = "info") {
const div = document.createElement("div");
div.classList.add("status-line");
if (type === "error") div.classList.add("error");
if (type === "success") div.classList.add("success");
if (type === "warn") div.classList.add("warn");
div.textContent = msg;
document.getElementById("status").appendChild(div);
const formatted = `[${type.toUpperCase()}] ${msg}`;
if (type === "error") {
user.error(formatted);
} else if (type === "warn") {
user.warn(formatted);
} else if (type === "success") {
user.log(formatted);
} else if (type === "info") {
dev.info(formatted);
} else {
dev.log(formatted);
}
}
function clear() {
document.getElementById("status").innerHTML = "";
}
if (!window.NostrTools) {
log("NostrTools not loaded. Check console or ad-blockers.", "error");
return;
}
const {
generateSecretKey,
getPublicKey,
finalizeEvent,
nip04,
nip19,
SimplePool,
Relay,
} = window.NostrTools;
// Set the recipient's NPUB (your personal NPUB) here.
const recipientNpub =
"npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe";
const RELAYS = [
"wss://relay.snort.social",
"wss://relay.damus.io",
"wss://relay.primal.net",
];
const pool = new SimplePool();
document
.getElementById("wl-form")
.addEventListener("submit", async (ev) => {
ev.preventDefault();
clear();
try {
// Retrieve applicant-provided input.
const applicantNpub = document
.getElementById("applicantNpub")
.value.trim();
const contactMethod = document
.getElementById("contactMethod")
.value.trim();
const username = document.getElementById("username").value.trim();
// Section 2: Content Intent
// Get all checked content types
const contentTypeNodes = document.querySelectorAll(
'input[name="contentType"]:checked'
);
let contentTypes = [];
contentTypeNodes.forEach((node) => {
contentTypes.push(node.value);
});
const otherContent = document
.getElementById("otherContent")
.value.trim();
const whyJoin = document.getElementById("whyJoin").value.trim();
const priorExperience = document
.getElementById("priorExperience")
.value.trim();
const experienceLinks = document
.getElementById("experienceLinks")
.value.trim();
// Section 3: Community Engagement
const familiarGuidelines = document
.getElementById("familiarGuidelines")
.value.trim();
const agreeGuidelines = document
.getElementById("agreeGuidelines")
.value.trim();
const communityContribution = document
.getElementById("communityContribution")
.value.trim();
// Section 4: Additional Information
const specialSkills = document
.getElementById("specialSkills")
.value.trim();
const testFeatures = document
.getElementById("testFeatures")
.value.trim();
// Section 5: Declaration
const signature = document
.getElementById("signature")
.value.trim();
const declarationDate = document
.getElementById("declarationDate")
.value.trim();
// Construct the whitelist application content.
const applicationContent = `
# **bitvid Whitelist Application Form**
**1. Applicant Information**
- **Nostr Public Key (npub):** ${applicantNpub}
- **Preferred Contact Method:** ${contactMethod || "N/A"}
- **Username or Alias:** ${username || "N/A"}
**2. Content Intent**
- **Planned Content Types:** ${contentTypes.join(", ") || "N/A"}
- **Other (if applicable):** ${otherContent || "N/A"}
- **Why do you want to join bitvid?**
${whyJoin || "N/A"}
- **Have you created content on other platforms before?** ${
priorExperience || "N/A"
}
- **Links or references to your previous work:**
${experienceLinks || "N/A"}
**3. Community Engagement**
- **Familiar with bitvid’s Community Guidelines?** ${
familiarGuidelines || "N/A"
}
- **Agree to follow guidelines and respect moderation?** ${
agreeGuidelines || "N/A"
}
- **How do you plan to contribute to the community?**
${communityContribution || "N/A"}
**4. Additional Information**
- **Special skills or interests:**
${specialSkills || "N/A"}
- **Interested in testing new features?** ${testFeatures || "N/A"}
**5. Declaration**
By submitting this application, you confirm that:
- The information provided is accurate.
- You understand that approval is based on community alignment and available capacity.
- You acknowledge that whitelist status may be revoked if guidelines are violated.
**Signature (Digital or Written):** ${signature || "N/A"}
**Date:** ${declarationDate || "N/A"}
---
**Processing Time:** Applications will be reviewed within **7-14 days**. If approved, you will receive a confirmation via your provided contact method. If additional information is needed, we will reach out.
For further questions, contact us through bitvid’s Nostr support channels.
`.trim();
log(
"[DEBUG] Constructed application content:\n" +
applicationContent
);
// Decode the recipient NPUB to get the public key.
log("Decoding recipient npub...");
const decoded = nip19.decode(recipientNpub);
log("[DEBUG] Decoded npub: " + JSON.stringify(decoded));
if (decoded.type !== "npub") {
throw new Error("Decoded type is not npub.");
}
const targetPubHex = decoded.data;
log("Recipient pubkey: " + targetPubHex.slice(0, 16) + "...");
// Generate an ephemeral key pair.
log("Generating ephemeral key...");
const ephemeralPriv = generateSecretKey();
const ephemeralPubHex = getPublicKey(ephemeralPriv);
log("Ephemeral pubkey: " + ephemeralPubHex.slice(0, 16) + "...");
// Encrypt the application content.
log("Encrypting application content (nip04)...");
const ciphertext = await nip04.encrypt(
ephemeralPriv,
targetPubHex,
applicationContent
);
log("[DEBUG] Ciphertext: " + ciphertext);
log("Encryption done.");
// Build the Nostr event template.
const now = Math.floor(Date.now() / 1000);
const eventTemplate = {
kind: 4,
created_at: now,
tags: [["p", targetPubHex]],
content: ciphertext,
};
log(
"[DEBUG] Event template before finalizing: " +
JSON.stringify(eventTemplate)
);
// Finalize the event (computing the id and signature).
const event = finalizeEvent(eventTemplate, ephemeralPriv);
log("[DEBUG] Final event: " + JSON.stringify(event));
// Publish the event to all relays.
log("Publishing the application to relays...");
await Promise.any(pool.publish(RELAYS, event));
log("At least one relay accepted the event.", "success");
// For each relay, subscribe to verify the event appears.
for (const url of RELAYS) {
log("Connecting to " + url + " for subscription...");
const relay = await Relay.connect(url);
relay.subscribe([{ authors: [ephemeralPubHex], kinds: [4] }], {
onEvent(foundEvent) {
if (foundEvent.id === event.id) {
log(
"[" +
url +
"] => Found our application in storage! ID: " +
foundEvent.id.slice(0, 8) +
"...",
"success"
);
}
},
onEose() {
relay.close();
},
});
}
log(
"Done. If the logs show that at least one relay accepted the event and the application was found in storage, a moderator will review your application within 7-14 days."
);
} catch (err) {
log("Error: " + err.message, "error");
user.error(err);
}
});
});
</script>
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
</body>
</html>