+
+
= {
- 'checkbox-list': { type: 'list-item' as any, props: { kind: 'check', checked: false, text: '' } },
- 'numbered-list': { type: 'list-item' as any, props: { kind: 'numbered', number: 1, text: '' } },
- 'bullet-list': { type: 'list-item' as any, props: { kind: 'bullet', text: '' } },
- 'table': { type: 'table', props: this.documentService.getDefaultProps('table') },
- 'image': { type: 'image', props: this.documentService.getDefaultProps('image') },
- 'file': null,
- 'heading-2': { type: 'heading', props: { level: 2, text: '' } },
- 'new-page': { type: 'paragraph', props: { text: '' } }, // Placeholder
- 'use-ai': { type: 'paragraph', props: { text: '' } }, // Placeholder
- };
-
- const config = typeMap[action];
- if (action === 'file') {
- this.filePicker.pick({ multiple: true, accept: '*/*' }).then(files => {
- if (!files.length) return;
- this.insertFilesAtCursor(files);
- });
- return;
- }
- if (config) {
- const block = this.documentService.createBlock(config.type, config.props);
- this.documentService.appendBlock(block);
- this.selectionService.setActive(block.id);
- }
+ return;
+ }
+ // Map toolbar action to palette item id
+ const idMap: Record
= {
+ 'checkbox-list': 'checkbox-list',
+ 'numbered-list': 'numbered-list',
+ 'bullet-list': 'bullet-list',
+ 'table': 'table',
+ 'image': 'image',
+ 'file': 'file',
+ 'heading-2': 'heading-2',
+ // 'new-page' and 'use-ai' are placeholders and not palette-backed
+ };
+ const id = idMap[action];
+ if (!id) return;
+ const item = PALETTE_ITEMS.find(i => i.id === id);
+ if (item) {
+ this.paletteService.applySelection(item);
}
}
onPaletteItemSelected(item: PaletteItem): void {
- // Special handling for File: open multi-picker and create N blocks
- if (item.type === 'file' || item.id === 'file') {
- this.filePicker.pick({ multiple: true, accept: '*/*' }).then(files => {
- if (!files.length) return;
- this.insertFilesAtCursor(files);
- });
- return;
- }
+ // Delegate to authoritative creation flow
+ this.paletteService.applySelection(item);
+ }
- // Convert list types to list-item for independent lines
- let blockType = item.type;
- let props = this.documentService.getDefaultProps(blockType);
- if (item.type === 'list') {
- blockType = 'list-item' as any;
- props = this.documentService.getDefaultProps(blockType);
- if (item.id === 'checkbox-list') { props.kind = 'check'; props.checked = false; }
- else if (item.id === 'numbered-list') { props.kind = 'numbered'; props.number = 1; }
- else if (item.id === 'bullet-list') { props.kind = 'bullet'; }
- }
-
- const block = this.documentService.createBlock(blockType, props);
- this.documentService.appendBlock(block);
- this.selectionService.setActive(block.id);
+ onClearDocument(): void {
+ // Empty page: remove all blocks and clear selection/menus
+ this.documentService.clearBlocks();
+ this.selectionService.clear();
+ this.showInitialMenu.set(false);
+ this.insertAfterBlockId.set(null);
}
/**
@@ -431,91 +424,64 @@ export class EditorShellComponent implements AfterViewInit {
}, 0);
}
- onInitialMenuAction(action: BlockMenuAction): void {
+ async onInitialMenuAction(action: BlockMenuAction): Promise {
// Hide menu immediately
this.showInitialMenu.set(false);
-
+
const blockId = this.insertAfterBlockId();
if (!blockId) return;
-
- // If paragraph type selected, just hide menu and keep the paragraph
+
+ // Ensure the placeholder paragraph is the active block so applySelection can convert/insert correctly
+ this.selectionService.setActive(blockId);
+
+ // Paragraph: keep as-is and just focus
if (action.type === 'paragraph') {
- // Focus on the paragraph
setTimeout(() => {
const element = document.querySelector(`[data-block-id="${blockId}"] [contenteditable]`) as HTMLElement;
- if (element) {
- element.focus();
- }
+ element?.focus();
}, 0);
return;
}
-
- // If "more" selected, open full palette
+
+ // More: open full palette
if (action.type === 'more') {
- this.paletteService.open();
+ this.paletteService.open(blockId);
return;
}
-
- // Otherwise, convert the paragraph block to the selected type
- let blockType: any = 'paragraph';
- let props: any = { text: '' };
-
- switch (action.type) {
- case 'heading':
- blockType = 'heading';
- props = { level: 2, text: '' };
- break;
- case 'checkbox':
- blockType = 'list-item';
- props = { kind: 'check', text: '', checked: false };
- break;
- case 'list':
- blockType = 'list-item';
- props = { kind: 'bullet', text: '' };
- break;
- case 'numbered':
- blockType = 'list-item';
- props = { kind: 'numbered', text: '', number: 1 };
- break;
- case 'formula':
- blockType = 'code';
- props = { language: 'latex', code: '' };
- break;
- case 'table':
- blockType = 'table';
- props = this.documentService.getDefaultProps('table');
- break;
- case 'code':
- blockType = 'code';
- props = this.documentService.getDefaultProps('code');
- break;
- case 'image':
- blockType = 'image';
- props = this.documentService.getDefaultProps('image');
- break;
- case 'file':
- // Open picker and replace the placeholder paragraph with N file blocks at the same index
- const currentBlocks = this.documentService.blocks();
- const idx = currentBlocks.findIndex(b => b.id === blockId);
- this.filePicker.pick({ multiple: true, accept: '*/*' }).then(files => {
- if (!files.length) return;
- // Delete the placeholder paragraph
- this.documentService.deleteBlock(blockId);
- // Insert at original index
- this.inserter.createFromFiles(files, idx);
- });
- return; // early exit; we handle asynchronously
- }
-
- // Convert the existing block
- this.documentService.updateBlock(blockId, { type: blockType, props });
-
- // Focus on the converted block
- setTimeout(() => {
- const newElement = document.querySelector(`[data-block-id="${blockId}"] [contenteditable]`) as HTMLElement;
- if (newElement) {
- newElement.focus();
+
+ // Map initial menu actions to palette items
+ const idMap: Record = {
+ heading: 'heading-2',
+ checkbox: 'checkbox-list',
+ list: 'bullet-list',
+ numbered: 'numbered-list',
+ table: 'table',
+ code: 'code',
+ image: 'image',
+ file: 'file',
+ };
+
+ // Special-case formula: insert a code block then switch language to LaTeX
+ if (action.type === 'formula') {
+ const codeItem = PALETTE_ITEMS.find(i => i.id === 'code');
+ if (codeItem) {
+ await this.paletteService.applySelection(codeItem);
+ const newActiveId = this.selectionService.getActive();
+ if (newActiveId) {
+ const blk: any = this.documentService.getBlock(newActiveId);
+ if (blk?.type === 'code') {
+ this.documentService.updateBlockProps(newActiveId, { ...(blk.props || {}), language: 'latex', code: '' });
+ }
+ }
}
- }, 0);
+ return;
+ }
+
+ const id = idMap[action.type];
+ if (!id) return;
+ const item = PALETTE_ITEMS.find(i => i.id === id);
+ if (item) {
+ await this.paletteService.applySelection(item);
+ }
}
}
diff --git a/src/app/editor/components/palette/block-menu.component.ts b/src/app/editor/components/palette/block-menu.component.ts
index 585e73a..1a0676d 100644
--- a/src/app/editor/components/palette/block-menu.component.ts
+++ b/src/app/editor/components/palette/block-menu.component.ts
@@ -13,15 +13,15 @@ import { PaletteItem, PaletteCategory, getPaletteItemsByCategory } from '../../c
-
-
SUGGESTIONS
+
SUGGESTIONS