@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,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview KeyboardShortcuts modal styles
|
|
3
|
+
*
|
|
4
|
+
* Modal styling for keyboard shortcuts help panel.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* ============================================================================
|
|
8
|
+
MODAL OVERLAY
|
|
9
|
+
============================================================================ */
|
|
10
|
+
|
|
11
|
+
.wn-shortcuts-overlay {
|
|
12
|
+
position: fixed;
|
|
13
|
+
inset: 0;
|
|
14
|
+
z-index: var(--wn-z-modal);
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
background-color: var(--wn-backdrop);
|
|
19
|
+
animation: wn-shortcuts-fadeIn var(--wn-transition-fast) ease-out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@keyframes wn-shortcuts-fadeIn {
|
|
23
|
+
from {
|
|
24
|
+
opacity: 0;
|
|
25
|
+
}
|
|
26
|
+
to {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ============================================================================
|
|
32
|
+
MODAL CONTAINER
|
|
33
|
+
============================================================================ */
|
|
34
|
+
|
|
35
|
+
.wn-shortcuts-modal {
|
|
36
|
+
width: 100%;
|
|
37
|
+
max-width: var(--wn-modal-sm);
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
border-radius: var(--wn-radius-lg);
|
|
40
|
+
border: 1px solid var(--wn-zinc-700);
|
|
41
|
+
background-color: var(--wn-zinc-900);
|
|
42
|
+
animation: wn-shortcuts-zoomIn var(--wn-transition-fast) ease-out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.wn-shortcuts-modal--large {
|
|
46
|
+
max-width: 32rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@keyframes wn-shortcuts-zoomIn {
|
|
50
|
+
from {
|
|
51
|
+
opacity: 0;
|
|
52
|
+
transform: scale(0.95);
|
|
53
|
+
}
|
|
54
|
+
to {
|
|
55
|
+
opacity: 1;
|
|
56
|
+
transform: scale(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ============================================================================
|
|
61
|
+
MODAL HEADER
|
|
62
|
+
============================================================================ */
|
|
63
|
+
|
|
64
|
+
.wn-shortcuts-header {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: space-between;
|
|
68
|
+
padding: var(--wn-space-4) var(--wn-space-5);
|
|
69
|
+
border-bottom: 1px solid var(--wn-zinc-700);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.wn-shortcuts-title {
|
|
73
|
+
font-size: var(--wn-font-base);
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
color: var(--wn-zinc-50);
|
|
76
|
+
margin: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.wn-shortcuts-close {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
width: var(--wn-icon-btn-md);
|
|
84
|
+
height: var(--wn-icon-btn-md);
|
|
85
|
+
padding: 0;
|
|
86
|
+
border: none;
|
|
87
|
+
border-radius: var(--wn-radius-md);
|
|
88
|
+
background-color: transparent;
|
|
89
|
+
color: var(--wn-zinc-400);
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
transition:
|
|
92
|
+
background-color var(--wn-transition-fast),
|
|
93
|
+
color var(--wn-transition-fast);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.wn-shortcuts-close:hover {
|
|
97
|
+
background-color: var(--wn-overlay-10);
|
|
98
|
+
color: var(--wn-zinc-50);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* ============================================================================
|
|
102
|
+
SHORTCUTS LIST
|
|
103
|
+
============================================================================ */
|
|
104
|
+
|
|
105
|
+
.wn-shortcuts-list {
|
|
106
|
+
max-height: 60vh;
|
|
107
|
+
overflow-y: auto;
|
|
108
|
+
padding: var(--wn-space-3);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.wn-shortcuts-section {
|
|
112
|
+
margin-bottom: var(--wn-space-5);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.wn-shortcuts-section:last-child {
|
|
116
|
+
margin-bottom: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.wn-shortcuts-category {
|
|
120
|
+
font-size: var(--wn-font-xs);
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
text-transform: uppercase;
|
|
123
|
+
letter-spacing: 0.05em;
|
|
124
|
+
color: var(--wn-zinc-500);
|
|
125
|
+
margin: 0 0 var(--wn-space-3) var(--wn-space-4);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.wn-shortcuts-item {
|
|
129
|
+
display: flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: space-between;
|
|
132
|
+
padding: var(--wn-space-3) var(--wn-space-4);
|
|
133
|
+
border-radius: var(--wn-radius-md);
|
|
134
|
+
transition: background-color var(--wn-transition-fast);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.wn-shortcuts-item:hover {
|
|
138
|
+
background-color: var(--wn-zinc-800);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.wn-shortcuts-item--disabled {
|
|
142
|
+
opacity: 0.4;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.wn-shortcuts-item--disabled:hover {
|
|
146
|
+
background-color: transparent;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.wn-shortcuts-label {
|
|
150
|
+
font-size: var(--wn-font-base);
|
|
151
|
+
color: var(--wn-zinc-400);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.wn-shortcuts-key {
|
|
155
|
+
padding: var(--wn-space-1) var(--wn-space-3);
|
|
156
|
+
border-radius: var(--wn-radius-sm);
|
|
157
|
+
border: 1px solid var(--wn-zinc-700);
|
|
158
|
+
background-color: var(--wn-zinc-950);
|
|
159
|
+
font-family: ui-monospace, monospace;
|
|
160
|
+
font-size: var(--wn-font-xs);
|
|
161
|
+
color: var(--wn-zinc-50);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ============================================================================
|
|
165
|
+
MODAL FOOTER
|
|
166
|
+
============================================================================ */
|
|
167
|
+
|
|
168
|
+
.wn-shortcuts-footer {
|
|
169
|
+
padding: var(--wn-space-4) var(--wn-space-5);
|
|
170
|
+
border-top: 1px solid var(--wn-zinc-700);
|
|
171
|
+
text-align: center;
|
|
172
|
+
font-size: var(--wn-font-xs);
|
|
173
|
+
color: var(--wn-zinc-600);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.wn-shortcuts-footer kbd {
|
|
177
|
+
padding: var(--wn-space-1) var(--wn-space-2);
|
|
178
|
+
border-radius: var(--wn-radius-sm);
|
|
179
|
+
background-color: var(--wn-zinc-800);
|
|
180
|
+
color: var(--wn-zinc-400);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* ============================================================================
|
|
184
|
+
LIGHT MODE OVERRIDES
|
|
185
|
+
============================================================================ */
|
|
186
|
+
|
|
187
|
+
.wn-light .wn-shortcuts-overlay {
|
|
188
|
+
background-color: var(--wn-backdrop-light);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.wn-light .wn-shortcuts-modal {
|
|
192
|
+
border-color: var(--wn-zinc-200);
|
|
193
|
+
background-color: #fff;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.wn-light .wn-shortcuts-header {
|
|
197
|
+
border-bottom-color: var(--wn-zinc-200);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.wn-light .wn-shortcuts-title {
|
|
201
|
+
color: var(--wn-zinc-900);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.wn-light .wn-shortcuts-close {
|
|
205
|
+
color: var(--wn-zinc-500);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.wn-light .wn-shortcuts-close:hover {
|
|
209
|
+
background-color: var(--wn-overlay-light-5);
|
|
210
|
+
color: var(--wn-zinc-900);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.wn-light .wn-shortcuts-category {
|
|
214
|
+
color: var(--wn-zinc-400);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.wn-light .wn-shortcuts-item:hover {
|
|
218
|
+
background-color: var(--wn-zinc-100);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.wn-light .wn-shortcuts-label {
|
|
222
|
+
color: var(--wn-zinc-600);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.wn-light .wn-shortcuts-key {
|
|
226
|
+
border-color: var(--wn-zinc-200);
|
|
227
|
+
background-color: var(--wn-zinc-50);
|
|
228
|
+
color: var(--wn-zinc-900);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.wn-light .wn-shortcuts-footer {
|
|
232
|
+
border-top-color: var(--wn-zinc-200);
|
|
233
|
+
color: var(--wn-zinc-400);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.wn-light .wn-shortcuts-footer kbd {
|
|
237
|
+
background-color: var(--wn-zinc-100);
|
|
238
|
+
color: var(--wn-zinc-600);
|
|
239
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Keyboard shortcuts help modal component
|
|
3
|
+
*
|
|
4
|
+
* Displays a modal with all available keyboard shortcuts including
|
|
5
|
+
* application shortcuts and built-in editor formatting shortcuts.
|
|
6
|
+
* Includes focus trap for accessibility compliance.
|
|
7
|
+
*
|
|
8
|
+
* @module @writenex/astro/client/components/KeyboardShortcuts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useRef } from "react";
|
|
12
|
+
import { X } from "lucide-react";
|
|
13
|
+
import { useFocusTrap } from "../../hooks/useFocusTrap";
|
|
14
|
+
import {
|
|
15
|
+
formatShortcut,
|
|
16
|
+
type ShortcutDefinition,
|
|
17
|
+
} from "../../hooks/useKeyboardShortcuts";
|
|
18
|
+
import "./KeyboardShortcuts.css";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Built-in editor shortcuts from MDXEditor/Lexical.
|
|
22
|
+
* These are always available when editing content.
|
|
23
|
+
*/
|
|
24
|
+
const EDITOR_SHORTCUTS: {
|
|
25
|
+
category: string;
|
|
26
|
+
shortcuts: { label: string; keys: string }[];
|
|
27
|
+
}[] = [
|
|
28
|
+
{
|
|
29
|
+
category: "Formatting",
|
|
30
|
+
shortcuts: [
|
|
31
|
+
{ label: "Bold", keys: "Ctrl+B" },
|
|
32
|
+
{ label: "Italic", keys: "Ctrl+I" },
|
|
33
|
+
{ label: "Underline", keys: "Ctrl+U" },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
category: "Actions",
|
|
38
|
+
shortcuts: [
|
|
39
|
+
{ label: "Undo", keys: "Ctrl+Z" },
|
|
40
|
+
{ label: "Redo", keys: "Ctrl+Shift+Z" },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Props for ShortcutsHelpModal component
|
|
47
|
+
*/
|
|
48
|
+
interface ShortcutsHelpModalProps {
|
|
49
|
+
/** List of application shortcuts to display */
|
|
50
|
+
shortcuts: ShortcutDefinition[];
|
|
51
|
+
/** Callback to close the modal */
|
|
52
|
+
onClose: () => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Keyboard shortcuts help modal component
|
|
57
|
+
*
|
|
58
|
+
* @component
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* {showHelp && (
|
|
62
|
+
* <ShortcutsHelpModal shortcuts={shortcuts} onClose={closeHelp} />
|
|
63
|
+
* )}
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function ShortcutsHelpModal({
|
|
67
|
+
shortcuts,
|
|
68
|
+
onClose,
|
|
69
|
+
}: ShortcutsHelpModalProps): React.ReactElement {
|
|
70
|
+
const triggerRef = useRef<HTMLElement | null>(null);
|
|
71
|
+
|
|
72
|
+
// Store the trigger element when modal mounts
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
triggerRef.current = document.activeElement as HTMLElement;
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
// Focus trap for accessibility
|
|
78
|
+
const { containerRef } = useFocusTrap({
|
|
79
|
+
enabled: true,
|
|
80
|
+
onEscape: onClose,
|
|
81
|
+
returnFocusTo: triggerRef.current,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const handleOverlayClick = (e: React.MouseEvent) => {
|
|
85
|
+
if (e.target === e.currentTarget) onClose();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="wn-shortcuts-overlay" onClick={handleOverlayClick}>
|
|
90
|
+
<div
|
|
91
|
+
ref={containerRef}
|
|
92
|
+
className="wn-shortcuts-modal wn-shortcuts-modal--large"
|
|
93
|
+
role="dialog"
|
|
94
|
+
aria-modal="true"
|
|
95
|
+
aria-labelledby="shortcuts-title"
|
|
96
|
+
>
|
|
97
|
+
{/* Header */}
|
|
98
|
+
<div className="wn-shortcuts-header">
|
|
99
|
+
<h2 id="shortcuts-title" className="wn-shortcuts-title">
|
|
100
|
+
Keyboard Shortcuts
|
|
101
|
+
</h2>
|
|
102
|
+
<button
|
|
103
|
+
className="wn-shortcuts-close"
|
|
104
|
+
onClick={onClose}
|
|
105
|
+
title="Close (Esc)"
|
|
106
|
+
aria-label="Close shortcuts help"
|
|
107
|
+
>
|
|
108
|
+
<X size={16} />
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{/* Shortcuts List */}
|
|
113
|
+
<div className="wn-shortcuts-list">
|
|
114
|
+
{/* Application Shortcuts */}
|
|
115
|
+
<div className="wn-shortcuts-section">
|
|
116
|
+
<h3 className="wn-shortcuts-category">Application</h3>
|
|
117
|
+
{shortcuts.map((shortcut) => (
|
|
118
|
+
<div
|
|
119
|
+
key={shortcut.key}
|
|
120
|
+
className={`wn-shortcuts-item ${shortcut.enabled === false ? "wn-shortcuts-item--disabled" : ""}`}
|
|
121
|
+
>
|
|
122
|
+
<span className="wn-shortcuts-label">{shortcut.label}</span>
|
|
123
|
+
<kbd className="wn-shortcuts-key">
|
|
124
|
+
{formatShortcut(shortcut)}
|
|
125
|
+
</kbd>
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Editor Shortcuts */}
|
|
131
|
+
{EDITOR_SHORTCUTS.map((group) => (
|
|
132
|
+
<div key={group.category} className="wn-shortcuts-section">
|
|
133
|
+
<h3 className="wn-shortcuts-category">{group.category}</h3>
|
|
134
|
+
{group.shortcuts.map((shortcut) => (
|
|
135
|
+
<div key={shortcut.label} className="wn-shortcuts-item">
|
|
136
|
+
<span className="wn-shortcuts-label">{shortcut.label}</span>
|
|
137
|
+
<kbd className="wn-shortcuts-key">{shortcut.keys}</kbd>
|
|
138
|
+
</div>
|
|
139
|
+
))}
|
|
140
|
+
</div>
|
|
141
|
+
))}
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{/* Footer */}
|
|
145
|
+
<div className="wn-shortcuts-footer">
|
|
146
|
+
Press <kbd>Ctrl+/</kbd> to toggle this help
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Lazy-loaded Editor component
|
|
3
|
+
*
|
|
4
|
+
* This module provides a lazy-loaded version of the MDXEditor component
|
|
5
|
+
* to improve initial bundle size and load time. The actual editor is
|
|
6
|
+
* loaded on-demand when the component mounts.
|
|
7
|
+
*
|
|
8
|
+
* @module @writenex/astro/client/components/LazyEditor
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { lazy, Suspense, memo } from "react";
|
|
12
|
+
import { EditorLoading } from "./Editor/Editor";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Props for LazyEditor - same as Editor
|
|
16
|
+
*/
|
|
17
|
+
interface LazyEditorProps {
|
|
18
|
+
/** Initial markdown content */
|
|
19
|
+
initialContent: string;
|
|
20
|
+
/** Callback when content changes */
|
|
21
|
+
onChange: (markdown: string) => void;
|
|
22
|
+
/** Whether the editor is read-only */
|
|
23
|
+
readOnly?: boolean;
|
|
24
|
+
/** Placeholder text when empty */
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
/** Handler for image uploads. Returns the image URL/path on success. */
|
|
27
|
+
onImageUpload?: (file: File) => Promise<string | null>;
|
|
28
|
+
/** Base path for API requests */
|
|
29
|
+
basePath?: string;
|
|
30
|
+
/** Current collection name (for image URL resolution) */
|
|
31
|
+
collection?: string;
|
|
32
|
+
/** Current content ID (for image URL resolution) */
|
|
33
|
+
contentId?: string;
|
|
34
|
+
/** Search query for highlighting */
|
|
35
|
+
searchQuery?: string;
|
|
36
|
+
/** Current search match index (1-based) */
|
|
37
|
+
searchActiveIndex?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Lazy-loaded Editor component
|
|
42
|
+
*
|
|
43
|
+
* Uses React.lazy to defer loading the MDXEditor bundle until needed.
|
|
44
|
+
*/
|
|
45
|
+
const LazyEditorComponent = lazy(() =>
|
|
46
|
+
import("./Editor/Editor").then((mod) => ({ default: mod.Editor }))
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Editor wrapper with Suspense fallback
|
|
51
|
+
*
|
|
52
|
+
* This component wraps the lazy-loaded Editor with a Suspense boundary
|
|
53
|
+
* that shows a loading indicator while the editor bundle loads.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* <LazyEditor
|
|
58
|
+
* initialContent={markdown}
|
|
59
|
+
* onChange={handleChange}
|
|
60
|
+
* placeholder="Start writing..."
|
|
61
|
+
* />
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export const LazyEditor = memo(function LazyEditor(
|
|
65
|
+
props: LazyEditorProps
|
|
66
|
+
): React.ReactElement {
|
|
67
|
+
return (
|
|
68
|
+
<Suspense fallback={<EditorLoading />}>
|
|
69
|
+
<LazyEditorComponent {...props} />
|
|
70
|
+
</Suspense>
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Re-export EditorEmpty and EditorLoading for convenience
|
|
75
|
+
export { EditorEmpty, EditorLoading } from "./Editor/Editor";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Live Region component styles
|
|
3
|
+
*
|
|
4
|
+
* Styles for the live region component that is visually hidden
|
|
5
|
+
* but accessible to screen readers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* Live region - visually hidden but accessible */
|
|
9
|
+
.wn-live-region {
|
|
10
|
+
position: absolute;
|
|
11
|
+
width: 1px;
|
|
12
|
+
height: 1px;
|
|
13
|
+
padding: 0;
|
|
14
|
+
margin: -1px;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
clip: rect(0, 0, 0, 0);
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
border: 0;
|
|
19
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Live Region component for screen reader announcements
|
|
3
|
+
*
|
|
4
|
+
* A global live region component that announces dynamic content changes
|
|
5
|
+
* to screen readers. Integrates with the useAnnounce hook to provide
|
|
6
|
+
* a centralized announcement system.
|
|
7
|
+
*
|
|
8
|
+
* @module @writenex/astro/client/components/LiveRegion
|
|
9
|
+
* @see {@link useAnnounce} - Hook for triggering announcements
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { AnnouncePoliteness } from "../../hooks/useAnnounce";
|
|
13
|
+
import "./LiveRegion.css";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props for the LiveRegion component
|
|
17
|
+
*/
|
|
18
|
+
export interface LiveRegionProps {
|
|
19
|
+
/** Current message to announce */
|
|
20
|
+
message: string;
|
|
21
|
+
/** Politeness level - polite waits, assertive interrupts */
|
|
22
|
+
politeness?: AnnouncePoliteness;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Live region component for screen reader announcements
|
|
27
|
+
*
|
|
28
|
+
* Renders an ARIA live region that announces messages to screen readers.
|
|
29
|
+
* The component is visually hidden but accessible to assistive technology.
|
|
30
|
+
*
|
|
31
|
+
* @component
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* function App() {
|
|
35
|
+
* const { currentMessage, currentPoliteness } = useAnnounce();
|
|
36
|
+
*
|
|
37
|
+
* return (
|
|
38
|
+
* <>
|
|
39
|
+
* <LiveRegion message={currentMessage} politeness={currentPoliteness} />
|
|
40
|
+
* {/* rest of app *\/}
|
|
41
|
+
* </>
|
|
42
|
+
* );
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function LiveRegion({
|
|
47
|
+
message,
|
|
48
|
+
politeness = "polite",
|
|
49
|
+
}: LiveRegionProps): React.ReactElement {
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
role="status"
|
|
53
|
+
aria-live={politeness}
|
|
54
|
+
aria-atomic="true"
|
|
55
|
+
className="wn-live-region"
|
|
56
|
+
>
|
|
57
|
+
{message}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|