# Image Button Implementation Plan ## Overview Add an image button to the MilkdownEditor that allows users to insert images at the cursor position. The button will provide a dropdown menu with two options: upload local file or input image URL. ## Current Architecture Analysis ### Existing Image Handling The editor already has image support through `@milkdown/crepe`: ```javascript // From MilkdownEditor.vue lines 217-231 features: { [Crepe.Feature.Latex]: true, [Crepe.Feature.ImageBlock]: true, }, featureConfigs: { [Crepe.Feature.ImageBlock]: { onUpload: (file) => { const objectUrl = URL.createObjectURL(file) objectUrls.add(objectUrl) performOCR(file, objectUrl) return objectUrl } } } ``` ### Editor Access Pattern The code uses `editorViewCtx` to access the ProseMirror editor view: ```javascript crepe.editor.action((ctx) => { const view = ctx.get(editorViewCtx) // manipulate editor state }) ``` ## Implementation Plan ### 1. Template Changes Add new button with dropdown menu in the `action-buttons` section: ```html
``` ### 2. Script Changes Add new refs and methods: ```javascript // New refs const imageInputRef = ref(null) const showImageDropdown = ref(false) const showUrlDialog = ref(false) const imageUrl = ref('') // Toggle dropdown const toggleImageDropdown = () => { showImageDropdown.value = !showImageDropdown.value } // Trigger file input const triggerImageUpload = () => { showImageDropdown.value = false imageInputRef.value?.click() } // Handle file upload - reuse existing onUpload logic const handleImageUpload = async (event) => { const file = event.target.files?.[0] if (!file) return const objectUrl = URL.createObjectURL(file) objectUrls.add(objectUrl) performOCR(file, objectUrl) // Insert image at cursor insertImageAtCursor(objectUrl) event.target.value = '' } // Insert image from URL const insertImageFromUrl = () => { if (!imageUrl.value.trim()) return insertImageAtCursor(imageUrl.value.trim()) imageUrl.value = '' showUrlDialog.value = false } // Core function: insert image at cursor position const insertImageAtCursor = (src) => { if (!crepe) return crepe.editor.action((ctx) => { const view = ctx.get(editorViewCtx) const { state } = view const { selection, schema } = state // Get image node type from schema const imageType = schema.nodes.image if (!imageType) return // Create image node const imageNode = imageType.create({ src }) // Create transaction to insert at cursor const tr = state.tr tr = tr.replaceSelectionWith(imageNode) view.dispatch(tr) }) } ``` ### 3. Style Changes Add styles for dropdown and dialog: ```css /* Image button wrapper */ .image-btn-wrapper { position: relative; } /* Dropdown menu */ .image-dropdown { position: absolute; bottom: 100%; right: 0; margin-bottom: 8px; background: #fff; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); overflow: hidden; z-index: 10000; min-width: 160px; } .image-dropdown button { display: block; width: 100%; padding: 10px 16px; border: none; background: none; text-align: left; cursor: pointer; font-size: 14px; color: #333; } .image-dropdown button:hover { background: #f5f5f5; } /* URL dialog overlay */ .url-dialog-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; z-index: 10001; } .url-dialog { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); } .url-dialog input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 12px; } .url-dialog button { padding: 8px 16px; margin-right: 8px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; } ``` ## Workflow Diagram ```mermaid flowchart TD A[Click Image Button] --> B{Toggle Dropdown} B --> C[Show Dropdown Menu] C --> D{User Choice} D -->|Upload Local| E[Open File Picker] D -->|From URL| F[Show URL Dialog] E --> G[Select Image File] G --> H[Create Object URL] H --> I[Perform OCR] I --> J[Insert Image at Cursor] F --> K[Enter URL] K --> L[Click Insert] L --> J J --> M[Image Appears in Editor] ``` ## Key Implementation Notes 1. **Reuse existing logic**: The `onUpload` callback logic for `Crepe.Feature.ImageBlock` should be reused for local file uploads to maintain consistency with OCR processing. 2. **ProseMirror API**: Use `schema.nodes.image.create()` and `replaceSelectionWith()` to insert images at cursor position. 3. **Click outside to close**: The dropdown should close when clicking outside. This can be achieved with a click-outside directive or by listening to document clicks. 4. **Accessibility**: Ensure proper ARIA labels and keyboard navigation support. ## Files to Modify - `src/components/MilkdownEditor.vue` - All changes will be in this single file ## Dependencies No new dependencies required. All functionality uses existing: - Vue 3 Composition API - Milkdown/ProseMirror APIs - Native browser APIs (URL.createObjectURL, FileReader)