@cueloop/client 0.1.0-alpha.70 → 0.1.0-alpha.72
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/package.json +5 -5
- package/src/App.tsx +245 -230
- package/src/App.workbench.test.tsx +34 -0
- package/src/components/AnnotationCards.stories.tsx +0 -1
- package/src/components/AnnotationCards.tsx +34 -30
- package/src/components/AppShell.tsx +5 -1
- package/src/components/AppShell.zoom.test.tsx +41 -0
- package/src/components/ThreadFooter.tsx +3 -3
- package/src/components/ThreadView.slash.test.tsx +86 -98
- package/src/components/ThreadView.tsx +0 -1
- package/src/components/syntax-highlight.ts +23 -0
- package/src/mark-runs.ts +5 -4
- package/src/skills.ts +7 -0
- package/src/slash-palette.test.ts +37 -5
- package/src/slash-palette.ts +33 -19
- package/src/use-annotation-surface.tsx +34 -25
- package/src/use-changes-workbench.ts +2 -0
|
@@ -38,8 +38,9 @@ import {
|
|
|
38
38
|
import { annotationPaletteFor, type AnnotationPalette } from "./annotation-palette";
|
|
39
39
|
import { printableSequence, type MarkRange, type VisualLine } from "./mark-runs";
|
|
40
40
|
import {
|
|
41
|
+
activeSlashToken,
|
|
42
|
+
isStandaloneSlashQuery,
|
|
41
43
|
mergeSlashItems,
|
|
42
|
-
resolveInlineSuggestion,
|
|
43
44
|
slashFilter,
|
|
44
45
|
slashItemsFrom,
|
|
45
46
|
} from "./slash-palette";
|
|
@@ -233,6 +234,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
233
234
|
};
|
|
234
235
|
const [folded, setFolded] = useState<Set<string>>(new Set());
|
|
235
236
|
const [composeText, setComposeText] = useState("");
|
|
237
|
+
const [caretOffset, setCaretOffset] = useState(0);
|
|
236
238
|
const [slashIndex, setSlashIndex] = useState(0);
|
|
237
239
|
const composerReady = useRef(false);
|
|
238
240
|
const composeRef = useRef<ComposeState | null>(null);
|
|
@@ -304,19 +306,26 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
304
306
|
composeRef.current = state;
|
|
305
307
|
setCompose(state);
|
|
306
308
|
setComposeText(state.seed);
|
|
309
|
+
setCaretOffset(state.seed.length);
|
|
307
310
|
setSlashIndex(0);
|
|
308
311
|
};
|
|
309
312
|
const closeCompose = (): void => {
|
|
310
313
|
composeRef.current = null;
|
|
311
314
|
setCompose(null);
|
|
312
315
|
setComposeText("");
|
|
316
|
+
setCaretOffset(0);
|
|
313
317
|
};
|
|
314
318
|
const skills = useContext(SlashSkillsContext);
|
|
315
319
|
const paletteItems = mergeSlashItems(slashItemsFrom(quickActions), skills);
|
|
316
|
-
//
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
+
// the "/word" under the caret, anywhere in the draft, so each new "/" reopens the palette
|
|
321
|
+
const slashToken = compose !== null ? activeSlashToken(composeText, caretOffset) : null;
|
|
322
|
+
const slashActive = slashToken !== null;
|
|
323
|
+
const slashItems = slashToken !== null ? slashFilter(paletteItems, slashToken.slice(1)) : [];
|
|
324
|
+
|
|
325
|
+
// a fresh token starts its selection at the top, so an earlier Down never leaks into it
|
|
326
|
+
useEffect(() => {
|
|
327
|
+
if (slashActive) setSlashIndex(0);
|
|
328
|
+
}, [slashActive]);
|
|
320
329
|
|
|
321
330
|
const saveComment = (body: string): void => {
|
|
322
331
|
// the body saves verbatim - typed newlines are the author's choice;
|
|
@@ -357,11 +366,13 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
357
366
|
});
|
|
358
367
|
};
|
|
359
368
|
|
|
360
|
-
// clicking away
|
|
361
|
-
//
|
|
369
|
+
// clicking away commits the draft (blur-save); only a standalone "/query" is a palette
|
|
370
|
+
// artifact, so prose that merely ends in a "/name" still saves
|
|
362
371
|
const blurSaveCompose = (): void => {
|
|
363
372
|
if (!composeRef.current) return;
|
|
364
|
-
if (
|
|
373
|
+
if (isStandaloneSlashQuery(composeText) || composeText.trim().length === 0) {
|
|
374
|
+
return closeCompose();
|
|
375
|
+
}
|
|
365
376
|
saveComment(composeText);
|
|
366
377
|
};
|
|
367
378
|
|
|
@@ -431,7 +442,14 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
431
442
|
return true;
|
|
432
443
|
}
|
|
433
444
|
if (key.name === "return" || key.name === "tab") {
|
|
434
|
-
|
|
445
|
+
const token = activeSlashToken(composeText, caretOffset) ?? "";
|
|
446
|
+
const cut = caretOffset - token.length;
|
|
447
|
+
const insertion = `/${slashItems[selected]!.name} `;
|
|
448
|
+
const seed = composeText.slice(0, cut) + insertion + composeText.slice(caretOffset);
|
|
449
|
+
|
|
450
|
+
openCompose({ ...activeCompose, seed });
|
|
451
|
+
// land the caret just after the inserted reference, not at the end of a chained draft
|
|
452
|
+
setCaretOffset(cut + insertion.length);
|
|
435
453
|
|
|
436
454
|
return true;
|
|
437
455
|
}
|
|
@@ -439,17 +457,6 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
439
457
|
return false;
|
|
440
458
|
};
|
|
441
459
|
|
|
442
|
-
/** Tab completes the trailing "/word" to the matched skill's full name, then a space to chain. */
|
|
443
|
-
const handleInlineSlashKey = (key: KeyEvent, activeCompose: ComposeState): boolean => {
|
|
444
|
-
if (inlineSlash === null || key.name !== "tab") return false;
|
|
445
|
-
const cut = composeText.length - inlineSlash.token.length;
|
|
446
|
-
const completed = `${composeText.slice(0, cut)}/${inlineSlash.suggestion.name} `;
|
|
447
|
-
|
|
448
|
-
openCompose({ ...activeCompose, seed: completed });
|
|
449
|
-
|
|
450
|
-
return true;
|
|
451
|
-
};
|
|
452
|
-
|
|
453
460
|
/** Pre-mount window: buffer printables, honor a fast cmd+enter or newline. */
|
|
454
461
|
const handlePremountKey = (key: KeyEvent, activeCompose: ComposeState): void => {
|
|
455
462
|
if (key.name === "return") {
|
|
@@ -458,6 +465,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
458
465
|
|
|
459
466
|
composeRef.current = grown;
|
|
460
467
|
setComposeText(grown.seed);
|
|
468
|
+
setCaretOffset(grown.seed.length);
|
|
461
469
|
|
|
462
470
|
return setCompose(grown);
|
|
463
471
|
}
|
|
@@ -468,6 +476,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
468
476
|
|
|
469
477
|
composeRef.current = grown;
|
|
470
478
|
setComposeText(grown.seed);
|
|
479
|
+
setCaretOffset(grown.seed.length);
|
|
471
480
|
setCompose(grown);
|
|
472
481
|
}
|
|
473
482
|
};
|
|
@@ -491,7 +500,6 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
491
500
|
return closeCompose();
|
|
492
501
|
}
|
|
493
502
|
if (handleSlashKey(key, activeCompose)) return;
|
|
494
|
-
if (handleInlineSlashKey(key, activeCompose)) return;
|
|
495
503
|
if (!composerReady.current) handlePremountKey(key, activeCompose);
|
|
496
504
|
};
|
|
497
505
|
|
|
@@ -670,18 +678,19 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
670
678
|
tokens={tokens}
|
|
671
679
|
onSave={saveComment}
|
|
672
680
|
onReady={() => (composerReady.current = true)}
|
|
673
|
-
onInput={
|
|
681
|
+
onInput={(text, caret) => {
|
|
682
|
+
setComposeText(text);
|
|
683
|
+
setCaretOffset(caret);
|
|
684
|
+
}}
|
|
674
685
|
/>
|
|
675
686
|
) : null;
|
|
676
|
-
//
|
|
677
|
-
// inline completion hint. Renders null when neither applies, so it always pushes.
|
|
687
|
+
// the palette list below the composer while the caret is on a "/word"; null otherwise, so it pushes
|
|
678
688
|
const paletteNode = (
|
|
679
689
|
<ComposerPalette
|
|
680
690
|
key="composer-palette"
|
|
681
691
|
slashActive={slashActive}
|
|
682
692
|
slashItems={slashItems}
|
|
683
693
|
slashIndex={slashIndex}
|
|
684
|
-
inline={inlineSlash}
|
|
685
694
|
tokens={tokens}
|
|
686
695
|
/>
|
|
687
696
|
);
|
|
@@ -64,6 +64,8 @@ export function useChangesWorkbench(options?: ChangesWorkbenchOptions): ChangesW
|
|
|
64
64
|
setChangesOpen(isDiff);
|
|
65
65
|
setProjectMode(isDiff ? "changes" : "tree");
|
|
66
66
|
rememberedChanges.current = isDiff;
|
|
67
|
+
// a non-diff thread has no Changes editor; leaving zoom on would strand the hidden Thread pane
|
|
68
|
+
if (!isDiff) setZoomed(false);
|
|
67
69
|
setGrid(makeGroup([changesTab()]));
|
|
68
70
|
setFocusedGroup(null);
|
|
69
71
|
};
|