Cut/Copy/Paste selection starting to work (WIP)
This commit is contained in:
parent
8425579718
commit
dd989f9a64
123
src/actions.js
123
src/actions.js
|
@ -1,4 +1,9 @@
|
|||
import { compileJSONPointer, toEsonPath, esonToJson, findNextProp } from './eson'
|
||||
import last from 'lodash/last'
|
||||
import initial from 'lodash/initial'
|
||||
import {
|
||||
compileJSONPointer, toEsonPath, esonToJson, findNextProp,
|
||||
pathsFromSelection, findRootPath, findSelectionIndices
|
||||
} from './eson'
|
||||
import { findUniqueName } from './utils/stringUtils'
|
||||
import { getIn } from './utils/immutabilityHelpers'
|
||||
import { isObject, stringConvert } from './utils/typeUtils'
|
||||
|
@ -168,6 +173,108 @@ export function insert (data, path, type) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSONPatch for an insert action.
|
||||
*
|
||||
* This function needs the current data in order to be able to determine
|
||||
* a unique property name for the inserted node in case of duplicating
|
||||
* and object property
|
||||
*
|
||||
* @param {ESON} data
|
||||
* @param {Path} path
|
||||
* @param {Array.<{name?: string, value: JSONType, type?: ESONType}>} values
|
||||
* @return {Array}
|
||||
*/
|
||||
export function insertBefore (data, path, values) { // TODO: find a better name and define datastructure for values
|
||||
const parentPath = initial(path)
|
||||
const esonPath = toEsonPath(data, parentPath)
|
||||
const parent = getIn(data, esonPath)
|
||||
|
||||
if (parent.type === 'Array') {
|
||||
const startIndex = parseInt(last(path))
|
||||
return values.map((entry, offset) => ({
|
||||
op: 'add',
|
||||
path: compileJSONPointer(parentPath.concat(startIndex + offset)),
|
||||
value: entry.value,
|
||||
jsoneditor: {
|
||||
type: entry.type
|
||||
}
|
||||
}))
|
||||
}
|
||||
else { // object.type === 'Object'
|
||||
const before = last(path)
|
||||
return values.map(entry => {
|
||||
const newProp = findUniqueName(entry.name, parent.props.map(p => p.name))
|
||||
return {
|
||||
op: 'add',
|
||||
path: compileJSONPointer(parentPath.concat(newProp)),
|
||||
value: entry.value,
|
||||
jsoneditor: {
|
||||
type: entry.type,
|
||||
before
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSONPatch for an insert action.
|
||||
*
|
||||
* This function needs the current data in order to be able to determine
|
||||
* a unique property name for the inserted node in case of duplicating
|
||||
* and object property
|
||||
*
|
||||
* @param {ESON} data
|
||||
* @param {Selection} selection
|
||||
* @param {Array.<{name?: string, value: JSONType, type?: ESONType}>} values
|
||||
* @return {Array}
|
||||
*/
|
||||
export function replace (data, selection, values) { // TODO: find a better name and define datastructure for values
|
||||
|
||||
const rootPath = findRootPath(selection)
|
||||
const start = selection.start.path[rootPath.length]
|
||||
const end = selection.end.path[rootPath.length]
|
||||
console.log('rootPath', rootPath, start, end)
|
||||
const root = getIn(data, toEsonPath(data, rootPath))
|
||||
const { minIndex, maxIndex } = findSelectionIndices(root, start, end)
|
||||
console.log('selection', minIndex, maxIndex)
|
||||
|
||||
if (root.type === 'Array') {
|
||||
const removeActions = removeAll(pathsFromSelection(data, selection))
|
||||
const insertActions = values.map((entry, offset) => ({
|
||||
op: 'add',
|
||||
path: compileJSONPointer(rootPath.concat(minIndex + offset)),
|
||||
value: entry.value,
|
||||
jsoneditor: {
|
||||
type: entry.type
|
||||
}
|
||||
}))
|
||||
|
||||
return removeActions.concat(insertActions)
|
||||
}
|
||||
else { // object.type === 'Object'
|
||||
const nextProp = root.props && root.props[maxIndex]
|
||||
const before = nextProp ? nextProp.name : null
|
||||
|
||||
const removeActions = removeAll(pathsFromSelection(data, selection))
|
||||
const insertActions = values.map(entry => {
|
||||
const newProp = findUniqueName(entry.name, root.props.map(p => p.name))
|
||||
return {
|
||||
op: 'add',
|
||||
path: compileJSONPointer(rootPath.concat(newProp)),
|
||||
value: entry.value,
|
||||
jsoneditor: {
|
||||
type: entry.type,
|
||||
before
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return removeActions.concat(insertActions)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSONPatch for an append action.
|
||||
*
|
||||
|
@ -214,6 +321,7 @@ export function append (data, parentPath, type) {
|
|||
/**
|
||||
* Create a JSONPatch for a remove action
|
||||
* @param {Path} path
|
||||
* @return {ESONPatch}
|
||||
*/
|
||||
export function remove (path) {
|
||||
return [{
|
||||
|
@ -222,6 +330,19 @@ export function remove (path) {
|
|||
}]
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSONPatch for a multiple remove action
|
||||
* @param {Path[]} paths
|
||||
* @return {ESONPatch}
|
||||
*/
|
||||
export function removeAll (paths) {
|
||||
return paths.map(path => ({
|
||||
op: 'remove',
|
||||
path: compileJSONPointer(path)
|
||||
}))
|
||||
}
|
||||
// TODO: test removeAll
|
||||
|
||||
/**
|
||||
* Create a JSONPatch to order the items of an array or the properties of an object in ascending
|
||||
* or descending order
|
||||
|
|
|
@ -22,8 +22,8 @@ import {
|
|||
} from '../eson'
|
||||
import { patchEson } from '../patchEson'
|
||||
import {
|
||||
duplicate, insert, append, remove,
|
||||
changeType, changeValue, changeProperty, sort
|
||||
duplicate, insert, insertBefore, append, remove, removeAll, replace,
|
||||
createEntry, changeType, changeValue, changeProperty, sort
|
||||
} from '../actions'
|
||||
import JSONNode from './JSONNode'
|
||||
import JSONNodeView from './JSONNodeView'
|
||||
|
@ -90,14 +90,15 @@ export default class TreeMode extends Component {
|
|||
onChangeValue: this.handleChangeValue,
|
||||
onChangeType: this.handleChangeType,
|
||||
onInsert: this.handleInsert,
|
||||
onInsertStructure: this.handleInsertStructure,
|
||||
onAppend: this.handleAppend,
|
||||
onDuplicate: this.handleDuplicate,
|
||||
onRemove: this.handleRemove,
|
||||
onSort: this.handleSort,
|
||||
|
||||
onCut: this.handleMenuCut,
|
||||
onCopy: this.handleMenuCopy,
|
||||
onPaste: this.handleMenuPaste,
|
||||
onCut: this.handleCut,
|
||||
onCopy: this.handleCopy,
|
||||
onPaste: this.handlePaste,
|
||||
|
||||
onExpand: this.handleExpand,
|
||||
|
||||
|
@ -117,7 +118,7 @@ export default class TreeMode extends Component {
|
|||
end: null, // ESONPointer
|
||||
},
|
||||
|
||||
clipboard: null // array entries {prop: string, value: JSON}
|
||||
clipboard: null // array entries {name: string, value: JSONType}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,6 +130,15 @@ export default class TreeMode extends Component {
|
|||
this.applyProps(nextProps, this.props)
|
||||
}
|
||||
|
||||
// TODO: use or cleanup
|
||||
// componentDidMount () {
|
||||
// document.addEventListener('keydown', this.handleKeyDown)
|
||||
// }
|
||||
//
|
||||
// componentWillUnmount () {
|
||||
// document.removeEventListener('keydown', this.handleKeyDown)
|
||||
// }
|
||||
|
||||
// TODO: create some sort of watcher structure for these props? Is there a React pattern for that?
|
||||
applyProps (nextProps, currentProps) {
|
||||
// Apply text
|
||||
|
@ -340,12 +350,21 @@ export default class TreeMode extends Component {
|
|||
}
|
||||
|
||||
handleInsert = (path, type) => {
|
||||
this.handlePatch(insert(this.state.data, path, type))
|
||||
this.handlePatch(insert(this.state.data, path, createEntry(type), type))
|
||||
|
||||
this.setState({ selection : null }) // TODO: select the inserted entry
|
||||
|
||||
// apply focus to new node
|
||||
this.focusToNext(path)
|
||||
}
|
||||
|
||||
handleInsertStructure = (path) => {
|
||||
// TODO: implement handleInsertStructure
|
||||
console.log('handleInsertStructure', path)
|
||||
alert('not yet implemented...')
|
||||
|
||||
}
|
||||
|
||||
handleAppend = (parentPath, type) => {
|
||||
this.handlePatch(append(this.state.data, parentPath, type))
|
||||
|
||||
|
@ -361,15 +380,24 @@ export default class TreeMode extends Component {
|
|||
}
|
||||
|
||||
handleRemove = (path) => {
|
||||
// apply focus to next sibling element if existing, else to the previous element
|
||||
const fromElement = findNode(this.refs.contents, path)
|
||||
const success = moveDownSibling(fromElement, 'property')
|
||||
if (!success) {
|
||||
moveUp(fromElement, 'property')
|
||||
}
|
||||
if (path) {
|
||||
// apply focus to next sibling element if existing, else to the previous element
|
||||
const fromElement = findNode(this.refs.contents, path)
|
||||
const success = moveDownSibling(fromElement, 'property')
|
||||
if (!success) {
|
||||
moveUp(fromElement, 'property')
|
||||
}
|
||||
|
||||
this.setState({ selection : null })
|
||||
this.handlePatch(remove(path))
|
||||
this.setState({ selection : null })
|
||||
this.handlePatch(remove(path))
|
||||
}
|
||||
else if (this.state.selection) {
|
||||
// remove selection
|
||||
// TODO: select next property? (same as when removing a path?)
|
||||
const paths = pathsFromSelection(this.state.data, this.state.selection)
|
||||
this.setState({ selection: null })
|
||||
this.handlePatch(removeAll(paths))
|
||||
}
|
||||
}
|
||||
|
||||
moveUp = (event) => {
|
||||
|
@ -403,54 +431,32 @@ export default class TreeMode extends Component {
|
|||
}
|
||||
|
||||
handleKeyDownCut = (event) => {
|
||||
const { selection } = this.state
|
||||
if (selection) {
|
||||
if (this.state.selection) {
|
||||
event.preventDefault()
|
||||
}
|
||||
this.handleCut(selection)
|
||||
this.handleCut()
|
||||
}
|
||||
|
||||
handleKeyDownCopy = (event) => {
|
||||
const { selection } = this.state
|
||||
if (selection) {
|
||||
if (this.state.selection) {
|
||||
event.preventDefault()
|
||||
}
|
||||
this.handleCopy(selection)
|
||||
this.handleCopy()
|
||||
}
|
||||
|
||||
handleKeyDownPaste = (event) => {
|
||||
const { clipboard, selection } = this.state
|
||||
const { clipboard, data } = this.state
|
||||
|
||||
if (clipboard && clipboard.length > 0) {
|
||||
event.preventDefault()
|
||||
if (selection) {
|
||||
this.handlePaste(clipboard, selection, null)
|
||||
}
|
||||
else {
|
||||
// no selection -> paste after current path
|
||||
const path = this.findDataPathFromElement(event.target)
|
||||
this.handlePaste(clipboard, null, path)
|
||||
}
|
||||
|
||||
const path = this.findDataPathFromElement(event.target)
|
||||
this.handlePatch(insertBefore(data, path, clipboard))
|
||||
}
|
||||
}
|
||||
|
||||
handleMenuCut = (path) => {
|
||||
const selection = { start: { path }, end: { path }}
|
||||
this.handleCut(selection)
|
||||
}
|
||||
|
||||
handleMenuCopy = (path) => {
|
||||
const selection = { start: { path }, end: { path }}
|
||||
this.handleCopy(selection)
|
||||
}
|
||||
|
||||
handleMenuPaste = (path) => {
|
||||
const { clipboard } = this.state
|
||||
if (clipboard && clipboard.length > 0) {
|
||||
this.handlePaste(clipboard, null, path)
|
||||
}
|
||||
}
|
||||
|
||||
handleCut = (selection: ESONSelection) => {
|
||||
handleCut = () => {
|
||||
const selection = this.state.selection
|
||||
if (selection && selection.start && selection.end) {
|
||||
const data = this.state.data
|
||||
const paths = pathsFromSelection(data, selection)
|
||||
|
@ -469,7 +475,8 @@ export default class TreeMode extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleCopy = (selection: ESONSelection) => {
|
||||
handleCopy = () => {
|
||||
const selection = this.state.selection
|
||||
if (selection && selection.start && selection.end) {
|
||||
const data = this.state.data
|
||||
const paths = pathsFromSelection(data, selection)
|
||||
|
@ -483,42 +490,12 @@ export default class TreeMode extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handlePaste = (clipboard, selection: ESONSelection, path: JSONPath) => {
|
||||
const { data } = this.state
|
||||
handlePaste = () => {
|
||||
const { data, selection, clipboard } = this.state
|
||||
|
||||
if (clipboard && clipboard.length > 0) {
|
||||
if (selection && clipboard && clipboard.length > 0) {
|
||||
// FIXME: handle pasting in an empty object or array
|
||||
|
||||
if (path && path.length > 0) {
|
||||
const parentPath = initial(path)
|
||||
const parent = getIn(data, toEsonPath(data, parentPath))
|
||||
const isObject = parent.type === 'Object'
|
||||
|
||||
if (parent.type === 'Object') {
|
||||
const existingProps = parent.props.map(p => p.name)
|
||||
const prop = last(path)
|
||||
const patch = clipboard.map(entry => ({
|
||||
op: 'add',
|
||||
path: compileJSONPointer(parentPath.concat(findUniqueName(entry.name, existingProps))),
|
||||
value: entry.value,
|
||||
jsoneditor: { before: prop }
|
||||
}))
|
||||
|
||||
this.handlePatch(patch)
|
||||
}
|
||||
else { // parent.type === 'Array'
|
||||
const patch = clipboard.map(entry => ({
|
||||
op: 'add',
|
||||
path: compileJSONPointer(path),
|
||||
value: entry.value
|
||||
}))
|
||||
|
||||
this.handlePatch(patch)
|
||||
}
|
||||
}
|
||||
else if (selection){
|
||||
console.log('TODO: replace selection')
|
||||
}
|
||||
this.handlePatch(replace(data, selection, clipboard))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -673,7 +650,10 @@ export default class TreeMode extends Component {
|
|||
|
||||
handleTouchStart = (event) => {
|
||||
const pointer = this.findESONPointerFromElement(event.target)
|
||||
if (pointer) {
|
||||
const clickedOnEmptySpace = (event.target.nodeName === 'DIV') &&
|
||||
(event.target.contentEditable !== 'true')
|
||||
|
||||
if (clickedOnEmptySpace && pointer) {
|
||||
this.setState({ selection: {start: pointer, end: pointer}})
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -6,13 +6,13 @@ import { keyComboFromEvent } from '../../utils/keyBindings'
|
|||
const MENU_CLASS_NAME = 'jsoneditor-floating-menu'
|
||||
const MENU_ITEM_CLASS_NAME = 'jsoneditor-floating-menu-item'
|
||||
|
||||
// Array: Sort | Map | Filter | Duplicate | Cut | Copy | Remove
|
||||
// Array: Sort | Map | Filter | Duplicate | Cut | Copy | Paste | Remove
|
||||
// advanced sort (asc, desc, nested fields, custom comparator)
|
||||
// sort, map, filter, open a popup covering the editor (not the whole page)
|
||||
// (or if it's small, can be a dropdown)
|
||||
// Object: Sort | Duplicate | Cut | Copy | Remove
|
||||
// Object: Sort | Duplicate | Cut | Copy | Paste | Remove
|
||||
// simple sort (asc/desc)
|
||||
// Value: [x] String | Duplicate | Cut | Copy | Remove
|
||||
// Value: [x] String | Duplicate | Cut | Copy | Paste | Remove
|
||||
// String is a checkmark
|
||||
// Between: Insert Structure | Insert Value | Insert Object | Insert Array | Paste
|
||||
// inserting (value selected): [field] [value]
|
||||
|
@ -81,52 +81,55 @@ const CREATE_TYPE = {
|
|||
remove: (path, events) => h('button', {
|
||||
key: 'remove',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
onClick: () => events.onRemove(path),
|
||||
onClick: () => events.onRemove(null), // do not pass path: we want to remove selection
|
||||
title: 'Remove'
|
||||
}, 'Remove'),
|
||||
|
||||
insertStructure: (path, events) => h('button', {
|
||||
key: 'insertStructure',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
// onClick: () => events.onRemove(path),
|
||||
onClick: () => events.onInsertStructure(path),
|
||||
title: 'Insert a new object with the same data structure as the item above'
|
||||
}, 'Insert structure'),
|
||||
|
||||
insertValue: (path, events) => h('button', {
|
||||
key: 'insertValue',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
// onClick: () => events.onRemove(path),
|
||||
onClick: () => events.onInsert(path, 'value'),
|
||||
title: 'Insert value'
|
||||
}, 'Insert value'),
|
||||
|
||||
insertObject: (path, events) => h('button', {
|
||||
key: 'insertObject',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
// onClick: () => events.onRemove(path),
|
||||
onClick: () => events.onInsert(path, 'Object'),
|
||||
title: 'Insert Object'
|
||||
}, 'Insert Object'),
|
||||
|
||||
insertArray: (path, events) => h('button', {
|
||||
key: 'insertArray',
|
||||
className: MENU_ITEM_CLASS_NAME,
|
||||
// onClick: () => events.onRemove(path),
|
||||
onClick: () => events.onInsert(path, 'Array'),
|
||||
title: 'Insert Array'
|
||||
}, 'Insert Array'),
|
||||
|
||||
}
|
||||
|
||||
export default class FloatingMenu extends PureComponent {
|
||||
componentDidMount () {
|
||||
setTimeout(() => {
|
||||
const firstButton = this.refs.root && this.refs.root.querySelector('button')
|
||||
if (firstButton) {
|
||||
firstButton.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
// TODO: use or cleanup
|
||||
// componentDidMount () {
|
||||
// setTimeout(() => {
|
||||
// const firstButton = this.refs.root && this.refs.root.querySelector('button')
|
||||
// // TODO: find a better way to ensure the JSONEditor has focus so the quickkeys work
|
||||
// // console.log(document.activeElement)
|
||||
// if (firstButton && document.activeElement === document.body) {
|
||||
// firstButton.focus()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
render () {
|
||||
return h('div', {ref: 'root', className: MENU_CLASS_NAME}, this.props.items.map(item => {
|
||||
const items = this.props.items.map(item => {
|
||||
const type = typeof item === 'string' ? item : item.type
|
||||
const createType = CREATE_TYPE[type]
|
||||
if (createType) {
|
||||
|
@ -135,6 +138,17 @@ export default class FloatingMenu extends PureComponent {
|
|||
else {
|
||||
throw new Error('Unknown type of menu item for floating menu: ' + JSON.stringify(item))
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
return h('div', {
|
||||
// ref: 'root',
|
||||
className: MENU_CLASS_NAME,
|
||||
onMouseDown: this.handleTouchStart,
|
||||
onTouchStart: this.handleTouchStart,
|
||||
}, items)
|
||||
}
|
||||
|
||||
handleTouchStart = (event) => {
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
|
82
src/eson.js
82
src/eson.js
|
@ -363,33 +363,23 @@ export function applySelection (eson: ESON, selection: ESONSelection) {
|
|||
}
|
||||
|
||||
// find the parent node shared by both start and end of the selection
|
||||
const rootPath = findSharedPath(selection.start.path, selection.end.path)
|
||||
const rootPath = findRootPath(selection)
|
||||
const rootEsonPath = toEsonPath(eson, rootPath)
|
||||
|
||||
if (rootPath.length === selection.start.path.length || rootPath.length === selection.end.path.length) {
|
||||
// select a single node
|
||||
const selectionType = (selection.start.area === 'after') ? SELECTED_AFTER : SELECTED_END
|
||||
console.log('selectionType', selectionType, selection)
|
||||
return updateIn(eson, rootEsonPath, (root) => {
|
||||
const start = selection.start.path[rootPath.length]
|
||||
const end = selection.end.path[rootPath.length]
|
||||
const { minIndex, maxIndex } = findSelectionIndices(root, start, end)
|
||||
|
||||
const childsKey = (root.type === 'Object') ? 'props' : 'items' // property name of the array with props/items
|
||||
const childsBefore = root[childsKey].slice(0, minIndex)
|
||||
const childsUpdated = root[childsKey].slice(minIndex, maxIndex)
|
||||
.map((child, index) => setIn(child, ['value', 'selected'], index === 0 ? SELECTED_END : SELECTED))
|
||||
const childsAfter = root[childsKey].slice(maxIndex)
|
||||
// FIXME: actually mark the end index as SELECTED_END, currently we select the first index
|
||||
return setIn(eson, rootEsonPath.concat(['selected']), selectionType)
|
||||
}
|
||||
else {
|
||||
// select multiple childs of an object or array
|
||||
return updateIn(eson, rootEsonPath, (root) => {
|
||||
const start = selection.start.path[rootPath.length]
|
||||
const end = selection.end.path[rootPath.length]
|
||||
const { minIndex, maxIndex } = findSelectionIndices(root, start, end)
|
||||
|
||||
const childsKey = (root.type === 'Object') ? 'props' : 'items' // property name of the array with props/items
|
||||
const childsBefore = root[childsKey].slice(0, minIndex)
|
||||
const childsUpdated = root[childsKey].slice(minIndex, maxIndex)
|
||||
.map((child, index) => setIn(child, ['value', 'selected'], index === 0 ? SELECTED_END : SELECTED))
|
||||
const childsAfter = root[childsKey].slice(maxIndex)
|
||||
// FIXME: actually mark the end index as SELECTED_END, currently we select the first index
|
||||
|
||||
return setIn(root, [childsKey], childsBefore.concat(childsUpdated, childsAfter))
|
||||
})
|
||||
}
|
||||
return setIn(root, [childsKey], childsBefore.concat(childsUpdated, childsAfter))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -413,26 +403,19 @@ export function findSelectionIndices (root: ESON, start: string, end: string) :
|
|||
*/
|
||||
export function pathsFromSelection (eson: ESON, selection: ESONSelection): JSONPath[] {
|
||||
// find the parent node shared by both start and end of the selection
|
||||
const rootPath = findSharedPath(selection.start.path, selection.end.path)
|
||||
const rootPath = findRootPath(selection)
|
||||
const rootEsonPath = toEsonPath(eson, rootPath)
|
||||
|
||||
if (rootPath.length === selection.start.path.length || rootPath.length === selection.end.path.length) {
|
||||
// select a single node
|
||||
return [ rootPath ]
|
||||
}
|
||||
else {
|
||||
// select multiple childs of an object or array
|
||||
const root = getIn(eson, rootEsonPath)
|
||||
const start = selection.start.path[rootPath.length]
|
||||
const end = selection.end.path[rootPath.length]
|
||||
const { minIndex, maxIndex } = findSelectionIndices(root, start, end)
|
||||
const root = getIn(eson, rootEsonPath)
|
||||
const start = selection.start.path[rootPath.length]
|
||||
const end = selection.end.path[rootPath.length]
|
||||
const { minIndex, maxIndex } = findSelectionIndices(root, start, end)
|
||||
|
||||
if (root.type === 'Object') {
|
||||
return times(maxIndex - minIndex, i => rootPath.concat(root.props[i + minIndex].name))
|
||||
}
|
||||
else { // root.type === 'Array'
|
||||
return times(maxIndex - minIndex, i => rootPath.concat(String(i + minIndex)))
|
||||
}
|
||||
if (root.type === 'Object') {
|
||||
return times(maxIndex - minIndex, i => rootPath.concat(root.props[i + minIndex].name))
|
||||
}
|
||||
else { // root.type === 'Array'
|
||||
return times(maxIndex - minIndex, i => rootPath.concat(String(i + minIndex)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,10 +431,29 @@ export function contentsFromPaths (data: ESON, paths: JSONPath[]) {
|
|||
return {
|
||||
name: getIn(data, initial(esonPath).concat('name')) || String(esonPath[esonPath.length - 2]),
|
||||
value: esonToJson(getIn(data, esonPath))
|
||||
// FIXME: also store the type and expanded state
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the root path of a selection: the parent node shared by both start
|
||||
* and end of the selection
|
||||
* @param {Selection} selection
|
||||
* @return {JSONPath}
|
||||
*/
|
||||
export function findRootPath(selection) {
|
||||
const sharedPath = findSharedPath(selection.start.path, selection.end.path)
|
||||
|
||||
if (sharedPath.length === selection.start.path.length &&
|
||||
sharedPath.length === selection.end.path.length) {
|
||||
// there is just one node selected, return it's parent
|
||||
return initial(sharedPath)
|
||||
}
|
||||
else {
|
||||
return sharedPath
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the common path of two paths.
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import isEqual from 'lodash/isEqual'
|
||||
import initial from 'lodash/initial'
|
||||
|
||||
import type { ESON, Path, ESONPatch, ESONPatchOptions, ESONPatchResult, ESONSelection } from './types'
|
||||
import type { ESON, Path, ESONPatch } from './types'
|
||||
import { setIn, updateIn, getIn, deleteIn, insertAt } from './utils/immutabilityHelpers'
|
||||
import {
|
||||
jsonToEson, esonToJson, toEsonPath,
|
||||
parseJSONPointer, compileJSONPointer,
|
||||
expandAll, pathExists, resolvePathIndex, findPropertyIndex, findNextProp, getId,
|
||||
pathsFromSelection
|
||||
expandAll, pathExists, resolvePathIndex, findPropertyIndex, findNextProp, getId
|
||||
} from './eson'
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue