This commit is contained in:
Keep Creating Online
2025-01-29 22:21:04 -05:00
parent 1990020942
commit b32de9540d
3 changed files with 190 additions and 553 deletions

View File

@@ -1,329 +0,0 @@
# **bitvid: Enhanced Multi-View Architecture Migration Plan**
This plan describes how to transform your current `index.html` file so that different sections of content are loaded as separate views. It keeps the header and footer consistent on each view, while the center portion of the page switches among “most recent videos,” user profiles, trending feeds, and more. Future features like personalized feeds or channel pages can be added following the same approach.
---
## **1. Goals**
1. **Preserve Navigation Bar & Footer**
Keep the top navigation (logo, login/logout, “add video” controls) and the bottom footer in `index.html` at all times.
2. **Separate the Content Grid**
Move the existing video grid or “recent videos” listing into its own file, for example `views/most-recent-videos.html`. You will load this content into a main container within `index.html`.
3. **Handle Additional Views**
Prepare to load other views (profiles, trending, personalized feeds) in the same container. Each view can be its own HTML snippet or partial, stored separately (e.g. `views/profile-view.html`, `views/trending.html`, etc.).
4. **Single-Page Navigation**
Use JavaScript to switch or load the correct view based on the URL. This keeps the user on a single page, but updates what they see in the main section.
5. **Maintain Existing Modal**
The video-modal (`video-modal.html`) will remain a separate file, loaded into the DOM as is. This ensures consistent playback.
---
## **2. Proposed File Structure**
Below is an example layout. You do not need to follow it exactly, but it helps you see where each piece goes.
```
project/
├─ index.html
├─ components/
│ └─ video-modal.html
├─ views/
│ ├─ most-recent-videos.html
│ ├─ profile-view.html
│ ├─ trending.html
│ └─ ...
├─ js/
│ ├─ app.js
│ ├─ nostr.js
│ ├─ webtorrent.js
│ ├─ ...
│ └─ viewManager.js <-- new file for handling view loading
├─ css/
│ └─ style.css
└─ ...
```
1. **`index.html`**
- Contains the header, top nav, login/logout, plus the footer.
- Has a single `<div>` where content from your partial views will be loaded.
2. **`views/most-recent-videos.html`**
- Contains only the HTML (and minimal inline scripts) for the grid of most recent videos.
- No header or footer.
- No scripts for Nostr or WebTorrent—those remain in your main JS files.
3. **Other Views** (optional)
- Similar structure to `most-recent-videos.html`.
- Example: `profile-view.html`, `trending.html`, etc.
4. **`video-modal.html`**
- Remains a separate component file for the modal.
- Inserted into the DOM in `index.html` or on demand, as you already do.
5. **`viewManager.js`** (new optional file)
- Manages the logic of fetching these partial view files and inserting them into the page container.
- Handles route changes to decide which view to load.
---
## **3. Modifying `index.html`**
Below is a suggested strategy for `index.html`:
1. **Keep the current `<header>`**
It has your logo and login/logout UI.
2. **Keep the current `<footer>`**
It has the links to GitHub, Nostr, blog, and so on.
3. **Replace the big video listing area with a single container**
For example:
```html
<div id="viewContainer" class="flex-grow">
<!-- Dynamically loaded view content goes here -->
</div>
```
4. **Move the “most recent videos” grid**
- Copy that section (including the `<div id="videoList">...</div>`) into `views/most-recent-videos.html`.
- You can remove it from the main `index.html`, leaving only your `<header>`, login controls, disclaimers, and `<footer>`.
5. **Load the newly created partial**
- In your JavaScript, on page load, fetch `views/most-recent-videos.html` via `fetch()` and place it inside `#viewContainer`.
- Example:
```js
async function loadMostRecentVideosView() {
const res = await fetch('views/most-recent-videos.html');
const html = await res.text();
document.getElementById('viewContainer').innerHTML = html;
// Then re-initialize anything needed (e.g. event listeners, etc.)
}
```
- This keeps the same content, but its now in a separate file.
6. **Keep the existing disclaimers and disclaimers modal**
- The disclaimer modal can stay in `index.html` since it is site-wide.
- The same applies to the video player modal. You can keep a `<div id="modalContainer"></div>` to insert `video-modal.html`, or load it separately.
---
## **4. JavaScript for View Switching**
### **4.1 Single-File Approach**
You could place view-loading code in `app.js`. For example:
```js
// app.js
async function showView(viewName) {
let viewUrl = '';
switch (viewName) {
case 'recent':
viewUrl = 'views/most-recent-videos.html';
break;
case 'profile':
viewUrl = 'views/profile-view.html';
break;
// etc.
default:
viewUrl = 'views/most-recent-videos.html';
break;
}
const res = await fetch(viewUrl);
const html = await res.text();
document.getElementById('viewContainer').innerHTML = html;
// Re-initialize any needed scripts for the new view
}
```
Then when the page loads, you do `showView('recent');`
### **4.2 Dedicated `viewManager.js`**
Alternatively, create a separate file (`viewManager.js`) with functions like:
```js
export async function loadView(viewUrl, containerId = 'viewContainer') {
const res = await fetch(viewUrl);
const html = await res.text();
document.getElementById(containerId).innerHTML = html;
}
```
Then in your main app code, call `loadView('views/most-recent-videos.html');`.
---
## **5. Preserving Existing Functionality**
1. **Form Submission**
Your “Share Video” form can remain in `index.html` or be placed within a dedicated “upload video” view. If you do move it, youll just need to ensure the forms JS event listeners are attached once the view loads.
2. **Login/Logout**
The login button and user status references can remain in the header. This code continues to be managed by `app.js` without changes, as it is global.
3. **Video List**
Since the “most recent videos” grid moves to a partial, the original `<div id="videoList">...</div>` is replaced by a container in `most-recent-videos.html`. After loading that partial, your existing script logic for populating the video list (like `app.renderVideoList()`) will still work. You just need to ensure the new partial has the same IDs.
4. **Modal**
The `video-modal.html` can stay a separate component. You already fetch and inject it into the DOM. Nothing changes there, aside from making sure the container is in `index.html`, so the modal can appear on top of whichever view the user is in.
5. **Disclaimer Modal**
Similar approach. It can stay in `index.html` or be a partial if you prefer. Keep using the same logic to display it on first load.
---
## **6. Routing for Future Features**
### **6.1 Hash Routing**
As your platform grows, you may want a URL like `/#/profile/npub123` for user profiles. In that case:
1. **Listen for `hashchange`**:
```js
window.addEventListener('hashchange', handleRouting);
```
2. **Parse the hash**:
```js
function handleRouting() {
const hash = window.location.hash; // e.g. "#/profile/npub123"
if (hash.startsWith('#/profile/')) {
const parts = hash.split('/');
const npub = parts[2];
loadProfileView(npub);
} else {
// default
showView('recent');
}
}
```
3. **Load partial** (e.g. `profile-view.html`), then run logic to fetch users videos.
### **6.2 Future Sections**
- **Trending:** `/#/trending`
- **Personalized:** `/#/for-you`
- **Channel:** `/#/channel/someid`
Each route calls the correct partial load function, then re-initializes the data fetch and rendering.
---
## **7. Example of New `index.html` Structure**
A simplified version (in concept):
```html
<!DOCTYPE html>
<html lang="en">
<head>
<!-- meta, styles, etc. -->
</head>
<body class="bg-gray-100">
<!-- Container for the entire site -->
<div id="app" class="container mx-auto px-4 py-8 min-h-screen flex flex-col">
<!-- Header / Nav -->
<header class="mb-8">
<div class="flex items-start">
<!-- Logo -->
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</div>
<!-- Buttons: login, logout, etc. -->
</header>
<!-- Error and success containers, disclaimers, etc. -->
<!-- ... -->
<!-- Main content container for dynamic views -->
<main id="viewContainer" class="flex-grow">
<!-- This is where we load something like most-recent-videos.html -->
</main>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center">
<!-- Footer links, contact info, IPNS, etc. -->
</footer>
</div>
<!-- Modal for video player -->
<div id="modalContainer"></div>
<!-- Scripts -->
<!-- Example:
<script type="module" src="js/viewManager.js"></script>
<script type="module" src="js/app.js"></script>
-->
</body>
</html>
```
---
## **8. Step-by-Step Migration**
1. **Create `views/most-recent-videos.html`**
- Copy the entire grid section from your current `index.html` into this file.
- Keep the same IDs (`videoList`, etc.) so the existing code that populates it still works.
2. **Replace the Original Grid in `index.html`**
- Remove that big chunk of HTML, leaving just `<div id="viewContainer"></div>`.
3. **Load the Partial**
- On your page initialization in `app.js` (or a new `viewManager.js`), run a function to fetch `most-recent-videos.html` and inject it into `viewContainer`.
```js
import { loadView } from './viewManager.js';
document.addEventListener('DOMContentLoaded', () => {
loadView('views/most-recent-videos.html');
// Then call your existing code to load videos, etc.
});
```
4. **Re-link Any JS Event Listeners**
- After loading the partial, you might need to re-run any code that attaches event listeners to elements like `#videoList`. If your existing code is triggered on DOMContentLoaded, it should be aware that some elements appear later.
5. **Check Modal**
- Make sure that your code for injecting `video-modal.html` or referencing it still points to the correct container (`modalContainer`). That part likely wont change.
6. **Future Views**
- Create additional partial files (e.g., `profile-view.html`, `trending.html`) using the same approach.
- Expand your router logic to load different views based on the URL.
---
## **9. Potential Enhancements**
1. **Deep Linking**
- Use URLs to direct users to specific videos, profiles, or sections.
- For example, a link like `bitvid.com/#/profile/npub1234` opens that users channel.
2. **Templating Libraries**
- If your views become complex, you might adopt a minimal client-side templating approach or even a lightweight framework.
- For now, simple partial HTML with `fetch()` is enough.
3. **Reusable Components**
- Create a folder for shared components (like your disclaimers, video card layout, or new sidebars).
- Load them when needed, or store them as `<template>` elements that can be cloned into the DOM.
4. **Animation / Transition**
- Add simple fade-in or slide-in effects when swapping views to make the UI more polished.
---
## **10. Summary**
By isolating the main grid into `most-recent-videos.html` (and doing the same for future content sections), youll keep `index.html` focused on the site-wide header, footer, and scripts. This approach makes it easy to add new views (trending, user profiles, etc.) without cluttering the main file. Your existing functionality—like the video modal, login system, and disclaimers—remains intact and available across all views. Over time, you can add routing for advanced sections such as personalized feeds or advanced channel pages, all while loading them into the same container.
In short, this plan preserves the existing UI elements that should remain global (header, footer) and relocates the content grid into a dedicated partial. It positions you to grow the platform with more views and a clean single-page architecture.

