Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[form-builder] Move pathUtils to util package #1181

Merged
merged 1 commit into from
Jan 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@sanity/form-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@sanity/imagetool": "0.140.0",
"@sanity/mutator": "0.140.0",
"@sanity/schema": "0.140.0",
"@sanity/util": "0.140.0",
"attr-accept": "^1.1.0",
"canvas-to-blob": "^0.0.0",
"classnames": "^2.2.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/@sanity/form-builder/src/FormBuilderInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react'
import type {Path} from './typedefs/path'
import PatchEvent from './PatchEvent'
import generateHelpUrl from '@sanity/generate-help-url'
import * as PathUtils from './utils/pathUtils'
import * as PathUtils from '@sanity/util/paths'
import type {Type} from './typedefs'

type Props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {Marker, Type} from '../../typedefs'
import type {Path} from '../../typedefs/path'
import type {Subscription} from '../../typedefs/observable'
import {resolveTypeName} from '../../utils/resolveTypeName'
import {FOCUS_TERMINATOR, startsWith} from '../../utils/pathUtils'
import {FOCUS_TERMINATOR, startsWith} from '@sanity/util/paths'
import UploadTargetFieldset from '../../utils/UploadTargetFieldset'
import {insert, PatchEvent, set, setIfMissing, unset} from '../../PatchEvent'
import styles from './styles/ArrayInput.css'
Expand Down Expand Up @@ -296,9 +296,7 @@ export default class ArrayInput extends React.Component<Props, State> {
Some items in this list are missing their keys. We need to fix this before the list can
be edited.
<div className={styles.fixMissingKeysButtonWrapper}>
<Button onClick={this.handleFixMissingKeys}>
Fix missing keys
</Button>
<Button onClick={this.handleFixMissingKeys}>Fix missing keys</Button>
</div>
<Details title={<b>Why is this happening?</b>}>
This usually happens when items are created through the API client from outside the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Preview from '../../Preview'
import {resolveTypeName} from '../../utils/resolveTypeName'
import type {Path} from '../../typedefs/path'
import type {Marker, Type} from '../../typedefs'
import * as PathUtils from '../../utils/pathUtils'
import * as PathUtils from '@sanity/util/paths'
import ConfirmButton from './ConfirmButton'
import styles from './styles/ItemValue.css'
import type {ArrayType, ItemValue} from './typedefs'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Item as SortableItem, List as SortableList} from 'part:@sanity/component
import ArrayFunctions from 'part:@sanity/form-builder/input/array/functions'
import Fieldset from 'part:@sanity/components/fieldsets/default'
import {PatchEvent, set, unset} from '../../PatchEvent'
import {startsWith} from '../../utils/pathUtils'
import {startsWith} from '@sanity/util/paths'
import {resolveTypeName} from '../../utils/resolveTypeName'
import type {Path} from '../../typedefs/path'
import type {Type, Marker} from '../../typedefs'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import LinkIcon from 'part:@sanity/base/link-icon'
import SanityLogoIcon from 'part:@sanity/base/sanity-logo-icon'
import ToggleButton from 'part:@sanity/components/toggles/button'
import type {BlockContentFeature, BlockContentFeatures, Path, SlateEditor} from '../typeDefs'
import {FOCUS_TERMINATOR} from '../../../utils/pathUtils'
import {FOCUS_TERMINATOR} from '@sanity/util/paths'
import CustomIcon from './CustomIcon'
import ToolbarClickAction from './ToolbarClickAction'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import BlockObjectIcon from 'part:@sanity/base/block-object-icon'
import InlineObjectIcon from 'part:@sanity/base/inline-object-icon'

import type {Type, SlateValue, SlateEditor, Path} from '../typeDefs'
import {FOCUS_TERMINATOR} from '../../../utils/pathUtils'
import {FOCUS_TERMINATOR} from '@sanity/util/paths'
import styles from './styles/InsertMenu.css'

type Props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import type {
} from '../typeDefs'

import {PatchEvent} from '../../../PatchEvent'
import {FOCUS_TERMINATOR} from '../../../utils/pathUtils'
import {FOCUS_TERMINATOR} from '@sanity/util/paths'
import {resolveTypeName} from '../../../utils/resolveTypeName'

