fix(workspace): Enter keeps an empty bullet on its level when siblings follow
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m17s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m17s
Outdenting an empty bullet on Enter only happens when no bullet on the same level follows in the branch; otherwise a new bullet is added below. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b136e5dfad
commit
05355989a8
4 files changed
+50
-3
No files matched your search
@@ -16,6 +16,7 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
|
||||
- PWA Web Share Target: Text, Links und Bilder können über den nativen Teilen-Dialog des Smartphones direkt in den MindSift-Posteingang geteilt werden (`share_target` im Manifest, Abfangen im Service Worker via IndexedDB und Übernahme beim App-Start; Fallback-Route `POST /share-target` im Server).
|
||||
|
||||
### Changed
|
||||
- Notizen: Enter in einem leeren eingerückten Punkt rückt ihn nur noch aus, wenn danach kein Punkt mehr auf derselben Ebene folgt; sonst entsteht wie gewohnt ein neuer Punkt darunter (auch nummerierte Listen werden dann nicht beendet).
|
||||
- Workspace-Leiste: Live, Geteilt und Speicherstatus ohne Rahmen im gleichen Stil, ein senkrechter Trennstrich vor den Notiz-Knöpfen. Footer ohne Linie darüber.
|
||||
- Journal: Panel und Kalender öffnen sich mit einer sanften Animation, die Journal-Ansicht blendet ein.
|
||||
- Einfügen und Markdown-Import: Eine Liste direkt nach einem Absatz („Folgende Punkte:“) wird unter diesem Absatz eingerückt.
|
||||
|
||||
@@ -106,6 +106,8 @@ export interface OutlinerBlockProps {
|
||||
onPasteMultiLines?: (blockId: string, textBefore: string, textAfter: string, rawLines: string[]) => void;
|
||||
/** Number or letter of a numbered-list item ("1.", "b.", "iii.") */
|
||||
listMarker?: string;
|
||||
/** Another bullet on the same level follows: Enter in this empty bullet adds a bullet instead of outdenting */
|
||||
siblingFollows?: boolean;
|
||||
lockedBy?: string; // someone else is editing this block
|
||||
canAppendBelow?: boolean; // the user may edit the note, so Enter in a locked block still adds one below
|
||||
onResolveConflict?: (blockId: string, accept: boolean) => void;
|
||||
@@ -155,6 +157,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
registerTextareaRef,
|
||||
onPasteMultiLines,
|
||||
listMarker,
|
||||
siblingFollows = false,
|
||||
lockedBy,
|
||||
canAppendBelow,
|
||||
onResolveConflict,
|
||||
@@ -961,13 +964,13 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
|
||||
const minLevel = levelOffset || 0;
|
||||
if (localContent === '') {
|
||||
if (Math.max(0, block.level || 0) > minLevel) {
|
||||
if (Math.max(0, block.level || 0) > minLevel && !siblingFollows) {
|
||||
flushDebounce(localContent);
|
||||
onIndentBlockRef.current(block.id, -1);
|
||||
return;
|
||||
}
|
||||
// Enter in an empty numbered item at the top ends the list
|
||||
if (block.type === 'numbered') {
|
||||
// Enter in an empty last numbered item at the top ends the list
|
||||
if (block.type === 'numbered' && !siblingFollows) {
|
||||
flushDebounce('', { type: 'text' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -244,6 +244,21 @@ export const OutlinerTree = forwardRef<OutlinerTreeHandle, OutlinerTreeProps>(({
|
||||
|
||||
const listLabels = useMemo(() => numberedLabels(blocks), [blocks]);
|
||||
|
||||
// Whether another bullet on the same level follows (before the branch ends); an empty bullet
|
||||
// is then not outdented by Enter
|
||||
const siblingFollows = useCallback(
|
||||
(index: number) => {
|
||||
const level = blocks[index]?.level || 0;
|
||||
for (let i = index + 1; i < blocks.length; i++) {
|
||||
const l = blocks[i].level || 0;
|
||||
if (l < level) return false;
|
||||
if (l === level) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[blocks]
|
||||
);
|
||||
|
||||
const handleCreateBlockBelow = useCallback(
|
||||
(afterBlockId: string, newContent: string = '') => {
|
||||
if (!canEdit) return;
|
||||
@@ -617,6 +632,7 @@ export const OutlinerTree = forwardRef<OutlinerTreeHandle, OutlinerTreeProps>(({
|
||||
block={block}
|
||||
index={index}
|
||||
isLast={isLast}
|
||||
siblingFollows={siblingFollows(index)}
|
||||
maxAllowedLevel={maxAllowedLevel}
|
||||
hasChildren={hasChildren}
|
||||
isFocused={focusedBlockId === block.id}
|
||||
|
||||
@@ -207,6 +207,33 @@ describe('slash command menu and outliner fixes', () => {
|
||||
expect(onCreateBelow).toHaveBeenCalledWith('b1', '');
|
||||
});
|
||||
|
||||
it('keeps an empty indented bullet on its level when another bullet on that level follows', () => {
|
||||
const onIndent = vi.fn();
|
||||
const onCreateBelow = vi.fn();
|
||||
const { container } = render(
|
||||
<OutlinerBlock
|
||||
docId="d"
|
||||
block={{ id: 'b1', type: 'bullet', content: '', level: 1 } as DocumentBlock}
|
||||
index={0}
|
||||
isLast={false}
|
||||
siblingFollows
|
||||
maxAllowedLevel={2}
|
||||
hasChildren={false}
|
||||
isFocused
|
||||
canEdit
|
||||
onUpdateBlock={() => {}}
|
||||
onCreateBlockBelow={onCreateBelow}
|
||||
onDeleteBlock={() => {}}
|
||||
onIndentBlock={onIndent}
|
||||
onNavigateFocus={() => {}}
|
||||
registerTextareaRef={() => {}}
|
||||
/>
|
||||
);
|
||||
fireEvent.keyDown(container.querySelector('textarea') as HTMLTextAreaElement, { key: 'Enter' });
|
||||
expect(onIndent).not.toHaveBeenCalled();
|
||||
expect(onCreateBelow).toHaveBeenCalledWith('b1', '');
|
||||
});
|
||||
|
||||
it('inserts a bare URL for /link with a web address in an empty bullet, and [Text](url) otherwise', () => {
|
||||
const updates: DocumentBlock[] = [];
|
||||
const block = { id: 'b1', type: 'bullet', content: '', level: 0 } as DocumentBlock;
|
||||
|
||||
Reference in new issue
Block a user