|
1 | 1 | "use client"; |
2 | 2 |
|
3 | | -import React, { useMemo } from "react"; |
| 3 | +import React, { useMemo, useEffect } from "react"; |
4 | 4 | import { useAtom } from "jotai"; |
5 | 5 | import { notebookAtom } from "@/store"; |
6 | 6 | import katex from "katex"; |
@@ -74,6 +74,72 @@ const parseContent = (text: string) => { |
74 | 74 | export default function NotebookPage() { |
75 | 75 | const [content, setContent] = useAtom(notebookAtom); |
76 | 76 |
|
| 77 | + useEffect(() => { |
| 78 | + const html = document.documentElement; |
| 79 | + |
| 80 | + const apply = (enabled: boolean) => { |
| 81 | + html.classList.toggle("notebook--preview-only", enabled); |
| 82 | + }; |
| 83 | + |
| 84 | + // URL param fallback |
| 85 | + const params = new URLSearchParams(window.location.search); |
| 86 | + const forced = |
| 87 | + params.get("preview") === "1" || params.get("stream") === "right"; |
| 88 | + if (forced) apply(true); |
| 89 | + |
| 90 | + // Experimental Chromium capture detection |
| 91 | + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ |
| 92 | + const docAny = document as any; |
| 93 | + let cleanup: (() => void) | undefined; |
| 94 | + |
| 95 | + const setup = async () => { |
| 96 | + try { |
| 97 | + // Variant A: document.hasCapture + capturestatechange |
| 98 | + if (typeof docAny.hasCapture === "boolean") { |
| 99 | + const updateA = () => { |
| 100 | + if (!forced) apply(!!docAny.hasCapture); |
| 101 | + }; |
| 102 | + updateA(); |
| 103 | + docAny.addEventListener?.("capturestatechange", updateA); |
| 104 | + cleanup = () => |
| 105 | + docAny.removeEventListener?.("capturestatechange", updateA); |
| 106 | + return; |
| 107 | + } |
| 108 | + |
| 109 | + // Variant B: document.getCaptureState() + capturestatechange |
| 110 | + if (typeof docAny.getCaptureState === "function") { |
| 111 | + const updateB = async () => { |
| 112 | + try { |
| 113 | + const state = await docAny.getCaptureState(); // { active, displaySurface? } |
| 114 | + const isBrowserTab = |
| 115 | + !!state?.active && state?.displaySurface === "browser"; |
| 116 | + if (!forced) apply(isBrowserTab); |
| 117 | + } catch { |
| 118 | + // ignore |
| 119 | + } |
| 120 | + }; |
| 121 | + await updateB(); |
| 122 | + const handler = () => { |
| 123 | + void updateB(); |
| 124 | + }; |
| 125 | + docAny.addEventListener?.("capturestatechange", handler); |
| 126 | + cleanup = () => |
| 127 | + docAny.removeEventListener?.("capturestatechange", handler); |
| 128 | + return; |
| 129 | + } |
| 130 | + } catch { |
| 131 | + // ignore |
| 132 | + } |
| 133 | + }; |
| 134 | + |
| 135 | + void setup(); |
| 136 | + |
| 137 | + return () => { |
| 138 | + cleanup?.(); |
| 139 | + html.classList.remove("notebook--preview-only"); |
| 140 | + }; |
| 141 | + }, []); |
| 142 | + |
77 | 143 | const renderedContent = useMemo(() => parseContent(content), [content]); |
78 | 144 |
|
79 | 145 | return ( |
|
0 commit comments