diff --git a/src/lib/components/__snapshots__/JSONEditor.test.ts.snap b/src/lib/components/__snapshots__/JSONEditor.test.ts.snap index cd46531e..2a2f2623 100644 --- a/src/lib/components/__snapshots__/JSONEditor.test.ts.snap +++ b/src/lib/components/__snapshots__/JSONEditor.test.ts.snap @@ -471,7 +471,7 @@ exports[`JSONEditor > render text mode 1`] = ` class="jse-main svelte-ybuk0j" >
render text mode 1`] = `
void + export let onSearch: (result: SearchResultDetails | undefined) => void export let onFocus: (path: JSONPath) => Promise export let onPatch: OnPatch export let onClose: () => void let text = '' - let previousText = '' + let appliedText = '' let replaceText = '' let searching = false - let searchResult: SearchResult | undefined + let searchResult: SearchResultDetails | undefined $: resultCount = searchResult?.items?.length || 0 $: activeIndex = searchResult?.activeIndex || 0 @@ -75,10 +75,9 @@ if (combo === 'Enter') { event.preventDefault() - const pendingChanges = text !== previousText + const pendingChanges = text !== appliedText if (pendingChanges) { applyChangedSearchTextDebounced.flush() - previousText = text } else { handleNext() } @@ -155,7 +154,8 @@ ) onPatch(operations, (_, patchedState) => ({ - state: { ...patchedState, selection: newSelection } + state: patchedState, + selection: newSelection })) // immediately trigger updating the search results @@ -182,7 +182,8 @@ ) onPatch(operations, (_, patchedState) => ({ - state: { ...patchedState, selection: newSelection } + state: patchedState, + selection: newSelection })) await handleFocus() @@ -249,13 +250,14 @@ return } + appliedText = text searching = true return new Promise((resolve) => { setTimeout(() => { // wait until the search icon has been rendered const newResultItems = search(text, json, { maxResults: MAX_SEARCH_RESULTS, columns }) - searchResult = updateSearchResult(json, newResultItems, searchResult) + searchResult = updateSearchResult(newResultItems, searchResult) searching = false resolve() diff --git a/src/lib/components/modes/tablemode/TableMode.svelte b/src/lib/components/modes/tablemode/TableMode.svelte index 354c394f..fc75aca2 100644 --- a/src/lib/components/modes/tablemode/TableMode.svelte +++ b/src/lib/components/modes/tablemode/TableMode.svelte @@ -28,7 +28,8 @@ OnTransformModal, ParseError, PastedJson, - SearchResult, + SearchResults, + SearchResultDetails, SortedColumn, TransformModalOptions, ValidationError, @@ -39,7 +40,6 @@ import TableMenu from './menu/TableMenu.svelte' import { compileJSONPointer, - compileJSONPointerProp, existsIn, getIn, immutableJSONPatch, @@ -86,7 +86,9 @@ expandMinimal, expandWithCallback, getEnforceString, - setEnforceString + getInRecursiveState, + setInDocumentState, + syncDocumentState } from '$lib/logic/documentState.js' import { isObjectOrArray, isUrl, stringConvert } from '$lib/utils/typeUtils.js' import InlineValue from './tag/InlineValue.svelte' @@ -147,8 +149,7 @@ import type { Context } from 'svelte-simple-modal' import createTableContextMenuItems from './contextmenu/createTableContextMenuItems' import ContextMenu from '../../controls/contextmenu/ContextMenu.svelte' - import { filterValueSearchResults } from '$lib/logic/search.js' - import { filterPointerOrUndefined } from 'svelte-jsoneditor/utils/jsonPointer' + import { flattenSearchResults, toRecursiveSearchResults } from '$lib/logic/search.js' const debug = createDebug('jsoneditor:TableMode') const { open } = getContext('simple-modal') @@ -220,7 +221,8 @@ let pastedJson: PastedJson - let searchResult: SearchResult | undefined + let searchResultDetails: SearchResultDetails | undefined + let searchResults: SearchResults | undefined let showSearch = false let showReplace = false @@ -238,15 +240,15 @@ }) } - function handleSearch(result: SearchResult | undefined) { - searchResult = result + function handleSearch(result: SearchResultDetails | undefined) { + searchResultDetails = result + searchResults = searchResultDetails + ? toRecursiveSearchResults(json, searchResultDetails.items) + : undefined } async function handleFocusSearch(path: JSONPath) { - documentState = { - ...documentState, - selection: null // navigation path of current selection would be confusing - } + selection = null // navigation path of current selection would be confusing await scrollTo(path) } @@ -306,56 +308,42 @@ } } - function clearSortedColumn() { - if (documentState.sortedColumn) { - documentState = { - ...documentState, - sortedColumn: null - } - } - } - function updateSelection( - selection: + updatedSelection: | JSONSelection | null | ((selection: JSONSelection | null) => JSONSelection | null | void | undefined) ) { - debug('updateSelection', selection) - - const updatedSelection = - typeof selection === 'function' ? selection(documentState.selection) || null : selection + debug('updateSelection', updatedSelection) - if (!isEqual(updatedSelection, documentState.selection)) { - documentState = { - ...documentState, - selection: updatedSelection - } + const appliedSelection = + typeof updatedSelection === 'function' + ? updatedSelection(selection) || null + : updatedSelection - onSelect(updatedSelection) + if (!isEqual(appliedSelection, selection)) { + selection = appliedSelection + onSelect(appliedSelection) } } function clearSelectionWhenNotExisting(json: unknown | undefined) { - if (!documentState.selection || json === undefined) { + if (!selection || json === undefined) { return } - if ( - existsIn(json, getAnchorPath(documentState.selection)) && - existsIn(json, getFocusPath(documentState.selection)) - ) { + if (existsIn(json, getAnchorPath(selection)) && existsIn(json, getFocusPath(selection))) { return } - debug('clearing selection: path does not exist anymore', documentState.selection) - documentState = { - ...documentState, - selection: null // TODO: try find the closest cell that still exists (similar to getInitialSelection) - } + debug('clearing selection: path does not exist anymore', selection) + selection = null // TODO: try find the closest cell that still exists (similar to getInitialSelection) } - let documentState = createDocumentState() + let documentState: DocumentState | undefined = + json !== undefined ? createDocumentState({ json }) : undefined + let selection: JSONSelection | null = null + let sortedColumn: SortedColumn | null = null let textIsRepaired = false function onSortByHeader(newSortedColumn: SortedColumn) { @@ -370,10 +358,8 @@ const operations = sortJson(json, rootPath, newSortedColumn.path, direction) handlePatch(operations, (_, patchedState) => { return { - state: { - ...patchedState, - sortedColumn: newSortedColumn - } + state: patchedState, + sortedColumn: newSortedColumn } }) } @@ -415,26 +401,26 @@ return } - const previousJson = json - const previousState = documentState - const previousText = text - const previousTextIsRepaired = textIsRepaired + const previousState = { json, documentState, selection, sortedColumn, text, textIsRepaired } if (isTextContent(content)) { try { json = parseMemoizeOne(content.text) + documentState = syncDocumentState(json, documentState) text = content.text textIsRepaired = false parseError = undefined } catch (err) { try { json = parseMemoizeOne(jsonrepair(content.text)) + documentState = syncDocumentState(json, documentState) text = content.text textIsRepaired = true parseError = undefined } catch (repairError) { // no valid JSON, will show empty document or invalid json json = undefined + documentState = undefined text = content.text textIsRepaired = false parseError = @@ -445,6 +431,7 @@ } } else { json = content.json + documentState = syncDocumentState(json, documentState) text = undefined textIsRepaired = false parseError = undefined @@ -454,18 +441,13 @@ clearSelectionWhenNotExisting(json) // reset the sorting order (we don't know...) - clearSortedColumn() + sortedColumn = null - addHistoryItem({ - previousJson, - previousState, - previousText, - previousTextIsRepaired - }) + addHistoryItem(previousState) } function applyExternalSelection(externalSelection: JSONEditorSelection | null) { - if (!isEqual(documentState.selection, externalSelection)) { + if (!isEqual(selection, externalSelection)) { debug('applyExternalSelection', externalSelection) if (isJSONSelection(externalSelection) || externalSelection === null) { @@ -474,82 +456,43 @@ } } - // TODO: addHistoryItem is a duplicate of addHistoryItem in TreeMode.svelte. Can we extract and reuse this logic? - function addHistoryItem({ - previousJson, - previousState, - previousText, - previousTextIsRepaired - }: { - previousJson: unknown | undefined - previousText: string | undefined - previousState: DocumentState - previousTextIsRepaired: boolean - }) { - if (previousJson === undefined && previousText === undefined) { + interface PreviousState { + json: unknown | undefined + text: string | undefined + documentState: DocumentState | undefined + selection: JSONSelection | null + textIsRepaired: boolean + sortedColumn: SortedColumn | null + } + + function addHistoryItem(previous: PreviousState) { + if (previous.json === undefined && previous.text === undefined) { // initialization -> do not create a history item return } - if (json !== undefined) { - if (previousJson !== undefined) { - // regular undo/redo with JSON patch - history.add({ - undo: { - patch: [{ op: 'replace', path: '', value: previousJson }], - state: removeEditModeFromSelection(previousState), - json: undefined, - text: previousText, - textIsRepaired: previousTextIsRepaired - }, - redo: { - patch: [{ op: 'replace', path: '', value: json }], - state: removeEditModeFromSelection(documentState), - json: undefined, - text, - textIsRepaired - } - }) - } else { - history.add({ - undo: { - patch: undefined, - json: undefined, - text: previousText, - state: removeEditModeFromSelection(previousState), - textIsRepaired: previousTextIsRepaired - }, - redo: { - patch: undefined, - json, - state: removeEditModeFromSelection(documentState), - text, - textIsRepaired - } - }) - } - } else { - if (previousJson !== undefined) { - history.add({ - undo: { - patch: undefined, - json: previousJson, - state: removeEditModeFromSelection(previousState), - text: previousText, - textIsRepaired: previousTextIsRepaired - }, - redo: { - patch: undefined, - json: undefined, - text, - textIsRepaired, - state: removeEditModeFromSelection(documentState) - } - }) - } else { - // this cannot happen. Nothing to do, no change + const canPatch = json !== undefined && previous.json !== undefined + + history.add({ + undo: { + patch: canPatch ? [{ op: 'replace', path: '', value: previous.json }] : undefined, + json: previous.json, + text: previous.text, + documentState: previous.documentState, + textIsRepaired: previous.textIsRepaired, + selection: removeEditModeFromSelection(previous.selection), + sortedColumn: previous.sortedColumn + }, + redo: { + patch: canPatch ? [{ op: 'replace', path: '', value: json }] : undefined, + json, + text, + documentState, + textIsRepaired, + selection: removeEditModeFromSelection(selection), + sortedColumn } - } + }) } let validationErrors: ValidationError[] = [] @@ -617,8 +560,14 @@ } const previousJson = json - const previousState = documentState - const previousTextIsRepaired = textIsRepaired + const previousState: PreviousState = { + json: undefined, // not needed: we use patch to reconstruct the json + text, + documentState, + selection: removeEditModeFromSelection(selection), + sortedColumn, + textIsRepaired + } // execute the patch operations const undo: JSONPatchDocument = revertJSONPatchWithMoveOperations( @@ -630,20 +579,22 @@ // Clear the sorted column when needed. We need to do this before `afterPatch`, // else we clear any changed made in the callback. It is a bit odd that // afterPatch does not receive the actual previousDocumentState. Better ideas? - const patchedJson = patched.json - const patchedDocumentState = clearSortedColumnWhenAffectedByOperations( - documentState, + const patchedSortedColumn = clearSortedColumnWhenAffectedByOperations( + sortedColumn, operations, columns ) const callback = - typeof afterPatch === 'function' ? afterPatch(patchedJson, patchedDocumentState) : undefined - - json = callback && callback.json !== undefined ? callback.json : patchedJson - const newState = - callback && callback.state !== undefined ? callback.state : patchedDocumentState - documentState = newState + typeof afterPatch === 'function' + ? afterPatch(patched.json, patched.state, selection) + : undefined + + json = callback?.json !== undefined ? callback.json : patched.json + documentState = callback?.state !== undefined ? callback.state : patched.state + selection = callback?.selection !== undefined ? callback.selection : selection + sortedColumn = + callback?.sortedColumn !== undefined ? callback.sortedColumn : patchedSortedColumn text = undefined textIsRepaired = false pastedJson = undefined @@ -652,16 +603,15 @@ history.add({ undo: { patch: undo, - json: undefined, - text: undefined, - state: removeEditModeFromSelection(previousState), - textIsRepaired: previousTextIsRepaired + ...previousState }, redo: { patch: operations, - json: undefined, - state: removeEditModeFromSelection(newState), + json: undefined, // not needed: we use patch to reconstruct the json text: undefined, + documentState, + selection: removeEditModeFromSelection(selection), + sortedColumn, textIsRepaired } }) @@ -761,10 +711,7 @@ const path = getDataPathFromTarget(target) if (path) { // when clicking inside the current selection, editing a value, do nothing - if ( - isEditingSelection(documentState.selection) && - pathInSelection(json, documentState.selection, path) - ) { + if (isEditingSelection(selection) && pathInSelection(json, selection, path)) { return } @@ -791,18 +738,15 @@ } function createDefaultSelectionWhenUndefined() { - if (!documentState.selection) { + if (!selection) { updateSelection(createDefaultSelection()) } } export function acceptAutoRepair() { if (textIsRepaired && json !== undefined) { - const previousState = documentState - const previousJson = json - const previousText = text const previousContent = { json, text } - const previousTextIsRepaired = textIsRepaired + const previousState = { json, documentState, selection, sortedColumn, text, textIsRepaired } // json stays as is text = undefined @@ -810,12 +754,7 @@ clearSelectionWhenNotExisting(json) - addHistoryItem({ - previousJson, - previousState, - previousText, - previousTextIsRepaired - }) + addHistoryItem(previousState) // we could work out a patchResult, or use patch(), but only when the previous and new // contents are both json and not text. We go for simplicity and consistency here and @@ -964,6 +903,7 @@ const defaultItems: ContextMenuItem[] = createTableContextMenuItems({ json, documentState, + selection, readOnly, parser, @@ -1020,7 +960,7 @@ } function handleContextMenu(event: Event) { - if (isEditingSelection(documentState.selection)) { + if (isEditingSelection(selection)) { return } @@ -1079,11 +1019,11 @@ } function handleEditValue() { - if (readOnly || !documentState.selection) { + if (readOnly || !selection) { return } - const path = getFocusPath(documentState.selection) + const path = getFocusPath(selection) const value = getIn(json, path) if (isObjectOrArray(value)) { openJSONEditorModal(path) @@ -1093,24 +1033,24 @@ } function handleEditRow() { - if (readOnly || !documentState.selection) { + if (readOnly || !selection) { return } - const path = getFocusPath(documentState.selection) + const path = getFocusPath(selection) const pathRow = path.slice(0, 1) openJSONEditorModal(pathRow) } function handleToggleEnforceString() { - if (readOnly || !isValueSelection(documentState.selection)) { + if (readOnly || !isValueSelection(selection)) { return } - const path = documentState.selection.path + const path = selection.path const pointer = compileJSONPointer(path) const value = getIn(json, path) - const enforceString = !getEnforceString(value, documentState.enforceStringMap, pointer, parser) + const enforceString = !getEnforceString(json, documentState, path, parser) const updatedValue = enforceString ? String(value) : stringConvert(String(value), parser) debug('handleToggleEnforceString', { enforceString, value, updatedValue }) @@ -1125,7 +1065,7 @@ ], (_, patchedState) => { return { - state: setEnforceString(patchedState, pointer, enforceString) + state: setInDocumentState(json, patchedState, path, { type: 'value', enforceString }) } } ) @@ -1189,7 +1129,7 @@ async function handleCut(indent: boolean) { await onCut({ json, - documentState, + selection, indentation: indent ? indentation : undefined, readOnly, parser, @@ -1204,7 +1144,7 @@ await onCopy({ json, - documentState, + selection, indentation: indent ? indentation : undefined, parser }) @@ -1214,7 +1154,7 @@ onRemove({ json, text, - documentState, + selection, keepSelection: true, readOnly, onChange, @@ -1223,19 +1163,19 @@ } function handleDuplicateRow() { - onDuplicateRow({ json, documentState, columns, readOnly, onPatch: handlePatch }) + onDuplicateRow({ json, selection, columns, readOnly, onPatch: handlePatch }) } function handleInsertBeforeRow() { - onInsertBeforeRow({ json, documentState, columns, readOnly, onPatch: handlePatch }) + onInsertBeforeRow({ json, selection, columns, readOnly, onPatch: handlePatch }) } function handleInsertAfterRow() { - onInsertAfterRow({ json, documentState, columns, readOnly, onPatch: handlePatch }) + onInsertAfterRow({ json, selection, columns, readOnly, onPatch: handlePatch }) } function handleRemoveRow() { - onRemoveRow({ json, documentState, columns, readOnly, onPatch: handlePatch }) + onRemoveRow({ json, selection, columns, readOnly, onPatch: handlePatch }) } async function handleInsertCharacter(char: string) { @@ -1244,7 +1184,7 @@ selectInside: false, refJsonEditor, json, - selection: documentState.selection, + selection: selection, readOnly, parser, onPatch: handlePatch, @@ -1307,8 +1247,8 @@ createDefaultSelectionWhenUndefined() - if (documentState.selection) { - const newSelection = selectPreviousColumn(columns, documentState.selection) + if (selection) { + const newSelection = selectPreviousColumn(columns, selection) updateSelection(newSelection) scrollIntoView(getFocusPath(newSelection)) } @@ -1319,8 +1259,8 @@ createDefaultSelectionWhenUndefined() - if (documentState.selection) { - const newSelection = selectNextColumn(columns, documentState.selection) + if (selection) { + const newSelection = selectNextColumn(columns, selection) updateSelection(newSelection) scrollIntoView(getFocusPath(newSelection)) } @@ -1331,8 +1271,8 @@ createDefaultSelectionWhenUndefined() - if (documentState.selection) { - const newSelection = selectPreviousRow(columns, documentState.selection) + if (selection) { + const newSelection = selectPreviousRow(columns, selection) updateSelection(newSelection) scrollIntoView(getFocusPath(newSelection)) } @@ -1343,18 +1283,18 @@ createDefaultSelectionWhenUndefined() - if (documentState.selection) { - const newSelection = selectNextRow(json, columns, documentState.selection) + if (selection) { + const newSelection = selectNextRow(json, columns, selection) updateSelection(newSelection) scrollIntoView(getFocusPath(newSelection)) } } - if (combo === 'Enter' && documentState.selection) { - if (isValueSelection(documentState.selection)) { + if (combo === 'Enter' && selection) { + if (isValueSelection(selection)) { event.preventDefault() - const path = documentState.selection.path + const path = selection.path const value = getIn(json, path) if (isObjectOrArray(value)) { // edit nested object/array @@ -1362,14 +1302,14 @@ } else { if (!readOnly) { // go to value edit mode - updateSelection({ ...documentState.selection, edit: true }) + updateSelection({ ...selection, edit: true }) } } } } const normalizedCombo = combo.replace(/^Shift\+/, '') // replace 'Shift+A' with 'A' - if (normalizedCombo.length === 1 && documentState.selection) { + if (normalizedCombo.length === 1 && selection) { // a regular key like a, A, _, etc is entered. // Replace selected contents with a new value having this first character as text event.preventDefault() @@ -1377,8 +1317,8 @@ return } - if (combo === 'Ctrl+Enter' && isValueSelection(documentState.selection)) { - const value = getIn(json, documentState.selection.path) + if (combo === 'Ctrl+Enter' && isValueSelection(selection)) { + const value = getIn(json, selection.path) if (isUrl(value)) { // open url in new page @@ -1386,7 +1326,7 @@ } } - if (combo === 'Escape' && documentState.selection) { + if (combo === 'Escape' && selection) { event.preventDefault() updateSelection(null) } @@ -1425,7 +1365,7 @@ onPaste({ clipboardText, json, - selection: documentState.selection, + selection: selection, readOnly, parser, onPatch: handlePatch, @@ -1436,19 +1376,25 @@ // TODO: this function is duplicated from TreeMode. See if we can reuse the code instead function handleReplaceJson(updatedJson: unknown, afterPatch?: AfterPatchCallback) { - const previousState = documentState - const previousJson = json - const previousText = text const previousContent = { json, text } - const previousTextIsRepaired = textIsRepaired + const previousState = { json, documentState, selection, sortedColumn, text, textIsRepaired } - const updatedState = expandWithCallback(json, documentState, [], expandMinimal) + const updatedState = expandWithCallback( + json, + syncDocumentState(updatedJson, documentState), + [], + expandMinimal + ) const callback = - typeof afterPatch === 'function' ? afterPatch(updatedJson, updatedState) : undefined - - json = callback && callback.json !== undefined ? callback.json : updatedJson - documentState = callback && callback.state !== undefined ? callback.state : updatedState + typeof afterPatch === 'function' + ? afterPatch(updatedJson, updatedState, selection) + : undefined + + json = callback?.json !== undefined ? callback.json : updatedJson + documentState = callback?.state !== undefined ? callback.state : updatedState + selection = callback?.selection !== undefined ? callback.selection : selection + sortedColumn = null // we can't know whether the new json is still sorted or not text = undefined textIsRepaired = false parseError = undefined @@ -1456,12 +1402,7 @@ // make sure the selection is valid clearSelectionWhenNotExisting(json) - addHistoryItem({ - previousJson, - previousState, - previousText, - previousTextIsRepaired - }) + addHistoryItem(previousState) // we could work out a patchResult, or use patch(), but only when the previous and new // contents are both json and not text. We go for simplicity and consistency here and @@ -1475,29 +1416,36 @@ function handleChangeText(updatedText: string, afterPatch?: AfterPatchCallback) { debug('handleChangeText') - const previousState = documentState - const previousJson = json - const previousText = text const previousContent = { json, text } - const previousTextIsRepaired = textIsRepaired + const previousState = { json, documentState, selection, sortedColumn, text, textIsRepaired } try { json = parseMemoizeOne(updatedText) - documentState = expandWithCallback(json, documentState, [], expandMinimal) + documentState = expandWithCallback( + json, + syncDocumentState(json, documentState), + [], + expandMinimal + ) text = undefined textIsRepaired = false parseError = undefined } catch (err) { try { json = parseMemoizeOne(jsonrepair(updatedText)) - documentState = expandWithCallback(json, documentState, [], expandMinimal) + documentState = expandWithCallback( + json, + syncDocumentState(json, documentState), + [], + expandMinimal + ) text = updatedText textIsRepaired = true parseError = undefined } catch (repairError) { // no valid JSON, will show empty document or invalid json json = undefined - documentState = createDocumentState({ json, expand: expandMinimal }) + documentState = undefined text = updatedText textIsRepaired = false parseError = @@ -1508,21 +1456,17 @@ } if (typeof afterPatch === 'function') { - const callback = afterPatch(json, documentState) + const callback = afterPatch(json, documentState, selection) - json = callback && callback.json ? callback.json : json - documentState = callback && callback.state ? callback.state : documentState + json = callback?.json !== undefined ? callback.json : json + documentState = callback?.state !== undefined ? callback.state : documentState + selection = callback?.selection !== undefined ? callback.selection : selection } // ensure the selection is valid clearSelectionWhenNotExisting(json) - addHistoryItem({ - previousJson, - previousState, - previousText, - previousTextIsRepaired - }) + addHistoryItem(previousState) // no JSON patch actions available in text mode const patchResult = null @@ -1554,12 +1498,10 @@ handlePatch(operations, (_, patchedState) => { return { - state: { - ...patchedState, - sortedColumn: { - path: itemPath, - sortDirection: direction === -1 ? SortDirection.desc : SortDirection.asc - } + state: patchedState, + sortedColumn: { + path: itemPath, + sortDirection: direction === -1 ? SortDirection.desc : SortDirection.asc } } }) @@ -1697,7 +1639,9 @@ const previousContent = { json, text } json = item.undo.patch ? immutableJSONPatch(json, item.undo.patch) : item.undo.json - documentState = item.undo.state + documentState = item.undo.documentState + selection = item.undo.selection + sortedColumn = item.undo.sortedColumn text = item.undo.text textIsRepaired = item.undo.textIsRepaired parseError = undefined @@ -1717,8 +1661,8 @@ emitOnChange(previousContent, patchResult) focus() - if (documentState.selection) { - scrollTo(getFocusPath(documentState.selection), false) + if (selection) { + scrollTo(getFocusPath(selection), false) } } @@ -1739,7 +1683,9 @@ const previousContent = { json, text } json = item.redo.patch ? immutableJSONPatch(json, item.redo.patch) : item.redo.json - documentState = item.redo.state + documentState = item.redo.documentState + selection = item.redo.selection + sortedColumn = item.redo.sortedColumn text = item.redo.text textIsRepaired = item.redo.textIsRepaired parseError = undefined @@ -1759,8 +1705,8 @@ emitOnChange(previousContent, patchResult) focus() - if (documentState.selection) { - scrollTo(getFocusPath(documentState.selection), false) + if (selection) { + scrollTo(getFocusPath(selection), false) } } @@ -1849,12 +1795,7 @@ {#each columns as column} - + {/each} {#if showRefreshButton} @@ -1877,9 +1818,9 @@ [String(rowIndex)], validationErrorsByRow?.row )} - {@const searchResultItemsByRow = searchResult?.itemsMap - ? filterPointerOrUndefined(searchResult?.itemsMap, compileJSONPointerProp(rowIndex)) - : undefined} + {@const searchResultByRow = getInRecursiveState(json, searchResults, [ + String(rowIndex) + ])} {#key rowIndex} {#if isObjectOrArray(value)} - {@const searchResultItemsByCell = searchResultItemsByRow - ? filterPointerOrUndefined(searchResultItemsByRow, pointer) - : undefined} - {@const containsActiveSearchResult = searchResultItemsByCell - ? Object.values(searchResultItemsByCell).some((items) => - items.some((item) => item.active) - ) + {@const searchResultsByCell = flattenSearchResults( + getInRecursiveState(item, searchResultByRow, column) + )} + + {@const containsActiveSearchResult = searchResultsByCell + ? searchResultsByCell.some((item) => item.active) : false} {:else} - {@const searchResultItemsByCell = searchResult?.itemsMap - ? filterValueSearchResults(searchResult?.itemsMap, pointer) - : undefined} + {@const searchResultItemsByCell = getInRecursiveState( + json, + searchResults, + path + )?.searchResults} {/if}{#if !readOnly && isSelected && !isEditingSelection(documentState.selection)} + />{/if}{#if !readOnly && isSelected && !isEditingSelection(selection)}
diff --git a/src/lib/components/modes/tablemode/contextmenu/createTableContextMenuItems.ts b/src/lib/components/modes/tablemode/contextmenu/createTableContextMenuItems.ts index f510f525..ac0369b8 100644 --- a/src/lib/components/modes/tablemode/contextmenu/createTableContextMenuItems.ts +++ b/src/lib/components/modes/tablemode/contextmenu/createTableContextMenuItems.ts @@ -1,4 +1,4 @@ -import type { ContextMenuItem, DocumentState, JSONParser } from 'svelte-jsoneditor' +import type { ContextMenuItem, DocumentState, JSONParser, JSONSelection } from 'svelte-jsoneditor' import { faCheckSquare, faClone, @@ -11,7 +11,7 @@ import { faTrashCan } from '@fortawesome/free-solid-svg-icons' import { isKeySelection, isMultiSelection, isValueSelection } from '$lib/logic/selection' -import { compileJSONPointer, getIn } from 'immutable-json-patch' +import { getIn } from 'immutable-json-patch' import { getFocusPath, singleItemSelected } from '$lib/logic/selection' import { isObjectOrArray } from '$lib/utils/typeUtils' import { getEnforceString } from '$lib/logic/documentState' @@ -19,6 +19,7 @@ import { getEnforceString } from '$lib/logic/documentState' export default function ({ json, documentState, + selection, readOnly, parser, onEditValue, @@ -34,7 +35,8 @@ export default function ({ onRemoveRow }: { json: unknown | undefined - documentState: DocumentState + documentState: DocumentState | undefined + selection: JSONSelection | null readOnly: boolean parser: JSONParser onEditValue: () => void @@ -49,8 +51,6 @@ export default function ({ onInsertAfterRow: () => void onRemoveRow: () => void }): ContextMenuItem[] { - const selection = documentState.selection - const hasJson = json !== undefined const hasSelection = !!selection const focusValue = @@ -66,13 +66,8 @@ export default function ({ const canCut = !readOnly && hasSelectionContents const enforceString = - selection != null && focusValue !== undefined - ? getEnforceString( - focusValue, - documentState.enforceStringMap, - compileJSONPointer(getFocusPath(selection)), - parser - ) + selection != null + ? getEnforceString(json, documentState, getFocusPath(selection), parser) : false return [ diff --git a/src/lib/components/modes/treemode/JSONKey.svelte b/src/lib/components/modes/treemode/JSONKey.svelte index d53c4626..2458650c 100644 --- a/src/lib/components/modes/treemode/JSONKey.svelte +++ b/src/lib/components/modes/treemode/JSONKey.svelte @@ -13,11 +13,11 @@ import { addNewLineSuffix } from '$lib/utils/domUtils.js' import type { ExtendedSearchResultItem, JSONSelection, TreeModeContext } from '$lib/types.js' import { UpdateSelectionAfterChange } from '$lib/types.js' - import type { JSONPath } from 'immutable-json-patch' + import { parseJSONPointer, type JSONPath, type JSONPointer } from 'immutable-json-patch' import ContextMenuPointer from '../../../components/controls/contextmenu/ContextMenuPointer.svelte' import { classnames } from '$lib/utils/cssUtils.js' - export let path: JSONPath + export let pointer: JSONPointer export let key: string export let selection: JSONSelection | null export let searchResultItems: ExtendedSearchResultItem[] | undefined @@ -25,6 +25,9 @@ export let context: TreeModeContext + let path: JSONPath + $: path = parseJSONPointer(pointer) + $: isKeySelected = selection ? isKeySelection(selection) && isEqual(selection.path, path) : false $: isEditingKey = isKeySelected && isEditingSelection(selection) diff --git a/src/lib/components/modes/treemode/JSONNode.svelte b/src/lib/components/modes/treemode/JSONNode.svelte index 705463f6..c30887b3 100644 --- a/src/lib/components/modes/treemode/JSONNode.svelte +++ b/src/lib/components/modes/treemode/JSONNode.svelte @@ -3,8 +3,8 @@