import InvalidValue from '../../InvalidValueInput'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {

import {resolveTypeName} from '../../../utils/resolveTypeName'
import {PatchEvent} from '../../../PatchEvent'
import {FOCUS_TERMINATOR} from '../../../utils/pathUtils'
import {FOCUS_TERMINATOR} from '@sanity/util/paths'

import InvalidValue from '../../InvalidValueInput'
import Preview from '../../../Preview'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type {Node} from 'react'
import React from 'react'
import type {BlockContentFeatures, Type, Marker, Path, SlateEditor} from '../typeDefs'
import {FOCUS_TERMINATOR} from '../../../utils/pathUtils'
import {FOCUS_TERMINATOR} from '@sanity/util/paths'
import styles from './styles/Span.css'
import {Inline} from 'slate'

Expand Down
3 changes: 2 additions & 1 deletion packages/@sanity/util/.babelrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module.exports = {
extends: '../../../.babelrc.js'
extends: '../../../.babelrc.js',
presets: ['@babel/preset-flow']
}
7 changes: 7 additions & 0 deletions packages/@sanity/util/.flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[include]
./src
../../../
[libs]
defs
[ignore]
.*/examples/.*
1 change: 1 addition & 0 deletions packages/@sanity/util/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"resolve-from": "^4.0.0"
},
"devDependencies": {
"@babel/preset-flow": "^7.0.0",
"jest": "^23.6.0",
"rimraf": "^2.6.2"
}
Expand Down
1 change: 1 addition & 0 deletions packages/@sanity/util/paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./lib/pathUtils')
Original file line number Diff line number Diff line change
@@ -1,15 +1,57 @@
/* eslint-disable max-depth */
// @flow
import type {Path, PathSegment} from '../typedefs/path'

const rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g
const reKeySegment = /_key\s*==\s*['"](.*)['"]/

export const FOCUS_TERMINATOR = '$'

// eslint-disable-next-line complexity
export function get(obj: mixed, path: Path | string, defaultVal: mixed) {
const select = typeof path === 'string' ? fromString(path) : path
if (!Array.isArray(select)) {
throw new Error('Path must be an array or a string')
}

let acc = obj
for (let i = 0; i < select.length; i++) {
const segment = select[i]
if (isIndexSegment(segment)) {
if (!Array.isArray(acc)) {
return defaultVal
}

acc = acc[segment]
}

if (isKeySegment(segment)) {
if (!Array.isArray(acc)) {
return defaultVal
}

acc = acc.find(item => item._key === segment._key)
}

if (typeof segment === 'string') {
acc = typeof acc === 'object' && acc !== null ? acc[segment] : undefined
}

if (typeof acc === 'undefined') {
return defaultVal
}
}

return acc
}

export function isEqual(path: Path, otherPath: Path) {
return (
path.length === otherPath.length &&
path.every((segment, i) => isSegmentEqual(segment, otherPath[i]))
)
}

export const FOCUS_TERMINATOR = '$'

export function isSegmentEqual(pathSegment: PathSegment, otherPathSegment: PathSegment) {
const pathSegmentType = typeof pathSegment
const otherPathSegmentType = typeof otherPathSegment
Expand Down Expand Up @@ -56,7 +98,7 @@ export function trimLeft(prefix: Path, path: Path): Path {
return trimLeft(prefixTail, pathTail)
}

export function trimRight(suffix, path) {
export function trimRight(suffix: Path, path: Path): Path {
const sufLen = suffix.length
const pathLen = path.length
if (sufLen === 0 || pathLen === 0) {
Expand All @@ -75,7 +117,11 @@ export function trimRight(suffix, path) {
return path.slice(0, pathLen - i)
}

export function toString(path) {
export function toString(path: Path): string {
if (!Array.isArray(path)) {
throw new Error('Path is not an array')
}

return path.reduce((target, segment, i) => {
const segmentType = typeof segment
if (segmentType === 'number') {
Expand All @@ -94,3 +140,49 @@ export function toString(path) {
throw new Error(`Unsupported path segment \`${JSON.stringify(segment)}\``)
}, '')
}

export function fromString(path: string): Path {
if (typeof path !== 'string') {
throw new Error('Path is not a string')
}

const segments = path.match(rePropName)
if (!segments) {
throw new Error('Invalid path string')
}

return segments.map(normalizePathSegment)
}

function normalizePathSegment(segment: string): PathSegment {
if (isIndexSegment(segment)) {
return normalizeIndexSegment(segment)
}

if (isKeySegment(segment)) {
return normalizeKeySegment(segment)
}

return segment
}

function normalizeIndexSegment(segment: string): PathSegment {
return Number(segment.replace(/[^\d]/g, ''))
}

function normalizeKeySegment(segment: string): PathSegment {
const segments = segment.match(reKeySegment)
return {_key: segments[1]}
}

function isIndexSegment(segment: string | number): boolean {
return typeof segment === 'number' || /^\[\d+\]$/.test(segment)
}

function isKeySegment(segment: PathSegment): boolean {
if (typeof segment === 'string') {
return reKeySegment.test(segment.trim())
}

return segment && segment._key
}
7 changes: 7 additions & 0 deletions packages/@sanity/util/src/typedefs/path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type KeyedSegment = {
_key: string
}

export type PathSegment = string | number | KeyedSegment

export type Path = Array<PathSegment>
Loading