View File

@@ -1,193 +1,90 @@
# **bitvid: Enhanced Nostr Video/Audio Note Specification: Version 3**
This document updates the existing Version 2 specification by adding optional fields for adult content, multiple categories, extended metadata, audio/podcast features, and more. These changes remain backward-compatible so Version 2 clients can continue to display basic information.
This specification updates the previous version (Version 2) and introduces:
1. **videoRootId**: A dedicated field in the JSON content to group related edits and deletes under one “root” post.
2. **Support for Audio/Podcast**: Additional fields for music/podcast metadata.
3. **Adult Content, Multi-Category, and Extended Metadata**: Optional fields to handle specialized use cases.
4. **Backward Compatibility**: Clients implementing Version 2 can still display the basic fields.
---
## Overview
## **1. Overview**
Nostr posts use **kind = 30078**, with a JSON structure stored in the `content` field. Version 3 retains all fields from Version 2 while adding new ones for richer functionality, easier filtering, and support for audio content.
Nostr posts of **kind = 30078** are used for sharing media (videos, audio, podcasts, etc.). The JSON payload goes into the `content` field, while tags array can store quick references like `["t", "video"]` or `["d", "<unique-id>"]`.
### **Key Concepts**
- **videoRootId**: A unique identifier stored in the `content` JSON.
- Ensures multiple edits or a delete event are recognized as referring to the same underlying post.
- If missing (legacy posts), clients may fall back to the d-tag or event ID to group events, but using `videoRootId` is strongly recommended for consistent overshadow logic.
- **Backward Compatibility**: All newly introduced fields are optional. Version 2 clients see fields like `title`, `magnet`, `description`, but ignore new fields such as `adult`, `audioQuality`, etc.
---
## General Format
## **2. Event Structure**
A typical note follows this format:
A typical event:
| **Field** | **Type** | **Description** |
|---------------|----------------|--------------------------------------------------------------------|
| `kind` | Integer | Fixed as `30078` for media-sharing events (video, music, etc.). |
| `pubkey` | String | Public key of the note creator. |
| ------------ | ------------- | ------------------------------------------------------------------------------------------ |
| `kind` | Integer | Fixed at `30078` for these media notes. |
| `pubkey` | String | Creators pubkey in hex. |
| `created_at` | Integer | Unix timestamp (seconds). |
| `tags` | Array of Arrays| Includes metadata such as `["t", "video"]` or `["t", "music"]` and `["d", "<id>"]`. |
| `content` | JSON String | JSON object containing the post data (detailed below). |
| `tags` | Array | Includes metadata such as `["t", "video"]` or `["t", "music"]` and `["d", "<unique-id>"]`. |
| `content` | String (JSON) | JSON specifying metadata (see table below). |
---
## Post Content for Version 3
## **3. Version 3 Content JSON**
| **Field** | **Type** | **Description** |
|-------------------|------------------------|----------------------------------------------------------------------------------------------------------------------|
| --------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `videoRootId` | String (optional) | A stable root ID used to link multiple versions (edits) and a delete event together. **Recommended** for overshadow logic. |
| `version` | Integer | Now set to `3`. |
| `deleted` | Boolean | Indicates soft deletion. |
| `isPrivate` | Boolean | `true` if content is private (magnet and certain optional fields may be encrypted). |
| `title` | String | Title of the media (video title, track title, podcast episode, etc.). |
| `magnet` | String | Magnet link for the primary media file (encrypted if `isPrivate = true`). |
| `extraMagnets` | Array (optional) | Additional magnet links for multiple resolutions/versions (commonly used for video but can be used for audio too). |
| `thumbnail` | String (optional) | URL or magnet link to a thumbnail image. |
| `description` | String (optional) | Description of the media. |
| `mode` | String | Indicates `live` or `dev` mode for streaming or test scenarios. |
| `adult` | Boolean (optional) | `true` if content is adult-only. Default is `false` or omitted. |
| `categories` | Array (optional) | A list of categories or tags, e.g., `["comedy", "music"]`. |
| `language` | String (optional) | Language code (e.g., `en`, `es`). |
| `payment` | String (optional) | Monetization field, such as a Lightning address. |
| `i18n` | Object (optional) | Holds internationalized fields (e.g., `{"title_en": "Hello", "title_es": "Hola"}`). |
| `encryptedMeta` | Boolean (optional) | If `true`, indicates fields like `description` or `thumbnail` may be encrypted. |
| `deleted` | Boolean | `true` marks the post as soft deleted.” |
| `isPrivate` | Boolean | Indicates if `magnet` (and possibly other fields) are encrypted. |
| `title` | String | Display title for the media. |
| `magnet` | String | Magnet link for primary media (encrypted if `isPrivate = true`). |
| `extraMagnets` | Array (optional) | Additional magnet links (e.g., multiple resolutions). |
| `thumbnail` | String (optional) | URL or magnet link to a thumbnail image (encrypted if `isPrivate = true` and `encryptedMeta = true`). |
| `description` | String (optional) | A textual description (encrypted if `isPrivate = true` and `encryptedMeta = true`). |
| `mode` | String | Typically `live` or `dev`. |
| `adult` | Boolean (optional) | `true` if content is adult-only. Default: `false` or omitted. |
| `categories` | Array (optional) | Array of categories, e.g. `["comedy", "music"]`. |
| `language` | String (optional) | Language code (e.g. `"en"`, `"es"`). |
| `payment` | String (optional) | Monetization field (e.g. a Lightning address). |
| `i18n` | Object (optional) | Internationalization map (e.g. `{"title_en": "...", "description_es": "..."}`). |
| `encryptedMeta` | Boolean (optional) | Indicates if fields like `description` or `thumbnail` are encrypted. |
| **Audio/Podcast-Specific Fields** | | |
| `contentType` | String (optional) | Type of media, e.g., `"video"`, `"music"`, `"podcast"`, `"audiobook"`. |
| `albumName` | String (optional) | Name of the album (if part of a music album). |
| `contentType` | String (optional) | E.g., `"video"`, `"music"`, `"podcast"`, `"audiobook"`. |
| `albumName` | String (optional) | Name of the album (for music). |
| `trackNumber` | Integer (optional) | Track number in an album. |
| `trackTitle` | String (optional) | Track title if different from `title`. |
| `podcastSeries` | String (optional) | Name of the podcast series. |
| `seasonNumber` | Integer (optional) | Season number for a podcast. |
| `episodeNumber` | Integer (optional) | Episode number for a podcast series. |
| `duration` | Integer (optional) | Duration in seconds (useful for audio or video players). |
| `artistName` | String (optional) | Main artist or presenter. |
| `contributors` | Array (optional) | List of additional contributors, e.g., `[{"name": "John", "role": "Producer"}]`. |
| `audioQuality` | Array (optional) | Multiple magnet links with different audio formats or bitrates. Example: `[{"quality": "lossless", "magnet": "..."}]`.|
| `playlist` | Array (optional) | Array of magnet links or references forming a playlist (useful for albums or sets). |
| `artistName` | String (optional) | Artist or presenter name. |
| `contributors` | Array (optional) | List of additional contributors, e.g. `[{"name": "X", "role": "Producer"}]`. |
| `audioQuality` | Array (optional) | Array of objects indicating different audio bitrates/formats, each with a magnet link. |
| `playlist` | Array (optional) | For multi-track or multi-episode sets, e.g. `[ "magnet1", "magnet2" ]`. |
> **Note**: All fields except `version`, `deleted`, `isPrivate`, `title`, `magnet`, and `mode` are optional. You can omit fields that are not relevant to your post.
> **Note**: All fields except `version`, `deleted`, `title`, `magnet`, and `videoRootId` are optional. If an older post lacks `videoRootId`, fallback grouping may rely on the `d` tag or event ID.
---
## Tagging
## **4. Using `videoRootId`**
### `tags`
- Purpose: Quick lookups for content type, unique IDs, or user-defined categories.
- Examples:
- `["t", "video"]` — Type indicator for videos.
- `["t", "music"]` — Type indicator for audio/music.
- `["d", "unique-identifier"]` — Unique ID for direct references.
- `["adult", "true"]` — Optional. Some clients may store adult flags here rather than in `content`.
- **Purpose**: Ensures all edits and a final delete event share the same “root.”
- **Edit**: Keep the same `videoRootId` in the new content so clients know its an update of the same item.
- **Delete**: Reuse the same `videoRootId` (and typically the same `d` tag). Mark `deleted = true` to overshadow the old event.
No changes are required for Version 3 tagging, but more detailed tags (e.g., `"music"`, `"podcast"`, or `"audiobook"`) can be used to help clients sort or filter specific media.
**Fallback**: Legacy or older notes might not have a `videoRootId`. Clients can group them by `["d", "<id>"]` or treat the old events own ID as its “root.” However, for new posts, **always** set `videoRootId`.
---
## Behavior Based on Privacy
### Public Posts
- `isPrivate = false`.
- Magnet and other fields are in plaintext.
- Visible to all users.
### Private Posts
- `isPrivate = true`.
- Main magnet link (and optional fields like `description` or `thumbnail`) are encrypted with the preferred encryption method.
- `extraMagnets` may also be encrypted if the user chooses.
---
## New or Expanded Features in Version 3
1. **Adult Content Flag**
- `adult: true` marks content as adult-only.
- Clients can filter this by default.
2. **Multiple Categories**
- `categories` can hold several entries, e.g. `["comedy", "music"]`.
- Advanced filtering is possible.
3. **Extended Metadata**
- Fields like `language`, `payment`, and custom data let creators provide more details.
- Optional, so older clients ignore what they do not recognize.
4. **Multi-Resolution or Multi-Format Links**
- `extraMagnets` for video variants.
- `audioQuality` for different audio formats or bitrates.
5. **Internationalization (`i18n`)**
- Include translated or localized titles, descriptions, and more.
6. **Encrypted Metadata**
- `encryptedMeta: true` to signal that certain fields (e.g., `description`, `thumbnail`) may be encrypted.
7. **Audio, Podcast, Audiobook Support**
- New optional fields: `contentType`, `albumName`, `trackNumber`, `podcastSeries`, etc.
- `playlist` can reference multiple tracks under one post.
---
Below is a section you can drop into your Version 3 specification. It expands on filtering logic for adult content and outlines a basic submission flow to help implementers. Feel free to adjust headings or formatting as needed.
---
## Filtering Logic and Submission Flow
### Filtering Logic
1. **Adult Content Detection**
- To mark posts as adult-only, set `adult = true` inside the content object (e.g., `"adult": true`) or include `["adult", "true"]` in the `tags` array.
- Clients should exclude these posts by default unless users explicitly enable adult content in their settings or preferences.
2. **Category-Based Filtering**
- If `categories` is present (e.g., `["comedy", "music"]`), clients can allow users to search or filter based on these entries.
- Alternatively, if tags such as `["t", "video"]` or `["category", "comedy"]` are used, handle them in the same way by grouping or filtering.
3. **Default UI Behavior**
- Provide a toggle or checkbox for “Show Adult Content.” If unchecked, do not display posts marked as adult content.
- Offer a dropdown or checkbox list for category filters. Only display posts that match selected categories.
4. **Edge Cases**
- If both `adult = true` and one or more categories are set, the post should remain hidden unless the user opts into adult content, even if it matches other selected categories.
- When a post omits `adult`, assume it is not adult content unless the user or platform policy states otherwise.
### Submission Flow Example
Below is a simple illustration of how a client could guide users when creating or editing a post:
1. **User Fills Out the Form**
- Title, magnet link, description, and any other relevant fields.
2. **Set the Version**
- When adding new Version 3 features (e.g., multiple categories, adult flag), ensure `version` is set to `3`.
- If editing a Version 2 post to add adult or category data, update `version` to `3`.
3. **Adult Content Checkbox**
- If the user marks the post as adult-only, set `"adult": true` in `content` or include `["adult", "true"]` in `tags`.
- If not marked, leave the field out or set it to `false`.
4. **Category Selection**
- Let users select categories (e.g., “comedy,” “music,” “gaming”). Store them in `categories` or as tags (`["category", "comedy"]`).
5. **Publish**
- Create the Nostr event with `kind = 30078`.
- In the `content` field (as JSON), include the user-provided data plus the new fields (e.g., `adult`, `categories`).
- In the `tags` array, ensure `["t", "video"]` or any other relevant tags. Add `["adult", "true"]` if needed.
6. **Post-Submission**
- When the post is published, clients or relays can immediately filter or categorize it according to the adult flag and categories.
- Users who opted in to adult content see the post, while others do not.
This process helps developers maintain a consistent approach to adult content handling, category-based filtering, and integration of new Version 3 features.
---
## Editing and Deleting Posts
### Editing
- Retain the same `["d", "<unique-id>"]` tag.
- Update or add new fields (e.g., adding `extraMagnets` or switching `contentType` to `"music"`).
- When moving from `version=2` to `version=3`, older clients ignore new fields but still see basic fields like `title` and `magnet`.
### Deletion
- Mark `deleted: true` in the `content`.
- Remove or encrypt sensitive fields (e.g., `magnet`, `description`) so they are not visible to others.
---
## Example Post: Version 3 (Video)
## **5. Example Post: Version 3 (Video)**
```jsonc
{
@@ -196,10 +93,10 @@ This process helps developers maintain a consistent approach to adult content ha
"created_at": 1700000000,
"tags": [
["t", "video"],
["d", "unique-identifier"]
// ["adult", "true"] // optional if storing adult info in tags
["d", "my-unique-handle"]
],
"content": "{
\"videoRootId\": \"root-1678551042-abc123\",
\"version\": 3,
\"deleted\": false,
\"isPrivate\": false,
@@ -224,20 +121,24 @@ This process helps developers maintain a consistent approach to adult content ha
}
```
- **videoRootId**: `root-1678551042-abc123` ensures future edits or deletes reference the same item.
- If `deleted = true`, the client sees it as a soft delete overshadowing the original.
---
## Example Post: Version 3 (Music Track)
## **6. Example Post: Version 3 (Audio)**
```jsonc
{
"kind": 30078,
"pubkey": "npub1...",
"created_at": 1700000000,
"created_at": 1700000001,
"tags": [
["t", "music"],
["d", "unique-id-for-track"]
["d", "my-song-handle"]
],
"content": "{
\"videoRootId\": \"root-1678551042-xyz999\",
\"version\": 3,
\"deleted\": false,
\"isPrivate\": false,
@@ -259,7 +160,7 @@ This process helps developers maintain a consistent approach to adult content ha
\"magnet\": \"magnet:?xt=urn:btih:mp3Hash\"
}
],
\"description\": \"This is an amazing track from the Great Album.\",
\"description\": \"A track from the Great Album.\",
\"categories\": [\"music\", \"pop\"],
\"contributors\": [
{ \"name\": \"John Doe\", \"role\": \"Producer\" },
@@ -269,62 +170,127 @@ This process helps developers maintain a consistent approach to adult content ha
}
```
- **videoRootId**: `root-1678551042-xyz999` used to link edits/deletes.
---
## Example Post: Version 3 (Playlist or Album)
## **7. Tagging**
```jsonc
{
"kind": 30078,
"pubkey": "npub1...",
"created_at": 1700000000,
"tags": [
["t", "playlist"],
["d", "unique-id-for-playlist"]
],
"content": "{
\"version\": 3,
\"deleted\": false,
\"isPrivate\": false,
\"title\": \"Chill Vibes Playlist\",
\"contentType\": \"music\",
\"playlist\": [
\"magnet:?xt=urn:btih:hashTrack1\",
\"magnet:?xt=urn:btih:hashTrack2\",
\"magnet:?xt=urn:btih:hashTrack3\"
],
\"description\": \"Curated set of relaxing tracks.\",
\"categories\": [\"music\", \"chill\"]
}"
- `["t", "video"]` or `["t", "music"]` for quick type references.
- `["d", "<unique-handle>"]` for a stable “address” pointer.
- You can store adult flags or categories in tags, e.g. `["adult", "true"]` or `["category", "comedy"]`.
- However, storing them inside `content` (e.g. `adult=true` or `categories=["comedy"]`) is generally recommended so older clients can ignore them gracefully.
---
## **8. Handling Edits and Deletes**
### **8.1 Edits**
1. **videoRootId**: Keep the same `videoRootId` to overshadow the previous version.
2. **version**: Bump from `2``3` or `3` → a higher sub-version if you wish to track changes.
3. **tags**: Typically reuse `["d", "<unique-handle>"]` or create a new d-tag. The important part is to keep `videoRootId` consistent.
### **8.2 Deletion**
1. **deleted = true**: Mark the item as deleted in the `content` JSON.
2. **Remove or encrypt** sensitive fields (`magnet`, `description`, etc.).
3. **videoRootId**: Must remain the same as the original so clients remove/overshadow the old item.
4. **subscribeVideos** Logic:
```js
if (video.deleted) {
// remove from activeMap or overshadow the old entry
}
```
5. **fetchVideos** Logic:
```js
for (const [id, video] of allEvents.entries()) {
if (video.deleted) continue; // skip deleted
// ...
}
```
---
## Transition Plan from Version 2 to 3
## **9. Filtering Logic**
1. **Backward Compatibility**
- All new fields are optional.
- Version 2 clients still see basic fields like `title` and `magnet`.
2. **Gradual Roll-Out**
- Existing posts remain at `version=2`.
- Users can adopt `version=3` when editing or creating new posts with extra features.
3. **Client Handling**
- If a client detects `version=3`, it can display or parse additional fields.
- Otherwise, it treats posts with older logic.
4. **Relay Compatibility**
- The same `kind=30078` is used.
- Relays generally store the data as-is.
5. **Potential Breaking Changes**
- If certain new fields are mandatory in your app, older clients may not parse them.
- Keep new features optional where possible.
1. **Adult Content**: If `adult = true`, clients typically hide the post unless the user enables adult content in settings.
2. **Categories**: Provide optional grouping or searching by `categories`.
3. **Language**: If specified, clients can filter by language.
4. **Encryption**: If `isPrivate = true`, some fields (e.g., `magnet`) may need client-side decryption.
---
## Summary
## **10. Submission Flow**
Version 3 extends the specification to include adult flags, multi-category tagging, multi-resolution magnet links, internationalization, and now audio-specific fields like `contentType = "music"`, `podcastSeries`, and `playlist`. Older clients can keep working without error, while new clients can take advantage of these extra fields for better organization and search.
1. **Client Form**: Title, magnet link, optional category checkboxes, adult toggle, etc.
2. **videoRootId**: For new posts, generate a new root ID. For edits, reuse the old posts root ID.
3. **Publish**:
- `kind = 30078`
- `content` → JSON with `videoRootId`, `version=3`, `title`, `magnet`, etc.
- `tags` → e.g., `["t","video"]`, `["d","my-handle"]`.
4. **Visibility**: If `adult=true`, hide from minors or default searches. If `deleted=true`, overshadow old entry.
---
## **11. Example “Delete” Flow**
1. **Original Post** (not deleted):
```json
{
"content": "{
\"videoRootId\": \"root-1234\",
\"version\": 3,
\"deleted\": false,
\"title\": \"My Video\",
\"magnet\": \"magnet:?xt=hash\"
// ...
}"
}
```
2. **Delete Post** (new event):
```jsonc
{
"kind": 30078,
"pubkey": "...",
"tags": [
["t","video"],
["d","my-handle"] // same d-tag
],
"content": "{
\"videoRootId\": \"root-1234\", // same root as original
\"version\": 3,
\"deleted\": true,
\"title\": \"My Video\",
\"magnet\": \"\", // blank or removed
\"description\": \"Video was deleted by creator.\"
}"
}
```
3. **subscribeVideos** sees `deleted=true` and overshadow logic removes the old item from active view.
---
## **12. Backward Compatibility**
- **Version 2** fields remain recognized: `title`, `magnet`, `mode`, etc.
- **Older Clients**: Ignore fields like `videoRootId`, `categories`, `adult`, etc.
- **When Upgrading**: If you add `videoRootId` or adult flags to an older post, older clients still display basic info but wont filter on the new fields.
---
## **13. Summary**
**Version 3** is a superset of previous versions, adding:
1. **videoRootId** for overshadow logic and grouping multi-edit threads.
2. **Adult content flag**, multi-category, i18n, and other optional fields.
3. **Audio/podcast support**: fields like `contentType = "music"`, `podcastSeries`, `episodeNumber`, and `playlist`.
Clients that implement these features can sort, filter, or display richer media experiences while remaining compatible with older Nostr note readers.
---
**End of Document**
This expanded spec ensures that **videoRootId** is used consistently, clarifies how to handle adult content, and extends the data model for new media types. By following this Version 3 guidance, youll maintain backward compatibility while enabling advanced features for media sharing on Nostr.

View File

@@ -61,7 +61,7 @@
style="background-color: #fe0032"
class="inline-flex items-center justify-center w-12 h-12 rounded-full text-white text-sm font-bold leading-none whitespace-nowrap appearance-none hover:bg-[#e6002c] focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
>
login
NIP-07
</button>
<!-- Upload (Add Video) Button -->