@mieweb/ui 0.6.1-dev.169 → 0.6.1-dev.170
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brands/index.cjs +18 -18
- package/dist/brands/index.js +3 -3
- package/dist/chunk-3Q2VWPML.cjs +1802 -0
- package/dist/chunk-3Q2VWPML.cjs.map +1 -0
- package/dist/{chunk-6W3HLFAJ.cjs → chunk-3YRDZBW6.cjs} +3 -3
- package/dist/{chunk-6W3HLFAJ.cjs.map → chunk-3YRDZBW6.cjs.map} +1 -1
- package/dist/{chunk-UVQEHZRF.js → chunk-6VGXX6L6.js} +2 -2
- package/dist/{chunk-UVQEHZRF.js.map → chunk-6VGXX6L6.js.map} +1 -1
- package/dist/chunk-CH26CHPH.js +1776 -0
- package/dist/chunk-CH26CHPH.js.map +1 -0
- package/dist/{chunk-TXRQQMG5.js → chunk-CYMZ2QS5.js} +2 -2
- package/dist/{chunk-TXRQQMG5.js.map → chunk-CYMZ2QS5.js.map} +1 -1
- package/dist/chunk-FGP64Z24.js +131 -0
- package/dist/chunk-FGP64Z24.js.map +1 -0
- package/dist/chunk-FIWKHLWY.js +197 -0
- package/dist/chunk-FIWKHLWY.js.map +1 -0
- package/dist/chunk-GLXTVCHN.cjs +4 -0
- package/dist/chunk-GLXTVCHN.cjs.map +1 -0
- package/dist/chunk-J7KWQJOB.js +14 -0
- package/dist/chunk-J7KWQJOB.js.map +1 -0
- package/dist/chunk-JYZW2PZS.cjs +166 -0
- package/dist/chunk-JYZW2PZS.cjs.map +1 -0
- package/dist/{chunk-XVF472GT.js → chunk-NSLR3B7K.js} +2 -2
- package/dist/{chunk-XVF472GT.js.map → chunk-NSLR3B7K.js.map} +1 -1
- package/dist/chunk-NTYGCBCL.js +543 -0
- package/dist/chunk-NTYGCBCL.js.map +1 -0
- package/dist/chunk-RISZWWEQ.js +3 -0
- package/dist/chunk-RISZWWEQ.js.map +1 -0
- package/dist/chunk-S7JQ3FV5.cjs +16 -0
- package/dist/chunk-S7JQ3FV5.cjs.map +1 -0
- package/dist/{chunk-JWTCEWQ4.cjs → chunk-SNEQXSC5.cjs} +2 -2
- package/dist/{chunk-JWTCEWQ4.cjs.map → chunk-SNEQXSC5.cjs.map} +1 -1
- package/dist/chunk-SQHMGT4W.js +164 -0
- package/dist/chunk-SQHMGT4W.js.map +1 -0
- package/dist/chunk-UO3KEUYK.cjs +220 -0
- package/dist/chunk-UO3KEUYK.cjs.map +1 -0
- package/dist/{chunk-FETRH4V5.js → chunk-X763OLF2.js} +3 -3
- package/dist/{chunk-FETRH4V5.js.map → chunk-X763OLF2.js.map} +1 -1
- package/dist/{chunk-DKGUOMBC.cjs → chunk-X7J45FU5.cjs} +2 -2
- package/dist/{chunk-DKGUOMBC.cjs.map → chunk-X7J45FU5.cjs.map} +1 -1
- package/dist/chunk-Y4ESBDQ5.cjs +552 -0
- package/dist/chunk-Y4ESBDQ5.cjs.map +1 -0
- package/dist/{chunk-MJ7YITLN.cjs → chunk-Y65SK5Y2.cjs} +2 -2
- package/dist/{chunk-MJ7YITLN.cjs.map → chunk-Y65SK5Y2.cjs.map} +1 -1
- package/dist/chunk-YD23ZGRU.cjs +155 -0
- package/dist/chunk-YD23ZGRU.cjs.map +1 -0
- package/dist/components/Markdown/index.cjs +10 -10
- package/dist/components/Markdown/index.js +2 -2
- package/dist/components/MediaEditor/index.cjs +42 -0
- package/dist/components/MediaEditor/index.cjs.map +1 -0
- package/dist/components/MediaEditor/index.d.cts +151 -0
- package/dist/components/MediaEditor/index.d.ts +151 -0
- package/dist/components/MediaEditor/index.js +17 -0
- package/dist/components/MediaEditor/index.js.map +1 -0
- package/dist/components/MediaPlayer/index.cjs +24 -0
- package/dist/components/MediaPlayer/index.cjs.map +1 -0
- package/dist/components/MediaPlayer/index.d.cts +71 -0
- package/dist/components/MediaPlayer/index.d.ts +71 -0
- package/dist/components/MediaPlayer/index.js +7 -0
- package/dist/components/MediaPlayer/index.js.map +1 -0
- package/dist/components/TranscriptView/index.cjs +21 -0
- package/dist/components/TranscriptView/index.cjs.map +1 -0
- package/dist/components/TranscriptView/index.d.cts +55 -0
- package/dist/components/TranscriptView/index.d.ts +55 -0
- package/dist/components/TranscriptView/index.js +4 -0
- package/dist/components/TranscriptView/index.js.map +1 -0
- package/dist/esheet.cjs +5 -5
- package/dist/esheet.js +2 -2
- package/dist/hooks/index.cjs +49 -11
- package/dist/hooks/index.d.cts +88 -1
- package/dist/hooks/index.d.ts +88 -1
- package/dist/hooks/index.js +3 -1
- package/dist/index.cjs +374 -247
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +87 -40
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/transcript-oaWqY3yr.d.cts +81 -0
- package/dist/transcript-oaWqY3yr.d.ts +81 -0
- package/dist/useTranscriptEdits-BUylQFg0.d.cts +168 -0
- package/dist/useTranscriptEdits-CWYnjbGP.d.ts +168 -0
- package/package.json +1 -1
|
@@ -189,5 +189,5 @@ exports.useIsTablet = useIsTablet;
|
|
|
189
189
|
exports.useKeyboardShortcut = useKeyboardShortcut;
|
|
190
190
|
exports.useMediaQuery = useMediaQuery;
|
|
191
191
|
exports.useScrollSpy = useScrollSpy;
|
|
192
|
-
//# sourceMappingURL=chunk-
|
|
193
|
-
//# sourceMappingURL=chunk-
|
|
192
|
+
//# sourceMappingURL=chunk-X7J45FU5.cjs.map
|
|
193
|
+
//# sourceMappingURL=chunk-X7J45FU5.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useKeyboardShortcut.ts","../src/hooks/useMediaQuery.ts","../src/hooks/useScrollSpy.ts"],"names":["useCallback","useEffect","useState","useRef","entries"],"mappings":"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","file":"chunk-DKGUOMBC.cjs","sourcesContent":["import { useEffect, useCallback } from 'react';\n\n/**\n * Options for keyboard shortcut hook\n */\nexport interface KeyboardShortcutOptions {\n /** Whether the shortcut is currently enabled (default: true) */\n enabled?: boolean;\n /** Modifier keys required (default: none) */\n modifiers?: {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n };\n /** Whether to prevent default browser behavior (default: true) */\n preventDefault?: boolean;\n /** Whether to stop event propagation (default: false) */\n stopPropagation?: boolean;\n /** Element types to ignore when the shortcut is pressed (default: ['INPUT', 'TEXTAREA', 'SELECT']) */\n ignoreInputs?: boolean;\n}\n\n/**\n * Hook that triggers a callback when a specific keyboard shortcut is pressed.\n * Supports modifier keys (Ctrl, Shift, Alt, Meta) and ignores input fields by default.\n *\n * @param key - The key to listen for (e.g., 'k', 'Enter', 'Escape', '/')\n * @param callback - Function to call when the shortcut is pressed\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * // Cmd/Ctrl+K to open search\n * useKeyboardShortcut('k', openSearch, { modifiers: { meta: true, ctrl: true } });\n *\n * // Forward slash to focus search (ignoring when typing)\n * useKeyboardShortcut('/', focusSearch);\n *\n * // Escape to close modal\n * useKeyboardShortcut('Escape', closeModal);\n * ```\n */\nexport function useKeyboardShortcut(\n key: string,\n callback: (event: KeyboardEvent) => void,\n options: KeyboardShortcutOptions = {}\n): void {\n const {\n enabled = true,\n modifiers = {},\n preventDefault = true,\n stopPropagation = false,\n ignoreInputs = true,\n } = options;\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => {\n // Check if we should ignore input fields\n if (ignoreInputs) {\n const target = event.target as HTMLElement;\n const tagName = target.tagName.toUpperCase();\n if (\n tagName === 'INPUT' ||\n tagName === 'TEXTAREA' ||\n tagName === 'SELECT' ||\n target.isContentEditable\n ) {\n return;\n }\n }\n\n // Check modifiers\n const { ctrl, shift, alt, meta } = modifiers;\n\n // For Cmd+K style shortcuts, allow either meta (Mac) or ctrl (Windows/Linux)\n const metaOrCtrl = meta || ctrl;\n if (metaOrCtrl) {\n if (!(event.metaKey || event.ctrlKey)) return;\n } else {\n // If no meta/ctrl modifier specified, ensure neither is pressed\n if (event.metaKey || event.ctrlKey) return;\n }\n\n if (shift && !event.shiftKey) return;\n if (alt && !event.altKey) return;\n\n // Check key match (case-insensitive for letters)\n const eventKey =\n event.key.length === 1 ? event.key.toLowerCase() : event.key;\n const targetKey = key.length === 1 ? key.toLowerCase() : key;\n if (eventKey !== targetKey) return;\n\n if (preventDefault) {\n event.preventDefault();\n }\n if (stopPropagation) {\n event.stopPropagation();\n }\n\n callback(event);\n },\n [key, callback, modifiers, preventDefault, stopPropagation, ignoreInputs]\n );\n\n useEffect(() => {\n if (!enabled) return;\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [handleKeyDown, enabled]);\n}\n\n/**\n * Hook for the common Cmd/Ctrl+K shortcut pattern (command palette, search, etc.)\n *\n * @param callback - Function to call when Cmd/Ctrl+K is pressed\n * @param enabled - Whether the shortcut is active (default: true)\n *\n * @example\n * ```tsx\n * useCommandK(() => setIsOpen(true));\n * ```\n */\nexport function useCommandK(callback: () => void, enabled = true): void {\n useKeyboardShortcut('k', callback, {\n enabled,\n modifiers: { meta: true, ctrl: true },\n ignoreInputs: false, // Cmd+K should work even in inputs\n });\n}\n","import { useState, useEffect } from 'react';\n\n/**\n * Hook that tracks whether a media query matches.\n * Uses the native `matchMedia` API for efficient media query tracking.\n *\n * @param query - CSS media query string (e.g., '(min-width: 768px)')\n * @returns Boolean indicating whether the media query matches\n *\n * @example\n * ```tsx\n * function ResponsiveComponent() {\n * const isMobile = useMediaQuery('(max-width: 767px)');\n * const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1023px)');\n * const isDesktop = useMediaQuery('(min-width: 1024px)');\n *\n * return (\n * <div>\n * {isMobile && <MobileLayout />}\n * {isTablet && <TabletLayout />}\n * {isDesktop && <DesktopLayout />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMediaQuery(query: string): boolean {\n // Initialize with null to indicate SSR/initial state\n const [matches, setMatches] = useState<boolean>(() => {\n // Check if we're in a browser environment\n if (typeof window === 'undefined') return false;\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia(query);\n\n // Set initial value\n setMatches(mediaQuery.matches);\n\n // Handler for media query changes\n const handler = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n // Modern browsers use addEventListener\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }\n\n // Fallback for older browsers\n mediaQuery.addListener(handler);\n return () => mediaQuery.removeListener(handler);\n }, [query]);\n\n return matches;\n}\n\n/**\n * Preset breakpoint hooks following common responsive design patterns\n */\n\n/** Returns true when viewport is smaller than 640px (mobile) */\nexport function useIsMobile(): boolean {\n return useMediaQuery('(max-width: 639px)');\n}\n\n/** Returns true when viewport is 640px-767px (large mobile / small tablet) */\nexport function useIsSmallTablet(): boolean {\n return useMediaQuery('(min-width: 640px) and (max-width: 767px)');\n}\n\n/** Returns true when viewport is 768px-1023px (tablet) */\nexport function useIsTablet(): boolean {\n return useMediaQuery('(min-width: 768px) and (max-width: 1023px)');\n}\n\n/** Returns true when viewport is 1024px or larger (desktop) */\nexport function useIsDesktop(): boolean {\n return useMediaQuery('(min-width: 1024px)');\n}\n\n/** Returns true when viewport is 1280px or larger (large desktop) */\nexport function useIsLargeDesktop(): boolean {\n return useMediaQuery('(min-width: 1280px)');\n}\n\n/** Returns true when viewport is smaller than 1024px (mobile/tablet) */\nexport function useIsMobileOrTablet(): boolean {\n return useMediaQuery('(max-width: 1023px)');\n}\n","import { useEffect, useState, useRef, type RefObject } from 'react';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface UseScrollSpyOptions {\n /** CSS selector for the elements to observe (e.g. 'h2, h3, h4') */\n selectors?: string;\n /** Explicit list of element IDs to observe (alternative to selectors) */\n ids?: string[];\n /** IntersectionObserver root margin (default: '0px 0px -60% 0px') */\n rootMargin?: string;\n /** IntersectionObserver threshold (default: 0) */\n threshold?: number | number[];\n /** Scroll container element ref. Defaults to document viewport. */\n root?: RefObject<HTMLElement | null>;\n /** Whether the hook is active (default: true) */\n enabled?: boolean;\n}\n\nexport interface UseScrollSpyReturn {\n /** The ID of the currently active (in-view) element */\n activeId: string | null;\n}\n\n// =============================================================================\n// Hook\n// =============================================================================\n\n/**\n * Tracks which section is currently in the viewport using IntersectionObserver.\n * Pairs naturally with `TableOfContents` to highlight the active heading.\n *\n * @example\n * ```tsx\n * function DocsPage() {\n * const { activeId } = useScrollSpy({ selectors: 'h2, h3' });\n * return (\n * <aside>\n * <TableOfContents activeId={activeId} />\n * </aside>\n * );\n * }\n * ```\n */\nexport function useScrollSpy({\n selectors,\n ids,\n rootMargin = '0px 0px -60% 0px',\n threshold = 0,\n root,\n enabled = true,\n}: UseScrollSpyOptions = {}): UseScrollSpyReturn {\n const [activeId, setActiveId] = useState<string | null>(null);\n const visibleEntries = useRef<Map<string, IntersectionObserverEntry>>(\n new Map()\n );\n\n // Track root.current so the effect re-runs when the ref attaches\n const rootEl = root?.current ?? null;\n\n useEffect(() => {\n if (!enabled) return;\n\n const container = rootEl ?? document;\n\n // Resolve target elements from the DOM\n function resolveElements(): Element[] {\n let els: Element[] = [];\n if (ids && ids.length > 0) {\n els = ids\n .map((id) => document.getElementById(id))\n .filter((el): el is HTMLElement => el !== null);\n } else if (selectors) {\n els = Array.from(container.querySelectorAll(selectors));\n }\n return els.filter((el) => el.id);\n }\n\n let intersectionObserver: IntersectionObserver | null = null;\n let mutationObserver: MutationObserver | null = null;\n\n function startObserving(): boolean {\n const elements = resolveElements();\n if (elements.length === 0) return false;\n\n intersectionObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n visibleEntries.current.set(entry.target.id, entry);\n } else {\n visibleEntries.current.delete(entry.target.id);\n }\n }\n\n // Pick the topmost visible heading (by DOM order)\n if (visibleEntries.current.size > 0) {\n let topmost: string | null = null;\n let topmostTop = Infinity;\n\n for (const [id, entry] of visibleEntries.current) {\n const top = entry.boundingClientRect.top;\n if (top < topmostTop) {\n topmostTop = top;\n topmost = id;\n }\n }\n\n if (topmost) {\n setActiveId(topmost);\n }\n }\n },\n {\n root: rootEl,\n rootMargin,\n threshold,\n }\n );\n\n for (const el of elements) {\n intersectionObserver.observe(el);\n }\n\n // Elements found — stop watching for new DOM nodes\n mutationObserver?.disconnect();\n mutationObserver = null;\n return true;\n }\n\n // If target elements don't exist yet (e.g. headings mount after ToC),\n // watch for DOM changes and retry until they appear.\n if (!startObserving()) {\n const watchRoot = rootEl ?? document.body;\n mutationObserver = new MutationObserver(() => {\n startObserving();\n });\n mutationObserver.observe(watchRoot, { childList: true, subtree: true });\n }\n\n const entries = visibleEntries.current;\n return () => {\n mutationObserver?.disconnect();\n intersectionObserver?.disconnect();\n entries.clear();\n };\n }, [selectors, ids, rootMargin, threshold, rootEl, enabled]);\n\n return { activeId };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useKeyboardShortcut.ts","../src/hooks/useMediaQuery.ts","../src/hooks/useScrollSpy.ts"],"names":["useCallback","useEffect","useState","useRef","entries"],"mappings":"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","file":"chunk-X7J45FU5.cjs","sourcesContent":["import { useEffect, useCallback } from 'react';\n\n/**\n * Options for keyboard shortcut hook\n */\nexport interface KeyboardShortcutOptions {\n /** Whether the shortcut is currently enabled (default: true) */\n enabled?: boolean;\n /** Modifier keys required (default: none) */\n modifiers?: {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n };\n /** Whether to prevent default browser behavior (default: true) */\n preventDefault?: boolean;\n /** Whether to stop event propagation (default: false) */\n stopPropagation?: boolean;\n /** Element types to ignore when the shortcut is pressed (default: ['INPUT', 'TEXTAREA', 'SELECT']) */\n ignoreInputs?: boolean;\n}\n\n/**\n * Hook that triggers a callback when a specific keyboard shortcut is pressed.\n * Supports modifier keys (Ctrl, Shift, Alt, Meta) and ignores input fields by default.\n *\n * @param key - The key to listen for (e.g., 'k', 'Enter', 'Escape', '/')\n * @param callback - Function to call when the shortcut is pressed\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * // Cmd/Ctrl+K to open search\n * useKeyboardShortcut('k', openSearch, { modifiers: { meta: true, ctrl: true } });\n *\n * // Forward slash to focus search (ignoring when typing)\n * useKeyboardShortcut('/', focusSearch);\n *\n * // Escape to close modal\n * useKeyboardShortcut('Escape', closeModal);\n * ```\n */\nexport function useKeyboardShortcut(\n key: string,\n callback: (event: KeyboardEvent) => void,\n options: KeyboardShortcutOptions = {}\n): void {\n const {\n enabled = true,\n modifiers = {},\n preventDefault = true,\n stopPropagation = false,\n ignoreInputs = true,\n } = options;\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => {\n // Check if we should ignore input fields\n if (ignoreInputs) {\n const target = event.target as HTMLElement;\n const tagName = target.tagName.toUpperCase();\n if (\n tagName === 'INPUT' ||\n tagName === 'TEXTAREA' ||\n tagName === 'SELECT' ||\n target.isContentEditable\n ) {\n return;\n }\n }\n\n // Check modifiers\n const { ctrl, shift, alt, meta } = modifiers;\n\n // For Cmd+K style shortcuts, allow either meta (Mac) or ctrl (Windows/Linux)\n const metaOrCtrl = meta || ctrl;\n if (metaOrCtrl) {\n if (!(event.metaKey || event.ctrlKey)) return;\n } else {\n // If no meta/ctrl modifier specified, ensure neither is pressed\n if (event.metaKey || event.ctrlKey) return;\n }\n\n if (shift && !event.shiftKey) return;\n if (alt && !event.altKey) return;\n\n // Check key match (case-insensitive for letters)\n const eventKey =\n event.key.length === 1 ? event.key.toLowerCase() : event.key;\n const targetKey = key.length === 1 ? key.toLowerCase() : key;\n if (eventKey !== targetKey) return;\n\n if (preventDefault) {\n event.preventDefault();\n }\n if (stopPropagation) {\n event.stopPropagation();\n }\n\n callback(event);\n },\n [key, callback, modifiers, preventDefault, stopPropagation, ignoreInputs]\n );\n\n useEffect(() => {\n if (!enabled) return;\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [handleKeyDown, enabled]);\n}\n\n/**\n * Hook for the common Cmd/Ctrl+K shortcut pattern (command palette, search, etc.)\n *\n * @param callback - Function to call when Cmd/Ctrl+K is pressed\n * @param enabled - Whether the shortcut is active (default: true)\n *\n * @example\n * ```tsx\n * useCommandK(() => setIsOpen(true));\n * ```\n */\nexport function useCommandK(callback: () => void, enabled = true): void {\n useKeyboardShortcut('k', callback, {\n enabled,\n modifiers: { meta: true, ctrl: true },\n ignoreInputs: false, // Cmd+K should work even in inputs\n });\n}\n","import { useState, useEffect } from 'react';\n\n/**\n * Hook that tracks whether a media query matches.\n * Uses the native `matchMedia` API for efficient media query tracking.\n *\n * @param query - CSS media query string (e.g., '(min-width: 768px)')\n * @returns Boolean indicating whether the media query matches\n *\n * @example\n * ```tsx\n * function ResponsiveComponent() {\n * const isMobile = useMediaQuery('(max-width: 767px)');\n * const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1023px)');\n * const isDesktop = useMediaQuery('(min-width: 1024px)');\n *\n * return (\n * <div>\n * {isMobile && <MobileLayout />}\n * {isTablet && <TabletLayout />}\n * {isDesktop && <DesktopLayout />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMediaQuery(query: string): boolean {\n // Initialize with null to indicate SSR/initial state\n const [matches, setMatches] = useState<boolean>(() => {\n // Check if we're in a browser environment\n if (typeof window === 'undefined') return false;\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia(query);\n\n // Set initial value\n setMatches(mediaQuery.matches);\n\n // Handler for media query changes\n const handler = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n // Modern browsers use addEventListener\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }\n\n // Fallback for older browsers\n mediaQuery.addListener(handler);\n return () => mediaQuery.removeListener(handler);\n }, [query]);\n\n return matches;\n}\n\n/**\n * Preset breakpoint hooks following common responsive design patterns\n */\n\n/** Returns true when viewport is smaller than 640px (mobile) */\nexport function useIsMobile(): boolean {\n return useMediaQuery('(max-width: 639px)');\n}\n\n/** Returns true when viewport is 640px-767px (large mobile / small tablet) */\nexport function useIsSmallTablet(): boolean {\n return useMediaQuery('(min-width: 640px) and (max-width: 767px)');\n}\n\n/** Returns true when viewport is 768px-1023px (tablet) */\nexport function useIsTablet(): boolean {\n return useMediaQuery('(min-width: 768px) and (max-width: 1023px)');\n}\n\n/** Returns true when viewport is 1024px or larger (desktop) */\nexport function useIsDesktop(): boolean {\n return useMediaQuery('(min-width: 1024px)');\n}\n\n/** Returns true when viewport is 1280px or larger (large desktop) */\nexport function useIsLargeDesktop(): boolean {\n return useMediaQuery('(min-width: 1280px)');\n}\n\n/** Returns true when viewport is smaller than 1024px (mobile/tablet) */\nexport function useIsMobileOrTablet(): boolean {\n return useMediaQuery('(max-width: 1023px)');\n}\n","import { useEffect, useState, useRef, type RefObject } from 'react';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface UseScrollSpyOptions {\n /** CSS selector for the elements to observe (e.g. 'h2, h3, h4') */\n selectors?: string;\n /** Explicit list of element IDs to observe (alternative to selectors) */\n ids?: string[];\n /** IntersectionObserver root margin (default: '0px 0px -60% 0px') */\n rootMargin?: string;\n /** IntersectionObserver threshold (default: 0) */\n threshold?: number | number[];\n /** Scroll container element ref. Defaults to document viewport. */\n root?: RefObject<HTMLElement | null>;\n /** Whether the hook is active (default: true) */\n enabled?: boolean;\n}\n\nexport interface UseScrollSpyReturn {\n /** The ID of the currently active (in-view) element */\n activeId: string | null;\n}\n\n// =============================================================================\n// Hook\n// =============================================================================\n\n/**\n * Tracks which section is currently in the viewport using IntersectionObserver.\n * Pairs naturally with `TableOfContents` to highlight the active heading.\n *\n * @example\n * ```tsx\n * function DocsPage() {\n * const { activeId } = useScrollSpy({ selectors: 'h2, h3' });\n * return (\n * <aside>\n * <TableOfContents activeId={activeId} />\n * </aside>\n * );\n * }\n * ```\n */\nexport function useScrollSpy({\n selectors,\n ids,\n rootMargin = '0px 0px -60% 0px',\n threshold = 0,\n root,\n enabled = true,\n}: UseScrollSpyOptions = {}): UseScrollSpyReturn {\n const [activeId, setActiveId] = useState<string | null>(null);\n const visibleEntries = useRef<Map<string, IntersectionObserverEntry>>(\n new Map()\n );\n\n // Track root.current so the effect re-runs when the ref attaches\n const rootEl = root?.current ?? null;\n\n useEffect(() => {\n if (!enabled) return;\n\n const container = rootEl ?? document;\n\n // Resolve target elements from the DOM\n function resolveElements(): Element[] {\n let els: Element[] = [];\n if (ids && ids.length > 0) {\n els = ids\n .map((id) => document.getElementById(id))\n .filter((el): el is HTMLElement => el !== null);\n } else if (selectors) {\n els = Array.from(container.querySelectorAll(selectors));\n }\n return els.filter((el) => el.id);\n }\n\n let intersectionObserver: IntersectionObserver | null = null;\n let mutationObserver: MutationObserver | null = null;\n\n function startObserving(): boolean {\n const elements = resolveElements();\n if (elements.length === 0) return false;\n\n intersectionObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n visibleEntries.current.set(entry.target.id, entry);\n } else {\n visibleEntries.current.delete(entry.target.id);\n }\n }\n\n // Pick the topmost visible heading (by DOM order)\n if (visibleEntries.current.size > 0) {\n let topmost: string | null = null;\n let topmostTop = Infinity;\n\n for (const [id, entry] of visibleEntries.current) {\n const top = entry.boundingClientRect.top;\n if (top < topmostTop) {\n topmostTop = top;\n topmost = id;\n }\n }\n\n if (topmost) {\n setActiveId(topmost);\n }\n }\n },\n {\n root: rootEl,\n rootMargin,\n threshold,\n }\n );\n\n for (const el of elements) {\n intersectionObserver.observe(el);\n }\n\n // Elements found — stop watching for new DOM nodes\n mutationObserver?.disconnect();\n mutationObserver = null;\n return true;\n }\n\n // If target elements don't exist yet (e.g. headings mount after ToC),\n // watch for DOM changes and retry until they appear.\n if (!startObserving()) {\n const watchRoot = rootEl ?? document.body;\n mutationObserver = new MutationObserver(() => {\n startObserving();\n });\n mutationObserver.observe(watchRoot, { childList: true, subtree: true });\n }\n\n const entries = visibleEntries.current;\n return () => {\n mutationObserver?.disconnect();\n intersectionObserver?.disconnect();\n entries.clear();\n };\n }, [selectors, ids, rootMargin, threshold, rootEl, enabled]);\n\n return { activeId };\n}\n"]}
|
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/hooks/useTranscriptEdits.ts
|
|
6
|
+
var DEFAULT_FILLER_WORDS = [
|
|
7
|
+
"um",
|
|
8
|
+
"uh",
|
|
9
|
+
"umm",
|
|
10
|
+
"uhh",
|
|
11
|
+
"uh-huh",
|
|
12
|
+
"mm-hmm",
|
|
13
|
+
"hmm",
|
|
14
|
+
"hm",
|
|
15
|
+
"er",
|
|
16
|
+
"err",
|
|
17
|
+
"ah",
|
|
18
|
+
"ahh",
|
|
19
|
+
"eh",
|
|
20
|
+
"oh",
|
|
21
|
+
"ooh",
|
|
22
|
+
"like",
|
|
23
|
+
"you know",
|
|
24
|
+
"i mean",
|
|
25
|
+
"so",
|
|
26
|
+
"well",
|
|
27
|
+
"actually",
|
|
28
|
+
"basically",
|
|
29
|
+
"literally",
|
|
30
|
+
"right",
|
|
31
|
+
"okay",
|
|
32
|
+
"ok"
|
|
33
|
+
];
|
|
34
|
+
var DEFAULT_MIN_SILENCE_MS = 400;
|
|
35
|
+
var DEFAULT_NL_SILENCE_MS = 1500;
|
|
36
|
+
var SILENCE_ANALYSIS_THRESHOLDS = [0.3, 0.4, 0.5, 0.75, 1, 1.5, 2];
|
|
37
|
+
function buildPlaybackSegments(editedWords) {
|
|
38
|
+
const activeWords = editedWords.filter((w) => !w.deleted);
|
|
39
|
+
if (activeWords.length === 0) return [];
|
|
40
|
+
const segments = [];
|
|
41
|
+
let currentSegment = null;
|
|
42
|
+
let lastOriginalIndex = -2;
|
|
43
|
+
let lastWasInserted = false;
|
|
44
|
+
for (let i = 0; i < editedWords.length; i++) {
|
|
45
|
+
const ew = editedWords[i];
|
|
46
|
+
if (ew.deleted) continue;
|
|
47
|
+
const isConsecutive = !ew.inserted && !lastWasInserted && ew.originalIndex === lastOriginalIndex + 1;
|
|
48
|
+
if (currentSegment && isConsecutive) {
|
|
49
|
+
currentSegment.endMs = ew.word.endMs;
|
|
50
|
+
currentSegment.editedIndices.push(i);
|
|
51
|
+
} else {
|
|
52
|
+
if (currentSegment) {
|
|
53
|
+
segments.push(currentSegment);
|
|
54
|
+
}
|
|
55
|
+
currentSegment = {
|
|
56
|
+
startMs: ew.word.startMs,
|
|
57
|
+
endMs: ew.word.endMs,
|
|
58
|
+
editedIndices: [i]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
lastOriginalIndex = ew.originalIndex;
|
|
62
|
+
lastWasInserted = ew.inserted ?? false;
|
|
63
|
+
}
|
|
64
|
+
if (currentSegment) {
|
|
65
|
+
segments.push(currentSegment);
|
|
66
|
+
}
|
|
67
|
+
return segments;
|
|
68
|
+
}
|
|
69
|
+
function getSpeedAtIndex(wordIndex, speedMarkers, defaultSpeed) {
|
|
70
|
+
const sortedMarkers = [...speedMarkers].sort(
|
|
71
|
+
(a, b) => a.wordIndex - b.wordIndex
|
|
72
|
+
);
|
|
73
|
+
let effectiveSpeed = defaultSpeed;
|
|
74
|
+
for (const marker of sortedMarkers) {
|
|
75
|
+
if (marker.wordIndex <= wordIndex) {
|
|
76
|
+
effectiveSpeed = marker.speed;
|
|
77
|
+
} else {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return effectiveSpeed;
|
|
82
|
+
}
|
|
83
|
+
function insertSilences(words, minSilenceMs = DEFAULT_MIN_SILENCE_MS, nlSilenceMs = DEFAULT_NL_SILENCE_MS) {
|
|
84
|
+
if (words.length === 0) return [];
|
|
85
|
+
const result = [];
|
|
86
|
+
for (let i = 0; i < words.length; i++) {
|
|
87
|
+
const word = words[i];
|
|
88
|
+
if (i === 0 && word.startMs > minSilenceMs) {
|
|
89
|
+
const isNewline = word.startMs >= nlSilenceMs;
|
|
90
|
+
result.push({
|
|
91
|
+
text: `[${(word.startMs / 1e3).toFixed(1)}s]`,
|
|
92
|
+
startMs: 0,
|
|
93
|
+
endMs: word.startMs,
|
|
94
|
+
wordType: isNewline ? "silence-newline" : "silence"
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
result.push({ ...word, wordType: word.wordType ?? "word" });
|
|
98
|
+
if (i < words.length - 1) {
|
|
99
|
+
const nextWord = words[i + 1];
|
|
100
|
+
const gapMs = nextWord.startMs - word.endMs;
|
|
101
|
+
if (gapMs >= minSilenceMs) {
|
|
102
|
+
const isNewline = gapMs >= nlSilenceMs;
|
|
103
|
+
result.push({
|
|
104
|
+
text: `[${(gapMs / 1e3).toFixed(1)}s]`,
|
|
105
|
+
startMs: word.endMs,
|
|
106
|
+
endMs: nextWord.startMs,
|
|
107
|
+
wordType: isNewline ? "silence-newline" : "silence"
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
function initEditableWords(transcript, minSilenceMs = DEFAULT_MIN_SILENCE_MS, nlSilenceMs = DEFAULT_NL_SILENCE_MS) {
|
|
115
|
+
const wordsWithSilences = insertSilences(
|
|
116
|
+
transcript.words,
|
|
117
|
+
minSilenceMs,
|
|
118
|
+
nlSilenceMs
|
|
119
|
+
);
|
|
120
|
+
return wordsWithSilences.map((word, index) => ({
|
|
121
|
+
originalIndex: index,
|
|
122
|
+
word,
|
|
123
|
+
deleted: false
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
function normalizeWordText(text) {
|
|
127
|
+
return text.toLowerCase().replace(/[.,!?;:]/g, "");
|
|
128
|
+
}
|
|
129
|
+
function useTranscriptEdits(options) {
|
|
130
|
+
const {
|
|
131
|
+
transcript,
|
|
132
|
+
initialEditedWords,
|
|
133
|
+
initialUndoStack,
|
|
134
|
+
onChange,
|
|
135
|
+
minSilenceMs: initialMinSilenceMs = DEFAULT_MIN_SILENCE_MS,
|
|
136
|
+
nlSilenceMs: initialNlSilenceMs = DEFAULT_NL_SILENCE_MS
|
|
137
|
+
} = options;
|
|
138
|
+
const initializedFromSaved = react.useRef(false);
|
|
139
|
+
const [editedWords, setEditedWords] = react.useState(
|
|
140
|
+
() => initialEditedWords || initEditableWords(transcript, initialMinSilenceMs, initialNlSilenceMs)
|
|
141
|
+
);
|
|
142
|
+
const [undoStack, setUndoStack] = react.useState(
|
|
143
|
+
initialUndoStack || []
|
|
144
|
+
);
|
|
145
|
+
const [clipboard, setClipboard] = react.useState(null);
|
|
146
|
+
const [hasEdits, setHasEdits] = react.useState(() => {
|
|
147
|
+
if (initialEditedWords) {
|
|
148
|
+
return initialEditedWords.some((ew) => ew.deleted || ew.inserted);
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
});
|
|
152
|
+
const [minSilenceMs, setMinSilenceMs] = react.useState(initialMinSilenceMs);
|
|
153
|
+
const [nlSilenceMs, setNlSilenceMs] = react.useState(initialNlSilenceMs);
|
|
154
|
+
const [defaultSpeed, setDefaultSpeed] = react.useState(1);
|
|
155
|
+
const [speedMarkers, setSpeedMarkers] = react.useState([]);
|
|
156
|
+
const pushUndo = react.useCallback(() => {
|
|
157
|
+
setUndoStack((prev) => [...prev, editedWords]);
|
|
158
|
+
}, [editedWords]);
|
|
159
|
+
const prevTranscriptRef = react.useRef(transcript);
|
|
160
|
+
react.useEffect(() => {
|
|
161
|
+
if (prevTranscriptRef.current === transcript) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
prevTranscriptRef.current = transcript;
|
|
165
|
+
setEditedWords(initEditableWords(transcript, minSilenceMs, nlSilenceMs));
|
|
166
|
+
setUndoStack([]);
|
|
167
|
+
setHasEdits(false);
|
|
168
|
+
setClipboard(null);
|
|
169
|
+
initializedFromSaved.current = false;
|
|
170
|
+
}, [transcript, minSilenceMs, nlSilenceMs]);
|
|
171
|
+
react.useEffect(() => {
|
|
172
|
+
if (!initializedFromSaved.current && initialEditedWords) {
|
|
173
|
+
initializedFromSaved.current = true;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
initializedFromSaved.current = true;
|
|
177
|
+
if (hasEdits || undoStack.length > 0) {
|
|
178
|
+
onChange?.(editedWords, undoStack);
|
|
179
|
+
}
|
|
180
|
+
}, [editedWords, undoStack, hasEdits, onChange, initialEditedWords]);
|
|
181
|
+
const undo = react.useCallback(() => {
|
|
182
|
+
if (undoStack.length === 0) return;
|
|
183
|
+
const previousState = undoStack[undoStack.length - 1];
|
|
184
|
+
setUndoStack((prev) => prev.slice(0, -1));
|
|
185
|
+
setEditedWords(previousState);
|
|
186
|
+
const isOriginal = previousState.every((ew, i) => ew.originalIndex === i && !ew.deleted) && previousState.length === transcript.words.length;
|
|
187
|
+
setHasEdits(!isOriginal);
|
|
188
|
+
}, [undoStack, transcript.words.length]);
|
|
189
|
+
const toggleWordDeleted = react.useCallback(
|
|
190
|
+
(index) => {
|
|
191
|
+
const ew = editedWords[index];
|
|
192
|
+
if (!ew) return;
|
|
193
|
+
pushUndo();
|
|
194
|
+
const newEditedWords = [...editedWords];
|
|
195
|
+
newEditedWords[index] = { ...ew, deleted: !ew.deleted };
|
|
196
|
+
setEditedWords(newEditedWords);
|
|
197
|
+
setHasEdits(true);
|
|
198
|
+
},
|
|
199
|
+
[editedWords, pushUndo]
|
|
200
|
+
);
|
|
201
|
+
const deleteRange = react.useCallback(
|
|
202
|
+
(indices) => {
|
|
203
|
+
if (indices.length === 0) return;
|
|
204
|
+
const anchor = editedWords[indices[0]];
|
|
205
|
+
if (!anchor) return;
|
|
206
|
+
pushUndo();
|
|
207
|
+
const targetDeletedState = !anchor.deleted;
|
|
208
|
+
setEditedWords((prev) => {
|
|
209
|
+
const updated = [...prev];
|
|
210
|
+
for (const i of indices) {
|
|
211
|
+
if (updated[i]) {
|
|
212
|
+
updated[i] = { ...updated[i], deleted: targetDeletedState };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return updated;
|
|
216
|
+
});
|
|
217
|
+
setHasEdits(true);
|
|
218
|
+
},
|
|
219
|
+
[editedWords, pushUndo]
|
|
220
|
+
);
|
|
221
|
+
const cut = react.useCallback(
|
|
222
|
+
(indices) => {
|
|
223
|
+
if (indices.length === 0) return;
|
|
224
|
+
const cutWords = indices.map((i) => editedWords[i]).filter((ew) => ew !== void 0);
|
|
225
|
+
if (cutWords.length === 0) return;
|
|
226
|
+
pushUndo();
|
|
227
|
+
setClipboard({ words: cutWords, operation: "cut" });
|
|
228
|
+
setEditedWords((prev) => {
|
|
229
|
+
const updated = [...prev];
|
|
230
|
+
for (const i of indices) {
|
|
231
|
+
if (updated[i]) {
|
|
232
|
+
updated[i] = { ...updated[i], deleted: true };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return updated;
|
|
236
|
+
});
|
|
237
|
+
setHasEdits(true);
|
|
238
|
+
},
|
|
239
|
+
[editedWords, pushUndo]
|
|
240
|
+
);
|
|
241
|
+
const copy = react.useCallback(
|
|
242
|
+
(indices) => {
|
|
243
|
+
const selectedWords = indices.map((i) => editedWords[i]).filter((ew) => ew !== void 0 && !ew.deleted);
|
|
244
|
+
if (selectedWords.length === 0) return;
|
|
245
|
+
setClipboard({ words: selectedWords, operation: "copy" });
|
|
246
|
+
},
|
|
247
|
+
[editedWords]
|
|
248
|
+
);
|
|
249
|
+
const paste = react.useCallback(
|
|
250
|
+
(atIndex, before = true) => {
|
|
251
|
+
if (!clipboard) return null;
|
|
252
|
+
pushUndo();
|
|
253
|
+
const insertIndex = before ? atIndex : atIndex + 1;
|
|
254
|
+
setEditedWords((prev) => {
|
|
255
|
+
const updated = [...prev];
|
|
256
|
+
const wordsToInsert = clipboard.words.map((w) => ({
|
|
257
|
+
...w,
|
|
258
|
+
deleted: false,
|
|
259
|
+
inserted: true
|
|
260
|
+
}));
|
|
261
|
+
updated.splice(insertIndex, 0, ...wordsToInsert);
|
|
262
|
+
return updated;
|
|
263
|
+
});
|
|
264
|
+
setHasEdits(true);
|
|
265
|
+
return insertIndex;
|
|
266
|
+
},
|
|
267
|
+
[clipboard, pushUndo]
|
|
268
|
+
);
|
|
269
|
+
const setWordText = react.useCallback(
|
|
270
|
+
(index, text) => {
|
|
271
|
+
const ew = editedWords[index];
|
|
272
|
+
if (!ew || text === ew.word.text) return;
|
|
273
|
+
pushUndo();
|
|
274
|
+
const newEditedWords = [...editedWords];
|
|
275
|
+
newEditedWords[index] = {
|
|
276
|
+
...ew,
|
|
277
|
+
word: { ...ew.word, text }
|
|
278
|
+
};
|
|
279
|
+
setEditedWords(newEditedWords);
|
|
280
|
+
setHasEdits(true);
|
|
281
|
+
},
|
|
282
|
+
[editedWords, pushUndo]
|
|
283
|
+
);
|
|
284
|
+
const splitSilence = react.useCallback(
|
|
285
|
+
(index, durationsSec) => {
|
|
286
|
+
const ew = editedWords[index];
|
|
287
|
+
if (!ew || ew.word.wordType !== "silence") return;
|
|
288
|
+
pushUndo();
|
|
289
|
+
const silenceStart = ew.word.startMs;
|
|
290
|
+
const newSilences = [];
|
|
291
|
+
let currentMs = silenceStart;
|
|
292
|
+
for (let i = 0; i < durationsSec.length; i++) {
|
|
293
|
+
const durationMs = Math.round(durationsSec[i] * 1e3);
|
|
294
|
+
const endMs = currentMs + durationMs;
|
|
295
|
+
newSilences.push({
|
|
296
|
+
originalIndex: -1,
|
|
297
|
+
// Negative indicates a split/inserted silence
|
|
298
|
+
word: {
|
|
299
|
+
text: `[${durationsSec[i].toFixed(1)}s]`,
|
|
300
|
+
startMs: currentMs,
|
|
301
|
+
endMs,
|
|
302
|
+
wordType: "silence"
|
|
303
|
+
},
|
|
304
|
+
deleted: false,
|
|
305
|
+
inserted: true
|
|
306
|
+
});
|
|
307
|
+
currentMs = endMs;
|
|
308
|
+
}
|
|
309
|
+
const newEditedWords = [...editedWords];
|
|
310
|
+
newEditedWords.splice(index, 1, ...newSilences);
|
|
311
|
+
setEditedWords(newEditedWords);
|
|
312
|
+
setHasEdits(true);
|
|
313
|
+
},
|
|
314
|
+
[editedWords, pushUndo]
|
|
315
|
+
);
|
|
316
|
+
const deleteWord = react.useCallback(
|
|
317
|
+
(index) => {
|
|
318
|
+
toggleWordDeleted(index);
|
|
319
|
+
},
|
|
320
|
+
[toggleWordDeleted]
|
|
321
|
+
);
|
|
322
|
+
const replaceEditedWords = react.useCallback(
|
|
323
|
+
(next) => {
|
|
324
|
+
pushUndo();
|
|
325
|
+
setEditedWords(next);
|
|
326
|
+
setHasEdits(true);
|
|
327
|
+
},
|
|
328
|
+
[pushUndo]
|
|
329
|
+
);
|
|
330
|
+
const removeFillers = react.useCallback(
|
|
331
|
+
(fillerWords, removeSilenceAboveSec) => {
|
|
332
|
+
const fillerSet = new Set(fillerWords.map((f) => f.toLowerCase()));
|
|
333
|
+
const silenceThresholdMs = removeSilenceAboveSec !== null ? removeSilenceAboveSec * 1e3 : null;
|
|
334
|
+
const newEditedWords = editedWords.map((ew) => {
|
|
335
|
+
if (ew.deleted) return ew;
|
|
336
|
+
if ((ew.word.wordType === "silence" || ew.word.wordType === "silence-newline") && silenceThresholdMs !== null) {
|
|
337
|
+
const durationMs = ew.word.endMs - ew.word.startMs;
|
|
338
|
+
if (durationMs > silenceThresholdMs) {
|
|
339
|
+
return { ...ew, deleted: true };
|
|
340
|
+
}
|
|
341
|
+
return ew;
|
|
342
|
+
}
|
|
343
|
+
if (ew.word.wordType !== "silence" && ew.word.wordType !== "silence-newline") {
|
|
344
|
+
const wordText = normalizeWordText(ew.word.text);
|
|
345
|
+
if (fillerSet.has(wordText)) {
|
|
346
|
+
return { ...ew, deleted: true };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return ew;
|
|
350
|
+
});
|
|
351
|
+
const deletedCount = newEditedWords.filter(
|
|
352
|
+
(ew, i) => ew.deleted && !editedWords[i].deleted
|
|
353
|
+
).length;
|
|
354
|
+
if (deletedCount > 0) {
|
|
355
|
+
pushUndo();
|
|
356
|
+
setEditedWords(newEditedWords);
|
|
357
|
+
setHasEdits(true);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
[editedWords, pushUndo]
|
|
361
|
+
);
|
|
362
|
+
const setSilenceThresholds = react.useCallback(
|
|
363
|
+
(newMinSilenceMs, newNlSilenceMs) => {
|
|
364
|
+
setMinSilenceMs(newMinSilenceMs);
|
|
365
|
+
setNlSilenceMs(newNlSilenceMs);
|
|
366
|
+
const wordDeletedStatus = /* @__PURE__ */ new Map();
|
|
367
|
+
editedWords.forEach((ew) => {
|
|
368
|
+
if (ew.word.wordType !== "silence" && ew.word.wordType !== "silence-newline" && ew.originalIndex >= 0) {
|
|
369
|
+
wordDeletedStatus.set(ew.originalIndex, ew.deleted);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
const newEditedWords = initEditableWords(
|
|
373
|
+
transcript,
|
|
374
|
+
newMinSilenceMs,
|
|
375
|
+
newNlSilenceMs
|
|
376
|
+
);
|
|
377
|
+
let wordIdx = 0;
|
|
378
|
+
const restoredWords = newEditedWords.map((ew) => {
|
|
379
|
+
if (ew.word.wordType !== "silence" && ew.word.wordType !== "silence-newline") {
|
|
380
|
+
const wasDeleted = wordDeletedStatus.get(wordIdx) ?? false;
|
|
381
|
+
wordIdx++;
|
|
382
|
+
return { ...ew, deleted: wasDeleted };
|
|
383
|
+
}
|
|
384
|
+
return ew;
|
|
385
|
+
});
|
|
386
|
+
setEditedWords(restoredWords);
|
|
387
|
+
},
|
|
388
|
+
[editedWords, transcript]
|
|
389
|
+
);
|
|
390
|
+
const getSpeedMarkerAtIndex = react.useCallback(
|
|
391
|
+
(wordIndex) => {
|
|
392
|
+
return speedMarkers.find((m) => m.wordIndex === wordIndex);
|
|
393
|
+
},
|
|
394
|
+
[speedMarkers]
|
|
395
|
+
);
|
|
396
|
+
const toggleSpeedMarker = react.useCallback(
|
|
397
|
+
(wordIndex, speed) => {
|
|
398
|
+
setSpeedMarkers((prev) => {
|
|
399
|
+
const existingIdx = prev.findIndex((m) => m.wordIndex === wordIndex);
|
|
400
|
+
if (existingIdx >= 0) {
|
|
401
|
+
if (prev[existingIdx].speed === speed) {
|
|
402
|
+
return prev.filter((_, i) => i !== existingIdx);
|
|
403
|
+
}
|
|
404
|
+
const updated = [...prev];
|
|
405
|
+
updated[existingIdx] = { wordIndex, speed };
|
|
406
|
+
return updated;
|
|
407
|
+
}
|
|
408
|
+
return [...prev, { wordIndex, speed }].sort(
|
|
409
|
+
(a, b) => a.wordIndex - b.wordIndex
|
|
410
|
+
);
|
|
411
|
+
});
|
|
412
|
+
setHasEdits(true);
|
|
413
|
+
},
|
|
414
|
+
[]
|
|
415
|
+
);
|
|
416
|
+
const removeSpeedMarker = react.useCallback(
|
|
417
|
+
(wordIndex) => {
|
|
418
|
+
const filtered = speedMarkers.filter((m) => m.wordIndex !== wordIndex);
|
|
419
|
+
if (filtered.length !== speedMarkers.length) {
|
|
420
|
+
setSpeedMarkers(filtered);
|
|
421
|
+
setHasEdits(true);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
[speedMarkers]
|
|
425
|
+
);
|
|
426
|
+
const getSpeedAtIndexBound = react.useCallback(
|
|
427
|
+
(index) => {
|
|
428
|
+
return getSpeedAtIndex(index, speedMarkers, defaultSpeed);
|
|
429
|
+
},
|
|
430
|
+
[speedMarkers, defaultSpeed]
|
|
431
|
+
);
|
|
432
|
+
const playbackSegments = react.useMemo(
|
|
433
|
+
() => buildPlaybackSegments(editedWords),
|
|
434
|
+
[editedWords]
|
|
435
|
+
);
|
|
436
|
+
const stats = react.useMemo(() => {
|
|
437
|
+
const activeWordCount = editedWords.filter(
|
|
438
|
+
(ew) => !ew.deleted && ew.word.wordType !== "silence"
|
|
439
|
+
).length;
|
|
440
|
+
const activeSilenceCount = editedWords.filter(
|
|
441
|
+
(ew) => !ew.deleted && ew.word.wordType === "silence"
|
|
442
|
+
).length;
|
|
443
|
+
const deletedWordCount = editedWords.filter(
|
|
444
|
+
(ew) => ew.deleted && ew.word.wordType !== "silence"
|
|
445
|
+
).length;
|
|
446
|
+
const deletedSilenceCount = editedWords.filter(
|
|
447
|
+
(ew) => ew.deleted && ew.word.wordType === "silence"
|
|
448
|
+
).length;
|
|
449
|
+
const editedDurationMs = editedWords.reduce((sum, ew, index) => {
|
|
450
|
+
if (ew.deleted) return sum;
|
|
451
|
+
const wordDuration = ew.word.endMs - ew.word.startMs;
|
|
452
|
+
const speed = getSpeedAtIndex(index, speedMarkers, defaultSpeed);
|
|
453
|
+
return sum + wordDuration / speed;
|
|
454
|
+
}, 0);
|
|
455
|
+
return {
|
|
456
|
+
activeWordCount,
|
|
457
|
+
activeSilenceCount,
|
|
458
|
+
deletedWordCount,
|
|
459
|
+
deletedSilenceCount,
|
|
460
|
+
editedDurationMs
|
|
461
|
+
};
|
|
462
|
+
}, [editedWords, speedMarkers, defaultSpeed]);
|
|
463
|
+
const fillerAnalysis = react.useMemo(() => {
|
|
464
|
+
const matchCounts = /* @__PURE__ */ new Map();
|
|
465
|
+
for (const ew of editedWords) {
|
|
466
|
+
if (ew.deleted || ew.word.wordType === "silence") continue;
|
|
467
|
+
const wordText = normalizeWordText(ew.word.text);
|
|
468
|
+
for (const filler of DEFAULT_FILLER_WORDS) {
|
|
469
|
+
if (wordText === filler) {
|
|
470
|
+
matchCounts.set(filler, (matchCounts.get(filler) || 0) + 1);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
matchCounts.set(wordText, (matchCounts.get(wordText) || 0) + 1);
|
|
474
|
+
}
|
|
475
|
+
const durations = /* @__PURE__ */ new Map();
|
|
476
|
+
for (const ew of editedWords) {
|
|
477
|
+
if (ew.deleted || ew.word.wordType === "silence" || ew.word.wordType === "silence-newline")
|
|
478
|
+
continue;
|
|
479
|
+
const wordText = normalizeWordText(ew.word.text);
|
|
480
|
+
const wordDuration = ew.word.endMs - ew.word.startMs;
|
|
481
|
+
for (const filler of DEFAULT_FILLER_WORDS) {
|
|
482
|
+
if (wordText === filler) {
|
|
483
|
+
durations.set(filler, (durations.get(filler) || 0) + wordDuration);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
durations.set(wordText, (durations.get(wordText) || 0) + wordDuration);
|
|
487
|
+
}
|
|
488
|
+
const silenceCounts = SILENCE_ANALYSIS_THRESHOLDS.map((threshold) => {
|
|
489
|
+
const thresholdMs = threshold * 1e3;
|
|
490
|
+
let count = 0;
|
|
491
|
+
let durationMs = 0;
|
|
492
|
+
for (const ew of editedWords) {
|
|
493
|
+
if (ew.deleted) continue;
|
|
494
|
+
if (ew.word.wordType !== "silence" && ew.word.wordType !== "silence-newline")
|
|
495
|
+
continue;
|
|
496
|
+
const silenceDuration = ew.word.endMs - ew.word.startMs;
|
|
497
|
+
if (silenceDuration > thresholdMs) {
|
|
498
|
+
count++;
|
|
499
|
+
durationMs += silenceDuration;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return { threshold, count, durationMs };
|
|
503
|
+
});
|
|
504
|
+
return {
|
|
505
|
+
matchCounts,
|
|
506
|
+
durations,
|
|
507
|
+
silenceCounts,
|
|
508
|
+
defaultFillerWords: DEFAULT_FILLER_WORDS
|
|
509
|
+
};
|
|
510
|
+
}, [editedWords]);
|
|
511
|
+
return {
|
|
512
|
+
editedWords,
|
|
513
|
+
undoStack,
|
|
514
|
+
clipboard,
|
|
515
|
+
hasEdits,
|
|
516
|
+
pushUndo,
|
|
517
|
+
undo,
|
|
518
|
+
toggleWordDeleted,
|
|
519
|
+
deleteRange,
|
|
520
|
+
cut,
|
|
521
|
+
copy,
|
|
522
|
+
paste,
|
|
523
|
+
setWordText,
|
|
524
|
+
splitSilence,
|
|
525
|
+
deleteWord,
|
|
526
|
+
replaceEditedWords,
|
|
527
|
+
removeFillers,
|
|
528
|
+
setSilenceThresholds,
|
|
529
|
+
silenceThresholds: { minSilenceMs, nlSilenceMs },
|
|
530
|
+
speedMarkers,
|
|
531
|
+
defaultSpeed,
|
|
532
|
+
setDefaultSpeed,
|
|
533
|
+
toggleSpeedMarker,
|
|
534
|
+
removeSpeedMarker,
|
|
535
|
+
getSpeedAtIndex: getSpeedAtIndexBound,
|
|
536
|
+
getSpeedMarkerAtIndex,
|
|
537
|
+
playbackSegments,
|
|
538
|
+
stats,
|
|
539
|
+
fillerAnalysis
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
exports.DEFAULT_FILLER_WORDS = DEFAULT_FILLER_WORDS;
|
|
544
|
+
exports.DEFAULT_MIN_SILENCE_MS = DEFAULT_MIN_SILENCE_MS;
|
|
545
|
+
exports.DEFAULT_NL_SILENCE_MS = DEFAULT_NL_SILENCE_MS;
|
|
546
|
+
exports.buildPlaybackSegments = buildPlaybackSegments;
|
|
547
|
+
exports.getSpeedAtIndex = getSpeedAtIndex;
|
|
548
|
+
exports.initEditableWords = initEditableWords;
|
|
549
|
+
exports.insertSilences = insertSilences;
|
|
550
|
+
exports.useTranscriptEdits = useTranscriptEdits;
|
|
551
|
+
//# sourceMappingURL=chunk-Y4ESBDQ5.cjs.map
|
|
552
|
+
//# sourceMappingURL=chunk-Y4ESBDQ5.cjs.map
|