From 9745812e0f106ac222cf45e626af3b5b8708688f Mon Sep 17 00:00:00 2001 From: onePone Date: Mon, 4 Mar 2024 19:41:43 +0800 Subject: [PATCH 1/5] feat: hide context menu when onRenderContextMenu return false --- README.md | 4 +- .../components/modes/JSONEditorRoot.svelte | 5 +- .../modes/tablemode/TableMode.svelte | 237 +++++++++-- .../contextmenu/TableContextMenu.svelte | 238 +----------- .../components/modes/treemode/TreeMode.svelte | 367 ++++++++++++++++-- .../contextmenu/TreeContextMenu.svelte | 359 +---------------- src/lib/types.ts | 4 +- 7 files changed, 556 insertions(+), 658 deletions(-) diff --git a/README.md b/README.md index 91af9399..c8ae4327 100644 --- a/README.md +++ b/README.md @@ -517,10 +517,10 @@ A menu item `MenuItem` can be one of the following types: #### onRenderContextMenu ```ts -onRenderContextMenu(items: ContextMenuItem[], context: { mode: 'tree' | 'text' | 'table', modal: boolean, selection: JSONEditorSelection | null }) : ContextMenuItem[] | undefined +onRenderContextMenu(items: ContextMenuItem[], context: { mode: 'tree' | 'text' | 'table', modal: boolean, selection: JSONEditorSelection | null }) : ContextMenuItem[] | false | undefined ``` -Callback which can be used to make changes to the context menu items. New items can be added, or existing items can be removed or reorganized. When the function returns `undefined`, the original `items` will be applied. Using the context values `mode`, `modal` and `selection`, different actions can be taken depending on the mode of the editor, whether the editor is rendered inside a modal or not and the path of selection. +Callback which can be used to make changes to the context menu items. New items can be added, or existing items can be removed or reorganized. When the function returns `undefined`, the original `items` will be applied and the context menu will be displayed when `readOnly` is `false`. When the function returns `false`, the context menu will never be displayed. Using the context values `mode`, `modal` and `selection`, different actions can be taken depending on the mode of the editor, whether the editor is rendered inside a modal or not and the path of selection. A menu item `ContextMenuItem` can be one of the following types: diff --git a/src/lib/components/modes/JSONEditorRoot.svelte b/src/lib/components/modes/JSONEditorRoot.svelte index 410c5bc5..9fa1be7a 100644 --- a/src/lib/components/modes/JSONEditorRoot.svelte +++ b/src/lib/components/modes/JSONEditorRoot.svelte @@ -122,7 +122,10 @@ $: handleRenderContextMenu = (items: ContextMenuItem[]) => { const itemsOriginal = cloneDeep(items) // the user may change items in the callback - return onRenderContextMenu(items, { mode, modal: insideModal, selection }) || itemsOriginal + return ( + onRenderContextMenu(items, { mode, modal: insideModal, selection }) ?? + (readOnly ? false : itemsOriginal) + ) } export function patch(operations: JSONPatchDocument): JSONPatchResult { diff --git a/src/lib/components/modes/tablemode/TableMode.svelte b/src/lib/components/modes/tablemode/TableMode.svelte index 823328b3..5b51f9dc 100644 --- a/src/lib/components/modes/tablemode/TableMode.svelte +++ b/src/lib/components/modes/tablemode/TableMode.svelte @@ -95,10 +95,13 @@ getFocusPath, isEditingSelection, isJSONSelection, + isKeySelection, + isMultiSelection, isValueSelection, pathInSelection, pathStartsWith, - removeEditModeFromSelection + removeEditModeFromSelection, + singleItemSelected } from '$lib/logic/selection.js' import { createHistory } from '$lib/logic/history.js' import ColumnHeader from './ColumnHeader.svelte' @@ -108,7 +111,18 @@ import { getContext, onDestroy, onMount, tick } from 'svelte' import { jsonrepair } from 'jsonrepair' import Message from '../../controls/Message.svelte' - import { faCheck, faCode, faWrench } from '@fortawesome/free-solid-svg-icons' + import { + faCheck, + faClone, + faCode, + faCopy, + faCut, + faPaste, + faPen, + faPlus, + faTrashCan, + faWrench + } from '@fortawesome/free-solid-svg-icons' import { measure } from '$lib/utils/timeUtils.js' import memoizeOne from 'memoize-one' import { validateJSON } from '$lib/logic/validation.js' @@ -142,6 +156,8 @@ import JSONPreview from '../../controls/JSONPreview.svelte' import RefreshColumnHeader from './RefreshColumnHeader.svelte' import type { Context } from 'svelte-simple-modal' + import type { ContextMenuItem } from '$lib/types' + import { faCheckSquare, faSquare } from '@fortawesome/free-regular-svg-icons' const debug = createDebug('jsoneditor:TableMode') const { open } = getContext('simple-modal') @@ -184,6 +200,29 @@ escapeUnicodeCharacters }) + $: selection = documentState.selection + + $: hasJson = json !== undefined + $: hasSelection = !!selection + $: focusValue = json !== undefined && selection ? getIn(json, getFocusPath(selection)) : undefined + + $: hasSelectionContents = + hasJson && + (isMultiSelection(selection) || isKeySelection(selection) || isValueSelection(selection)) + + $: canEditValue = hasJson && selection != null && singleItemSelected(selection) + $: canEnforceString = canEditValue && !isObjectOrArray(focusValue) + + $: enforceString = + selection != null && focusValue !== undefined + ? getEnforceString( + focusValue, + documentState.enforceStringMap, + compileJSONPointer(getFocusPath(selection)), + parser + ) + : false + let refJsonEditor: HTMLDivElement let refContents: HTMLDivElement | undefined let refHiddenInput: HTMLInputElement @@ -921,25 +960,179 @@ offsetLeft, showTip }: AbsolutePopupOptions) { + let defaultItems: ContextMenuItem[] = [ + { type: 'separator' }, + { + type: 'row', + items: [ + { + type: 'column', + items: [ + { type: 'label', text: 'Table cell:' }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => handleEditValue(), + icon: faPen, + text: 'Edit', + title: 'Edit the value (Double-click on the value)', + disabled: !canEditValue + }, + width: '11em', + items: [ + { + type: 'button', + icon: faPen, + text: 'Edit', + title: 'Edit the value (Double-click on the value)', + onClick: () => handleEditValue(), + disabled: !canEditValue + }, + { + type: 'button', + icon: enforceString ? faCheckSquare : faSquare, + text: 'Enforce string', + title: 'Enforce keeping the value as string when it contains a numeric value', + onClick: () => handleToggleEnforceString(), + disabled: !canEnforceString + } + ] + }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => handleCut(true), + icon: faCut, + text: 'Cut', + title: 'Cut selected contents, formatted with indentation (Ctrl+X)', + disabled: !hasSelectionContents + }, + width: '10em', + items: [ + { + type: 'button', + icon: faCut, + text: 'Cut formatted', + title: 'Cut selected contents, formatted with indentation (Ctrl+X)', + onClick: () => handleCut(true), + disabled: !hasSelectionContents + }, + { + type: 'button', + icon: faCut, + text: 'Cut compacted', + title: 'Cut selected contents, without indentation (Ctrl+Shift+X)', + onClick: () => handleCut(false), + disabled: !hasSelectionContents + } + ] + }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => handleCopy(true), + icon: faCopy, + text: 'Copy', + title: 'Copy selected contents, formatted with indentation (Ctrl+C)', + disabled: !hasSelectionContents + }, + width: '12em', + items: [ + { + type: 'button', + icon: faCopy, + text: 'Copy formatted', + title: 'Copy selected contents, formatted with indentation (Ctrl+C)', + onClick: () => handleCopy(false), + disabled: !hasSelectionContents + }, + { + type: 'button', + icon: faCopy, + text: 'Copy compacted', + title: 'Copy selected contents, without indentation (Ctrl+Shift+C)', + onClick: () => handleCopy(false), + disabled: !hasSelectionContents + } + ] + }, + { + type: 'button', + onClick: () => handlePasteFromMenu(), + icon: faPaste, + text: 'Paste', + title: 'Paste clipboard contents (Ctrl+V)', + disabled: !hasSelection + }, + { + type: 'button', + onClick: () => handleRemove(), + icon: faTrashCan, + text: 'Remove', + title: 'Remove selected contents (Delete)', + disabled: !hasSelectionContents + } + ] + }, + { + type: 'column', + items: [ + { type: 'label', text: 'Table row:' }, + { + type: 'button', + onClick: () => handleEditRow(), + icon: faPen, + text: 'Edit row', + title: 'Edit the current row', + disabled: !hasSelectionContents + }, + { + type: 'button', + onClick: () => handleDuplicateRow(), + icon: faClone, + text: 'Duplicate row', + title: 'Duplicate the current row', + disabled: !hasSelection + }, + { + type: 'button', + onClick: () => handleInsertBeforeRow(), + icon: faPlus, + text: 'Insert before', + title: 'Insert a row before the current row', + disabled: !hasSelection + }, + { + type: 'button', + onClick: () => handleInsertAfterRow(), + icon: faPlus, + text: 'Insert after', + title: 'Insert a row after the current row', + disabled: !hasSelection + }, + { + type: 'button', + onClick: () => handleRemoveRow(), + icon: faTrashCan, + text: 'Remove row', + title: 'Remove current row', + disabled: !hasSelection + } + ] + } + ] + } + ] + + const items = onRenderContextMenu(defaultItems) + if (items === false) return + const props = { - json, - documentState: documentState, - parser, showTip, - - onEditValue: handleEditValue, - onEditRow: handleEditRow, - onToggleEnforceString: handleToggleEnforceString, - onCut: handleCut, - onCopy: handleCopy, - onPaste: handlePasteFromMenu, - onRemove: handleRemove, - onDuplicateRow: handleDuplicateRow, - onInsertBeforeRow: handleInsertBeforeRow, - onInsertAfterRow: handleInsertAfterRow, - onRemoveRow: handleRemoveRow, - - onRenderContextMenu, + items, onCloseContextMenu: function () { closeAbsolutePopup(popupId) focus() @@ -965,7 +1158,7 @@ } function handleContextMenu(event: Event) { - if (readOnly || isEditingSelection(documentState.selection)) { + if (isEditingSelection(documentState.selection)) { return } @@ -1014,10 +1207,6 @@ } function handleContextMenuFromTableMenu(event: MouseEvent) { - if (readOnly) { - return - } - openContextMenu({ anchor: findParentWithNodeName(event.target as HTMLElement, 'BUTTON'), offsetTop: 0, diff --git a/src/lib/components/modes/tablemode/contextmenu/TableContextMenu.svelte b/src/lib/components/modes/tablemode/contextmenu/TableContextMenu.svelte index 64ebefda..e7895c6a 100644 --- a/src/lib/components/modes/tablemode/contextmenu/TableContextMenu.svelte +++ b/src/lib/components/modes/tablemode/contextmenu/TableContextMenu.svelte @@ -1,246 +1,12 @@ import { createAutoScrollHandler } from '../../controls/createAutoScrollHandler.js' - import { faCheck, faCode, faWrench } from '@fortawesome/free-solid-svg-icons' + import { + faArrowRightArrowLeft, + faCaretSquareDown, + faCaretSquareUp, + faCheck, + faClone, + faCode, + faCopy, + faCropAlt, + faCut, + faFilter, + faPaste, + faPen, + faPlus, + faSortAmountDownAlt, + faTrashCan, + faWrench, + faCheckSquare, + faSquare + } from '@fortawesome/free-solid-svg-icons' import { createDebug } from '$lib/utils/debug.js' import type { JSONPatchDocument, JSONPath } from 'immutable-json-patch' import { compileJSONPointer, existsIn, getIn, immutableJSONPatch } from 'immutable-json-patch' @@ -61,7 +80,6 @@ getSelectionPaths, getSelectionRight, getSelectionUp, - hasSelectionContents, isAfterSelection, isEditingSelection, isInsideSelection, @@ -73,6 +91,8 @@ isValueSelection, removeEditModeFromSelection, selectAll, + singleItemSelected, + hasSelectionContents, updateSelectionInDocumentState } from '$lib/logic/selection.js' import { mapValidationErrors, validateJSON } from '$lib/logic/validation.js' @@ -96,7 +116,7 @@ repairPartialJson } from '$lib/utils/jsonUtils.js' import { keyComboFromEvent } from '$lib/utils/keyBindings.js' - import { isObjectOrArray, isUrl, stringConvert } from '$lib/utils/typeUtils.js' + import { isObject, isObjectOrArray, isUrl, stringConvert } from '$lib/utils/typeUtils.js' import { createFocusTracker } from '../../controls/createFocusTracker.js' import Message from '../../controls/Message.svelte' import ValidationErrorsOverview from '../../controls/ValidationErrorsOverview.svelte' @@ -161,6 +181,7 @@ } from '$lib/logic/actions.js' import JSONPreview from '../../controls/JSONPreview.svelte' import type { Context } from 'svelte-simple-modal' + import type { ContextMenuItem } from '$lib/types' const debug = createDebug('jsoneditor:TreeMode') @@ -211,6 +232,67 @@ // This is used to track whether the editor still has focus let modalOpen = false + $: selection = documentState.selection + + $: hasJson = json !== undefined + $: hasSelection = !!selection + $: rootSelected = selection ? isEmpty(getFocusPath(selection)) : false + $: focusValue = selection ? getIn(json, getFocusPath(selection)) : undefined + $: editValueText = Array.isArray(focusValue) + ? 'Edit array' + : isObject(focusValue) + ? 'Edit object' + : 'Edit value' + + $: canDuplicate = hasJson && hasSelectionContents(documentState.selection) && !rootSelected // must not be root + + $: canExtract = + hasJson && + selection != null && + (isMultiSelection(selection) || isValueSelection(selection)) && + !rootSelected // must not be root + + $: canEditKey = + hasJson && + selection != null && + singleItemSelected(selection) && + !rootSelected && + !Array.isArray(getIn(json, initial(getFocusPath(selection)))) + + $: canEditValue = hasJson && selection != null && singleItemSelected(selection) + $: canEnforceString = canEditValue && !isObjectOrArray(focusValue) + + $: convertMode = hasSelectionContents(documentState.selection) + $: insertOrConvertText = convertMode ? 'Convert to:' : 'Insert:' + $: canInsertOrConvertStructure = convertMode ? false : hasSelection + $: canInsertOrConvertObject = convertMode + ? canConvert(selection) && !isObject(focusValue) + : hasSelection + $: canInsertOrConvertArray = convertMode + ? canConvert(selection) && !Array.isArray(focusValue) + : hasSelection + $: canInsertOrConvertValue = convertMode + ? canConvert(selection) && isObjectOrArray(focusValue) + : hasSelection + + $: enforceString = + selection != null && focusValue + ? getEnforceString( + focusValue, + documentState.enforceStringMap, + compileJSONPointer(getFocusPath(selection)), + parser + ) + : false + + function handleInsertOrConvert(type: InsertType) { + if (convertMode) { + handleConvert(type) + } else { + handleInsertFromContextMenu(type) + } + } + createFocusTracker({ onMount, onDestroy, @@ -951,7 +1033,7 @@ readOnly || json === undefined || !documentState.selection || - !hasSelectionContents(documentState.selection) || + !hasSelectionContents || isEmpty(getFocusPath(documentState.selection)) // root selected, cannot duplicate ) { return @@ -1007,7 +1089,7 @@ }) } - function handleInsertFromContextMenu(type: 'value' | 'object' | 'array' | 'structure') { + function handleInsertFromContextMenu(type: InsertType) { if (isKeySelection(documentState.selection)) { // in this case, we do not want to rename the key, but replace the property updateSelection(createValueSelection(documentState.selection.path, false)) @@ -1020,7 +1102,7 @@ handleInsert(type) } - function handleConvert(type: 'value' | 'object' | 'array') { + function handleConvert(type: InsertType) { if (readOnly || !documentState.selection) { return } @@ -1033,7 +1115,11 @@ try { const path = getAnchorPath(documentState.selection) const currentValue: unknown = getIn(json, path) - const convertedValue = convertValue(currentValue, type, parser) + const convertedValue = convertValue( + currentValue, + type as 'value' | 'object' | 'array', + parser + ) if (convertedValue === currentValue) { // no change, do nothing return @@ -1801,33 +1887,246 @@ offsetLeft, showTip }: AbsolutePopupOptions) { - const props = { - json, - documentState: documentState, - parser, - showTip, - - onEditKey: handleEditKey, - onEditValue: handleEditValue, - onToggleEnforceString: handleToggleEnforceString, - - onCut: handleCut, - onCopy: handleCopy, - onPaste: handlePasteFromMenu, - - onRemove: handleRemove, - onDuplicate: handleDuplicate, - onExtract: handleExtract, + const defaultItems: ContextMenuItem[] = [ + { + type: 'row', + items: [ + { + type: 'button', + onClick: () => handleEditKey(), + icon: faPen, + text: 'Edit key', + title: 'Edit the key (Double-click on the key)', + disabled: !canEditKey + }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => handleEditValue(), + icon: faPen, + text: editValueText, + title: 'Edit the value (Double-click on the value)', + disabled: !canEditValue + }, + width: '11em', + items: [ + { + type: 'button', + icon: faPen, + text: editValueText, + title: 'Edit the value (Double-click on the value)', + onClick: () => handleEditValue(), + disabled: !canEditValue + }, + { + type: 'button', + icon: enforceString ? faCheckSquare : faSquare, + text: 'Enforce string', + title: 'Enforce keeping the value as string when it contains a numeric value', + onClick: () => handleToggleEnforceString(), + disabled: !canEnforceString + } + ] + } + ] + }, + { type: 'separator' }, + { + type: 'row', + items: [ + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => handleCut(true), + icon: faCut, + text: 'Cut', + title: 'Cut selected contents, formatted with indentation (Ctrl+X)', + disabled: !hasSelectionContents + }, + width: '10em', + items: [ + { + type: 'button', + icon: faCut, + text: 'Cut formatted', + title: 'Cut selected contents, formatted with indentation (Ctrl+X)', + onClick: () => handleCut(true), + disabled: !hasSelectionContents + }, + { + type: 'button', + icon: faCut, + text: 'Cut compacted', + title: 'Cut selected contents, without indentation (Ctrl+Shift+X)', + onClick: () => handleCut(false), + disabled: !hasSelectionContents + } + ] + }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => handleCopy(true), + icon: faCopy, + text: 'Copy', + title: 'Copy selected contents, formatted with indentation (Ctrl+C)', + disabled: !hasSelectionContents + }, + width: '12em', + items: [ + { + type: 'button', + icon: faCopy, + text: 'Copy formatted', + title: 'Copy selected contents, formatted with indentation (Ctrl+C)', + onClick: () => handleCopy(true), + disabled: !hasSelectionContents + }, + { + type: 'button', + icon: faCopy, + text: 'Copy compacted', + title: 'Copy selected contents, without indentation (Ctrl+Shift+C)', + onClick: () => handleCopy(false), + disabled: !hasSelectionContents + } + ] + }, + { + type: 'button', + onClick: () => handlePasteFromMenu(), + icon: faPaste, + text: 'Paste', + title: 'Paste clipboard contents (Ctrl+V)', + disabled: !hasSelection + } + ] + }, + { type: 'separator' }, + { + type: 'row', + items: [ + { + type: 'column', + items: [ + { + type: 'button', + onClick: () => handleDuplicate(), + icon: faClone, + text: 'Duplicate', + title: 'Duplicate selected contents (Ctrl+D)', + disabled: !canDuplicate + }, + { + type: 'button', + onClick: () => handleExtract(), + icon: faCropAlt, + text: 'Extract', + title: 'Extract selected contents', + disabled: !canExtract + }, + { + type: 'button', + onClick: () => handleSortSelection(), + icon: faSortAmountDownAlt, + text: 'Sort', + title: 'Sort array or object contents', + disabled: !hasSelectionContents + }, + { + type: 'button', + onClick: () => handleTransformSelection(), + icon: faFilter, + text: 'Transform', + title: 'Transform array or object contents (filter, sort, project)', + disabled: !hasSelectionContents + }, + { + type: 'button', + onClick: () => handleRemove(), + icon: faTrashCan, + text: 'Remove', + title: 'Remove selected contents (Delete)', + disabled: !hasSelectionContents + } + ] + }, + { + type: 'column', + items: [ + { type: 'label', text: insertOrConvertText }, + { + type: 'button', + onClick: () => handleInsertOrConvert('structure'), + icon: convertMode ? faArrowRightArrowLeft : faPlus, + text: 'Structure', + title: insertOrConvertText + ' structure', + disabled: !canInsertOrConvertStructure + }, + { + type: 'button', + onClick: () => handleInsertOrConvert('object'), + icon: convertMode ? faArrowRightArrowLeft : faPlus, + text: 'Object', + title: insertOrConvertText + ' structure', + disabled: !canInsertOrConvertObject + }, + { + type: 'button', + onClick: () => handleInsertOrConvert('array'), + icon: convertMode ? faArrowRightArrowLeft : faPlus, + text: 'Array', + title: insertOrConvertText + ' array', + disabled: !canInsertOrConvertArray + }, + { + type: 'button', + onClick: () => handleInsertOrConvert('value'), + icon: convertMode ? faArrowRightArrowLeft : faPlus, + text: 'Value', + title: insertOrConvertText + ' value', + disabled: !canInsertOrConvertValue + } + ] + } + ] + }, + { + type: 'separator' + }, + { + type: 'row', + items: [ + { + type: 'button', + onClick: () => handleInsertBefore(), + icon: faCaretSquareUp, + text: 'Insert before', + title: 'Select area before current entry to insert or paste contents', + disabled: !hasSelectionContents || rootSelected + }, + { + type: 'button', + onClick: () => handleInsertAfter(), + icon: faCaretSquareDown, + text: 'Insert after', + title: 'Select area after current entry to insert or paste contents', + disabled: !hasSelectionContents || rootSelected + } + ] + } + ] - onInsertBefore: handleInsertBefore, - onInsert: handleInsertFromContextMenu, - onConvert: handleConvert, - onInsertAfter: handleInsertAfter, + const items = onRenderContextMenu(defaultItems) - onSort: handleSortSelection, - onTransform: handleTransformSelection, + if (items === false) return - onRenderContextMenu, + const props = { + showTip, + items, onCloseContextMenu: function () { closeAbsolutePopup(popupId) focus() @@ -1853,7 +2152,7 @@ } function handleContextMenu(event?: Event) { - if (readOnly || isEditingSelection(documentState.selection)) { + if (isEditingSelection(documentState.selection)) { return } @@ -1902,10 +2201,6 @@ } function handleContextMenuFromTreeMenu(event: MouseEvent) { - if (readOnly) { - return - } - openContextMenu({ anchor: findParentWithNodeName(event.target as HTMLElement, 'BUTTON'), offsetTop: 0, diff --git a/src/lib/components/modes/treemode/contextmenu/TreeContextMenu.svelte b/src/lib/components/modes/treemode/contextmenu/TreeContextMenu.svelte index 85292286..e7895c6a 100644 --- a/src/lib/components/modes/treemode/contextmenu/TreeContextMenu.svelte +++ b/src/lib/components/modes/treemode/contextmenu/TreeContextMenu.svelte @@ -1,367 +1,12 @@ ContextMenuItem[] | undefined -export type OnRenderContextMenuInternal = (items: ContextMenuItem[]) => ContextMenuItem[] +) => ContextMenuItem[] | false | undefined +export type OnRenderContextMenuInternal = (items: ContextMenuItem[]) => ContextMenuItem[] | false export type OnError = (error: Error) => void export type OnFocus = () => void export type OnBlur = () => void From 243f509241ead7b641349e43467f006c775966d0 Mon Sep 17 00:00:00 2001 From: onePone Date: Thu, 7 Mar 2024 20:35:28 +0800 Subject: [PATCH 2/5] feat: create context menu items by function and remove ContextMenu component --- .../modes/tablemode/TableMode.svelte | 234 ++--------- .../contextmenu/TableContextMenu.svelte | 16 - .../createTableContextMenuItems.ts | 240 +++++++++++ .../components/modes/treemode/TreeMode.svelte | 351 ++--------------- .../contextmenu/TreeContextMenu.svelte | 16 - .../contextmenu/createTreeContextMenuItems.ts | 371 ++++++++++++++++++ src/routes/development/+page.svelte | 2 +- 7 files changed, 670 insertions(+), 560 deletions(-) delete mode 100644 src/lib/components/modes/tablemode/contextmenu/TableContextMenu.svelte create mode 100644 src/lib/components/modes/tablemode/contextmenu/createTableContextMenuItems.ts delete mode 100644 src/lib/components/modes/treemode/contextmenu/TreeContextMenu.svelte create mode 100644 src/lib/components/modes/treemode/contextmenu/createTreeContextMenuItems.ts diff --git a/src/lib/components/modes/tablemode/TableMode.svelte b/src/lib/components/modes/tablemode/TableMode.svelte index 5b51f9dc..c640956c 100644 --- a/src/lib/components/modes/tablemode/TableMode.svelte +++ b/src/lib/components/modes/tablemode/TableMode.svelte @@ -95,13 +95,10 @@ getFocusPath, isEditingSelection, isJSONSelection, - isKeySelection, - isMultiSelection, isValueSelection, pathInSelection, pathStartsWith, - removeEditModeFromSelection, - singleItemSelected + removeEditModeFromSelection } from '$lib/logic/selection.js' import { createHistory } from '$lib/logic/history.js' import ColumnHeader from './ColumnHeader.svelte' @@ -111,18 +108,7 @@ import { getContext, onDestroy, onMount, tick } from 'svelte' import { jsonrepair } from 'jsonrepair' import Message from '../../controls/Message.svelte' - import { - faCheck, - faClone, - faCode, - faCopy, - faCut, - faPaste, - faPen, - faPlus, - faTrashCan, - faWrench - } from '@fortawesome/free-solid-svg-icons' + import { faCheck, faCode, faWrench } from '@fortawesome/free-solid-svg-icons' import { measure } from '$lib/utils/timeUtils.js' import memoizeOne from 'memoize-one' import { validateJSON } from '$lib/logic/validation.js' @@ -149,7 +135,6 @@ } from '$lib/logic/actions.js' import JSONRepairModal from '../../modals/JSONRepairModal.svelte' import { resizeObserver } from '$lib/actions/resizeObserver.js' - import TableContextMenu from '../../../components/modes/tablemode/contextmenu/TableContextMenu.svelte' import CopyPasteModal from '../../../components/modals/CopyPasteModal.svelte' import ContextMenuPointer from '../../../components/controls/contextmenu/ContextMenuPointer.svelte' import TableModeWelcome from './TableModeWelcome.svelte' @@ -157,7 +142,8 @@ import RefreshColumnHeader from './RefreshColumnHeader.svelte' import type { Context } from 'svelte-simple-modal' import type { ContextMenuItem } from '$lib/types' - import { faCheckSquare, faSquare } from '@fortawesome/free-regular-svg-icons' + import createTableContextMenuItems from './contextmenu/createTableContextMenuItems' + import ContextMenu from '../../controls/contextmenu/ContextMenu.svelte' const debug = createDebug('jsoneditor:TableMode') const { open } = getContext('simple-modal') @@ -200,29 +186,6 @@ escapeUnicodeCharacters }) - $: selection = documentState.selection - - $: hasJson = json !== undefined - $: hasSelection = !!selection - $: focusValue = json !== undefined && selection ? getIn(json, getFocusPath(selection)) : undefined - - $: hasSelectionContents = - hasJson && - (isMultiSelection(selection) || isKeySelection(selection) || isValueSelection(selection)) - - $: canEditValue = hasJson && selection != null && singleItemSelected(selection) - $: canEnforceString = canEditValue && !isObjectOrArray(focusValue) - - $: enforceString = - selection != null && focusValue !== undefined - ? getEnforceString( - focusValue, - documentState.enforceStringMap, - compileJSONPointer(getFocusPath(selection)), - parser - ) - : false - let refJsonEditor: HTMLDivElement let refContents: HTMLDivElement | undefined let refHiddenInput: HTMLInputElement @@ -960,178 +923,31 @@ offsetLeft, showTip }: AbsolutePopupOptions) { - let defaultItems: ContextMenuItem[] = [ - { type: 'separator' }, - { - type: 'row', - items: [ - { - type: 'column', - items: [ - { type: 'label', text: 'Table cell:' }, - { - type: 'dropdown-button', - main: { - type: 'button', - onClick: () => handleEditValue(), - icon: faPen, - text: 'Edit', - title: 'Edit the value (Double-click on the value)', - disabled: !canEditValue - }, - width: '11em', - items: [ - { - type: 'button', - icon: faPen, - text: 'Edit', - title: 'Edit the value (Double-click on the value)', - onClick: () => handleEditValue(), - disabled: !canEditValue - }, - { - type: 'button', - icon: enforceString ? faCheckSquare : faSquare, - text: 'Enforce string', - title: 'Enforce keeping the value as string when it contains a numeric value', - onClick: () => handleToggleEnforceString(), - disabled: !canEnforceString - } - ] - }, - { - type: 'dropdown-button', - main: { - type: 'button', - onClick: () => handleCut(true), - icon: faCut, - text: 'Cut', - title: 'Cut selected contents, formatted with indentation (Ctrl+X)', - disabled: !hasSelectionContents - }, - width: '10em', - items: [ - { - type: 'button', - icon: faCut, - text: 'Cut formatted', - title: 'Cut selected contents, formatted with indentation (Ctrl+X)', - onClick: () => handleCut(true), - disabled: !hasSelectionContents - }, - { - type: 'button', - icon: faCut, - text: 'Cut compacted', - title: 'Cut selected contents, without indentation (Ctrl+Shift+X)', - onClick: () => handleCut(false), - disabled: !hasSelectionContents - } - ] - }, - { - type: 'dropdown-button', - main: { - type: 'button', - onClick: () => handleCopy(true), - icon: faCopy, - text: 'Copy', - title: 'Copy selected contents, formatted with indentation (Ctrl+C)', - disabled: !hasSelectionContents - }, - width: '12em', - items: [ - { - type: 'button', - icon: faCopy, - text: 'Copy formatted', - title: 'Copy selected contents, formatted with indentation (Ctrl+C)', - onClick: () => handleCopy(false), - disabled: !hasSelectionContents - }, - { - type: 'button', - icon: faCopy, - text: 'Copy compacted', - title: 'Copy selected contents, without indentation (Ctrl+Shift+C)', - onClick: () => handleCopy(false), - disabled: !hasSelectionContents - } - ] - }, - { - type: 'button', - onClick: () => handlePasteFromMenu(), - icon: faPaste, - text: 'Paste', - title: 'Paste clipboard contents (Ctrl+V)', - disabled: !hasSelection - }, - { - type: 'button', - onClick: () => handleRemove(), - icon: faTrashCan, - text: 'Remove', - title: 'Remove selected contents (Delete)', - disabled: !hasSelectionContents - } - ] - }, - { - type: 'column', - items: [ - { type: 'label', text: 'Table row:' }, - { - type: 'button', - onClick: () => handleEditRow(), - icon: faPen, - text: 'Edit row', - title: 'Edit the current row', - disabled: !hasSelectionContents - }, - { - type: 'button', - onClick: () => handleDuplicateRow(), - icon: faClone, - text: 'Duplicate row', - title: 'Duplicate the current row', - disabled: !hasSelection - }, - { - type: 'button', - onClick: () => handleInsertBeforeRow(), - icon: faPlus, - text: 'Insert before', - title: 'Insert a row before the current row', - disabled: !hasSelection - }, - { - type: 'button', - onClick: () => handleInsertAfterRow(), - icon: faPlus, - text: 'Insert after', - title: 'Insert a row after the current row', - disabled: !hasSelection - }, - { - type: 'button', - onClick: () => handleRemoveRow(), - icon: faTrashCan, - text: 'Remove row', - title: 'Remove current row', - disabled: !hasSelection - } - ] - } - ] - } - ] + let defaultItems: ContextMenuItem[] = createTableContextMenuItems({ + json, + documentState, + parser, + + onEditValue: handleEditValue, + onEditRow: handleEditRow, + onToggleEnforceString: handleToggleEnforceString, + onCut: handleCut, + onCopy: handleCopy, + onPaste: handlePasteFromMenu, + onRemove: handleRemove, + onDuplicateRow: handleDuplicateRow, + onInsertBeforeRow: handleInsertBeforeRow, + onInsertAfterRow: handleInsertAfterRow, + onRemoveRow: handleRemoveRow + }) const items = onRenderContextMenu(defaultItems) if (items === false) return const props = { - showTip, + tip: showTip + ? 'Tip: you can open this context menu via right-click or with Ctrl+Q' + : undefined, items, onCloseContextMenu: function () { closeAbsolutePopup(popupId) @@ -1141,7 +957,7 @@ modalOpen = true - const popupId = openAbsolutePopup(TableContextMenu, props, { + const popupId = openAbsolutePopup(ContextMenu, props, { left, top, offsetTop, diff --git a/src/lib/components/modes/tablemode/contextmenu/TableContextMenu.svelte b/src/lib/components/modes/tablemode/contextmenu/TableContextMenu.svelte deleted file mode 100644 index e7895c6a..00000000 --- a/src/lib/components/modes/tablemode/contextmenu/TableContextMenu.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/src/lib/components/modes/tablemode/contextmenu/createTableContextMenuItems.ts b/src/lib/components/modes/tablemode/contextmenu/createTableContextMenuItems.ts new file mode 100644 index 00000000..5c49d140 --- /dev/null +++ b/src/lib/components/modes/tablemode/contextmenu/createTableContextMenuItems.ts @@ -0,0 +1,240 @@ +import type { ContextMenuItem, DocumentState, JSONParser } from 'svelte-jsoneditor' +import { + faCheckSquare, + faClone, + faCopy, + faCut, + faPaste, + faPen, + faPlus, + faSquare, + faTrashCan +} from '@fortawesome/free-solid-svg-icons' +import { isKeySelection, isMultiSelection, isValueSelection } from 'svelte-jsoneditor' +import { compileJSONPointer, getIn } from 'immutable-json-patch' +import { getFocusPath, singleItemSelected } from '$lib/logic/selection' +import { isObjectOrArray } from '$lib/utils/typeUtils' +import { getEnforceString } from '$lib/logic/documentState' + +export default function ({ + json, + documentState, + parser, + onEditValue, + onEditRow, + onToggleEnforceString, + onCut, + onCopy, + onPaste, + onRemove, + onDuplicateRow, + onInsertBeforeRow, + onInsertAfterRow, + onRemoveRow +}: { + json: unknown | undefined + documentState: DocumentState + parser: JSONParser + onEditValue: () => void + onEditRow: () => void + onToggleEnforceString: () => void + onCut: (indent: boolean) => void + onCopy: (indent: boolean) => void + onPaste: () => void + onRemove: () => void + onDuplicateRow: () => void + onInsertBeforeRow: () => void + onInsertAfterRow: () => void + onRemoveRow: () => void +}): ContextMenuItem[] { + const selection = documentState.selection + + const hasJson = json !== undefined + const hasSelection = !!selection + const focusValue = + json !== undefined && selection ? getIn(json, getFocusPath(selection)) : undefined + + const hasSelectionContents = + hasJson && + (isMultiSelection(selection) || isKeySelection(selection) || isValueSelection(selection)) + + const canEditValue = hasJson && selection != null && singleItemSelected(selection) + const canEnforceString = canEditValue && !isObjectOrArray(focusValue) + + const enforceString = + selection != null && focusValue !== undefined + ? getEnforceString( + focusValue, + documentState.enforceStringMap, + compileJSONPointer(getFocusPath(selection)), + parser + ) + : false + + return [ + { type: 'separator' }, + { + type: 'row', + items: [ + { + type: 'column', + items: [ + { type: 'label', text: 'Table cell:' }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => onEditValue(), + icon: faPen, + text: 'Edit', + title: 'Edit the value (Double-click on the value)', + disabled: !canEditValue + }, + width: '11em', + items: [ + { + type: 'button', + icon: faPen, + text: 'Edit', + title: 'Edit the value (Double-click on the value)', + onClick: () => onEditValue(), + disabled: !canEditValue + }, + { + type: 'button', + icon: enforceString ? faCheckSquare : faSquare, + text: 'Enforce string', + title: 'Enforce keeping the value as string when it contains a numeric value', + onClick: () => onToggleEnforceString(), + disabled: !canEnforceString + } + ] + }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => onCut(true), + icon: faCut, + text: 'Cut', + title: 'Cut selected contents, formatted with indentation (Ctrl+X)', + disabled: !hasSelectionContents + }, + width: '10em', + items: [ + { + type: 'button', + icon: faCut, + text: 'Cut formatted', + title: 'Cut selected contents, formatted with indentation (Ctrl+X)', + onClick: () => onCut(true), + disabled: !hasSelectionContents + }, + { + type: 'button', + icon: faCut, + text: 'Cut compacted', + title: 'Cut selected contents, without indentation (Ctrl+Shift+X)', + onClick: () => onCut(false), + disabled: !hasSelectionContents + } + ] + }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => onCopy(true), + icon: faCopy, + text: 'Copy', + title: 'Copy selected contents, formatted with indentation (Ctrl+C)', + disabled: !hasSelectionContents + }, + width: '12em', + items: [ + { + type: 'button', + icon: faCopy, + text: 'Copy formatted', + title: 'Copy selected contents, formatted with indentation (Ctrl+C)', + onClick: () => onCopy(false), + disabled: !hasSelectionContents + }, + { + type: 'button', + icon: faCopy, + text: 'Copy compacted', + title: 'Copy selected contents, without indentation (Ctrl+Shift+C)', + onClick: () => onCopy(false), + disabled: !hasSelectionContents + } + ] + }, + { + type: 'button', + onClick: () => onPaste(), + icon: faPaste, + text: 'Paste', + title: 'Paste clipboard contents (Ctrl+V)', + disabled: !hasSelection + }, + { + type: 'button', + onClick: () => onRemove(), + icon: faTrashCan, + text: 'Remove', + title: 'Remove selected contents (Delete)', + disabled: !hasSelectionContents + } + ] + }, + { + type: 'column', + items: [ + { type: 'label', text: 'Table row:' }, + { + type: 'button', + onClick: () => onEditRow(), + icon: faPen, + text: 'Edit row', + title: 'Edit the current row', + disabled: !hasSelectionContents + }, + { + type: 'button', + onClick: () => onDuplicateRow(), + icon: faClone, + text: 'Duplicate row', + title: 'Duplicate the current row', + disabled: !hasSelection + }, + { + type: 'button', + onClick: () => onInsertBeforeRow(), + icon: faPlus, + text: 'Insert before', + title: 'Insert a row before the current row', + disabled: !hasSelection + }, + { + type: 'button', + onClick: () => onInsertAfterRow(), + icon: faPlus, + text: 'Insert after', + title: 'Insert a row after the current row', + disabled: !hasSelection + }, + { + type: 'button', + onClick: () => onRemoveRow(), + icon: faTrashCan, + text: 'Remove row', + title: 'Remove current row', + disabled: !hasSelection + } + ] + } + ] + } + ] +} diff --git a/src/lib/components/modes/treemode/TreeMode.svelte b/src/lib/components/modes/treemode/TreeMode.svelte index d731bec7..c5886f6b 100644 --- a/src/lib/components/modes/treemode/TreeMode.svelte +++ b/src/lib/components/modes/treemode/TreeMode.svelte @@ -2,26 +2,7 @@ - - diff --git a/src/lib/components/modes/treemode/contextmenu/createTreeContextMenuItems.ts b/src/lib/components/modes/treemode/contextmenu/createTreeContextMenuItems.ts new file mode 100644 index 00000000..8001cc5b --- /dev/null +++ b/src/lib/components/modes/treemode/contextmenu/createTreeContextMenuItems.ts @@ -0,0 +1,371 @@ +import { + faArrowRightArrowLeft, + faCaretSquareDown, + faCaretSquareUp, + faCheckSquare, + faClone, + faCopy, + faCropAlt, + faCut, + faFilter, + faPaste, + faPen, + faPlus, + faSortAmountDownAlt, + faSquare, + faTrashCan +} from '@fortawesome/free-solid-svg-icons' +import { + canConvert, + getFocusPath, + isKeySelection, + isMultiSelection, + isValueSelection, + singleItemSelected +} from '$lib/logic/selection' +import type { DocumentState, InsertType, JSONParser } from 'svelte-jsoneditor' +import { initial, isEmpty, isObject } from 'lodash-es' +import { compileJSONPointer, getIn } from 'immutable-json-patch' +import { isObjectOrArray } from '$lib/utils/typeUtils' +import { getEnforceString } from '$lib/logic/documentState' +import type { ContextMenuItem } from 'svelte-jsoneditor' + +export default function ({ + json, + documentState, + parser, + + onEditKey, + onEditValue, + onToggleEnforceString, + onCut, + onCopy, + onPaste, + onRemove, + onDuplicate, + onExtract, + onInsertBefore, + onInsert, + onConvert, + onInsertAfter, + onSort, + onTransform +}: { + json: unknown + documentState: DocumentState + parser: JSONParser + + onEditKey: () => void + onEditValue: () => void + onToggleEnforceString: () => void + onCut: (indent: boolean) => void + onCopy: (indent: boolean) => void + onPaste: () => void + onRemove: () => void + onDuplicate: () => void + onExtract: () => void + onInsertBefore: () => void + onInsert: (type: InsertType) => void + onConvert: (type: InsertType) => void + onInsertAfter: () => void + onSort: () => void + onTransform: () => void +}): ContextMenuItem[] { + const selection = documentState.selection + + const hasJson = json !== undefined + const hasSelection = !!selection + const rootSelected = selection ? isEmpty(getFocusPath(selection)) : false + const focusValue = selection ? getIn(json, getFocusPath(selection)) : undefined + const editValueText = Array.isArray(focusValue) + ? 'Edit array' + : isObject(focusValue) + ? 'Edit object' + : 'Edit value' + + const hasSelectionContents = + hasJson && + (isMultiSelection(selection) || isKeySelection(selection) || isValueSelection(selection)) + + const canDuplicate = hasJson && hasSelectionContents && !rootSelected // must not be root + const canExtract = + hasJson && + selection != null && + (isMultiSelection(selection) || isValueSelection(selection)) && + !rootSelected // must not be root + + const canEditKey = + hasJson && + selection != null && + singleItemSelected(selection) && + !rootSelected && + !Array.isArray(getIn(json, initial(getFocusPath(selection)))) + + const canEditValue = hasJson && selection != null && singleItemSelected(selection) + const canEnforceString = canEditValue && !isObjectOrArray(focusValue) + + const convertMode = hasSelectionContents + const insertOrConvertText = convertMode ? 'Convert to:' : 'Insert:' + + const canInsertOrConvertStructure = convertMode ? false : hasSelection + const canInsertOrConvertObject = convertMode + ? canConvert(selection) && !isObject(focusValue) + : hasSelection + const canInsertOrConvertArray = convertMode + ? canConvert(selection) && !Array.isArray(focusValue) + : hasSelection + const canInsertOrConvertValue = convertMode + ? canConvert(selection) && isObjectOrArray(focusValue) + : hasSelection + + const enforceString = + selection != null && focusValue + ? getEnforceString( + focusValue, + documentState.enforceStringMap, + compileJSONPointer(getFocusPath(selection)), + parser + ) + : false + + function handleInsertOrConvert(type: InsertType) { + if (hasSelectionContents) { + onConvert(type) + } else { + onInsert(type) + } + } + + return [ + { + type: 'row', + items: [ + { + type: 'button', + onClick: () => onEditKey(), + icon: faPen, + text: 'Edit key', + title: 'Edit the key (Double-click on the key)', + disabled: !canEditKey + }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => onEditValue(), + icon: faPen, + text: editValueText, + title: 'Edit the value (Double-click on the value)', + disabled: !canEditValue + }, + width: '11em', + items: [ + { + type: 'button', + icon: faPen, + text: editValueText, + title: 'Edit the value (Double-click on the value)', + onClick: () => onEditValue(), + disabled: !canEditValue + }, + { + type: 'button', + icon: enforceString ? faCheckSquare : faSquare, + text: 'Enforce string', + title: 'Enforce keeping the value as string when it contains a numeric value', + onClick: () => onToggleEnforceString(), + disabled: !canEnforceString + } + ] + } + ] + }, + { type: 'separator' }, + { + type: 'row', + items: [ + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => onCut(true), + icon: faCut, + text: 'Cut', + title: 'Cut selected contents, formatted with indentation (Ctrl+X)', + disabled: !hasSelectionContents + }, + width: '10em', + items: [ + { + type: 'button', + icon: faCut, + text: 'Cut formatted', + title: 'Cut selected contents, formatted with indentation (Ctrl+X)', + onClick: () => onCut(true), + disabled: !hasSelectionContents + }, + { + type: 'button', + icon: faCut, + text: 'Cut compacted', + title: 'Cut selected contents, without indentation (Ctrl+Shift+X)', + onClick: () => onCut(false), + disabled: !hasSelectionContents + } + ] + }, + { + type: 'dropdown-button', + main: { + type: 'button', + onClick: () => onCopy(true), + icon: faCopy, + text: 'Copy', + title: 'Copy selected contents, formatted with indentation (Ctrl+C)', + disabled: !hasSelectionContents + }, + width: '12em', + items: [ + { + type: 'button', + icon: faCopy, + text: 'Copy formatted', + title: 'Copy selected contents, formatted with indentation (Ctrl+C)', + onClick: () => onCopy(true), + disabled: !hasSelectionContents + }, + { + type: 'button', + icon: faCopy, + text: 'Copy compacted', + title: 'Copy selected contents, without indentation (Ctrl+Shift+C)', + onClick: () => onCopy(false), + disabled: !hasSelectionContents + } + ] + }, + { + type: 'button', + onClick: () => onPaste(), + icon: faPaste, + text: 'Paste', + title: 'Paste clipboard contents (Ctrl+V)', + disabled: !hasSelection + } + ] + }, + { type: 'separator' }, + { + type: 'row', + items: [ + { + type: 'column', + items: [ + { + type: 'button', + onClick: () => onDuplicate(), + icon: faClone, + text: 'Duplicate', + title: 'Duplicate selected contents (Ctrl+D)', + disabled: !canDuplicate + }, + { + type: 'button', + onClick: () => onExtract(), + icon: faCropAlt, + text: 'Extract', + title: 'Extract selected contents', + disabled: !canExtract + }, + { + type: 'button', + onClick: () => onSort(), + icon: faSortAmountDownAlt, + text: 'Sort', + title: 'Sort array or object contents', + disabled: !hasSelectionContents + }, + { + type: 'button', + onClick: () => onTransform(), + icon: faFilter, + text: 'Transform', + title: 'Transform array or object contents (filter, sort, project)', + disabled: !hasSelectionContents + }, + { + type: 'button', + onClick: () => onRemove(), + icon: faTrashCan, + text: 'Remove', + title: 'Remove selected contents (Delete)', + disabled: !hasSelectionContents + } + ] + }, + { + type: 'column', + items: [ + { type: 'label', text: insertOrConvertText }, + { + type: 'button', + onClick: () => handleInsertOrConvert('structure'), + icon: convertMode ? faArrowRightArrowLeft : faPlus, + text: 'Structure', + title: insertOrConvertText + ' structure', + disabled: !canInsertOrConvertStructure + }, + { + type: 'button', + onClick: () => handleInsertOrConvert('object'), + icon: convertMode ? faArrowRightArrowLeft : faPlus, + text: 'Object', + title: insertOrConvertText + ' structure', + disabled: !canInsertOrConvertObject + }, + { + type: 'button', + onClick: () => handleInsertOrConvert('array'), + icon: convertMode ? faArrowRightArrowLeft : faPlus, + text: 'Array', + title: insertOrConvertText + ' array', + disabled: !canInsertOrConvertArray + }, + { + type: 'button', + onClick: () => handleInsertOrConvert('value'), + icon: convertMode ? faArrowRightArrowLeft : faPlus, + text: 'Value', + title: insertOrConvertText + ' value', + disabled: !canInsertOrConvertValue + } + ] + } + ] + }, + { + type: 'separator' + }, + { + type: 'row', + items: [ + { + type: 'button', + onClick: () => onInsertBefore(), + icon: faCaretSquareUp, + text: 'Insert before', + title: 'Select area before current entry to insert or paste contents', + disabled: !hasSelectionContents || rootSelected + }, + { + type: 'button', + onClick: () => onInsertAfter(), + icon: faCaretSquareDown, + text: 'Insert after', + title: 'Select area after current entry to insert or paste contents', + disabled: !hasSelectionContents || rootSelected + } + ] + } + ] +} diff --git a/src/routes/development/+page.svelte b/src/routes/development/+page.svelte index c7a38b9d..b19fd3c2 100644 --- a/src/routes/development/+page.svelte +++ b/src/routes/development/+page.svelte @@ -380,7 +380,7 @@ } function onRenderContextMenu(items: ContextMenuItem[], content: RenderMenuContext) { console.log('onRenderContextMenu', items, content) - return items + return $readOnly ? false : items // This return is equivalent to onRenderContextMenu is undefined } function openInWindow() { From 01611bfa552928559f2c8267205b2a2537ebb145 Mon Sep 17 00:00:00 2001 From: onePone Date: Thu, 7 Mar 2024 20:38:44 +0800 Subject: [PATCH 3/5] chore: replace the inline return to have braces --- src/lib/components/modes/tablemode/TableMode.svelte | 5 ++++- src/lib/components/modes/treemode/TreeMode.svelte | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/components/modes/tablemode/TableMode.svelte b/src/lib/components/modes/tablemode/TableMode.svelte index c640956c..860ac51b 100644 --- a/src/lib/components/modes/tablemode/TableMode.svelte +++ b/src/lib/components/modes/tablemode/TableMode.svelte @@ -942,7 +942,10 @@ }) const items = onRenderContextMenu(defaultItems) - if (items === false) return + + if (items === false) { + return + } const props = { tip: showTip diff --git a/src/lib/components/modes/treemode/TreeMode.svelte b/src/lib/components/modes/treemode/TreeMode.svelte index c5886f6b..ebb7b9e7 100644 --- a/src/lib/components/modes/treemode/TreeMode.svelte +++ b/src/lib/components/modes/treemode/TreeMode.svelte @@ -1835,7 +1835,9 @@ const items = onRenderContextMenu(defaultItems) - if (items === false) return + if (items === false) { + return + } const props = { tip: showTip From 539d6c44b1240d4b5f8f14ebff797d945d96f149 Mon Sep 17 00:00:00 2001 From: onePone Date: Fri, 8 Mar 2024 10:55:19 +0800 Subject: [PATCH 4/5] feat: define ConvertType type --- src/lib/components/modes/treemode/TreeMode.svelte | 3 ++- src/lib/types.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/components/modes/treemode/TreeMode.svelte b/src/lib/components/modes/treemode/TreeMode.svelte index ebb7b9e7..ed71eac3 100644 --- a/src/lib/components/modes/treemode/TreeMode.svelte +++ b/src/lib/components/modes/treemode/TreeMode.svelte @@ -113,6 +113,7 @@ AfterPatchCallback, Content, ContentErrors, + ConvertType, DocumentState, HistoryItem, InsertType, @@ -1022,7 +1023,7 @@ handleInsert(type) } - function handleConvert(type: InsertType) { + function handleConvert(type: ConvertType) { if (readOnly || !documentState.selection) { return } diff --git a/src/lib/types.ts b/src/lib/types.ts index 18bad1a0..47afbdcd 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -392,7 +392,8 @@ export interface HistoryItem { } } -export type InsertType = 'value' | 'object' | 'array' | 'structure' +export type ConvertType = 'value' | 'object' | 'array' +export type InsertType = ConvertType | 'structure' export interface PopupEntry { id: number From f04418a14d88cfbbb767a586463313f47b56cad0 Mon Sep 17 00:00:00 2001 From: onePone Date: Fri, 8 Mar 2024 10:56:25 +0800 Subject: [PATCH 5/5] chore: define defaultItems as const --- src/lib/components/modes/tablemode/TableMode.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/modes/tablemode/TableMode.svelte b/src/lib/components/modes/tablemode/TableMode.svelte index 860ac51b..de54e012 100644 --- a/src/lib/components/modes/tablemode/TableMode.svelte +++ b/src/lib/components/modes/tablemode/TableMode.svelte @@ -923,7 +923,7 @@ offsetLeft, showTip }: AbsolutePopupOptions) { - let defaultItems: ContextMenuItem[] = createTableContextMenuItems({ + const defaultItems: ContextMenuItem[] = createTableContextMenuItems({ json, documentState, parser,