diff --git a/.gitignore b/.gitignore index b112af90..c26cbea6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ saved_config.yaml repo-context.txt -src/webtorrent-docs/ \ No newline at end of file +node_modules/ +src/webtorrent-docs/ diff --git a/README.md b/README.md index 33647842..4e8dee7c 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,33 @@ To run **bitvid** locally: ``` 3. Open the site in your browser: - ``` - http://localhost:8000 - ``` + ``` + http://localhost:8000 + ``` + +### CSS build pipeline + +Tailwind utilities are generated from `css/tailwind.source.css` using the build +script below. Run this whenever you add or remove classes in HTML or JavaScript +so the purged output stays in sync: + +```bash +npm install # first run only +npm run build:css +``` + +The command compiles Tailwind with `tailwind.config.cjs`, runs it through the +PostCSS pipeline defined in `postcss.config.cjs` (for autoprefixing), and emits +the purged, minified stylesheet at `css/tailwind.generated.css`. Commit the +regenerated file alongside any template changes so deployments pick up the +latest styles. ### Configuration +- **`config/instance-config.js`**: + - Central place for instance-specific values like the Super Admin npub and the + default whitelist-only mode setting. Update the documented exports here when + preparing a new deployment. - **`config.js`**: - Toggle `isDevMode` for development (`true`) or production (`false`). - **`js/constants.js`**: diff --git a/assets/svg/ellipsis.svg b/assets/svg/ellipsis.svg new file mode 100644 index 00000000..ca2af607 --- /dev/null +++ b/assets/svg/ellipsis.svg @@ -0,0 +1,11 @@ + diff --git a/blog.html b/blog.html index 395c5be8..13ce2e26 100644 --- a/blog.html +++ b/blog.html @@ -33,7 +33,12 @@ -
No revisions found.
'; + return; + } + + const fragment = document.createDocumentFragment(); + + history.forEach((entry) => { + if (!entry || !entry.id) { + return; + } + + const isCurrent = entry.id === currentId; + const isSelected = entry.id === selectedId; + const isDeleted = entry.deleted === true; + + const button = document.createElement("button"); + button.type = "button"; + button.dataset.revertVersionId = entry.id; + + const classes = [ + "w-full", + "text-left", + "rounded-md", + "border", + "px-3", + "py-3", + "text-sm", + "transition", + "duration-150", + "focus:outline-none", + "focus:ring-2", + "focus:ring-offset-2", + "focus:ring-offset-gray-900", + ]; + + if (isSelected) { + classes.push( + "border-blue-500", + "bg-blue-500/20", + "text-blue-100", + "focus:ring-blue-500" + ); + } else if (isCurrent) { + classes.push( + "border-green-500/60", + "bg-green-500/10", + "text-green-100", + "focus:ring-green-500/80" + ); + } else if (isDeleted) { + classes.push( + "border-red-800/70", + "bg-red-900/30", + "text-red-200/90", + "hover:bg-red-900/40" + ); + } else { + classes.push( + "border-gray-800", + "bg-gray-800/60", + "hover:bg-gray-700/70", + "text-gray-200" + ); + } + + button.className = classes.join(" "); + button.setAttribute("aria-pressed", isSelected ? "true" : "false"); + if (isCurrent) { + button.setAttribute("aria-current", "true"); + } + + const relative = this.formatTimeAgo(entry.created_at); + const absolute = this.formatAbsoluteTimestamp(entry.created_at); + const versionLabel = + entry.version !== undefined ? `v${entry.version}` : "v?"; + + const metaParts = []; + if (isCurrent) { + metaParts.push("Current version"); + } + if (entry.deleted) { + metaParts.push("Marked deleted"); + } + if (entry.isPrivate) { + metaParts.push("Private"); + } + const meta = metaParts.join(" • "); + + const metaClass = entry.deleted + ? "text-red-200/80" + : "text-gray-400"; + + button.innerHTML = ` +${this.escapeHTML( + entry.title || "Untitled" + )}
+${this.escapeHTML( + relative + )} • ${this.escapeHTML(absolute)}
+ ${ + meta + ? `` + : "" + } +${this.escapeHTML( + description + )}
` + : 'No description provided.
'; + + const rootId = + typeof version.videoRootId === "string" ? version.videoRootId : ""; + const rootDisplay = rootId + ? this.escapeHTML(truncateMiddle(rootId, 64)) + : ""; + const eventDisplay = version.id + ? this.escapeHTML(truncateMiddle(version.id, 64)) + : ""; + + this.revertVersionDetails.innerHTML = ` +${this.escapeHTML( + absolute + )} (${this.escapeHTML(relative)})
+${this.escapeHTML(
+ dTagValue
+ )}`
+ : 'Not provided'}
+ ${rootDisplay}`
+ : 'Not provided'}
+ ${eventDisplay}`
+ : 'Unknown'}
+