@imjp/writenex-astro 0.1.0
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/README.md +539 -0
- package/dist/chunk-5PM6EQE5.js +151 -0
- package/dist/chunk-5PM6EQE5.js.map +1 -0
- package/dist/chunk-7XU5X6CW.js +1331 -0
- package/dist/chunk-7XU5X6CW.js.map +1 -0
- package/dist/chunk-AAOQHQPU.js +574 -0
- package/dist/chunk-AAOQHQPU.js.map +1 -0
- package/dist/chunk-CF2XXJFF.js +1410 -0
- package/dist/chunk-CF2XXJFF.js.map +1 -0
- package/dist/chunk-CRPZUUDU.js +52 -0
- package/dist/chunk-CRPZUUDU.js.map +1 -0
- package/dist/chunk-CYLDJ3HZ.js +310 -0
- package/dist/chunk-CYLDJ3HZ.js.map +1 -0
- package/dist/chunk-KIKIPIFA.js +1 -0
- package/dist/chunk-KIKIPIFA.js.map +1 -0
- package/dist/chunk-XNTQTTJU.js +145 -0
- package/dist/chunk-XNTQTTJU.js.map +1 -0
- package/dist/client/index.css +2 -0
- package/dist/client/index.css.map +1 -0
- package/dist/client/index.js +375 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/styles.css +584 -0
- package/dist/client/variables.css +304 -0
- package/dist/config/index.d.ts +54 -0
- package/dist/config/index.js +38 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config-BmEdBDo_.d.ts +220 -0
- package/dist/content-BWR52vD-.d.ts +64 -0
- package/dist/discovery/index.d.ts +310 -0
- package/dist/discovery/index.js +38 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/errors-C0iYiDTv.d.ts +107 -0
- package/dist/filesystem/index.d.ts +1292 -0
- package/dist/filesystem/index.js +203 -0
- package/dist/filesystem/index.js.map +1 -0
- package/dist/image-FP7w5ZIs.d.ts +47 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -0
- package/dist/loader-55LWCXHA.js +12 -0
- package/dist/loader-55LWCXHA.js.map +1 -0
- package/dist/loader-CrdnaAWR.d.ts +327 -0
- package/dist/server/index.d.ts +357 -0
- package/dist/server/index.js +37 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +94 -0
- package/src/client/App.tsx +900 -0
- package/src/client/components/ConfigPanel/ConfigPanel.css +553 -0
- package/src/client/components/ConfigPanel/ConfigPanel.tsx +396 -0
- package/src/client/components/ConfigPanel/index.ts +6 -0
- package/src/client/components/CreateContentModal/CreateContentModal.css +327 -0
- package/src/client/components/CreateContentModal/CreateContentModal.tsx +216 -0
- package/src/client/components/CreateContentModal/index.ts +7 -0
- package/src/client/components/Editor/Editor.css +885 -0
- package/src/client/components/Editor/Editor.tsx +484 -0
- package/src/client/components/Editor/ImageDialog.css +344 -0
- package/src/client/components/Editor/ImageDialog.tsx +367 -0
- package/src/client/components/Editor/LinkDialog.css +326 -0
- package/src/client/components/Editor/LinkDialog.tsx +332 -0
- package/src/client/components/Editor/index.ts +6 -0
- package/src/client/components/FrontmatterForm/FrontmatterForm.css +468 -0
- package/src/client/components/FrontmatterForm/FrontmatterForm.tsx +914 -0
- package/src/client/components/FrontmatterForm/index.ts +7 -0
- package/src/client/components/Header/Header.css +300 -0
- package/src/client/components/Header/Header.tsx +300 -0
- package/src/client/components/Header/index.ts +7 -0
- package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.css +239 -0
- package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.tsx +151 -0
- package/src/client/components/KeyboardShortcuts/index.ts +6 -0
- package/src/client/components/LazyEditor.tsx +75 -0
- package/src/client/components/LiveRegion/LiveRegion.css +19 -0
- package/src/client/components/LiveRegion/LiveRegion.tsx +60 -0
- package/src/client/components/LiveRegion/index.ts +7 -0
- package/src/client/components/SearchReplace/SearchReplacePanel.css +300 -0
- package/src/client/components/SearchReplace/SearchReplacePanel.tsx +332 -0
- package/src/client/components/SearchReplace/index.ts +7 -0
- package/src/client/components/SelectCollectionModal/SelectCollectionModal.css +308 -0
- package/src/client/components/SelectCollectionModal/SelectCollectionModal.tsx +223 -0
- package/src/client/components/SelectCollectionModal/index.ts +7 -0
- package/src/client/components/Sidebar/Sidebar.css +570 -0
- package/src/client/components/Sidebar/Sidebar.tsx +617 -0
- package/src/client/components/Sidebar/index.ts +7 -0
- package/src/client/components/SkipLink/SkipLink.css +51 -0
- package/src/client/components/SkipLink/SkipLink.tsx +67 -0
- package/src/client/components/SkipLink/index.ts +7 -0
- package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.css +233 -0
- package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.tsx +160 -0
- package/src/client/components/UnsavedChangesModal/index.ts +1 -0
- package/src/client/components/VersionHistory/DiffViewer.css +430 -0
- package/src/client/components/VersionHistory/DiffViewer.tsx +383 -0
- package/src/client/components/VersionHistory/VersionActions.css +318 -0
- package/src/client/components/VersionHistory/VersionActions.tsx +277 -0
- package/src/client/components/VersionHistory/VersionHistoryPanel.css +369 -0
- package/src/client/components/VersionHistory/VersionHistoryPanel.tsx +469 -0
- package/src/client/components/VersionHistory/index.ts +9 -0
- package/src/client/context/ApiContext.tsx +154 -0
- package/src/client/context/ThemeContext.tsx +172 -0
- package/src/client/hooks/useAnnounce.ts +201 -0
- package/src/client/hooks/useApi.ts +374 -0
- package/src/client/hooks/useArrowNavigation.ts +286 -0
- package/src/client/hooks/useAutosave.ts +241 -0
- package/src/client/hooks/useFocusTrap.ts +178 -0
- package/src/client/hooks/useKeyboardShortcuts.ts +203 -0
- package/src/client/hooks/useSearch.ts +206 -0
- package/src/client/hooks/useVersionHistory.ts +451 -0
- package/src/client/index.tsx +70 -0
- package/src/client/styles.css +584 -0
- package/src/client/utils/focus.ts +57 -0
- package/src/client/utils/openInEditor.ts +130 -0
- package/src/client/variables.css +304 -0
- package/src/config/defaults.ts +109 -0
- package/src/config/index.ts +32 -0
- package/src/config/loader.ts +174 -0
- package/src/config/schema.ts +161 -0
- package/src/core/constants.ts +39 -0
- package/src/core/errors.ts +739 -0
- package/src/core/index.ts +11 -0
- package/src/discovery/collections.ts +216 -0
- package/src/discovery/index.ts +33 -0
- package/src/discovery/patterns.ts +702 -0
- package/src/discovery/schema.ts +453 -0
- package/src/filesystem/images.ts +798 -0
- package/src/filesystem/index.ts +107 -0
- package/src/filesystem/reader.ts +452 -0
- package/src/filesystem/version-config.ts +390 -0
- package/src/filesystem/versions.ts +1339 -0
- package/src/filesystem/watcher.ts +226 -0
- package/src/filesystem/writer.ts +540 -0
- package/src/index.ts +61 -0
- package/src/integration.ts +228 -0
- package/src/server/assets.ts +254 -0
- package/src/server/cache.ts +355 -0
- package/src/server/index.ts +33 -0
- package/src/server/middleware.ts +209 -0
- package/src/server/routes.ts +1428 -0
- package/src/types/api.ts +61 -0
- package/src/types/config.ts +134 -0
- package/src/types/content.ts +64 -0
- package/src/types/image.ts +48 -0
- package/src/types/index.ts +58 -0
- package/src/types/version.ts +117 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Theme context and provider for Writenex Astro
|
|
3
|
+
*
|
|
4
|
+
* Manages theme state (light/dark/system) and applies the appropriate
|
|
5
|
+
* class to the document root element. Persists preference to localStorage.
|
|
6
|
+
*
|
|
7
|
+
* @module @writenex/astro/client/context/ThemeContext
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createContext,
|
|
12
|
+
useContext,
|
|
13
|
+
useEffect,
|
|
14
|
+
useState,
|
|
15
|
+
useCallback,
|
|
16
|
+
type ReactNode,
|
|
17
|
+
} from "react";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Available theme options
|
|
21
|
+
*/
|
|
22
|
+
export type Theme = "light" | "dark" | "system";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Theme context value interface
|
|
26
|
+
*/
|
|
27
|
+
interface ThemeContextValue {
|
|
28
|
+
/** Current theme setting */
|
|
29
|
+
theme: Theme;
|
|
30
|
+
/** Resolved theme (actual light/dark being displayed) */
|
|
31
|
+
resolvedTheme: "light" | "dark";
|
|
32
|
+
/** Update theme preference */
|
|
33
|
+
setTheme: (theme: Theme) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const STORAGE_KEY = "writenex-astro-theme";
|
|
37
|
+
|
|
38
|
+
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get system color scheme preference
|
|
42
|
+
*/
|
|
43
|
+
function getSystemTheme(): "light" | "dark" {
|
|
44
|
+
if (typeof window === "undefined") return "dark";
|
|
45
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
46
|
+
? "dark"
|
|
47
|
+
: "light";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get stored theme from localStorage
|
|
52
|
+
*/
|
|
53
|
+
function getStoredTheme(): Theme {
|
|
54
|
+
if (typeof window === "undefined") return "system";
|
|
55
|
+
try {
|
|
56
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
57
|
+
if (stored === "light" || stored === "dark" || stored === "system") {
|
|
58
|
+
return stored;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// localStorage not available
|
|
62
|
+
}
|
|
63
|
+
return "system";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Props for ThemeProvider component
|
|
68
|
+
*/
|
|
69
|
+
interface ThemeProviderProps {
|
|
70
|
+
/** Child components */
|
|
71
|
+
children: ReactNode;
|
|
72
|
+
/** Default theme (optional) */
|
|
73
|
+
defaultTheme?: Theme;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Theme provider component that manages theme state and applies it to DOM.
|
|
78
|
+
*
|
|
79
|
+
* @component
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* <ThemeProvider>
|
|
83
|
+
* <App />
|
|
84
|
+
* </ThemeProvider>
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function ThemeProvider({
|
|
88
|
+
children,
|
|
89
|
+
defaultTheme = "system",
|
|
90
|
+
}: ThemeProviderProps): React.ReactElement {
|
|
91
|
+
const [theme, setThemeState] = useState<Theme>(() => {
|
|
92
|
+
if (typeof window === "undefined") return defaultTheme;
|
|
93
|
+
return getStoredTheme();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">(() => {
|
|
97
|
+
if (typeof window === "undefined") return "dark";
|
|
98
|
+
const stored = getStoredTheme();
|
|
99
|
+
if (stored === "system") return getSystemTheme();
|
|
100
|
+
return stored;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const setTheme = useCallback((newTheme: Theme) => {
|
|
104
|
+
setThemeState(newTheme);
|
|
105
|
+
try {
|
|
106
|
+
localStorage.setItem(STORAGE_KEY, newTheme);
|
|
107
|
+
} catch {
|
|
108
|
+
// localStorage not available
|
|
109
|
+
}
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
// Apply theme to document and handle system preference changes
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const root = document.documentElement;
|
|
115
|
+
|
|
116
|
+
const applyTheme = (isDark: boolean) => {
|
|
117
|
+
if (isDark) {
|
|
118
|
+
root.classList.add("wn-dark");
|
|
119
|
+
root.classList.remove("wn-light");
|
|
120
|
+
} else {
|
|
121
|
+
root.classList.add("wn-light");
|
|
122
|
+
root.classList.remove("wn-dark");
|
|
123
|
+
}
|
|
124
|
+
setResolvedTheme(isDark ? "dark" : "light");
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (theme === "dark") {
|
|
128
|
+
applyTheme(true);
|
|
129
|
+
} else if (theme === "light") {
|
|
130
|
+
applyTheme(false);
|
|
131
|
+
} else {
|
|
132
|
+
// System preference
|
|
133
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
134
|
+
applyTheme(mediaQuery.matches);
|
|
135
|
+
|
|
136
|
+
const listener = (e: MediaQueryListEvent) => {
|
|
137
|
+
applyTheme(e.matches);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
mediaQuery.addEventListener("change", listener);
|
|
141
|
+
return () => mediaQuery.removeEventListener("change", listener);
|
|
142
|
+
}
|
|
143
|
+
}, [theme]);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
|
|
147
|
+
{children}
|
|
148
|
+
</ThemeContext.Provider>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Hook to access theme context
|
|
154
|
+
*
|
|
155
|
+
* @returns Theme context value
|
|
156
|
+
* @throws Error if used outside ThemeProvider
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```tsx
|
|
160
|
+
* function MyComponent() {
|
|
161
|
+
* const { theme, setTheme, resolvedTheme } = useTheme();
|
|
162
|
+
* return <button onClick={() => setTheme('dark')}>Dark</button>;
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export function useTheme(): ThemeContextValue {
|
|
167
|
+
const context = useContext(ThemeContext);
|
|
168
|
+
if (!context) {
|
|
169
|
+
throw new Error("useTheme must be used within a ThemeProvider");
|
|
170
|
+
}
|
|
171
|
+
return context;
|
|
172
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Announce hook for screen reader announcements
|
|
3
|
+
*
|
|
4
|
+
* This hook provides a way to announce messages to screen readers
|
|
5
|
+
* using ARIA live regions. It supports both polite and assertive
|
|
6
|
+
* politeness levels and handles rapid announcement queuing.
|
|
7
|
+
*
|
|
8
|
+
* ## Features:
|
|
9
|
+
* - Polite and assertive announcement modes
|
|
10
|
+
* - Message queuing for rapid announcements
|
|
11
|
+
* - Automatic cleanup of old announcements
|
|
12
|
+
* - Integration with global live region
|
|
13
|
+
*
|
|
14
|
+
* @module @writenex/astro/client/hooks/useAnnounce
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Politeness level for announcements
|
|
21
|
+
* - polite: Waits for user to finish current task before announcing
|
|
22
|
+
* - assertive: Interrupts current task to announce immediately
|
|
23
|
+
*/
|
|
24
|
+
export type AnnouncePoliteness = "polite" | "assertive";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Announcement message structure
|
|
28
|
+
*/
|
|
29
|
+
export interface Announcement {
|
|
30
|
+
/** Unique identifier for the announcement */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Message to announce */
|
|
33
|
+
message: string;
|
|
34
|
+
/** Politeness level */
|
|
35
|
+
politeness: AnnouncePoliteness;
|
|
36
|
+
/** Timestamp when announcement was created */
|
|
37
|
+
timestamp: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Return value from useAnnounce hook
|
|
42
|
+
*/
|
|
43
|
+
export interface UseAnnounceReturn {
|
|
44
|
+
/** Announce a message to screen readers */
|
|
45
|
+
announce: (message: string, politeness?: AnnouncePoliteness) => void;
|
|
46
|
+
/** Current announcement message (for live region) */
|
|
47
|
+
currentMessage: string;
|
|
48
|
+
/** Current politeness level */
|
|
49
|
+
currentPoliteness: AnnouncePoliteness;
|
|
50
|
+
/** Clear the current announcement */
|
|
51
|
+
clear: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Maximum age for announcements in milliseconds (5 seconds) */
|
|
55
|
+
const MAX_ANNOUNCEMENT_AGE = 5000;
|
|
56
|
+
|
|
57
|
+
/** Delay between processing queued announcements */
|
|
58
|
+
const ANNOUNCEMENT_DELAY = 100;
|
|
59
|
+
|
|
60
|
+
/** Counter for generating unique announcement IDs */
|
|
61
|
+
let announcementIdCounter = 0;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate a unique announcement ID
|
|
65
|
+
*/
|
|
66
|
+
function generateAnnouncementId(): string {
|
|
67
|
+
return `announcement-${++announcementIdCounter}-${Date.now()}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Hook for announcing messages to screen readers via live regions
|
|
72
|
+
*
|
|
73
|
+
* This hook manages a queue of announcements and processes them
|
|
74
|
+
* sequentially to ensure screen readers can properly announce each
|
|
75
|
+
* message. It automatically discards stale announcements.
|
|
76
|
+
*
|
|
77
|
+
* @returns Object containing announce function and current message state
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```tsx
|
|
81
|
+
* function SaveButton() {
|
|
82
|
+
* const { announce } = useAnnounce();
|
|
83
|
+
*
|
|
84
|
+
* const handleSave = async () => {
|
|
85
|
+
* try {
|
|
86
|
+
* await saveContent();
|
|
87
|
+
* announce("Content saved", "polite");
|
|
88
|
+
* } catch (error) {
|
|
89
|
+
* announce("Save failed", "assertive");
|
|
90
|
+
* }
|
|
91
|
+
* };
|
|
92
|
+
*
|
|
93
|
+
* return <button onClick={handleSave}>Save</button>;
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function useAnnounce(): UseAnnounceReturn {
|
|
98
|
+
const [currentMessage, setCurrentMessage] = useState("");
|
|
99
|
+
const [currentPoliteness, setCurrentPoliteness] =
|
|
100
|
+
useState<AnnouncePoliteness>("polite");
|
|
101
|
+
|
|
102
|
+
const queueRef = useRef<Announcement[]>([]);
|
|
103
|
+
const isProcessingRef = useRef(false);
|
|
104
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Process the next announcement in the queue
|
|
108
|
+
*/
|
|
109
|
+
const processQueue = useCallback(() => {
|
|
110
|
+
if (isProcessingRef.current || queueRef.current.length === 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
isProcessingRef.current = true;
|
|
115
|
+
|
|
116
|
+
// Get the next announcement
|
|
117
|
+
const announcement = queueRef.current.shift();
|
|
118
|
+
|
|
119
|
+
if (!announcement) {
|
|
120
|
+
isProcessingRef.current = false;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check if announcement is stale
|
|
125
|
+
const age = Date.now() - announcement.timestamp;
|
|
126
|
+
if (age > MAX_ANNOUNCEMENT_AGE) {
|
|
127
|
+
// Skip stale announcement and process next
|
|
128
|
+
isProcessingRef.current = false;
|
|
129
|
+
processQueue();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Clear current message first to ensure screen reader announces new message
|
|
134
|
+
setCurrentMessage("");
|
|
135
|
+
|
|
136
|
+
// Set the new message after a brief delay
|
|
137
|
+
timeoutRef.current = setTimeout(() => {
|
|
138
|
+
setCurrentMessage(announcement.message);
|
|
139
|
+
setCurrentPoliteness(announcement.politeness);
|
|
140
|
+
|
|
141
|
+
// Clear after announcement and process next
|
|
142
|
+
timeoutRef.current = setTimeout(() => {
|
|
143
|
+
setCurrentMessage("");
|
|
144
|
+
isProcessingRef.current = false;
|
|
145
|
+
processQueue();
|
|
146
|
+
}, ANNOUNCEMENT_DELAY);
|
|
147
|
+
}, ANNOUNCEMENT_DELAY);
|
|
148
|
+
}, []);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Announce a message to screen readers
|
|
152
|
+
*/
|
|
153
|
+
const announce = useCallback(
|
|
154
|
+
(message: string, politeness: AnnouncePoliteness = "polite") => {
|
|
155
|
+
if (!message.trim()) return;
|
|
156
|
+
|
|
157
|
+
const announcement: Announcement = {
|
|
158
|
+
id: generateAnnouncementId(),
|
|
159
|
+
message: message.trim(),
|
|
160
|
+
politeness,
|
|
161
|
+
timestamp: Date.now(),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Add to queue
|
|
165
|
+
queueRef.current.push(announcement);
|
|
166
|
+
|
|
167
|
+
// Start processing if not already
|
|
168
|
+
processQueue();
|
|
169
|
+
},
|
|
170
|
+
[processQueue]
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Clear the current announcement
|
|
175
|
+
*/
|
|
176
|
+
const clear = useCallback(() => {
|
|
177
|
+
setCurrentMessage("");
|
|
178
|
+
queueRef.current = [];
|
|
179
|
+
if (timeoutRef.current) {
|
|
180
|
+
clearTimeout(timeoutRef.current);
|
|
181
|
+
timeoutRef.current = null;
|
|
182
|
+
}
|
|
183
|
+
isProcessingRef.current = false;
|
|
184
|
+
}, []);
|
|
185
|
+
|
|
186
|
+
// Cleanup on unmount
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
return () => {
|
|
189
|
+
if (timeoutRef.current) {
|
|
190
|
+
clearTimeout(timeoutRef.current);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}, []);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
announce,
|
|
197
|
+
currentMessage,
|
|
198
|
+
currentPoliteness,
|
|
199
|
+
clear,
|
|
200
|
+
};
|
|
201
|
+
}
|