mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Add design system feature flag and runtime integration
This commit is contained in:
1 parent
8288824872
commit
8265f9e2de
42 files changed
+359
-23
No files matched your search
@@ -10,7 +10,7 @@
|
||||
<!-- Include Tailwind CSS -->
|
||||
<link href="css/tailwind.generated.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="bg-surface text-text">
|
||||
<body class="bg-surface text-text" data-ds="legacy">
|
||||
<!-- Application Form Modal -->
|
||||
<div
|
||||
id="nostrFormModal"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="bugFixModalTitle"
|
||||
aria-describedby="bugFixModalDescription"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="contentAppealsModalTitle"
|
||||
aria-describedby="contentAppealsModalDescription"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="disclaimerModalTitle"
|
||||
aria-describedby="disclaimerModalDescription"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="-1"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div id="editVideoModalOverlay" class="bv-modal-backdrop"></div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="featureRequestModalTitle"
|
||||
aria-describedby="featureRequestModalDescription"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="generalFeedbackModalTitle"
|
||||
aria-describedby="generalFeedbackModalDescription"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body data-ds="legacy">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body data-ds="legacy">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body data-ds="legacy">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body data-ds="legacy">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<!-- Load nostr‑tools v2.10.4 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body data-ds="legacy">
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<p>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="loginModalTitle"
|
||||
aria-describedby="loginModalDescription"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="profileModalTitle"
|
||||
tabindex="-1"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop"></div>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="revertModalTitle"
|
||||
aria-describedby="revertModalSubtitle"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div
|
||||
id="revertVideoModalOverlay"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
id="sidebar"
|
||||
class="fade-in fade-in-delay-100 sidebar-collapsed"
|
||||
data-state="collapsed"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="sidebar__inner">
|
||||
<div class="sidebar-scroll">
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="uploadModalTitle"
|
||||
aria-describedby="uploadModalDescription"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<!-- components/video-modal.html -->
|
||||
<div id="playerModal" class="bv-modal hidden" role="dialog" aria-modal="true">
|
||||
<div
|
||||
id="playerModal"
|
||||
class="bv-modal hidden"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
data-ds="legacy"
|
||||
>
|
||||
<div class="bv-modal-backdrop" data-dismiss></div>
|
||||
<div class="bv-modal__panel" tabindex="-1">
|
||||
<div class="player-modal__container video-modal__container">
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Deployment Notes
|
||||
|
||||
## FEATURE_DESIGN_SYSTEM rollout
|
||||
|
||||
- **Default:** `false` (legacy styling). The flag lives in `js/constants.js` and is exposed through `window.__BITVID_RUNTIME_FLAGS__` at runtime.
|
||||
- **Enable for canaries:** Before deploying a canary, run `window.__BITVID_RUNTIME_FLAGS__.FEATURE_DESIGN_SYSTEM = true` (or call `setFeatureDesignSystemEnabled(true)`) in the browser console. The change propagates immediately, flipping every `[data-ds]` container to `data-ds="new"` so controllers can hydrate the updated primitives.
|
||||
- **Disable / rollback:** Set the flag back to `false` via the runtime object or redeploy with the default configuration. All root containers revert to `data-ds="legacy"`, forcing controllers and templates to stay on the existing class list.
|
||||
- **Template contract:** Every layout, view, and partial now includes `data-ds` on the root element. Keep this attribute intact when editing HTML so the runtime can swap modes without re-rendering.
|
||||
- **Controllers:** UI controllers receive a `designSystem` context in their constructors. Call `context.isNew()` before attaching new components or classes, and lean on `context.getMode()` when logging or telemetry need to differentiate between legacy and new renders.
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
bitvid's visual language is now powered by Tailwind utilities backed by our design tokens. This document lists the primitives available via `css/tailwind.source.css`, how they behave across states, and when to mix them with Tailwind utilities.
|
||||
|
||||
## Feature Flag Rollout
|
||||
|
||||
The new design system ships behind the `FEATURE_DESIGN_SYSTEM` runtime flag defined in `js/constants.js`. Deployments should keep the flag disabled (`false`) until the new primitives are ready for broad adoption.
|
||||
|
||||
- **Default state:** `FEATURE_DESIGN_SYSTEM` is `false`, so templates render with `data-ds="legacy"` on root containers.
|
||||
- **Runtime toggle:** Set `window.__BITVID_RUNTIME_FLAGS__.FEATURE_DESIGN_SYSTEM = true` (or call `setFeatureDesignSystemEnabled(true)`) to opt into the new primitives. The entrypoint automatically updates every `[data-ds]` container to `data-ds="new"` and notifies controllers.
|
||||
- **DOM contract:** Templates and partials must include `data-ds` on their root elements. Controllers should rely on the `designSystem` context (see `js/designSystem.js`) before attaching classes or behaviors that only exist in the new system.
|
||||
|
||||
## Theme System
|
||||
|
||||
The default experience renders the production dark palette. Pages and embeds can opt into alternates by setting the `data-theme` attribute on the `<html>` (or any ancestor that wraps the component). `data-theme="light"` promotes the light tokens defined in `css/tokens.css`, swapping surface, panel, border, muted, and overlay colors while keeping semantic accents consistent. Omit the attribute or set it back to `default` to inherit the dark baseline. Reserve additional theme keys (for example, `data-theme="contrast"`) for upcoming accessibility palettes—the same hook will activate them once their tokens land.
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@
|
||||
<link href="css/style.css?v=2024.10.29" rel="stylesheet" />
|
||||
<link href="css/markdown.css?v=2024.10.29" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="sidebar-collapsed">
|
||||
<body class="sidebar-collapsed" data-ds="legacy">
|
||||
<!--
|
||||
SIDEBAR CONTAINER:
|
||||
The <aside id="sidebar"> inside handles visibility.
|
||||
|
||||
@@ -84,6 +84,10 @@ import {
|
||||
applyRootTimestampToVideosMap,
|
||||
syncActiveVideoRootTimestamp,
|
||||
} from "./utils/videoTimestamps.js";
|
||||
import {
|
||||
getDesignSystemMode as readDesignSystemMode,
|
||||
subscribeToDesignSystemChanges,
|
||||
} from "./designSystem.js";
|
||||
import {
|
||||
getPubkey as getStoredPubkey,
|
||||
setPubkey as setStoredPubkey,
|
||||
@@ -182,6 +186,21 @@ class Application {
|
||||
this.watchHistoryTelemetry = null;
|
||||
this.authEventUnsubscribes = [];
|
||||
this.unsubscribeFromNostrService = null;
|
||||
this.unsubscribeFromDesignSystem = null;
|
||||
this.designSystemMode = readDesignSystemMode();
|
||||
this.designSystemContext = {
|
||||
getMode: () => this.getDesignSystemMode(),
|
||||
isNew: () => this.isDesignSystemNew(),
|
||||
};
|
||||
this.unsubscribeFromDesignSystem = subscribeToDesignSystemChanges(
|
||||
({ mode }) => {
|
||||
if (typeof mode === "string" && mode) {
|
||||
this.designSystemMode = mode;
|
||||
} else {
|
||||
this.designSystemMode = readDesignSystemMode();
|
||||
}
|
||||
}
|
||||
);
|
||||
this.videoModalReadyPromise = null;
|
||||
this.boundUploadSubmitHandler = null;
|
||||
this.boundEditModalSubmitHandler = null;
|
||||
@@ -287,6 +306,7 @@ class Application {
|
||||
this.dropWatchHistoryMetadata(pointerKey),
|
||||
getActivePubkey: () =>
|
||||
typeof this.pubkey === "string" && this.pubkey ? this.pubkey : "",
|
||||
designSystem: this.designSystemContext,
|
||||
});
|
||||
|
||||
this.watchHistoryTelemetry = new WatchHistoryTelemetry({
|
||||
@@ -549,6 +569,7 @@ class Application {
|
||||
services: profileModalServices,
|
||||
state: profileModalState,
|
||||
callbacks: profileModalCallbacks,
|
||||
designSystem: this.designSystemContext,
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
@@ -677,6 +698,7 @@ class Application {
|
||||
? navigator.clipboard
|
||||
: null,
|
||||
isDevMode,
|
||||
designSystem: this.designSystemContext,
|
||||
callbacks: {
|
||||
getCurrentVideo: () => this.currentVideo,
|
||||
getCurrentUserNpub: () => this.getCurrentUserNpub(),
|
||||
@@ -857,6 +879,7 @@ class Application {
|
||||
getLoadingMarkup: (message) => getSidebarLoadingMarkup(message),
|
||||
},
|
||||
allowNsfw: ALLOW_NSFW_CONTENT === true,
|
||||
designSystem: this.designSystemContext,
|
||||
};
|
||||
this.videoListView =
|
||||
(typeof ui.videoListView === "function"
|
||||
@@ -1302,6 +1325,14 @@ class Application {
|
||||
return this.lastModalTrigger;
|
||||
}
|
||||
|
||||
getDesignSystemMode() {
|
||||
return this.designSystemMode === "new" ? "new" : "legacy";
|
||||
}
|
||||
|
||||
isDesignSystemNew() {
|
||||
return this.getDesignSystemMode() === "new";
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the modal and set the "Please stand by" poster on the video.
|
||||
*/
|
||||
@@ -6595,6 +6626,18 @@ class Application {
|
||||
this.teardownModalViewCountSubscription();
|
||||
this.videoModalReadyPromise = null;
|
||||
|
||||
if (typeof this.unsubscribeFromDesignSystem === "function") {
|
||||
try {
|
||||
this.unsubscribeFromDesignSystem();
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"[Application] Failed to unsubscribe design system listener:",
|
||||
error
|
||||
);
|
||||
}
|
||||
this.unsubscribeFromDesignSystem = null;
|
||||
}
|
||||
|
||||
if (this.watchHistoryTelemetry) {
|
||||
try {
|
||||
this.watchHistoryTelemetry.destroy();
|
||||
|
||||
@@ -4,6 +4,7 @@ const DEFAULT_FLAGS = Object.freeze({
|
||||
VIEW_FILTER_INCLUDE_LEGACY_VIDEO: false,
|
||||
FEATURE_WATCH_HISTORY_V2: false,
|
||||
FEATURE_PUBLISH_NIP71: false,
|
||||
FEATURE_DESIGN_SYSTEM: false,
|
||||
WSS_TRACKERS: Object.freeze([
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
"wss://tracker.fastcast.nz",
|
||||
@@ -12,8 +13,48 @@ const DEFAULT_FLAGS = Object.freeze({
|
||||
]),
|
||||
});
|
||||
|
||||
export const DESIGN_SYSTEM_EVENT_NAME = "bitvid:design-system-flag-change";
|
||||
|
||||
const globalScope = typeof globalThis === "object" && globalThis ? globalThis : undefined;
|
||||
|
||||
function dispatchDesignSystemFlagChange(enabled) {
|
||||
if (
|
||||
!globalScope ||
|
||||
typeof globalScope.dispatchEvent !== "function" ||
|
||||
(typeof globalScope.CustomEvent !== "function" &&
|
||||
typeof globalScope.Event !== "function")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let event;
|
||||
if (typeof globalScope.CustomEvent === "function") {
|
||||
event = new globalScope.CustomEvent(DESIGN_SYSTEM_EVENT_NAME, {
|
||||
detail: { enabled: enabled === true },
|
||||
});
|
||||
} else {
|
||||
event = new globalScope.Event(DESIGN_SYSTEM_EVENT_NAME);
|
||||
try {
|
||||
Object.defineProperty(event, "detail", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: { enabled: enabled === true },
|
||||
});
|
||||
} catch (error) {
|
||||
// Older browsers may not allow redefining detail; ignore.
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
globalScope.dispatchEvent(event);
|
||||
} catch (error) {
|
||||
if (typeof console !== "undefined" && console && console.warn) {
|
||||
console.warn("[design-system] Failed to dispatch flag change event:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const runtimeFlags = (() => {
|
||||
if (globalScope && typeof globalScope.__BITVID_RUNTIME_FLAGS__ === "object") {
|
||||
return globalScope.__BITVID_RUNTIME_FLAGS__;
|
||||
@@ -25,6 +66,7 @@ const runtimeFlags = (() => {
|
||||
DEFAULT_FLAGS.VIEW_FILTER_INCLUDE_LEGACY_VIDEO,
|
||||
FEATURE_WATCH_HISTORY_V2: DEFAULT_FLAGS.FEATURE_WATCH_HISTORY_V2,
|
||||
FEATURE_PUBLISH_NIP71: DEFAULT_FLAGS.FEATURE_PUBLISH_NIP71,
|
||||
FEATURE_DESIGN_SYSTEM: DEFAULT_FLAGS.FEATURE_DESIGN_SYSTEM,
|
||||
WSS_TRACKERS: [...DEFAULT_FLAGS.WSS_TRACKERS],
|
||||
};
|
||||
if (globalScope) {
|
||||
@@ -115,6 +157,11 @@ export let FEATURE_PUBLISH_NIP71 = coerceBoolean(
|
||||
DEFAULT_FLAGS.FEATURE_PUBLISH_NIP71
|
||||
);
|
||||
|
||||
export let FEATURE_DESIGN_SYSTEM = coerceBoolean(
|
||||
runtimeFlags.FEATURE_DESIGN_SYSTEM,
|
||||
DEFAULT_FLAGS.FEATURE_DESIGN_SYSTEM
|
||||
);
|
||||
|
||||
export let WSS_TRACKERS = freezeTrackers(
|
||||
sanitizeTrackerList(runtimeFlags.WSS_TRACKERS)
|
||||
);
|
||||
@@ -183,6 +230,25 @@ Object.defineProperty(runtimeFlags, "FEATURE_PUBLISH_NIP71", {
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(runtimeFlags, "FEATURE_DESIGN_SYSTEM", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return FEATURE_DESIGN_SYSTEM;
|
||||
},
|
||||
set(next) {
|
||||
const previous = FEATURE_DESIGN_SYSTEM === true;
|
||||
FEATURE_DESIGN_SYSTEM = coerceBoolean(
|
||||
next,
|
||||
DEFAULT_FLAGS.FEATURE_DESIGN_SYSTEM
|
||||
);
|
||||
const current = FEATURE_DESIGN_SYSTEM === true;
|
||||
if (previous !== current) {
|
||||
dispatchDesignSystemFlagChange(current);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(runtimeFlags, "WSS_TRACKERS", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
@@ -200,6 +266,7 @@ runtimeFlags.ACCEPT_LEGACY_V1 = ACCEPT_LEGACY_V1;
|
||||
runtimeFlags.VIEW_FILTER_INCLUDE_LEGACY_VIDEO = VIEW_FILTER_INCLUDE_LEGACY_VIDEO;
|
||||
runtimeFlags.FEATURE_WATCH_HISTORY_V2 = FEATURE_WATCH_HISTORY_V2;
|
||||
runtimeFlags.FEATURE_PUBLISH_NIP71 = FEATURE_PUBLISH_NIP71;
|
||||
runtimeFlags.FEATURE_DESIGN_SYSTEM = FEATURE_DESIGN_SYSTEM;
|
||||
runtimeFlags.WSS_TRACKERS = WSS_TRACKERS;
|
||||
|
||||
export function setUrlFirstEnabled(next) {
|
||||
@@ -221,6 +288,10 @@ export function getWatchHistoryV2Enabled() {
|
||||
return FEATURE_WATCH_HISTORY_V2 === true;
|
||||
}
|
||||
|
||||
export function getFeatureDesignSystemEnabled() {
|
||||
return FEATURE_DESIGN_SYSTEM === true;
|
||||
}
|
||||
|
||||
export function setWssTrackers(next) {
|
||||
runtimeFlags.WSS_TRACKERS = next;
|
||||
return WSS_TRACKERS;
|
||||
@@ -233,6 +304,7 @@ export function resetRuntimeFlags() {
|
||||
DEFAULT_FLAGS.VIEW_FILTER_INCLUDE_LEGACY_VIDEO
|
||||
);
|
||||
setWatchHistoryV2Enabled(DEFAULT_FLAGS.FEATURE_WATCH_HISTORY_V2);
|
||||
setFeatureDesignSystemEnabled(DEFAULT_FLAGS.FEATURE_DESIGN_SYSTEM);
|
||||
setWssTrackers(DEFAULT_FLAGS.WSS_TRACKERS);
|
||||
}
|
||||
|
||||
@@ -243,3 +315,8 @@ export function setWatchHistoryV2Enabled(next) {
|
||||
return FEATURE_WATCH_HISTORY_V2;
|
||||
}
|
||||
|
||||
export function setFeatureDesignSystemEnabled(next) {
|
||||
runtimeFlags.FEATURE_DESIGN_SYSTEM = next;
|
||||
return FEATURE_DESIGN_SYSTEM;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
DESIGN_SYSTEM_EVENT_NAME,
|
||||
getFeatureDesignSystemEnabled,
|
||||
} from "./constants.js";
|
||||
|
||||
const LEGACY_MODE = "legacy";
|
||||
const NEW_MODE = "new";
|
||||
|
||||
const FALLBACK_CONTEXT = Object.freeze({
|
||||
getMode: () => LEGACY_MODE,
|
||||
isNew: () => false,
|
||||
});
|
||||
|
||||
function normalizeMode(mode) {
|
||||
return mode === NEW_MODE ? NEW_MODE : LEGACY_MODE;
|
||||
}
|
||||
|
||||
export function getDesignSystemMode() {
|
||||
return normalizeMode(getFeatureDesignSystemEnabled() ? NEW_MODE : LEGACY_MODE);
|
||||
}
|
||||
|
||||
export function isDesignSystemNew() {
|
||||
return getFeatureDesignSystemEnabled() === true;
|
||||
}
|
||||
|
||||
export function normalizeDesignSystemContext(candidate) {
|
||||
if (!candidate || typeof candidate !== "object") {
|
||||
return FALLBACK_CONTEXT;
|
||||
}
|
||||
|
||||
const resolvedGetMode =
|
||||
typeof candidate.getMode === "function"
|
||||
? candidate.getMode
|
||||
: FALLBACK_CONTEXT.getMode;
|
||||
|
||||
const resolvedIsNew =
|
||||
typeof candidate.isNew === "function"
|
||||
? candidate.isNew
|
||||
: () => normalizeMode(resolvedGetMode()) === NEW_MODE;
|
||||
|
||||
return {
|
||||
getMode: resolvedGetMode,
|
||||
isNew: resolvedIsNew,
|
||||
};
|
||||
}
|
||||
|
||||
export function applyDesignSystemAttributes(root = null) {
|
||||
if (typeof document === "undefined" || !document) {
|
||||
return getDesignSystemMode();
|
||||
}
|
||||
|
||||
const mode = getDesignSystemMode();
|
||||
|
||||
const enqueueTargets = (source) => {
|
||||
const targets = [];
|
||||
if (!source) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
const pushIfEligible = (element) => {
|
||||
if (!element || element.nodeType !== 1) {
|
||||
return;
|
||||
}
|
||||
if (element === document.body || element.hasAttribute("data-ds")) {
|
||||
targets.push(element);
|
||||
}
|
||||
};
|
||||
|
||||
if (source === document) {
|
||||
pushIfEligible(document.body);
|
||||
if (typeof document.querySelectorAll === "function") {
|
||||
document
|
||||
.querySelectorAll("[data-ds]")
|
||||
.forEach((element) => pushIfEligible(element));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
if (source.nodeType === 1) {
|
||||
pushIfEligible(source);
|
||||
if (typeof source.querySelectorAll === "function") {
|
||||
source
|
||||
.querySelectorAll("[data-ds]")
|
||||
.forEach((element) => pushIfEligible(element));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
if (source.nodeType === 11 && typeof source.querySelectorAll === "function") {
|
||||
source
|
||||
.querySelectorAll("[data-ds]")
|
||||
.forEach((element) => pushIfEligible(element));
|
||||
}
|
||||
|
||||
return targets;
|
||||
};
|
||||
|
||||
const targets = enqueueTargets(root || document);
|
||||
|
||||
targets.forEach((element) => {
|
||||
try {
|
||||
element.setAttribute("data-ds", mode);
|
||||
} catch (error) {
|
||||
if (typeof console !== "undefined" && console && console.warn) {
|
||||
console.warn("[design-system] Failed to set data-ds attribute:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
export function subscribeToDesignSystemChanges(listener) {
|
||||
if (typeof window !== "object" || window === null) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (typeof listener !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const handler = (event) => {
|
||||
listener({
|
||||
mode: getDesignSystemMode(),
|
||||
event,
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener(DESIGN_SYSTEM_EVENT_NAME, handler);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(DESIGN_SYSTEM_EVENT_NAME, handler);
|
||||
};
|
||||
}
|
||||
|
||||
export { DESIGN_SYSTEM_EVENT_NAME };
|
||||
+23
-2
@@ -8,6 +8,10 @@ import { setApplication, setApplicationReady } from "./applicationContext.js";
|
||||
import nostrService from "./services/nostrService.js";
|
||||
import r2Service from "./services/r2Service.js";
|
||||
import { loadView, viewInitRegistry } from "./viewManager.js";
|
||||
import {
|
||||
applyDesignSystemAttributes,
|
||||
subscribeToDesignSystemChanges,
|
||||
} from "./designSystem.js";
|
||||
import {
|
||||
prepareStaticModal,
|
||||
openStaticModal,
|
||||
@@ -16,6 +20,11 @@ import {
|
||||
|
||||
validateInstanceConfig();
|
||||
|
||||
applyDesignSystemAttributes();
|
||||
subscribeToDesignSystemChanges(() => {
|
||||
applyDesignSystemAttributes();
|
||||
});
|
||||
|
||||
let application = null;
|
||||
let applicationReadyPromise = Promise.resolve();
|
||||
|
||||
@@ -136,6 +145,10 @@ async function loadModal(url) {
|
||||
document
|
||||
.getElementById("modalContainer")
|
||||
.insertAdjacentHTML("beforeend", sanitizedHtml);
|
||||
const modalContainer = document.getElementById("modalContainer");
|
||||
if (modalContainer) {
|
||||
applyDesignSystemAttributes(modalContainer);
|
||||
}
|
||||
console.log(url, "loaded");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -146,7 +159,11 @@ async function loadModal(url) {
|
||||
async function loadSidebar(url, containerId) {
|
||||
try {
|
||||
const html = await fetchPartial(url);
|
||||
document.getElementById(containerId).innerHTML = html;
|
||||
const container = document.getElementById(containerId);
|
||||
if (container) {
|
||||
container.innerHTML = html;
|
||||
applyDesignSystemAttributes(container);
|
||||
}
|
||||
console.log(url, "loaded into", containerId);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -157,7 +174,11 @@ async function loadSidebar(url, containerId) {
|
||||
async function loadDisclaimer(url, containerId) {
|
||||
try {
|
||||
const html = await fetchPartial(url);
|
||||
document.getElementById(containerId).insertAdjacentHTML("beforeend", html);
|
||||
const container = document.getElementById(containerId);
|
||||
if (container) {
|
||||
container.insertAdjacentHTML("beforeend", html);
|
||||
applyDesignSystemAttributes(container);
|
||||
}
|
||||
console.log(url, "disclaimer loaded into", containerId);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
|
||||
export class VideoCard {
|
||||
constructor({
|
||||
document: doc,
|
||||
@@ -16,7 +18,8 @@ export class VideoCard {
|
||||
state = {},
|
||||
ensureGlobalMoreMenuHandlers,
|
||||
onRequestCloseAllMenus,
|
||||
nsfwContext = null
|
||||
nsfwContext = null,
|
||||
designSystem = null,
|
||||
} = {}) {
|
||||
if (!doc) {
|
||||
throw new Error("VideoCard requires a document reference.");
|
||||
@@ -76,6 +79,8 @@ export class VideoCard {
|
||||
? onRequestCloseAllMenus
|
||||
: null;
|
||||
|
||||
this.designSystem = normalizeDesignSystemContext(designSystem);
|
||||
|
||||
this.nsfwContext = {
|
||||
isNsfw: Boolean(nsfwContext?.isNsfw),
|
||||
allowNsfw: nsfwContext?.allowNsfw !== false,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { normalizeDesignSystemContext } from "../designSystem.js";
|
||||
|
||||
export default class MoreMenuController {
|
||||
constructor(options = {}) {
|
||||
const {
|
||||
@@ -8,6 +10,7 @@ export default class MoreMenuController {
|
||||
clipboard = typeof navigator !== "undefined" ? navigator.clipboard : null,
|
||||
isDevMode = false,
|
||||
callbacks = {},
|
||||
designSystem = null,
|
||||
} = options;
|
||||
|
||||
this.document = doc;
|
||||
@@ -16,6 +19,7 @@ export default class MoreMenuController {
|
||||
this.subscriptions = subscriptions;
|
||||
this.clipboard = clipboard;
|
||||
this.isDevMode = Boolean(isDevMode);
|
||||
this.designSystem = normalizeDesignSystemContext(designSystem);
|
||||
|
||||
this.callbacks = {
|
||||
getCurrentVideo: callbacks.getCurrentVideo || (() => null),
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
BITVID_WEBSITE_URL as CONFIG_BITVID_WEBSITE_URL,
|
||||
MAX_WALLET_DEFAULT_ZAP as CONFIG_MAX_WALLET_DEFAULT_ZAP,
|
||||
} from "../config.js";
|
||||
import { normalizeDesignSystemContext } from "../designSystem.js";
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
@@ -528,6 +529,7 @@ export class ProfileModalController {
|
||||
services = {},
|
||||
state = {},
|
||||
constants: providedConstants = {},
|
||||
designSystem = null,
|
||||
} = options;
|
||||
|
||||
this.modalContainer = modalContainer;
|
||||
@@ -537,6 +539,7 @@ export class ProfileModalController {
|
||||
this.showError = showError;
|
||||
this.showSuccess = showSuccess;
|
||||
this.showStatus = showStatus;
|
||||
this.designSystem = normalizeDesignSystemContext(designSystem);
|
||||
|
||||
const resolvedMaxWalletDefaultZap =
|
||||
typeof providedConstants.MAX_WALLET_DEFAULT_ZAP === "number" &&
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
subscribeToVideoViewCount,
|
||||
unsubscribeFromVideoViewCount,
|
||||
} from "../../viewCounter.js";
|
||||
import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
|
||||
const EMPTY_VIDEO_LIST_SIGNATURE = "__EMPTY__";
|
||||
|
||||
@@ -20,6 +21,7 @@ export class VideoListView {
|
||||
utils = {},
|
||||
renderers = {},
|
||||
allowNsfw = true,
|
||||
designSystem = null,
|
||||
} = options;
|
||||
|
||||
this.document = doc;
|
||||
@@ -165,6 +167,7 @@ export class VideoListView {
|
||||
};
|
||||
|
||||
this.allowNsfw = allowNsfw !== false;
|
||||
this.designSystem = normalizeDesignSystemContext(designSystem);
|
||||
|
||||
this.emitter = typeof EventTarget !== "undefined" ? new EventTarget() : null;
|
||||
this._boundClickHandler = this.handleContainerClick.bind(this);
|
||||
@@ -425,6 +428,7 @@ export class VideoListView {
|
||||
formatters: {
|
||||
formatTimeAgo: this.formatters.formatTimeAgo,
|
||||
},
|
||||
designSystem: this.designSystem,
|
||||
});
|
||||
|
||||
videoCard.onPlay = ({ event: domEvent, video: cardVideo }) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
normalizePointerInput,
|
||||
pointerKey as derivePointerKey,
|
||||
} from "../nostr.js";
|
||||
import { normalizeDesignSystemContext } from "../designSystem.js";
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
@@ -38,6 +39,7 @@ export default class WatchHistoryController {
|
||||
showSuccess,
|
||||
dropWatchHistoryMetadata,
|
||||
getActivePubkey,
|
||||
designSystem,
|
||||
} = {}) {
|
||||
this.watchHistoryService = watchHistoryService || null;
|
||||
this.nostrClient = nostrClient || null;
|
||||
@@ -49,6 +51,7 @@ export default class WatchHistoryController {
|
||||
: noop;
|
||||
this.getActivePubkey =
|
||||
typeof getActivePubkey === "function" ? getActivePubkey : () => "";
|
||||
this.designSystem = normalizeDesignSystemContext(designSystem);
|
||||
}
|
||||
|
||||
buildPointerFromDataset(dataset = {}) {
|
||||
|
||||
+7
-1
@@ -3,6 +3,7 @@ import { initChannelProfileView } from "./channelProfile.js";
|
||||
import { subscriptions } from "./subscriptions.js";
|
||||
import { getApplication } from "./applicationContext.js";
|
||||
import { ASSET_VERSION } from "../config/asset-version.js";
|
||||
import { applyDesignSystemAttributes } from "./designSystem.js";
|
||||
|
||||
const TRACKING_SCRIPT_PATTERN = /(?:^|\/)tracking\.js(?:$|\?)/;
|
||||
|
||||
@@ -37,6 +38,7 @@ export async function loadView(viewUrl) {
|
||||
const container = document.getElementById("viewContainer");
|
||||
|
||||
container.innerHTML = doc.body.innerHTML;
|
||||
applyDesignSystemAttributes(container);
|
||||
|
||||
// Copy and execute any inline scripts
|
||||
const scriptTags = doc.querySelectorAll("script");
|
||||
@@ -63,7 +65,11 @@ export async function loadView(viewUrl) {
|
||||
</article>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById("viewContainer").innerHTML = fallbackMarkup;
|
||||
const fallbackContainer = document.getElementById("viewContainer");
|
||||
if (fallbackContainer) {
|
||||
fallbackContainer.innerHTML = fallbackMarkup;
|
||||
applyDesignSystemAttributes(fallbackContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!-- views/about.html -->
|
||||
<section class="bv-stack" aria-labelledby="aboutHeading">
|
||||
<section class="bv-stack" data-ds="legacy" aria-labelledby="aboutHeading">
|
||||
<article class="card p-lg">
|
||||
<div class="bv-stack bv-stack--tight">
|
||||
<h2 id="aboutHeading" class="text-2xl font-semibold text-text-strong">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<section class="channel-profile-container">
|
||||
<section class="channel-profile-container" data-ds="legacy">
|
||||
<!-- Modified banner wrapper for taller, rounded style -->
|
||||
<div class="profile-banner relative mb-6 rounded-lg shadow-lg">
|
||||
<!-- Increase the default height classes -->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- views/community-guidelines.html -->
|
||||
<section class="bv-stack" aria-labelledby="communityGuidelinesHeading">
|
||||
<section class="bv-stack" data-ds="legacy" aria-labelledby="communityGuidelinesHeading">
|
||||
<article class="card p-lg">
|
||||
<div class="bv-stack bv-stack--tight">
|
||||
<h2
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!-- views/explore.html -->
|
||||
<section>
|
||||
<section data-ds="legacy">
|
||||
<h2 class="mb-4 text-xl text-text-strong">Explore</h2>
|
||||
<p class="text-muted-strong">Coming Soon...</p>
|
||||
</section>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- views/getting-started.html -->
|
||||
<section class="bv-stack" aria-labelledby="gettingStartedHeading">
|
||||
<section class="bv-stack" data-ds="legacy" aria-labelledby="gettingStartedHeading">
|
||||
<article class="card p-lg">
|
||||
<div class="bv-stack bv-stack--tight">
|
||||
<h2
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!-- views/history.html -->
|
||||
<section id="watchHistoryView" class="bv-stack">
|
||||
<section id="watchHistoryView" class="bv-stack" data-ds="legacy">
|
||||
<div class="bv-stack bv-stack--tight">
|
||||
<div
|
||||
class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!-- views/ipns.html -->
|
||||
<section class="bv-stack" aria-labelledby="ipnsHeading">
|
||||
<section class="bv-stack" data-ds="legacy" aria-labelledby="ipnsHeading">
|
||||
<article class="card p-lg">
|
||||
<div class="bv-stack bv-stack--tight">
|
||||
<h2 id="ipnsHeading" class="text-2xl font-semibold text-text-strong">
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!-- views/links.html -->
|
||||
<section class="bv-stack" aria-labelledby="linksHeading">
|
||||
<section class="bv-stack" data-ds="legacy" aria-labelledby="linksHeading">
|
||||
<article class="card p-lg">
|
||||
<div class="bv-stack bv-stack--tight">
|
||||
<h2 id="linksHeading" class="text-2xl font-semibold text-text-strong">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- views/most-recent-videos.html -->
|
||||
<section>
|
||||
<section data-ds="legacy">
|
||||
<!-- You can have a heading if you want -->
|
||||
<h2 class="mb-4 text-xl text-text-strong">Most Recent Videos</h2>
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!-- views/roadmap.html -->
|
||||
<section class="bv-stack" aria-labelledby="roadmapHeading">
|
||||
<section class="bv-stack" data-ds="legacy" aria-labelledby="roadmapHeading">
|
||||
<article class="card p-lg">
|
||||
<div class="bv-stack bv-stack--tight">
|
||||
<h2 id="roadmapHeading" class="text-2xl font-semibold text-text-strong">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- views/subscriptions.html -->
|
||||
<section>
|
||||
<section data-ds="legacy">
|
||||
<h2 class="mb-4 text-xl">Subscriptions</h2>
|
||||
<div id="subscriptionsVideoList" class="bv-grid-video">
|
||||
<div class="sidebar-loading-wrapper" role="status" aria-live="polite">
|
||||
|
||||
Reference in new issue
Block a user