mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-19 13:41:19 +02:00
first stab at adding a schema to core
This commit is contained in:
@@ -9,6 +9,48 @@ import initialState from './state.json'
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const schema = {
|
||||
rules: [
|
||||
{
|
||||
match: { type: 'block-quote' },
|
||||
component: props => <blockquote {...props.attributes}>{props.children}</blockquote>
|
||||
},
|
||||
{
|
||||
match: { type: 'bulleted-list' },
|
||||
component: props => <ul {...props.attributes}>{props.children}</ul>,
|
||||
},
|
||||
{
|
||||
match: { type: 'heading-one' },
|
||||
component: props => <h1 {...props.attributes}>{props.children}</h1>,
|
||||
},
|
||||
{
|
||||
match: { type: 'heading-two' },
|
||||
component: props => <h2 {...props.attributes}>{props.children}</h2>,
|
||||
},
|
||||
{
|
||||
match: { type: 'heading-three' },
|
||||
component: props => <h3 {...props.attributes}>{props.children}</h3>,
|
||||
},
|
||||
{
|
||||
match: { type: 'heading-four' },
|
||||
component: props => <h4 {...props.attributes}>{props.children}</h4>,
|
||||
},
|
||||
{
|
||||
match: { type: 'heading-five' },
|
||||
component: props => <h5 {...props.attributes}>{props.children}</h5>,
|
||||
},
|
||||
{
|
||||
match: { type: 'heading-six' },
|
||||
component: props => <h6 {...props.attributes}>{props.children}</h6>,
|
||||
},
|
||||
{
|
||||
match: { type: 'list-item' },
|
||||
component: props => <li {...props.attributes}>{props.children}</li>,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
const NODES = {
|
||||
'block-quote': props => <blockquote>{props.children}</blockquote>,
|
||||
'bulleted-list': props => <ul>{props.children}</ul>,
|
||||
|
@@ -6,11 +6,11 @@ import OffsetKey from '../utils/offset-key'
|
||||
import React from 'react'
|
||||
import Selection from '../models/selection'
|
||||
import Transfer from '../utils/transfer'
|
||||
import TYPES from '../utils/types'
|
||||
import TYPES from '../constants/types'
|
||||
import getWindow from 'get-window'
|
||||
import includes from 'lodash/includes'
|
||||
import keycode from 'keycode'
|
||||
import { IS_FIREFOX, IS_MAC } from '../utils/environment'
|
||||
import { IS_FIREFOX, IS_MAC } from '../constants/environment'
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
|
@@ -1,9 +1,11 @@
|
||||
|
||||
import Content from './content'
|
||||
import CorePlugin from '../plugins/core'
|
||||
import React from 'react'
|
||||
import State from '../models/state'
|
||||
import Debug from 'debug'
|
||||
import React from 'react'
|
||||
import Schema from '../models/schema'
|
||||
import State from '../models/state'
|
||||
import isReactComponent from '../utils/is-react-component'
|
||||
import typeOf from 'type-of'
|
||||
|
||||
/**
|
||||
@@ -77,6 +79,7 @@ class Editor extends React.Component {
|
||||
onSelectionChange: noop,
|
||||
plugins: [],
|
||||
readOnly: false,
|
||||
schema: {},
|
||||
spellCheck: true
|
||||
};
|
||||
|
||||
@@ -90,6 +93,7 @@ class Editor extends React.Component {
|
||||
super(props)
|
||||
this.tmp = {}
|
||||
this.state = {}
|
||||
this.state.schema = Schema.create(props.schema)
|
||||
this.state.plugins = this.resolvePlugins(props)
|
||||
this.state.state = this.onBeforeChange(props.state)
|
||||
|
||||
@@ -108,6 +112,12 @@ class Editor extends React.Component {
|
||||
*/
|
||||
|
||||
componentWillReceiveProps = (props) => {
|
||||
if (props.schema != this.props.schema) {
|
||||
this.setState({
|
||||
schema: Schema.create(props.schema)
|
||||
})
|
||||
}
|
||||
|
||||
if (props.plugins != this.props.plugins) {
|
||||
this.setState({
|
||||
plugins: this.resolvePlugins(props)
|
||||
@@ -145,6 +155,16 @@ class Editor extends React.Component {
|
||||
this.onChange(state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor's current `schema`.
|
||||
*
|
||||
* @return {Schema}
|
||||
*/
|
||||
|
||||
getSchema = () => {
|
||||
return this.state.schema
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor's current `state`.
|
||||
*
|
||||
@@ -288,7 +308,7 @@ class Editor extends React.Component {
|
||||
let ret = plugin.renderMark(mark, marks, this.state.state, this)
|
||||
|
||||
// Handle React components that aren't stateless functions.
|
||||
if (ret && ret.prototype && ret.prototype.isReactComponent) return ret
|
||||
if (isReactComponent(ret)) return ret
|
||||
|
||||
// Handle all other types...
|
||||
switch (typeOf(ret)) {
|
||||
|
@@ -3,7 +3,7 @@ import Base64 from '../serializers/base-64'
|
||||
import Debug from 'debug'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import TYPES from '../utils/types'
|
||||
import TYPES from '../constants/types'
|
||||
import Leaf from './leaf'
|
||||
import Void from './void'
|
||||
import scrollTo from '../utils/scroll-to'
|
||||
|
@@ -5,7 +5,7 @@ import OffsetKey from '../utils/offset-key'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import keycode from 'keycode'
|
||||
import { IS_FIREFOX } from '../utils/environment'
|
||||
import { IS_FIREFOX } from '../constants/environment'
|
||||
|
||||
/**
|
||||
* Noop.
|
||||
|
100
lib/constants/rules.js
Normal file
100
lib/constants/rules.js
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
/**
|
||||
* The default Slate schema rules, which enforce the most basic constraints.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const RULES = [
|
||||
{
|
||||
match: {
|
||||
kind: 'document'
|
||||
},
|
||||
validate: {
|
||||
nodes: {
|
||||
anyOf: [
|
||||
{ kind: 'block' }
|
||||
]
|
||||
}
|
||||
},
|
||||
transform: (transform, node) => {
|
||||
return node.nodes
|
||||
.filter(child => child.kind != 'block')
|
||||
.reduce((tr, child) => tr.removeNodeByKey(child.key), transform)
|
||||
}
|
||||
},
|
||||
// {
|
||||
// match: { kind: 'block' },
|
||||
// nodes: {
|
||||
// anyOf: [
|
||||
// { kind: 'block' },
|
||||
// { kind: 'inline' },
|
||||
// { kind: 'text' },
|
||||
// ]
|
||||
// },
|
||||
// transform: (transform, node) => {
|
||||
// return node
|
||||
// .filterChildren(child => {
|
||||
// return (
|
||||
// child.kind != 'block' ||
|
||||
// child.kind != 'inline' ||
|
||||
// child.kind != 'text'
|
||||
// )
|
||||
// })
|
||||
// .reduce((transform, child) => {
|
||||
// return transform.removeNodeByKey(child.key)
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// match: { kind: 'inline' },
|
||||
// nodes: {
|
||||
// anyOf: [
|
||||
// { kind: 'inline' },
|
||||
// { kind: 'text' }
|
||||
// ]
|
||||
// },
|
||||
// transform: (transform, node) => {
|
||||
// return node
|
||||
// .filterChildren(child => {
|
||||
// return child.kind != 'inline' || child.kind != 'text'
|
||||
// })
|
||||
// .reduce((transform, child) => {
|
||||
// return transform.removeNodeByKey(child.key)
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// match: { isVoid: true },
|
||||
// text: ' ',
|
||||
// transform: (transform, node) => {
|
||||
// const { state } = transform
|
||||
// const range = state.selection.moveToRangeOf(node)
|
||||
// return transform.delete().insertText(' ')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// match: (object) => {
|
||||
// return (
|
||||
// object.kind == 'block' &&
|
||||
// object.nodes.size == 1 &&
|
||||
// object.nodes.first().isVoid
|
||||
// )
|
||||
// },
|
||||
// invalid: true,
|
||||
// transform: (transform, node) => {
|
||||
// const child = node.nodes.first()
|
||||
// const text =
|
||||
// return transform
|
||||
// .insertNodeBeforeNodeByKey(child.)
|
||||
// }
|
||||
// }
|
||||
]
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
export default RULES
|
0
lib/constants/schema.js
Normal file
0
lib/constants/schema.js
Normal file
@@ -117,6 +117,21 @@ const Node = {
|
||||
}, Block.createList())
|
||||
},
|
||||
|
||||
/**
|
||||
* Recursively filter all ancestor nodes with `iterator`, depth-first.
|
||||
*
|
||||
* @param {Function} iterator
|
||||
* @return {List} nodes
|
||||
*/
|
||||
|
||||
filterDescendantsDeep(iterator) {
|
||||
return this.nodes.reduce((matches, child, i, nodes) => {
|
||||
if (child.kind != 'text') matches = matches.concat(child.filterDescendantsDeep(iterator))
|
||||
if (iterator(child, i, nodes)) matches = matches.push(child)
|
||||
return matches
|
||||
}, Block.createList())
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the closest block nodes for each text node in the node.
|
||||
*
|
||||
|
312
lib/models/schema.js
Normal file
312
lib/models/schema.js
Normal file
@@ -0,0 +1,312 @@
|
||||
|
||||
import RULES from '../constants/rules'
|
||||
import includes from 'lodash/includes'
|
||||
import isReactComponent from '../utils/is-react-component'
|
||||
import typeOf from 'type-of'
|
||||
import memoize from '../utils/memoize'
|
||||
import { Record } from 'immutable'
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
rules: [],
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema.
|
||||
*
|
||||
* @type {Record}
|
||||
*/
|
||||
|
||||
class Schema extends new Record(DEFAULTS) {
|
||||
|
||||
/**
|
||||
* Create a new `Schema` with `properties`.
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @return {Schema} mark
|
||||
*/
|
||||
|
||||
static create(properties = {}) {
|
||||
if (properties instanceof Schema) return properties
|
||||
|
||||
let rules = [
|
||||
...(properties.rules || []),
|
||||
...RULES,
|
||||
]
|
||||
|
||||
return new Schema({
|
||||
rules: rules.map(normalizeRule)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kind.
|
||||
*
|
||||
* @return {String} kind
|
||||
*/
|
||||
|
||||
get kind() {
|
||||
return 'schema'
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `state` against the schema.
|
||||
*
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
normalize(state) {
|
||||
const { document } = state
|
||||
let transform = state.transform()
|
||||
|
||||
document.filterDescendants((node) => {
|
||||
const rule = this.validateNode(node)
|
||||
if (rule) transform = rule.transform(transform, node, state)
|
||||
})
|
||||
|
||||
const next = transform.apply({ snapshot: false })
|
||||
return next
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a `state` against the schema.
|
||||
*
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
validate(state) {
|
||||
return !!state.document.findDescendant(node => this.validateNode(node))
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a `node` against the schema, returning the rule that was not met
|
||||
* if the node is invalid, or null if the rule was valid.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Object || Void}
|
||||
*/
|
||||
|
||||
validateNode(node) {
|
||||
return this.rules
|
||||
.filter(rule => rule.match(node))
|
||||
.find(rule => !rule.validate(node))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Memoize read methods.
|
||||
*/
|
||||
|
||||
memoize(Schema.prototype, [
|
||||
'normalize',
|
||||
'validate',
|
||||
'validateNode',
|
||||
])
|
||||
|
||||
/**
|
||||
* Normalize the `properties` of a schema.
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function normalizeProperties(properties) {
|
||||
let rules = []
|
||||
|
||||
// If there's a `rules` property, it is not the shorthand.
|
||||
if (properties.rules) {
|
||||
rules = properties.rules
|
||||
}
|
||||
|
||||
// Otherwise it's the shorthand syntax, so expand each of the properties.
|
||||
else {
|
||||
for (const key in properties) {
|
||||
const value = properties[key]
|
||||
let rule
|
||||
|
||||
if (isReactComponent(value)) {
|
||||
rule.match = { type: key }
|
||||
rule.component = value
|
||||
} else {
|
||||
rule = {
|
||||
type: key,
|
||||
...value
|
||||
}
|
||||
}
|
||||
|
||||
rules.push(rule)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rules: rules
|
||||
.concat(RULES)
|
||||
.map(normalizeRule)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `rule` object.
|
||||
*
|
||||
* @param {Object} rule
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function normalizeRule(rule) {
|
||||
return {
|
||||
match: normalizeMatch(rule.match),
|
||||
validate: normalizeValidate(rule.validate),
|
||||
transform: normalizeTransform(rule.transform),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `match` spec.
|
||||
*
|
||||
* @param {Function || Object || String} match
|
||||
* @return {Function}
|
||||
*/
|
||||
|
||||
function normalizeMatch(match) {
|
||||
switch (typeOf(match)) {
|
||||
case 'function': return match
|
||||
case 'object': return normalizeSpec(match)
|
||||
default: {
|
||||
throw new Error(`Invalid \`match\` spec: "${match}".`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `validate` spec.
|
||||
*
|
||||
* @param {Function || Object || String} validate
|
||||
* @return {Function}
|
||||
*/
|
||||
|
||||
function normalizeValidate(validate) {
|
||||
switch (typeOf(validate)) {
|
||||
case 'function': return validate
|
||||
case 'object': return normalizeSpec(validate)
|
||||
default: {
|
||||
throw new Error(`Invalid \`validate\` spec: "${validate}".`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `transform` spec.
|
||||
*
|
||||
* @param {Function || Object || String} transform
|
||||
* @return {Function}
|
||||
*/
|
||||
|
||||
function normalizeTransform(transform) {
|
||||
switch (typeOf(transform)) {
|
||||
case 'function': return transform
|
||||
default: {
|
||||
throw new Error(`Invalid \`transform\` spec: "${transform}".`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `spec` object.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function normalizeSpec(obj) {
|
||||
const spec = { ...obj }
|
||||
const { nodes } = spec
|
||||
|
||||
// Normalize recursively for the node specs.
|
||||
if (nodes) {
|
||||
if (nodes.exactlyOf) spec.nodes.exactlyOf = nodes.exactlyOf.map(normalizeSpec)
|
||||
if (nodes.anyOf) spec.nodes.anyOf = nodes.anyOf.map(normalizeSpec)
|
||||
if (nodes.noneOf) spec.nodes.noneOf = nodes.noneOf.map(normalizeSpec)
|
||||
}
|
||||
|
||||
// Return a checking function.
|
||||
return (node) => {
|
||||
// If marked as invalid explicitly, return early.
|
||||
if (spec.invalid === true) return false
|
||||
|
||||
// Run the simple equality checks first.
|
||||
if (
|
||||
(spec.kind != null && spec.kind != node.kind) ||
|
||||
(spec.type != null && spec.type != node.type) ||
|
||||
(spec.isVoid != null && spec.isVoid != node.type) ||
|
||||
(spec.kinds != null && !includes(spec.kinds, node.kind)) ||
|
||||
(spec.types != null && !includes(spec.types, node.type))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure that the node has nodes.
|
||||
if (spec.nodes && !node.nodes) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Run the node recursive checks next, start with `exactlyOf`, which likely
|
||||
// has the greatest chance of not matching.
|
||||
if (spec.nodes && spec.nodes.exactlyOf) {
|
||||
const specs = spec.nodes.exactlyOf
|
||||
const matches = node.nodes.reduce((valid, child, i) => {
|
||||
if (!valid) return false
|
||||
const checker = specs[i]
|
||||
if (!checker) return false
|
||||
return checker(child)
|
||||
}, true)
|
||||
|
||||
if (!matches) return false
|
||||
}
|
||||
|
||||
// Run the `anyOf` next check.
|
||||
if (spec.nodes && spec.nodes.anyOf) {
|
||||
const specs = spec.nodes.anyOf
|
||||
const matches = node.nodes.reduce((valid, child) => {
|
||||
if (!valid) return false
|
||||
return specs.reduce((pass, checker) => {
|
||||
if (!pass) return false
|
||||
return checker(child)
|
||||
}, true)
|
||||
})
|
||||
|
||||
if (!matches) return false
|
||||
}
|
||||
|
||||
// Run the `noneOf` next check.
|
||||
if (spec.nodes && spec.nodes.noneOf) {
|
||||
const specs = spec.nodes.noneOf
|
||||
const matches = node.nodes.reduce((valid, child) => {
|
||||
if (!valid) return false
|
||||
return specs.reduce((pass, checker) => {
|
||||
if (!pass) return false
|
||||
return !!checker(child)
|
||||
}, true)
|
||||
})
|
||||
|
||||
if (!matches) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Record}
|
||||
*/
|
||||
|
||||
export default Schema
|
@@ -73,6 +73,21 @@ function Plugin(options = {}) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* On before change, enforce the editor's schema.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Editor} schema
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onBeforeChange(state, editor) {
|
||||
if (state.isNative) return state
|
||||
return editor
|
||||
.getSchema()
|
||||
.normalize(state)
|
||||
}
|
||||
|
||||
/**
|
||||
* On before input, see if we can let the browser continue with it's native
|
||||
* input behavior, to avoid a re-render for performance.
|
||||
@@ -615,6 +630,7 @@ function Plugin(options = {}) {
|
||||
*/
|
||||
|
||||
return {
|
||||
onBeforeChange,
|
||||
onBeforeInput,
|
||||
onBlur,
|
||||
onCopy,
|
||||
|
23
lib/utils/is-react-component.js
Normal file
23
lib/utils/is-react-component.js
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
/**
|
||||
* Check if an `object` is a React component.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isReactComponent(object) {
|
||||
return (
|
||||
object &&
|
||||
object.prototype &&
|
||||
object.prototype.isReactComponent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default isReactComponent
|
@@ -1,6 +1,6 @@
|
||||
|
||||
import Base64 from '../serializers/base-64'
|
||||
import TYPES from './types'
|
||||
import TYPES from '../constants/types'
|
||||
|
||||
/**
|
||||
* Fragment matching regexp for HTML nodes.
|
||||
|
0
test/schema/fixtures/no-top-level-inline/index.js
Normal file
0
test/schema/fixtures/no-top-level-inline/index.js
Normal file
14
test/schema/fixtures/no-top-level-inline/input.yaml
Normal file
14
test/schema/fixtures/no-top-level-inline/input.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: default
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: one
|
||||
- kind: block
|
||||
type: default
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: two
|
8
test/schema/fixtures/no-top-level-inline/output.yaml
Normal file
8
test/schema/fixtures/no-top-level-inline/output.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: default
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: two
|
31
test/schema/index.js
Normal file
31
test/schema/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
import fs from 'fs'
|
||||
import strip from '../helpers/strip-dynamic'
|
||||
import readMetadata from 'read-metadata'
|
||||
import { Raw } from '../..'
|
||||
import { strictEqual } from '../helpers/assert-json'
|
||||
import { resolve } from 'path'
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
|
||||
describe('schema', () => {
|
||||
const tests = fs.readdirSync(resolve(__dirname, './fixtures'))
|
||||
|
||||
for (const test of tests) {
|
||||
if (test[0] == '.') continue
|
||||
|
||||
it(test, () => {
|
||||
const dir = resolve(__dirname, './fixtures', test)
|
||||
const input = readMetadata.sync(resolve(dir, 'input.yaml'))
|
||||
const expected = readMetadata.sync(resolve(dir, 'output.yaml'))
|
||||
const schema = require(dir)
|
||||
|
||||
let state = Raw.deserialize(input, { terse: true })
|
||||
state = schema.normalize(state)
|
||||
const output = Raw.serialize(state, { terse: true })
|
||||
strictEqual(strip(output), strip(expected))
|
||||
})
|
||||
}
|
||||
})
|
@@ -1,4 +1,5 @@
|
||||
|
||||
import './serializers'
|
||||
import './schema'
|
||||
import './rendering'
|
||||
import './transforms'
|
||||
|
@@ -1,11 +1,10 @@
|
||||
|
||||
import assert from 'assert'
|
||||
import fs from 'fs'
|
||||
import readMetadata from 'read-metadata'
|
||||
import strip from '../helpers/strip-dynamic'
|
||||
import toCamel from 'to-camel-case'
|
||||
import { Raw, State } from '../..'
|
||||
import { equal, strictEqual } from '../helpers/assert-json'
|
||||
import { Raw } from '../..'
|
||||
import { strictEqual } from '../helpers/assert-json'
|
||||
import { resolve } from 'path'
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user