@sutrata/editor 0.3.0 → 0.4.1
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/ai/VoiceConfirmDialog.js +3 -1
- package/dist/ai/VoiceToolbar.js +8 -8
- package/dist/context/DocumentContext.js +30 -24
- package/dist/editor/SelectionMenu.js +3 -1
- package/dist/file/builtin-exporters.js +20 -24
- package/dist/findreplace/FindReplace.js +31 -22
- package/dist/findreplace/scroll-to-match.d.ts +1 -1
- package/dist/findreplace/scroll-to-match.js +12 -2
- package/dist/i18n/useTranslation.js +1 -1
- package/dist/import/AIImportDialog.js +20 -18
- package/dist/locales/bn.json +195 -13
- package/dist/locales/en.json +194 -12
- package/dist/locales/gu.json +195 -13
- package/dist/locales/hi.json +195 -13
- package/dist/locales/kn.json +195 -13
- package/dist/locales/ml.json +195 -13
- package/dist/locales/mr.json +195 -13
- package/dist/locales/or.json +195 -13
- package/dist/locales/pa.json +194 -12
- package/dist/locales/si.json +195 -13
- package/dist/locales/ta.json +195 -13
- package/dist/locales/te.json +195 -13
- package/dist/navigator/SceneNavigator.js +118 -62
- package/dist/navigator/StatisticsDialog.js +4 -2
- package/dist/navigator/scene-list.d.ts +7 -0
- package/dist/navigator/scene-list.js +14 -0
- package/dist/shell/AppBar.js +2 -2
- package/dist/shell/AppShell.js +1 -1
- package/dist/shell/ConfirmDialog.js +4 -2
- package/dist/shell/ElementToolbar.js +4 -2
- package/dist/shell/LanguageStatus.js +3 -1
- package/dist/shell/StatusBar.js +1 -1
- package/dist/shell/VersionHistory.js +4 -2
- package/dist/source/find-highlight-source.d.ts +1 -1
- package/dist/source/find-highlight-source.js +3 -2
- package/dist/styles/screenplay.css +12 -0
- package/dist/titlepage/ExportDialog.js +6 -6
- package/dist/titlepage/TitlePageDialog.js +2 -2
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import React, { useEffect, useState, useCallback } from 'react';
|
|
|
3
3
|
import { TextSelection } from 'prosemirror-state';
|
|
4
4
|
import { useDocument } from '../context/DocumentContext';
|
|
5
5
|
import { useTranslation } from '../i18n/useTranslation';
|
|
6
|
-
import { buildSceneList } from './scene-list';
|
|
6
|
+
import { buildSceneList, buildSectionList } from './scene-list';
|
|
7
7
|
import { getEditorView, getSourceView } from '../editor/editor-bus';
|
|
8
8
|
import { schema } from '../editor/schema';
|
|
9
9
|
import { EditorView as CmView } from '@codemirror/view';
|
|
@@ -178,15 +178,15 @@ function generateSceneMetadata(ai, sceneText) {
|
|
|
178
178
|
* True when the provider can take calls; otherwise points the user at its
|
|
179
179
|
* setup (if it has one) and returns false.
|
|
180
180
|
*/
|
|
181
|
-
async function ensureAIReady(ai, showToast) {
|
|
181
|
+
async function ensureAIReady(ai, showToast, t) {
|
|
182
182
|
if (await ai.isConfigured())
|
|
183
183
|
return true;
|
|
184
184
|
if (ai.openSetup) {
|
|
185
|
-
showToast('
|
|
185
|
+
showToast(t('navigator.toast.aiSetup'), 'info');
|
|
186
186
|
ai.openSetup();
|
|
187
187
|
}
|
|
188
188
|
else {
|
|
189
|
-
showToast(
|
|
189
|
+
showToast(t('ai.unavailable').replace('{name}', ai.displayName), 'warn');
|
|
190
190
|
}
|
|
191
191
|
return false;
|
|
192
192
|
}
|
|
@@ -198,6 +198,7 @@ export function SceneNavigator() {
|
|
|
198
198
|
const ai = useAI();
|
|
199
199
|
const canEdit = useCanEdit();
|
|
200
200
|
const [scenes, setScenes] = useState([]);
|
|
201
|
+
const [sections, setSections] = useState([]);
|
|
201
202
|
const [dragIndex, setDragIndex] = useState(null);
|
|
202
203
|
const [dropIndex, setDropIndex] = useState(null);
|
|
203
204
|
// New States
|
|
@@ -213,6 +214,7 @@ export function SceneNavigator() {
|
|
|
213
214
|
// Rebuild scene list whenever text changes
|
|
214
215
|
useEffect(() => {
|
|
215
216
|
setScenes(buildSceneList(text));
|
|
217
|
+
setSections(buildSectionList(text));
|
|
216
218
|
}, [text]);
|
|
217
219
|
// Numbers that need renumbering (missing/duplicate/out of order; §7.4).
|
|
218
220
|
// Nothing is flagged until at least one scene has a number.
|
|
@@ -222,7 +224,7 @@ export function SceneNavigator() {
|
|
|
222
224
|
setIsProcessing(true);
|
|
223
225
|
setProgressText('Generating synopsis & estimating duration...');
|
|
224
226
|
try {
|
|
225
|
-
if (!ai || !(await ensureAIReady(ai, showToast)))
|
|
227
|
+
if (!ai || !(await ensureAIReady(ai, showToast, t)))
|
|
226
228
|
return;
|
|
227
229
|
const idx = scenes.findIndex(s => s.index === scene.index);
|
|
228
230
|
if (idx === -1)
|
|
@@ -241,10 +243,10 @@ export function SceneNavigator() {
|
|
|
241
243
|
nextText = updateSceneMetadataInText(nextText, scene.textOffset, 'est-duration', duration.trim());
|
|
242
244
|
setText(nextText);
|
|
243
245
|
}
|
|
244
|
-
showToast(
|
|
246
|
+
showToast(t('navigator.toast.generatedOne').replace('{scene}', String(scene.id || scene.index)), 'success');
|
|
245
247
|
}
|
|
246
248
|
catch (e) {
|
|
247
|
-
showToast(
|
|
249
|
+
showToast(t('navigator.toast.genFailed').replace('{error}', e.message || String(e)), 'error');
|
|
248
250
|
}
|
|
249
251
|
finally {
|
|
250
252
|
setIsProcessing(false);
|
|
@@ -255,7 +257,7 @@ export function SceneNavigator() {
|
|
|
255
257
|
setIsProcessing(true);
|
|
256
258
|
setProgressText('Starting batch synopsis & duration generation...');
|
|
257
259
|
try {
|
|
258
|
-
if (!ai || !(await ensureAIReady(ai, showToast)))
|
|
260
|
+
if (!ai || !(await ensureAIReady(ai, showToast, t)))
|
|
259
261
|
return;
|
|
260
262
|
const pmView = getEditorView();
|
|
261
263
|
const cmView = getSourceView();
|
|
@@ -263,7 +265,7 @@ export function SceneNavigator() {
|
|
|
263
265
|
let currentScenes = buildSceneList(currentText);
|
|
264
266
|
const missing = currentScenes.filter(s => !s.synopsis || !s.estDuration);
|
|
265
267
|
if (missing.length === 0) {
|
|
266
|
-
showToast('
|
|
268
|
+
showToast(t('navigator.toast.allHave'), 'info');
|
|
267
269
|
return;
|
|
268
270
|
}
|
|
269
271
|
let current = 0;
|
|
@@ -307,10 +309,10 @@ export function SceneNavigator() {
|
|
|
307
309
|
console.warn(`Failed scene metadata for scene ${scene.index}:`, err);
|
|
308
310
|
}
|
|
309
311
|
}
|
|
310
|
-
showToast(
|
|
312
|
+
showToast(t('navigator.toast.generatedMany').replace('{count}', String(missing.length)), 'success');
|
|
311
313
|
}
|
|
312
314
|
catch (e) {
|
|
313
|
-
showToast(
|
|
315
|
+
showToast(t('navigator.toast.batchFailed').replace('{error}', e.message || String(e)), 'error');
|
|
314
316
|
}
|
|
315
317
|
finally {
|
|
316
318
|
setIsProcessing(false);
|
|
@@ -389,6 +391,47 @@ export function SceneNavigator() {
|
|
|
389
391
|
/* at doc boundary — ignore */
|
|
390
392
|
}
|
|
391
393
|
}, [setNavVisible]);
|
|
394
|
+
const handleSectionJump = useCallback((section) => {
|
|
395
|
+
const view = getEditorView();
|
|
396
|
+
if (!view) {
|
|
397
|
+
const cmView = getSourceView();
|
|
398
|
+
if (cmView) {
|
|
399
|
+
cmView.dispatch({
|
|
400
|
+
selection: { anchor: section.textOffset },
|
|
401
|
+
effects: CmView.scrollIntoView(section.textOffset, { y: 'start' }),
|
|
402
|
+
});
|
|
403
|
+
cmView.focus();
|
|
404
|
+
if (typeof window !== 'undefined' && window.innerWidth <= 640)
|
|
405
|
+
setNavVisible(false);
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
let pos = null;
|
|
411
|
+
let count = 0;
|
|
412
|
+
view.state.doc.forEach((node, offset) => {
|
|
413
|
+
if (pos !== null)
|
|
414
|
+
return;
|
|
415
|
+
if (node.type.name === 'section') {
|
|
416
|
+
count++;
|
|
417
|
+
if (count === section.index)
|
|
418
|
+
pos = offset;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
if (pos === null)
|
|
422
|
+
return;
|
|
423
|
+
view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(pos + 1))));
|
|
424
|
+
view.focus();
|
|
425
|
+
const domNode = view.nodeDOM(pos);
|
|
426
|
+
const el = domNode instanceof Element ? domNode : domNode?.parentElement;
|
|
427
|
+
el?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
428
|
+
if (typeof window !== 'undefined' && window.innerWidth <= 640)
|
|
429
|
+
setNavVisible(false);
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
/* at doc boundary — ignore */
|
|
433
|
+
}
|
|
434
|
+
}, [setNavVisible]);
|
|
392
435
|
const handleDragStart = useCallback((idx) => {
|
|
393
436
|
setDragIndex(idx);
|
|
394
437
|
}, []);
|
|
@@ -484,12 +527,12 @@ export function SceneNavigator() {
|
|
|
484
527
|
const ids = next.map((n, i) => (n === list[i].id ? undefined : n));
|
|
485
528
|
const changed = ids.filter(n => n !== undefined).length;
|
|
486
529
|
if (changed === 0) {
|
|
487
|
-
showToast('
|
|
530
|
+
showToast(t('navigator.toast.inOrder'), 'info');
|
|
488
531
|
return;
|
|
489
532
|
}
|
|
490
533
|
applyTextEdit(setSceneIdsInText(current, ids), setText);
|
|
491
|
-
showToast(
|
|
492
|
-
}, [liveText, setText, showToast]);
|
|
534
|
+
showToast(t(changed === 1 ? 'navigator.toast.renumberedOne' : 'navigator.toast.renumberedMany').replace('{count}', String(changed)), 'success');
|
|
535
|
+
}, [liveText, setText, showToast, t]);
|
|
493
536
|
const handleOmitToggle = useCallback((scene) => {
|
|
494
537
|
const current = liveText();
|
|
495
538
|
const idx = buildSceneList(current).findIndex(s => s.index === scene.index);
|
|
@@ -498,7 +541,13 @@ export function SceneNavigator() {
|
|
|
498
541
|
const omitted = scene.status.toLowerCase() === 'omitted';
|
|
499
542
|
applyTextEdit(omitted ? restoreSceneInText(current, idx) : omitSceneInText(current, idx), setText);
|
|
500
543
|
}, [liveText, setText]);
|
|
501
|
-
|
|
544
|
+
const renderSection = (section) => (_jsx("div", { role: "button", tabIndex: 0, className: "cs-nav-item cs-nav-section-anchor", "aria-label": t('navigator.jumpSection').replace('{heading}', section.heading), onClick: () => handleSectionJump(section), onKeyDown: (e) => {
|
|
545
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
546
|
+
e.preventDefault();
|
|
547
|
+
handleSectionJump(section);
|
|
548
|
+
}
|
|
549
|
+
}, children: _jsx("div", { className: "cs-nav-scene-info", children: _jsx("div", { className: "cs-nav-heading", children: section.heading || '—' }) }) }, `section:${section.index}`));
|
|
550
|
+
return (_jsxs("div", { className: "cs-navigator", role: "navigation", "aria-label": t('navigator.aria'), children: [_jsxs("div", { className: "cs-nav-mobile-bar", children: [_jsx("span", { className: "cs-nav-mobile-title", children: t('navigator.title') }), _jsx("button", { type: "button", className: "cs-nav-mobile-close", "aria-label": t('navigator.close'), onClick: () => setNavVisible(false), children: _jsx(CloseIcon, { size: 18 }) })] }), _jsxs("div", { className: "cs-nav-list", children: [_jsx("button", { type: "button", className: "cs-nav-section-header cs-nav-frontmatter-btn", onClick: handleFrontmatter, title: t('navigator.frontmatter'), children: _jsx("span", { className: "cs-nav-title", children: t('navigator.frontmatter') }) }), _jsxs("div", { className: "cs-nav-section-header cs-nav-scenes-header", style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("span", { className: "cs-nav-title", children: t('navigator.title') }), _jsx("span", { className: "cs-nav-count", children: scenes.length })] }), _jsxs("div", { style: { display: 'flex', gap: '4px', alignItems: 'center' }, onClick: e => e.stopPropagation(), children: [_jsx("button", { type: "button", className: "cs-nav-action-btn", onClick: () => setShowNavSynopsis(!showNavSynopsis), title: showNavSynopsis ? t('navigator.hideSynopsis') : t('navigator.showSynopsis'), "aria-label": showNavSynopsis ? t('navigator.hideSynopsis') : t('navigator.showSynopsis'), children: showNavSynopsis ? _jsx(EyeIcon, { size: 14 }) : _jsx(EyeOffIcon, { size: 14 }) }), ai && (_jsx("button", { type: "button", className: "cs-nav-action-btn", onClick: handleGenerateAllSceneMetadata, disabled: isProcessing, title: t('navigator.genAll'), "aria-label": t('navigator.genAll'), style: { opacity: isProcessing ? 0.5 : 1 }, children: _jsx(SparklesIcon, { size: 14 }) })), canEdit && (_jsx("button", { type: "button", className: "cs-nav-action-btn", onClick: handleRenumber, title: t('navigator.renumber'), "aria-label": t('navigator.renumber'), children: _jsx(RenumberIcon, { size: 14 }) }))] })] }), isProcessing && progressText && (_jsxs("div", { className: "cs-nav-progress", style: {
|
|
502
551
|
display: 'flex',
|
|
503
552
|
alignItems: 'center',
|
|
504
553
|
gap: '8px',
|
|
@@ -509,58 +558,65 @@ export function SceneNavigator() {
|
|
|
509
558
|
borderBottom: '1px solid var(--cs-ui-border-light, #e2e0d6)',
|
|
510
559
|
fontWeight: 500,
|
|
511
560
|
}, children: [_jsx("span", { className: "cs-spinner", style: { width: '10px', height: '10px', borderWidth: '1.5px' } }), _jsx("span", { children: progressText })] })), scenes.map((scene, idx) => {
|
|
561
|
+
// Sections that start before this scene (and after the previous one) render as anchors above it.
|
|
562
|
+
const prevOffset = idx === 0 ? -1 : scenes[idx - 1].textOffset;
|
|
563
|
+
const leading = sections.filter(sec => sec.textOffset > prevOffset && sec.textOffset < scene.textOffset);
|
|
512
564
|
const issue = numberIssues[idx] ?? null;
|
|
513
565
|
const omitted = scene.status.toLowerCase() === 'omitted';
|
|
514
566
|
// The id is the scene number, so a missing id is one note, not two.
|
|
515
567
|
const note = issue ? issueLabel(issue) : scene.id ? null : t('navigator.missingId');
|
|
516
568
|
const notes = note ? [note] : [];
|
|
517
|
-
return (_jsxs(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
569
|
+
return (_jsxs(React.Fragment, { children: [leading.map(renderSection), _jsxs("div", {
|
|
570
|
+
// Ids can repeat until the writer renumbers (§7.4), so the index keeps keys unique.
|
|
571
|
+
role: "button", tabIndex: 0, "aria-label": t('navigator.jumpScene').replace('{heading}', scene.heading) + (notes.length ? ` (${notes.join('; ')})` : ''), onKeyDown: (e) => {
|
|
572
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
573
|
+
e.preventDefault();
|
|
574
|
+
handleJump(scene);
|
|
575
|
+
}
|
|
576
|
+
}, className: [
|
|
577
|
+
'cs-nav-item',
|
|
578
|
+
// No {#id}: comments, scene-level diff, and page locking can't anchor to it.
|
|
579
|
+
scene.id ? '' : 'cs-nav-scene-missing-id',
|
|
580
|
+
issue ? 'cs-nav-scene-number-issue' : '',
|
|
581
|
+
omitted ? 'cs-nav-scene-omitted' : '',
|
|
582
|
+
dragIndex === idx ? 'cs-nav-dragging' : '',
|
|
583
|
+
dropIndex === idx ? 'cs-nav-drop-target' : '',
|
|
584
|
+
]
|
|
585
|
+
.filter(Boolean)
|
|
586
|
+
.join(' '), draggable: canEdit, onClick: () => handleJump(scene), onDragStart: () => handleDragStart(idx), onDragOver: (e) => handleDragOver(e, idx), onDrop: (e) => handleDrop(e, idx), onDragEnd: () => {
|
|
587
|
+
setDragIndex(null);
|
|
588
|
+
setDropIndex(null);
|
|
589
|
+
}, children: [_jsxs("div", { className: "cs-nav-scene-info", children: [_jsx("div", { className: "cs-nav-heading", children: scene.heading }), showNavSynopsis && (_jsxs("div", { className: "cs-nav-synopsis-row", style: { marginTop: '4px' }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: !canEdit ? (scene.synopsis ? _jsx("div", { className: "cs-nav-scene-synopsis", children: scene.synopsis }) : null) : editingSceneIdx === idx ? (_jsx("input", { type: "text", className: "cs-nav-synopsis-input", value: editVal, onChange: e => setEditVal(e.target.value), onBlur: () => commitSynopsis(idx, scene), onKeyDown: e => {
|
|
590
|
+
if (e.key === 'Enter')
|
|
591
|
+
commitSynopsis(idx, scene);
|
|
592
|
+
if (e.key === 'Escape')
|
|
593
|
+
setEditingSceneIdx(null);
|
|
594
|
+
}, autoFocus: true, onClick: e => e.stopPropagation() })) : scene.synopsis ? (_jsx("div", { className: "cs-nav-scene-synopsis", onClick: e => {
|
|
595
|
+
e.stopPropagation();
|
|
596
|
+
setEditingSceneIdx(idx);
|
|
597
|
+
setEditVal(scene.synopsis);
|
|
598
|
+
}, title: t('navigator.editSynopsis'), children: scene.synopsis })) : (_jsx("div", { className: "cs-nav-scene-synopsis cs-nav-synopsis-placeholder", onClick: e => {
|
|
599
|
+
e.stopPropagation();
|
|
600
|
+
setEditingSceneIdx(idx);
|
|
601
|
+
setEditVal('');
|
|
602
|
+
}, role: "button", tabIndex: 0, "aria-label": t('navigator.addSynopsis'), onKeyDown: e => {
|
|
603
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
604
|
+
e.stopPropagation();
|
|
605
|
+
e.preventDefault();
|
|
606
|
+
setEditingSceneIdx(idx);
|
|
607
|
+
setEditVal('');
|
|
608
|
+
}
|
|
609
|
+
}, children: "+ Synopsis" })) }), ai && _jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); void handleSceneMetadata(scene); }, disabled: isProcessing, title: t('navigator.genOne'), "aria-label": t('navigator.genOne'), className: "cs-nav-action-btn cs-nav-synopsis-ai-btn", style: { color: 'var(--cs-ui-accent, #c4760a)', ...(isProcessing ? { opacity: 0.5 } : {}) }, children: _jsx(SparklesIcon, { size: 12 }) })] })), (scene.status || (showNavSynopsis && scene.estDuration)) && (_jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center', marginTop: '6px' }, children: [scene.status && (_jsx("span", { className: `cs-nav-status cs-status-${scene.status
|
|
610
|
+
.toLowerCase()
|
|
611
|
+
.replace(/\s+/g, '-')}`, children: scene.status })), showNavSynopsis && scene.estDuration && (_jsxs("span", { className: "cs-nav-status", title: t('navigator.duration'), style: {
|
|
612
|
+
background: 'rgba(217, 119, 6, 0.1)',
|
|
613
|
+
color: 'var(--cs-ui-accent, #c4760a)',
|
|
614
|
+
border: '1px solid rgba(217, 119, 6, 0.25)',
|
|
615
|
+
fontWeight: 600,
|
|
616
|
+
}, children: ["\u23F1 ", scene.estDuration] }))] }))] }), _jsxs("div", { className: "cs-nav-scene-side", children: [scene.id ? (_jsx("div", { className: `cs-nav-scene-num${issue ? ' cs-nav-num-issue' : ''}`, title: issue ? issueLabel(issue) : undefined, children: scene.id })) : (_jsx("div", { className: "cs-nav-missing-id-icon", title: issue ? issueLabel(issue) : t('navigator.missingId'), "aria-hidden": "true", children: "\u26A0" })), canEdit && (_jsx("button", { type: "button", className: "cs-nav-action-btn cs-nav-omit-btn", title: omitted ? t('navigator.restoreScene') : t('navigator.omitScene'), "aria-label": `${omitted ? t('navigator.restoreScene') : t('navigator.omitScene')}: ${scene.heading}`, onClick: e => { e.stopPropagation(); handleOmitToggle(scene); }, children: omitted ? _jsx(RestoreIcon, { size: 12 }) : _jsx(OmitIcon, { size: 12 }) }))] })] })] }, `${idx}:${scene.id ?? ''}`));
|
|
617
|
+
}), sections
|
|
618
|
+
.filter(sec => scenes.length === 0 || sec.textOffset > scenes[scenes.length - 1].textOffset)
|
|
619
|
+
.map(renderSection), scenes.length === 0 && sections.length === 0 && (_jsx("div", { className: "cs-nav-empty", children: t('navigator.empty') })), _jsx("div", { style: { marginTop: '20px', padding: '0 8px 10px', borderTop: '1px solid #eee', paddingTop: '15px' }, children: _jsxs("button", { type: "button", className: "cs-nav-section-header cs-nav-frontmatter-btn", onClick: () => setStatsOpen(true), style: { width: '100%', textAlign: 'left', display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }, children: [_jsx(StatsIcon, { size: 14 }), " ", _jsx("span", { className: "cs-nav-title", children: t('navigator.stats') })] }) })] }), statsOpen && _jsx(StatisticsDialog, { onClose: () => setStatsOpen(false) })] }));
|
|
564
620
|
}
|
|
565
621
|
/**
|
|
566
622
|
* Reorder scene blocks in the source text.
|
|
@@ -4,8 +4,10 @@ import { extractWorkflowData } from '../file/workflow-reports';
|
|
|
4
4
|
import { getEditorView } from '../editor/editor-bus';
|
|
5
5
|
import { schema } from '../editor/schema';
|
|
6
6
|
import { useCanEdit } from '../extensions/session';
|
|
7
|
+
import { useTranslation } from '../i18n/useTranslation';
|
|
7
8
|
export function StatisticsDialog({ onClose }) {
|
|
8
9
|
const { ast, text, setText } = useDocument();
|
|
10
|
+
const { t } = useTranslation();
|
|
9
11
|
const canEdit = useCanEdit();
|
|
10
12
|
const { scenes, characters } = extractWorkflowData(ast);
|
|
11
13
|
const handleUpdateActor = (charName, actorName) => {
|
|
@@ -82,13 +84,13 @@ export function StatisticsDialog({ onClose }) {
|
|
|
82
84
|
return (_jsx("div", { className: "cs-dialog-overlay", role: "presentation", onClick: e => {
|
|
83
85
|
if (e.target === e.currentTarget)
|
|
84
86
|
onClose();
|
|
85
|
-
}, children: _jsxs("div", { className: "cs-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "cs-stats-dialog-title", style: { maxWidth: '640px', width: '92%', maxHeight: '85vh', display: 'flex', flexDirection: 'column' }, children: [_jsxs("div", { className: "cs-panel-header", children: [_jsx("span", { id: "cs-stats-dialog-title", className: "cs-panel-title", children:
|
|
87
|
+
}, children: _jsxs("div", { className: "cs-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "cs-stats-dialog-title", style: { maxWidth: '640px', width: '92%', maxHeight: '85vh', display: 'flex', flexDirection: 'column' }, children: [_jsxs("div", { className: "cs-panel-header", children: [_jsx("span", { id: "cs-stats-dialog-title", className: "cs-panel-title", children: t('stats.title') }), _jsx("button", { className: "cs-panel-close", onClick: onClose, "aria-label": t('common.close'), children: "\u00D7" })] }), _jsxs("div", { className: "cs-panel-body", style: { overflowY: 'auto', flex: 1, padding: '20px' }, children: [_jsxs("div", { className: "cs-stats-summary-grid", children: [_jsxs("div", { className: "cs-stats-card", children: [_jsx("div", { className: "cs-stats-card-val", children: scenes.length }), _jsx("div", { className: "cs-stats-card-lbl", children: t('stats.scenes') })] }), _jsxs("div", { className: "cs-stats-card", children: [_jsx("div", { className: "cs-stats-card-val", children: wordCount.toLocaleString() }), _jsx("div", { className: "cs-stats-card-lbl", children: t('stats.words') })] }), _jsxs("div", { className: "cs-stats-card", children: [_jsx("div", { className: "cs-stats-card-val", children: totalDurationLabel }), _jsx("div", { className: "cs-stats-card-lbl", children: t('stats.duration') })] })] }), _jsxs("div", { className: "cs-stats-section-hdr", children: [t('stats.cast'), " (", characters.length, ")"] }), characters.length === 0 ? (_jsx("div", { style: { color: 'var(--cs-ui-text-light-muted, #8c897f)', fontStyle: 'italic', fontSize: '12px' }, children: t('stats.noCharacters') })) : (_jsxs("table", { className: "cs-stats-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { style: { textAlign: 'left', width: '35%' }, children: t('stats.character') }), _jsx("th", { style: { textAlign: 'left' }, children: t('stats.actor') }), _jsx("th", { style: { textAlign: 'center', width: '15%' }, children: t('stats.scenes') })] }) }), _jsx("tbody", { children: characters.map((c, i) => (_jsxs("tr", { children: [_jsx("td", { style: { fontWeight: '600' }, children: c.name }), _jsx("td", { children: _jsx("input", { type: "text", className: "cs-stats-actor-input", value: c.actor, onChange: (e) => handleUpdateActor(c.name, e.target.value), readOnly: !canEdit, placeholder: t('stats.assignActor'), "aria-label": t('stats.actorFor').replace('{name}', c.name) }) }), _jsx("td", { style: { textAlign: 'center', fontWeight: '500' }, children: c.sceneCount })] }, i))) })] }))] }), _jsx("div", { style: {
|
|
86
88
|
borderTop: '1px solid var(--cs-ui-border-light-subtle, #eeece4)',
|
|
87
89
|
display: 'flex',
|
|
88
90
|
justifyContent: 'flex-end',
|
|
89
91
|
padding: '12px 20px',
|
|
90
92
|
background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)',
|
|
91
|
-
}, children: _jsx("button", { type: "button", className: "cs-confirm-btn-primary", onClick: onClose, children:
|
|
93
|
+
}, children: _jsx("button", { type: "button", className: "cs-confirm-btn-primary", onClick: onClose, children: t('common.close') }) })] }) }));
|
|
92
94
|
}
|
|
93
95
|
function updateActorInText(text, charName, actorName) {
|
|
94
96
|
const lines = text.split('\n');
|
|
@@ -14,3 +14,10 @@ export interface SceneEntry {
|
|
|
14
14
|
* Scenes are identified by scene heading lines (## prefix).
|
|
15
15
|
*/
|
|
16
16
|
export declare function buildSceneList(text: string): SceneEntry[];
|
|
17
|
+
export interface SectionEntry {
|
|
18
|
+
heading: string;
|
|
19
|
+
index: number;
|
|
20
|
+
textOffset: number;
|
|
21
|
+
}
|
|
22
|
+
/** Derive the `# ` section headings (acts, sequences, …) from Sutra source text. */
|
|
23
|
+
export declare function buildSectionList(text: string): SectionEntry[];
|
|
@@ -68,3 +68,17 @@ export function buildSceneList(text) {
|
|
|
68
68
|
}
|
|
69
69
|
return scenes;
|
|
70
70
|
}
|
|
71
|
+
/** Derive the `# ` section headings (acts, sequences, …) from Sutra source text. */
|
|
72
|
+
export function buildSectionList(text) {
|
|
73
|
+
const sections = [];
|
|
74
|
+
let offset = 0;
|
|
75
|
+
for (const line of text.split('\n')) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (trimmed === '#' || trimmed.startsWith('# ') || trimmed.startsWith('#\t')) {
|
|
78
|
+
const { body } = splitAttributeBlock(trimmed.slice(1).trim());
|
|
79
|
+
sections.push({ heading: body.trim(), index: sections.length + 1, textOffset: offset });
|
|
80
|
+
}
|
|
81
|
+
offset += line.length + 1;
|
|
82
|
+
}
|
|
83
|
+
return sections;
|
|
84
|
+
}
|
package/dist/shell/AppBar.js
CHANGED
|
@@ -48,8 +48,8 @@ export function AppBar() {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
catch (err) {
|
|
51
|
-
showToast(err instanceof Error ? err.message : '
|
|
51
|
+
showToast(err instanceof Error ? err.message : t('appbar.importFailed'), 'error');
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
|
-
return (_jsxs("header", { className: "cs-app-bar", role: "banner", "aria-label": "Application header", children: [_jsxs("div", { className: "cs-ab-group cs-ab-left", children: [_jsxs("div", { className: "cs-ab-brand", title: "Sutrata Multilingual Screenplay Editor", children: [_jsx(SutrataLogo, { size: 20, showText: false }), _jsx("span", { className: "cs-ab-brand-title", children: "Sutrata" })] }), _jsx("span", { className: "cs-ab-sep cs-ab-desktop-only" }), _jsx(AbBtn, { label: t('appbar.navigator'), shortcut: "Ctrl+Shift+B", active: navVisible, onClick: () => setNavVisible(!navVisible), children: _jsx(NavigatorIcon, { size: 18 }) }), canEdit && (_jsx("span", { className: "cs-ab-desktop-only", children: _jsx(AbBtn, { label: t('appbar.titlePage'), onClick: openTitlePageForm, children: _jsx(TitlePageIcon, { size: 18 }) }) })), _jsxs("div", { className: "cs-ab-file-pill", title: name, children: [_jsx("span", { className: "cs-ab-file-name", children: name }), isDirty && _jsx("span", { className: "cs-ab-dirty", title: "Unsaved edits" })] })] }), _jsx("span", { className: "cs-ab-spacer cs-ab-desktop-only" }), _jsxs("div", { className: "cs-ab-group cs-ab-desktop-group", children: [_jsx(AbBtn, { label: mode === 'formatted' ? t('appbar.switchToSource') : t('appbar.switchToFormatted'), shortcut: "Ctrl+Shift+E", onClick: () => setMode(nextMode), children: mode === 'formatted' ? _jsx(SourceIcon, { size: 18 }) : _jsx(FormattedIcon, { size: 18 }) }), canEdit && (_jsx(AbBtn, { label: ribbonVisible ? t('appbar.hideToolbar') : t('appbar.showToolbar'), shortcut: "Ctrl+Shift+H", active: ribbonVisible, onClick: () => setRibbonVisible(!ribbonVisible), children: _jsx(RibbonIcon, { size: 18 }) })), speech && (_jsx(AbBtn, { label: "Voice Dictation", shortcut: "Ctrl+Shift+V", active: voiceActive, onClick: () => setVoiceActive(!voiceActive), children: _jsx(MicIcon, { size: 18 }) })), _jsx(AbBtn, { label: metadataVisible ? 'Hide Metadata' : 'Show Metadata', active: metadataVisible, onClick: () => setMetadataVisible(!metadataVisible), children: _jsx("span", { style: { fontWeight: 700, fontSize: '13px' }, children: "&" }) })] }), _jsx("span", { className: "cs-ab-spacer cs-ab-desktop-only" }), _jsxs("div", { className: "cs-ab-group cs-ab-desktop-group", children: [canEdit && (_jsxs(_Fragment, { children: [localFiles && (_jsxs(_Fragment, { children: [_jsx(AbBtn, { label: t('appbar.new'), onClick: handleNew, children: _jsx(NewIcon, { size: 18 }) }), _jsx(AbBtn, { label: t('appbar.open'), shortcut: "Ctrl+O", onClick: () => { void openFile(); }, children: _jsx(OpenIcon, { size: 18 }) })] })), _jsx(AbBtn, { label: t('appbar.importDocx'), onClick: () => { void handleImportDocx(); }, children: _jsx(ImportDocxIcon, { size: 18 }) }), ai && (_jsx(AbBtn, { label: "Import with AI", onClick: () => setAiImportOpen(true), children: _jsx(SparklesIcon, { size: 18 }) })), _jsx("span", { className: "cs-ab-sep" }), _jsx(AbBtn, { label: t('appbar.save'), shortcut: "Ctrl+S", variant: "primary", onClick: () => { void saveFile(); }, children: _jsx(SaveIcon, { size: 17 }) }), localFiles && (_jsx(AbBtn, { label: t('appbar.saveAs'), shortcut: "Ctrl+Shift+S", onClick: () => { void saveFileAs(); }, children: _jsx(SaveAsIcon, { size: 17 }) })), _jsx("span", { className: "cs-ab-sep" })] })), _jsx(AbBtn, { label: t('appbar.export'), variant: "secondary", active: exportVisible, onClick: () => setExportVisible(!exportVisible), children: _jsx(ExportIcon, { size: 18 }) }), canEdit && (_jsx(AbBtn, { label: t('appbar.style'), active: styleVisible, onClick: () => setStyleVisible(!styleVisible), children: _jsx(StyleIcon, { size: 18 }) })), (appbarCommands.length > 0 || appbarPanels.length > 0) && _jsx("span", { className: "cs-ab-sep" }), appbarCommands.map(c => (_jsx(AbBtn, { label: c.label, shortcut: displayShortcut(c.shortcut ?? c.shortcutHint), onClick: () => c.run(commandContext()), children: c.icon ? c.icon(18) : _jsx("span", { className: "cs-ab-text-label", children: c.label }) }, c.id))), appbarPanels.map(p => (_jsx("span", { className: "cs-ab-panel", "aria-label": p.title, children: p.render(panelContext) }, p.id))), _jsx(AbBtn, { label: t('settings.title'), active: settingsVisible, onClick: () => setSettingsVisible(!settingsVisible), children: _jsx(SettingsIcon, { size: 18 }) })] }), _jsxs("div", { className: "cs-ab-group cs-ab-mobile-controls", children: [appbarPanels.map(p => (_jsx("span", { className: "cs-ab-panel", "aria-label": p.title, children: p.render(panelContext) }, p.id))), canEdit && (_jsx(AbBtn, { label: t('appbar.save'), variant: isDirty ? 'primary' : undefined, onClick: () => { void saveFile(); }, children: _jsx(SaveIcon, { size: 17 }) })), _jsx(AbBtn, { label: "Menu", active: mobileMenuOpen, onClick: () => setMobileMenuOpen(!mobileMenuOpen), children: mobileMenuOpen ? _jsx(CloseIcon, { size: 18 }) : _jsx(MenuIcon, { size: 18 }) })] }), mobileMenuOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "cs-mobile-menu-backdrop", "aria-hidden": "true", onClick: () => setMobileMenuOpen(false) }), _jsxs("div", { className: "cs-mobile-menu-drawer", role: "dialog", "aria-modal": "true", "aria-label": "Application Menu", children: [_jsxs("div", { className: "cs-mm-header", children: [_jsxs("div", { className: "cs-mm-brand", children: [_jsx(SutrataLogo, { size: 22, showText: false }), _jsx("span", { children: "Sutrata" })] }), _jsx("button", { type: "button", className: "cs-mm-close", "aria-label": "Close menu", onClick: () => setMobileMenuOpen(false), children: _jsx(CloseIcon, { size: 18 }) })] }), _jsxs("div", { className: "cs-mm-file-info", children: [_jsx("span", { className: "cs-mm-file-name", children: name }), isDirty && _jsx("span", { className: "cs-ab-dirty", title: "Unsaved edits" })] }), _jsxs("div", { className: "cs-mm-section", children: [_jsx("div", { className: "cs-mm-section-title", children: "Views & Tools" }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setMode(nextMode); setMobileMenuOpen(false); }, children: [mode === 'formatted' ? _jsx(SourceIcon, { size: 18 }) : _jsx(FormattedIcon, { size: 18 }), _jsx("span", { children: mode === 'formatted' ? t('appbar.switchToSource') : t('appbar.switchToFormatted') })] }), _jsxs("button", { type: "button", className: `cs-mm-item${navVisible ? ' cs-mm-active' : ''}`, onClick: () => { setNavVisible(!navVisible); setMobileMenuOpen(false); }, children: [_jsx(NavigatorIcon, { size: 18 }), _jsx("span", { children: t('appbar.navigator') })] }), canEdit && _jsxs("button", { type: "button", className: `cs-mm-item${ribbonVisible ? ' cs-mm-active' : ''}`, onClick: () => { setRibbonVisible(!ribbonVisible); setMobileMenuOpen(false); }, children: [_jsx(RibbonIcon, { size: 18 }), _jsx("span", { children: ribbonVisible ? t('appbar.hideToolbar') : t('appbar.showToolbar') })] }), speech && _jsxs("button", { type: "button", className: `cs-mm-item${voiceActive ? ' cs-mm-active' : ''}`, onClick: () => { setVoiceActive(!voiceActive); setMobileMenuOpen(false); }, children: [_jsx(MicIcon, { size: 18 }), _jsx("span", { children: "Voice Dictation" })] }), _jsxs("button", { type: "button", className: `cs-mm-item${metadataVisible ? ' cs-mm-active' : ''}`, onClick: () => { setMetadataVisible(!metadataVisible); setMobileMenuOpen(false); }, children: [_jsx("span", { style: { fontWeight: 700, width: 18, textAlign: 'center' }, children: "&" }), _jsx("span", { children: metadataVisible ? 'Hide Metadata' : 'Show Metadata' })] })] }), canEdit && _jsxs("div", { className: "cs-mm-section", children: [_jsx("div", { className: "cs-mm-section-title", children: "File Operations" }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { openTitlePageForm(); setMobileMenuOpen(false); }, children: [_jsx(TitlePageIcon, { size: 18 }), _jsx("span", { children: t('appbar.titlePage') })] }), localFiles && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { handleNew(); setMobileMenuOpen(false); }, children: [_jsx(NewIcon, { size: 18 }), _jsx("span", { children: t('appbar.new') })] }), localFiles && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { void openFile(); setMobileMenuOpen(false); }, children: [_jsx(OpenIcon, { size: 18 }), _jsx("span", { children: t('appbar.open') })] }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { void handleImportDocx(); setMobileMenuOpen(false); }, children: [_jsx(ImportDocxIcon, { size: 18 }), _jsx("span", { children: t('appbar.importDocx') })] }), ai && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setAiImportOpen(true); setMobileMenuOpen(false); }, children: [_jsx(SparklesIcon, { size: 18 }), _jsx("span", { children: "Import with AI" })] }), _jsxs("button", { type: "button", className: "cs-mm-item cs-mm-primary", onClick: () => { void saveFile(); setMobileMenuOpen(false); }, children: [_jsx(SaveIcon, { size: 18 }), _jsx("span", { children: t('appbar.save') })] }), localFiles && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { void saveFileAs(); setMobileMenuOpen(false); }, children: [_jsx(SaveAsIcon, { size: 18 }), _jsx("span", { children: t('appbar.saveAs') })] }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setExportVisible(true); setMobileMenuOpen(false); }, children: [_jsx(ExportIcon, { size: 18 }), _jsx("span", { children: t('appbar.export') })] })] }), !canEdit && _jsx("div", { className: "cs-mm-section", children: _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setExportVisible(true); setMobileMenuOpen(false); }, children: [_jsx(ExportIcon, { size: 18 }), _jsx("span", { children: t('appbar.export') })] }) }), appbarCommands.length > 0 && _jsx("div", { className: "cs-mm-section", children: appbarCommands.map(c => (_jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { c.run(commandContext()); setMobileMenuOpen(false); }, children: [c.icon?.(18), _jsx("span", { children: c.label })] }, c.id))) }), _jsxs("div", { className: "cs-mm-section", children: [_jsx("div", { className: "cs-mm-section-title", children: "Preferences" }), canEdit && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setStyleVisible(true); setMobileMenuOpen(false); }, children: [_jsx(StyleIcon, { size: 18 }), _jsx("span", { children: t('appbar.style') })] }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setSettingsVisible(true); setMobileMenuOpen(false); }, children: [_jsx(SettingsIcon, { size: 18 }), _jsx("span", { children: t('settings.title') })] })] })] })] })), aiImportOpen && _jsx(AIImportDialog, { onClose: () => setAiImportOpen(false) })] }));
|
|
54
|
+
return (_jsxs("header", { className: "cs-app-bar", role: "banner", "aria-label": t('appbar.header'), children: [_jsxs("div", { className: "cs-ab-group cs-ab-left", children: [_jsxs("div", { className: "cs-ab-brand", title: t('appbar.brandTitle'), children: [_jsx(SutrataLogo, { size: 20, showText: false }), _jsx("span", { className: "cs-ab-brand-title", children: "Sutrata" })] }), _jsx("span", { className: "cs-ab-sep cs-ab-desktop-only" }), _jsx(AbBtn, { label: t('appbar.navigator'), shortcut: "Ctrl+Shift+B", active: navVisible, onClick: () => setNavVisible(!navVisible), children: _jsx(NavigatorIcon, { size: 18 }) }), canEdit && (_jsx("span", { className: "cs-ab-desktop-only", children: _jsx(AbBtn, { label: t('appbar.titlePage'), onClick: openTitlePageForm, children: _jsx(TitlePageIcon, { size: 18 }) }) })), _jsxs("div", { className: "cs-ab-file-pill", title: name, children: [_jsx("span", { className: "cs-ab-file-name", children: name }), isDirty && _jsx("span", { className: "cs-ab-dirty", title: t('appbar.unsaved') })] })] }), _jsx("span", { className: "cs-ab-spacer cs-ab-desktop-only" }), _jsxs("div", { className: "cs-ab-group cs-ab-desktop-group", children: [_jsx(AbBtn, { label: mode === 'formatted' ? t('appbar.switchToSource') : t('appbar.switchToFormatted'), shortcut: "Ctrl+Shift+E", onClick: () => setMode(nextMode), children: mode === 'formatted' ? _jsx(SourceIcon, { size: 18 }) : _jsx(FormattedIcon, { size: 18 }) }), canEdit && (_jsx(AbBtn, { label: ribbonVisible ? t('appbar.hideToolbar') : t('appbar.showToolbar'), shortcut: "Ctrl+Shift+H", active: ribbonVisible, onClick: () => setRibbonVisible(!ribbonVisible), children: _jsx(RibbonIcon, { size: 18 }) })), speech && (_jsx(AbBtn, { label: t('appbar.voice'), shortcut: "Ctrl+Shift+V", active: voiceActive, onClick: () => setVoiceActive(!voiceActive), children: _jsx(MicIcon, { size: 18 }) })), _jsx(AbBtn, { label: metadataVisible ? t('appbar.hideMetadata') : t('appbar.showMetadata'), active: metadataVisible, onClick: () => setMetadataVisible(!metadataVisible), children: _jsx("span", { style: { fontWeight: 700, fontSize: '13px' }, children: "&" }) })] }), _jsx("span", { className: "cs-ab-spacer cs-ab-desktop-only" }), _jsxs("div", { className: "cs-ab-group cs-ab-desktop-group", children: [canEdit && (_jsxs(_Fragment, { children: [localFiles && (_jsxs(_Fragment, { children: [_jsx(AbBtn, { label: t('appbar.new'), onClick: handleNew, children: _jsx(NewIcon, { size: 18 }) }), _jsx(AbBtn, { label: t('appbar.open'), shortcut: "Ctrl+O", onClick: () => { void openFile(); }, children: _jsx(OpenIcon, { size: 18 }) })] })), _jsx(AbBtn, { label: t('appbar.importDocx'), onClick: () => { void handleImportDocx(); }, children: _jsx(ImportDocxIcon, { size: 18 }) }), ai && (_jsx(AbBtn, { label: t('appbar.importAI'), onClick: () => setAiImportOpen(true), children: _jsx(SparklesIcon, { size: 18 }) })), _jsx("span", { className: "cs-ab-sep" }), _jsx(AbBtn, { label: t('appbar.save'), shortcut: "Ctrl+S", variant: "primary", onClick: () => { void saveFile(); }, children: _jsx(SaveIcon, { size: 17 }) }), localFiles && (_jsx(AbBtn, { label: t('appbar.saveAs'), shortcut: "Ctrl+Shift+S", onClick: () => { void saveFileAs(); }, children: _jsx(SaveAsIcon, { size: 17 }) })), _jsx("span", { className: "cs-ab-sep" })] })), _jsx(AbBtn, { label: t('appbar.export'), variant: "secondary", active: exportVisible, onClick: () => setExportVisible(!exportVisible), children: _jsx(ExportIcon, { size: 18 }) }), canEdit && (_jsx(AbBtn, { label: t('appbar.style'), active: styleVisible, onClick: () => setStyleVisible(!styleVisible), children: _jsx(StyleIcon, { size: 18 }) })), (appbarCommands.length > 0 || appbarPanels.length > 0) && _jsx("span", { className: "cs-ab-sep" }), appbarCommands.map(c => (_jsx(AbBtn, { label: c.label, shortcut: displayShortcut(c.shortcut ?? c.shortcutHint), onClick: () => c.run(commandContext()), children: c.icon ? c.icon(18) : _jsx("span", { className: "cs-ab-text-label", children: c.label }) }, c.id))), appbarPanels.map(p => (_jsx("span", { className: "cs-ab-panel", "aria-label": p.title, children: p.render(panelContext) }, p.id))), _jsx(AbBtn, { label: t('settings.title'), active: settingsVisible, onClick: () => setSettingsVisible(!settingsVisible), children: _jsx(SettingsIcon, { size: 18 }) })] }), _jsxs("div", { className: "cs-ab-group cs-ab-mobile-controls", children: [appbarPanels.map(p => (_jsx("span", { className: "cs-ab-panel", "aria-label": p.title, children: p.render(panelContext) }, p.id))), canEdit && (_jsx(AbBtn, { label: t('appbar.save'), variant: isDirty ? 'primary' : undefined, onClick: () => { void saveFile(); }, children: _jsx(SaveIcon, { size: 17 }) })), _jsx(AbBtn, { label: t('appbar.menu'), active: mobileMenuOpen, onClick: () => setMobileMenuOpen(!mobileMenuOpen), children: mobileMenuOpen ? _jsx(CloseIcon, { size: 18 }) : _jsx(MenuIcon, { size: 18 }) })] }), mobileMenuOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "cs-mobile-menu-backdrop", "aria-hidden": "true", onClick: () => setMobileMenuOpen(false) }), _jsxs("div", { className: "cs-mobile-menu-drawer", role: "dialog", "aria-modal": "true", "aria-label": t('appbar.appMenu'), children: [_jsxs("div", { className: "cs-mm-header", children: [_jsxs("div", { className: "cs-mm-brand", children: [_jsx(SutrataLogo, { size: 22, showText: false }), _jsx("span", { children: "Sutrata" })] }), _jsx("button", { type: "button", className: "cs-mm-close", "aria-label": t('appbar.closeMenu'), onClick: () => setMobileMenuOpen(false), children: _jsx(CloseIcon, { size: 18 }) })] }), _jsxs("div", { className: "cs-mm-file-info", children: [_jsx("span", { className: "cs-mm-file-name", children: name }), isDirty && _jsx("span", { className: "cs-ab-dirty", title: t('appbar.unsaved') })] }), _jsxs("div", { className: "cs-mm-section", children: [_jsx("div", { className: "cs-mm-section-title", children: t('appbar.viewsTools') }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setMode(nextMode); setMobileMenuOpen(false); }, children: [mode === 'formatted' ? _jsx(SourceIcon, { size: 18 }) : _jsx(FormattedIcon, { size: 18 }), _jsx("span", { children: mode === 'formatted' ? t('appbar.switchToSource') : t('appbar.switchToFormatted') })] }), _jsxs("button", { type: "button", className: `cs-mm-item${navVisible ? ' cs-mm-active' : ''}`, onClick: () => { setNavVisible(!navVisible); setMobileMenuOpen(false); }, children: [_jsx(NavigatorIcon, { size: 18 }), _jsx("span", { children: t('appbar.navigator') })] }), canEdit && _jsxs("button", { type: "button", className: `cs-mm-item${ribbonVisible ? ' cs-mm-active' : ''}`, onClick: () => { setRibbonVisible(!ribbonVisible); setMobileMenuOpen(false); }, children: [_jsx(RibbonIcon, { size: 18 }), _jsx("span", { children: ribbonVisible ? t('appbar.hideToolbar') : t('appbar.showToolbar') })] }), speech && _jsxs("button", { type: "button", className: `cs-mm-item${voiceActive ? ' cs-mm-active' : ''}`, onClick: () => { setVoiceActive(!voiceActive); setMobileMenuOpen(false); }, children: [_jsx(MicIcon, { size: 18 }), _jsx("span", { children: t('appbar.voice') })] }), _jsxs("button", { type: "button", className: `cs-mm-item${metadataVisible ? ' cs-mm-active' : ''}`, onClick: () => { setMetadataVisible(!metadataVisible); setMobileMenuOpen(false); }, children: [_jsx("span", { style: { fontWeight: 700, width: 18, textAlign: 'center' }, children: "&" }), _jsx("span", { children: metadataVisible ? t('appbar.hideMetadata') : t('appbar.showMetadata') })] })] }), canEdit && _jsxs("div", { className: "cs-mm-section", children: [_jsx("div", { className: "cs-mm-section-title", children: t('appbar.fileOps') }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { openTitlePageForm(); setMobileMenuOpen(false); }, children: [_jsx(TitlePageIcon, { size: 18 }), _jsx("span", { children: t('appbar.titlePage') })] }), localFiles && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { handleNew(); setMobileMenuOpen(false); }, children: [_jsx(NewIcon, { size: 18 }), _jsx("span", { children: t('appbar.new') })] }), localFiles && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { void openFile(); setMobileMenuOpen(false); }, children: [_jsx(OpenIcon, { size: 18 }), _jsx("span", { children: t('appbar.open') })] }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { void handleImportDocx(); setMobileMenuOpen(false); }, children: [_jsx(ImportDocxIcon, { size: 18 }), _jsx("span", { children: t('appbar.importDocx') })] }), ai && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setAiImportOpen(true); setMobileMenuOpen(false); }, children: [_jsx(SparklesIcon, { size: 18 }), _jsx("span", { children: t('appbar.importAI') })] }), _jsxs("button", { type: "button", className: "cs-mm-item cs-mm-primary", onClick: () => { void saveFile(); setMobileMenuOpen(false); }, children: [_jsx(SaveIcon, { size: 18 }), _jsx("span", { children: t('appbar.save') })] }), localFiles && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { void saveFileAs(); setMobileMenuOpen(false); }, children: [_jsx(SaveAsIcon, { size: 18 }), _jsx("span", { children: t('appbar.saveAs') })] }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setExportVisible(true); setMobileMenuOpen(false); }, children: [_jsx(ExportIcon, { size: 18 }), _jsx("span", { children: t('appbar.export') })] })] }), !canEdit && _jsx("div", { className: "cs-mm-section", children: _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setExportVisible(true); setMobileMenuOpen(false); }, children: [_jsx(ExportIcon, { size: 18 }), _jsx("span", { children: t('appbar.export') })] }) }), appbarCommands.length > 0 && _jsx("div", { className: "cs-mm-section", children: appbarCommands.map(c => (_jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { c.run(commandContext()); setMobileMenuOpen(false); }, children: [c.icon?.(18), _jsx("span", { children: c.label })] }, c.id))) }), _jsxs("div", { className: "cs-mm-section", children: [_jsx("div", { className: "cs-mm-section-title", children: t('appbar.preferences') }), canEdit && _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setStyleVisible(true); setMobileMenuOpen(false); }, children: [_jsx(StyleIcon, { size: 18 }), _jsx("span", { children: t('appbar.style') })] }), _jsxs("button", { type: "button", className: "cs-mm-item", onClick: () => { setSettingsVisible(true); setMobileMenuOpen(false); }, children: [_jsx(SettingsIcon, { size: 18 }), _jsx("span", { children: t('settings.title') })] })] })] })] })), aiImportOpen && _jsx(AIImportDialog, { onClose: () => setAiImportOpen(false) })] }));
|
|
55
55
|
}
|
package/dist/shell/AppShell.js
CHANGED
|
@@ -40,5 +40,5 @@ export function AppShell() {
|
|
|
40
40
|
window.addEventListener(OPEN_TITLE_PAGE_EVENT, open);
|
|
41
41
|
return () => window.removeEventListener(OPEN_TITLE_PAGE_EVENT, open);
|
|
42
42
|
}, []);
|
|
43
|
-
return (_jsxs("div", { className: `cs-shell ${metadataVisible ? '' : 'cs-hide-metadata'}`, children: [_jsx(Watermark, {}), _jsx(AppBar, {}), voiceActive && speech && _jsx(VoiceToolbar, {}), ribbonVisible && canEdit && _jsx(ElementToolbar, {}), _jsxs("div", { className: "cs-shell-body", children: [navVisible && (_jsxs(_Fragment, { children: [_jsx("div", { className: "cs-nav-backdrop", "aria-hidden": "true", onClick: () => setNavVisible(false) }), sidebarPanels.length === 0 ? (_jsx(SceneNavigator, {})) : (_jsxs("div", { className: "cs-sidebar", children: [_jsx("div", { className: "cs-sidebar-tabs", role: "tablist", "aria-label":
|
|
43
|
+
return (_jsxs("div", { className: `cs-shell ${metadataVisible ? '' : 'cs-hide-metadata'}`, children: [_jsx(Watermark, {}), _jsx(AppBar, {}), voiceActive && speech && _jsx(VoiceToolbar, {}), ribbonVisible && canEdit && _jsx(ElementToolbar, {}), _jsxs("div", { className: "cs-shell-body", children: [navVisible && (_jsxs(_Fragment, { children: [_jsx("div", { className: "cs-nav-backdrop", "aria-hidden": "true", onClick: () => setNavVisible(false) }), sidebarPanels.length === 0 ? (_jsx(SceneNavigator, {})) : (_jsxs("div", { className: "cs-sidebar", children: [_jsx("div", { className: "cs-sidebar-tabs", role: "tablist", "aria-label": t('shell.sidebar'), children: [{ id: 'navigator', title: t('navigator.title') }, ...sidebarPanels].map(p => (_jsx("button", { type: "button", role: "tab", "aria-selected": (activeSidebar?.id ?? 'navigator') === p.id, className: `cs-sidebar-tab${(activeSidebar?.id ?? 'navigator') === p.id ? ' cs-sidebar-tab-active' : ''}`, onClick: () => setSidebarTab(p.id), children: p.title }, p.id))) }), activeSidebar ? (_jsx("div", { className: "cs-sidebar-panel", role: "tabpanel", "aria-label": activeSidebar.title, children: activeSidebar.render(panelContext) })) : (_jsx(SceneNavigator, {}))] }))] })), _jsx("div", { className: "cs-editor-area", children: _jsx("main", { className: "cs-main", children: mode === 'formatted' ? _jsx(EditorView, {}) : _jsx(SourceView, {}) }) }), inspectorPanels.length > 0 && (_jsx("aside", { className: "cs-inspector", "aria-label": t('shell.inspector'), children: inspectorPanels.map(p => (_jsxs("section", { className: "cs-inspector-section", "aria-label": p.title, children: [_jsx("div", { className: "cs-inspector-title", children: p.title }), p.render(panelContext)] }, p.id))) }))] }), _jsx(StatusBar, {}), _jsx(FindReplace, {}), exportVisible && _jsx(ExportDialog, {}), titlePageVisible && canEdit && _jsx(TitlePageDialog, { onClose: () => setTitlePageVisible(false) }), settingsVisible && _jsx(SettingsDialog, { onClose: () => setSettingsVisible(false) }), styleVisible && canEdit && _jsx(StyleDialog, { onClose: () => setStyleVisible(false) }), confirmModal && _jsx(ConfirmDialog, { options: confirmModal, onClose: closeConfirm }), _jsx(ToastContainer, { toasts: toasts, onDismiss: dismissToast }), canEdit && _jsx(SelectionMenu, {})] }));
|
|
44
44
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from '../i18n/useTranslation';
|
|
2
3
|
import { useEffect, useRef } from 'react';
|
|
3
4
|
export function ConfirmDialog({ options, onClose }) {
|
|
5
|
+
const { t } = useTranslation();
|
|
4
6
|
const confirmBtnRef = useRef(null);
|
|
5
7
|
useEffect(() => {
|
|
6
8
|
confirmBtnRef.current?.focus();
|
|
@@ -16,8 +18,8 @@ export function ConfirmDialog({ options, onClose }) {
|
|
|
16
18
|
return (_jsx("div", { className: "cs-confirm-overlay", role: "presentation", onClick: () => { options.onCancel?.(); onClose(); }, children: _jsxs("div", { className: "cs-confirm-dialog", role: "alertdialog", "aria-modal": "true", "aria-labelledby": "cs-confirm-title", "aria-describedby": "cs-confirm-desc", onClick: (e) => e.stopPropagation(), children: [_jsx("div", { id: "cs-confirm-title", className: "cs-confirm-title", children: options.title }), _jsx("div", { id: "cs-confirm-desc", className: "cs-confirm-msg", children: options.message }), _jsxs("div", { className: "cs-confirm-actions", children: [_jsx("button", { type: "button", className: "cs-confirm-btn-cancel", onClick: () => {
|
|
17
19
|
options.onCancel?.();
|
|
18
20
|
onClose();
|
|
19
|
-
}, children: options.cancelLabel ?? '
|
|
21
|
+
}, children: options.cancelLabel ?? t('common.cancel') }), _jsx("button", { ref: confirmBtnRef, type: "button", className: options.destructive ? 'cs-confirm-btn-danger' : 'cs-confirm-btn-primary', onClick: () => {
|
|
20
22
|
options.onConfirm();
|
|
21
23
|
onClose();
|
|
22
|
-
}, children: options.confirmLabel ?? '
|
|
24
|
+
}, children: options.confirmLabel ?? t('common.confirm') })] })] }) }));
|
|
23
25
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { useTranslation } from '../i18n/useTranslation';
|
|
3
4
|
import { ToolbarButton } from './ToolbarButton';
|
|
4
5
|
import { subscribe } from '../editor/editor-bus';
|
|
5
6
|
import { OverflowIcon, CloseIcon } from './icons';
|
|
@@ -21,6 +22,7 @@ function groupsOf(commands) {
|
|
|
21
22
|
* separator between groups.
|
|
22
23
|
*/
|
|
23
24
|
export function ElementToolbar() {
|
|
25
|
+
const { t } = useTranslation();
|
|
24
26
|
const [, setTick] = useState(0);
|
|
25
27
|
const [overflowOpen, setOverflowOpen] = useState(false);
|
|
26
28
|
const commands = useCommands().filter(c => c.menu === 'toolbar');
|
|
@@ -33,8 +35,8 @@ export function ElementToolbar() {
|
|
|
33
35
|
const elements = commands.filter(c => c.group === 'elements');
|
|
34
36
|
const others = commands.filter(c => !['marks', 'history', 'elements'].includes(c.group ?? ''));
|
|
35
37
|
const run = (c) => { c.run(context()); setOverflowOpen(false); };
|
|
36
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cs-element-toolbar", role: "toolbar", "aria-label":
|
|
38
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cs-element-toolbar", role: "toolbar", "aria-label": t('tb.editing'), children: [groups.map((group, gi) => (_jsxs(React.Fragment, { children: [gi > 0 && _jsx("span", { className: "cs-tb-sep" }), group.map(c => (_jsx(ToolbarButton, { label: c.label, sigil: c.sigil, shortcut: displayShortcut(c.shortcut ?? c.shortcutHint), active: c.isActive?.(ctx) ?? false, onClick: () => c.run(context()), children: c.icon ? c.icon(20) : _jsx("span", { className: "cs-tb-text-label", children: c.label }) }, c.id)))] }, group[0].group ?? `g${gi}`))), _jsx("button", { type: "button", className: `cs-tb-overflow${overflowOpen ? ' cs-tb-overflow-active' : ''}`, "aria-label": t('tb.more'), "aria-expanded": overflowOpen, onClick: () => setOverflowOpen(!overflowOpen), children: _jsx(OverflowIcon, { size: 18 }) })] }), overflowOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "cs-tb-overflow-backdrop", "aria-hidden": "true", onClick: () => setOverflowOpen(false) }), _jsxs("div", { className: "cs-tb-overflow-sheet", role: "dialog", "aria-modal": "true", "aria-label": t('tb.sheetLabel'), children: [_jsxs("div", { className: "cs-tb-overflow-header", children: [_jsx("span", { className: "cs-tb-overflow-title", children: t('tb.sheetTitle') }), _jsx("button", { type: "button", className: "cs-tb-overflow-close", "aria-label": t('common.close'), onClick: () => setOverflowOpen(false), children: _jsx(CloseIcon, { size: 18 }) })] }), _jsx("div", { className: "cs-tb-overflow-section-label", children: t('tb.inline') }), _jsx("div", { className: "cs-tb-overflow-inline-grid", children: inline.map(c => (_jsxs("button", { type: "button", className: "cs-tb-overflow-item", onClick: () => run(c), children: [c.icon?.(18), _jsx("span", { children: c.label })] }, c.id))) }), _jsx("div", { className: "cs-tb-overflow-section-label", children: t('tb.elements') }), _jsx("div", { className: "cs-tb-overflow-list", children: [...elements, ...others].map(c => {
|
|
37
39
|
const active = c.isActive?.(ctx) ?? false;
|
|
38
|
-
return (_jsxs("button", { type: "button", className: `cs-tb-overflow-list-item${active ? ' cs-tb-overflow-item-active' : ''}`, onClick: () => run(c), children: [_jsx("div", { className: "cs-tb-overflow-list-icon", children: c.icon?.(18) }), _jsxs("div", { className: "cs-tb-overflow-list-text", children: [_jsx("span", { className: "cs-tb-overflow-list-name", children: c.label }), c.sigil && _jsx("span", { className: "cs-tb-overflow-list-sigil", children: c.sigil })] }), active && _jsx("span", { className: "cs-tb-overflow-current-badge", children:
|
|
40
|
+
return (_jsxs("button", { type: "button", className: `cs-tb-overflow-list-item${active ? ' cs-tb-overflow-item-active' : ''}`, onClick: () => run(c), children: [_jsx("div", { className: "cs-tb-overflow-list-icon", children: c.icon?.(18) }), _jsxs("div", { className: "cs-tb-overflow-list-text", children: [_jsx("span", { className: "cs-tb-overflow-list-name", children: c.label }), c.sigil && _jsx("span", { className: "cs-tb-overflow-list-sigil", children: c.sigil })] }), active && _jsx("span", { className: "cs-tb-overflow-current-badge", children: t('tb.active') })] }, c.id));
|
|
39
41
|
}) })] })] }))] }));
|
|
40
42
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { useLanguage } from '../i18n/LanguageContext';
|
|
4
|
+
import { useTranslation } from '../i18n/useTranslation';
|
|
4
5
|
import { useDocument } from '../context/DocumentContext';
|
|
5
6
|
import { getEditorView } from '../editor/editor-bus';
|
|
6
7
|
import { useCanEdit } from '../extensions/session';
|
|
@@ -18,6 +19,7 @@ const LANG_NAMES = {
|
|
|
18
19
|
si: 'Sinhala',
|
|
19
20
|
};
|
|
20
21
|
export function LanguageStatus() {
|
|
22
|
+
const { t } = useTranslation();
|
|
21
23
|
const { currentLanguage } = useLanguage();
|
|
22
24
|
const { text, setText } = useDocument();
|
|
23
25
|
const canEdit = useCanEdit();
|
|
@@ -61,7 +63,7 @@ export function LanguageStatus() {
|
|
|
61
63
|
}
|
|
62
64
|
setMenuOpen(false);
|
|
63
65
|
};
|
|
64
|
-
return (_jsxs("div", { className: "cs-lang-status", ref: menuRef, children: [_jsxs("button", { type: "button", className: "cs-lang-status-btn cs-lang-pill", title:
|
|
66
|
+
return (_jsxs("div", { className: "cs-lang-status", ref: menuRef, children: [_jsxs("button", { type: "button", className: "cs-lang-status-btn cs-lang-pill", title: t('lang.tooltip').replace('{name}', langName).replace('{code}', currentLanguage.toUpperCase()), "aria-label": t('lang.aria').replace('{name}', langName), "aria-haspopup": "menu", "aria-expanded": menuOpen, onClick: () => setMenuOpen(!menuOpen), children: [_jsx("span", { className: "cs-lang-pill-script", children: "\u0905/A" }), _jsx("span", { className: "cs-lang-code", children: currentLanguage.toUpperCase() })] }), menuOpen && (_jsx("div", { className: "cs-lang-menu", role: "menu", "aria-label": t('lang.menu'), children: Object.entries(LANG_NAMES).map(([code, name]) => {
|
|
65
67
|
const isActive = currentLanguage === code;
|
|
66
68
|
return (_jsxs("button", { type: "button", role: "menuitem", className: `cs-lang-option${isActive ? ' cs-lang-active' : ''}`, onClick: (e) => {
|
|
67
69
|
e.stopPropagation();
|
package/dist/shell/StatusBar.js
CHANGED
|
@@ -55,5 +55,5 @@ export function StatusBar() {
|
|
|
55
55
|
const estPages = Math.max(1, Math.round(lines / 54));
|
|
56
56
|
return { wordCount: words, pageEstimate: estPages };
|
|
57
57
|
}, [text]);
|
|
58
|
-
return (_jsxs(_Fragment, { children: [_jsxs("footer", { className: "cs-status-bar", role: "contentinfo", "aria-label":
|
|
58
|
+
return (_jsxs(_Fragment, { children: [_jsxs("footer", { className: "cs-status-bar", role: "contentinfo", "aria-label": t('statusbar.label'), children: [_jsxs("div", { className: "cs-sb-left", children: [_jsxs("span", { className: "cs-sb-metric cs-sb-metric-scenes", title: t('statusbar.scenesTooltip'), children: [_jsx("strong", { children: scenes.length }), " ", _jsx("span", { className: "cs-sb-metric-label", children: scenes.length === 1 ? t('statusbar.scene') : t('statusbar.scenes') })] }), _jsx("span", { className: "cs-sb-sep", children: "\u2022" }), _jsxs("span", { className: "cs-sb-metric cs-sb-metric-pages", title: t('statusbar.pagesTooltip'), children: [_jsxs("strong", { children: ["~", pageEstimate] }), " ", _jsx("span", { className: "cs-sb-metric-label", children: pageEstimate === 1 ? t('statusbar.page') : t('statusbar.pages') })] }), _jsx("span", { className: "cs-sb-sep cs-sb-words-sep", children: "\u2022" }), _jsxs("span", { className: "cs-sb-metric cs-sb-metric-words", title: t('statusbar.wordsTooltip'), children: [_jsx("strong", { children: wordCount.toLocaleString() }), " ", _jsx("span", { className: "cs-sb-metric-label", children: t('statusbar.words') })] })] }), _jsx("div", { className: "cs-sb-center", children: _jsxs("div", { className: "cs-sb-save", role: "status", "aria-live": "polite", title: saveTooltip, children: [_jsx("span", { className: `cs-sb-dot ${isDirty ? 'cs-sb-dot-dirty' : 'cs-sb-dot-clean'}` }), _jsx("span", { className: isDirty ? 'cs-sb-save-dirty' : 'cs-sb-save-clean', children: saveLabel })] }) }), _jsxs("div", { className: "cs-sb-right", children: [_jsx(AIStatusBarStatus, {}), canEdit && filePath && versions.length > 0 && (_jsxs("button", { type: "button", className: "cs-nav-action-btn", onClick: () => setVersionHistoryVisible(true), title: `${versions.length} version${versions.length === 1 ? '' : 's'} saved`, style: { fontSize: '11px', gap: '4px', padding: '2px 6px' }, children: [_jsx(HistoryIcon, { size: 13 }), _jsx("span", { children: versions.length })] })), _jsxs("button", { type: "button", className: "cs-nav-action-btn", onClick: () => setStatsOpen(true), title: t('statusbar.statsTooltip'), style: { fontSize: '11px', gap: '4px', padding: '2px 6px' }, children: [_jsx(StatsIcon, { size: 13 }), _jsx("span", { children: t('statusbar.statsButton') })] }), _jsx(LanguageStatus, {})] })] }), statsOpen && _jsx(StatisticsDialog, { onClose: () => setStatsOpen(false) }), canEdit && filePath && (_jsx(VersionHistory, { versions: versions, filePath: filePath, onRestore: restoreVersion, onClearAll: clearVersionHistory, visible: versionHistoryVisible, onClose: () => setVersionHistoryVisible(false) }))] }));
|
|
59
59
|
}
|