Skip to content

Commit a6b35f5

Browse files
committed
Extract useInlineEdit composable from App.vue with 40 tests
1 parent d6e0a5a commit a6b35f5

3 files changed

Lines changed: 650 additions & 136 deletions

File tree

src/App.vue

Lines changed: 34 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useDetachedWindow } from './composables/useDetachedWindow.js'
77
import { useSelection } from './composables/useSelection.js'
88
import { useCardDrag } from './composables/useCardDrag.js'
99
import { useSearch } from './composables/useSearch.js'
10+
import { useInlineEdit } from './composables/useInlineEdit.js'
1011
import { nodeTypes, getImportanceLabel, getTypeIcon, getTypeColors, typeConfig, personIconSvg } from './utils/constants.js'
1112
import DetailPanel from './components/DetailPanel.vue'
1213
import GraphView from './components/GraphView.vue'
@@ -215,14 +216,7 @@ function closeDetail() {
215216
detailPinned.value = false
216217
}
217218
218-
// Inline editing state
219-
const editingCardId = ref(null)
220-
const editingTitle = ref('')
221-
222-
// Inline notes-only editing (separate from full card editing)
223-
const inlineNotesId = ref(null)
224-
const inlineNotesText = ref('')
225-
const inlineNotesRef = ref(null)
219+
// Inline editing state is managed by useInlineEdit composable (initialized after flatChildren)
226220
// Sensitive info visibility - restore from localStorage
227221
const hideSensitive = ref(localStorage.getItem('graphcore-hideSensitive') === 'true')
228222
@@ -636,6 +630,35 @@ const {
636630
flatChildren
637631
})
638632
633+
// Initialize inline editing composable
634+
const {
635+
editingCardId,
636+
editingTitle,
637+
inlineNotesId,
638+
inlineNotesText,
639+
inlineNotesRef,
640+
startEditing,
641+
saveEditing,
642+
cancelEditing,
643+
handleEditKeydown,
644+
startInlineNotes,
645+
saveInlineNotes,
646+
cancelInlineNotes,
647+
handleInlineNotesKeydown
648+
} = useInlineEdit({
649+
findNode: (nodeId) => flatChildren.value.find(n => n.id === nodeId),
650+
onSaveTitle: async (nodeId, newTitle) => {
651+
await api.updateNode(nodeId, { title: newTitle })
652+
await loadChildren(currentContainerId.value)
653+
},
654+
onSaveNotes: async (nodeId, newNotes, { autoSave }) => {
655+
await api.updateNode(nodeId, { notes: newNotes })
656+
if (!autoSave) {
657+
await loadChildren(currentContainerId.value)
658+
}
659+
}
660+
})
661+
639662
const contextTitle = computed(() => {
640663
if (currentContainer.value) {
641664
return currentContainer.value.title
@@ -2213,140 +2236,15 @@ async function handleViewContextMenu({ event, node }) {
22132236
await showContextMenu(event, node)
22142237
}
22152238
2216-
// Inline editing functions
2217-
function startEditing(node, e) {
2218-
e?.stopPropagation()
2219-
editingCardId.value = node.id
2220-
editingTitle.value = node.title
2221-
}
2222-
2223-
async function saveEditing() {
2224-
if (!editingCardId.value) return
2225-
2226-
const nodeId = editingCardId.value
2227-
const originalNode = flatChildren.value.find(n => n.id === nodeId)
2228-
if (!originalNode) {
2229-
editingCardId.value = null
2230-
return
2231-
}
2232-
2233-
// Only update if title changed
2234-
if (editingTitle.value !== originalNode.title) {
2235-
try {
2236-
await api.updateNode(nodeId, { title: editingTitle.value })
2237-
await loadChildren(currentContainerId.value)
2238-
} catch (e) {
2239-
error.value = e.message
2240-
}
2241-
}
2242-
2243-
editingCardId.value = null
2244-
}
2245-
2246-
function cancelEditing() {
2247-
editingCardId.value = null
2248-
editingTitle.value = ''
2249-
}
2250-
2251-
function handleEditKeydown(e) {
2252-
if (e.key === 'Escape') {
2253-
e.preventDefault()
2254-
cancelEditing()
2255-
} else if (e.key === 'Enter' && !e.shiftKey) {
2256-
e.preventDefault()
2257-
saveEditing()
2258-
}
2259-
}
2260-
2261-
// Inline notes-only editing functions
2262-
async function startInlineNotes(node, e) {
2263-
e?.stopPropagation()
2264-
inlineNotesId.value = node.id
2265-
inlineNotesText.value = node.notes || ''
2266-
await nextTick()
2267-
// Handle both single ref and array of refs (when multiple textareas exist)
2268-
const ref = inlineNotesRef.value
2269-
if (Array.isArray(ref)) {
2270-
ref[0]?.focus()
2271-
} else {
2272-
ref?.focus()
2273-
}
2274-
}
2275-
2276-
// Debounced auto-save for inline notes
2277-
let autoSaveTimeout = null
2278-
async function autoSaveInlineNotes() {
2279-
if (!inlineNotesId.value) return
2280-
2281-
const nodeId = inlineNotesId.value
2282-
try {
2283-
await api.updateNode(nodeId, { notes: inlineNotesText.value })
2284-
} catch (e) {
2285-
console.error('Auto-save failed:', e)
2286-
}
2287-
}
2288-
2289-
function debouncedAutoSave() {
2290-
if (autoSaveTimeout) clearTimeout(autoSaveTimeout)
2291-
autoSaveTimeout = setTimeout(autoSaveInlineNotes, 500) // Save after 500ms of no typing
2292-
}
2293-
2294-
// Watch for notes changes and auto-save
2295-
watch(inlineNotesText, (newValue, oldValue) => {
2296-
if (inlineNotesId.value && newValue !== oldValue) {
2297-
debouncedAutoSave()
2298-
}
2299-
})
2300-
2301-
async function saveInlineNotes() {
2302-
if (!inlineNotesId.value) return
2303-
2304-
// Clear any pending auto-save
2305-
if (autoSaveTimeout) {
2306-
clearTimeout(autoSaveTimeout)
2307-
autoSaveTimeout = null
2308-
}
2309-
2310-
const nodeId = inlineNotesId.value
2311-
const originalNode = flatChildren.value.find(n => n.id === nodeId)
2312-
if (!originalNode) {
2313-
inlineNotesId.value = null
2314-
return
2315-
}
2316-
2317-
if (inlineNotesText.value !== (originalNode.notes || '')) {
2318-
try {
2319-
await api.updateNode(nodeId, { notes: inlineNotesText.value })
2320-
// Reload to get fresh data
2321-
await loadChildren(currentContainerId.value)
2322-
} catch (e) {
2323-
error.value = e.message
2324-
}
2325-
}
2326-
2327-
inlineNotesId.value = null
2328-
}
2239+
// Inline editing functions (startEditing, saveEditing, cancelEditing, handleEditKeydown,
2240+
// startInlineNotes, saveInlineNotes, cancelInlineNotes, handleInlineNotesKeydown)
2241+
// are now provided by useInlineEdit composable initialized above
23292242
23302243
function renderMarkdown(text) {
23312244
if (!text) return ''
23322245
return marked.parse(text)
23332246
}
23342247
2335-
function cancelInlineNotes() {
2336-
inlineNotesId.value = null
2337-
inlineNotesText.value = ''
2338-
}
2339-
2340-
function handleInlineNotesKeydown(e) {
2341-
if (e.key === 'Escape') {
2342-
e.preventDefault()
2343-
cancelInlineNotes()
2344-
} else if (e.key === 'Enter' && e.metaKey) {
2345-
e.preventDefault()
2346-
saveInlineNotes()
2347-
}
2348-
}
2349-
23502248
// Wrapper functions for tooltip - use composable
23512249
function showCardTooltip(event, node) {
23522250
// Don't show tooltip if editing

0 commit comments

Comments
 (0)