mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-31 19:01:54 +02:00
Next (#3093)
* remove some key usage from core, refactor Operations.apply * undeprecate some methods * convert more key usage to paths * update deprecations * convert selection commands to use all paths * refactor word boundary selection logic * convert many at-range commands to use paths * convert wrapBlock and wrapInline to not use keys * cleanup * remove chainability from editor * simplify commands, queries and middleware * convert deleteAtRange * remove key usage from schema, deprecate *ByKey methods * migrate *ByKey tests, remove index from *ByPath signatures * rename at-current-range tests * deprecate mode key usage, migrate more tests away from keys * deprecate range and point methods which rely on keys to work * refactor insertBlock, without fixing warnings * add pathRef/pointRef, fix insertBlock/Inline deprecations, work on insertFragment * refactor insertFragment * get rich-text example rendering * fix lint * refactor query files, fix more tests * remove unused queries, refactor others * deprecate splitDescendantsByPath * merge master * add typescript, convert slate, slate-hyperscript, slate-plain-serializer * add Point, Path, Range, Annotation tests * add Annotation, Change, Element, Fragment, Mark, Range, Selection, Value interfaces tests * add Operation and Text tests * add Node tests * get operations and normalization tests working for slate * get *AtPath command tests passing * rename *AtPath command tests * rename * get *AtPoint tests working * rename * rename * add value queries tests * add element, mark and path queries tests * convert most on-selection tests * convert on-selection commands * rename * get addMarks and delete commands working * rename * rename * rename * refactor value.positions(), work on delete tests * progress on delete tests * more delete work * finish delete tests * start converting to at-based commands * restructure query tests * restructure operations tests * more work converting to multi-purpose commands * lots of progress on converting to at-based commands * add unwrapNodes * remove setValue * more progress * refactor node commands to use consistent matching logic * cleanup, get non-fragment commands passing * remove annotations and isAtomic * rename surround/pluck to cover/uncover * add location concept, change at-path to from-path for iterables * refactor batches * add location-based queries * refactor hanging logic * more location query work * renaming * use getMatch more * add split to wrap/unwrap * flip levels/ancestors ordering * switch splitNodes to use levels * change split to always:false by default * fix tests * add more queries tests * fixing more delete logic * add more splitNodes tests * get rest of delete tests passing * fix location-based logic in some commands * cleanup * get previous packages tests passing again * add slate-history package * start slate-schema work * start of react working * rendering fixes * get rich and plain text examples working * get image example working with hooks and dropping * refactor onDrop to be internal * inline more event handlers * refactor lots of event-related logic * change rendering to use render props * delete unused stuff * cleanup dom utils * remove unused deps * remove unnecessary packages, add placeholder * remove slate-react-placeholder package * remove unused dep * remove unnecessary tests, fix readonly example * convert checklists example * switch to next from webpack * get link example working * convert more examples * preserve keys, memoized leafs/texts, fix node lookup * fix to always useLayoutEffect for ordering * fix annotations to be maps, memoize elements * remove Change interface * remove String interface * rename Node.entries to Node.nodes * remove unnecessary value queries * default to selection when iterating, cleanup * remove unused files * update scroll into view logic * fix undoing, remove constructor types * dont sync selection while composing * add workflows * remove unused deps * convert mentions example * tweaks * convert remaining examples * rename h to jsx, update schema * fix schema tests * fix slate-schema logic and tests * really fix slate-schema and forced-layout example * get start of insertFragment tests working * remove Fragment interface * remove debugger * get all non-skipped tests passing * cleanup deps * run prettier * configure eslint for typescript * more eslint fixes... * more passing * update some docs * fix examples * port windows undo hotkey change * fix deps, add basic firefox support * add event overriding, update walkthroughs * add commands, remove classes, cleanup examples * cleanup rollup config * update tests * rename queries tests * update other tests * update walkthroughs * cleanup interface exports * cleanup, change mark transforms to require location * undo mark transform change * more * fix tests * fix example * update walkthroughs * update docs * update docs * remove annotations * remove value, move selection and children to editor * add migrating doc * fix lint * fix tests * fix DOM types aliasing * add next export * update deps, fix prod build * fix prod build * update scripts * update docs and changelogs * update workflow and pull request template
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This document maintains a list of changes to the `slate-base64-serializer` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes won't be accounted for since the library is moving quickly.
|
||||
|
||||
---
|
||||
|
||||
### `0.2.0` — October 27, 2017
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Updated to work with `slate@0.29.0`.** This is required because `slate-base64-serializer` needs access to the new `Value` model.
|
||||
|
||||
---
|
||||
|
||||
### `0.1.0` — September 17, 2017
|
||||
|
||||
:tada:
|
@@ -1 +0,0 @@
|
||||
This package contains a base 64 serializer for Slate documents.
|
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "slate-base64-serializer",
|
||||
"description": "A Base64 serializer for Slate editors.",
|
||||
"version": "0.2.112",
|
||||
"license": "MIT",
|
||||
"repository": "git://github.com/ianstormtaylor/slate.git",
|
||||
"main": "lib/slate-base64-serializer.js",
|
||||
"module": "lib/slate-base64-serializer.es.js",
|
||||
"umd": "dist/slate-base64-serializer.js",
|
||||
"umdMin": "dist/slate-base64-serializer.min.js",
|
||||
"files": [
|
||||
"dist/",
|
||||
"lib/"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.32.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^2.5.3",
|
||||
"slate": "^0.47.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"isomorphic-base64": "^1.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist ./lib ./node_modules"
|
||||
},
|
||||
"umdGlobals": {
|
||||
"slate": "Slate"
|
||||
},
|
||||
"keywords": [
|
||||
"deserialize",
|
||||
"base64",
|
||||
"editor",
|
||||
"serialize",
|
||||
"serializer",
|
||||
"slate"
|
||||
]
|
||||
}
|
@@ -1,93 +0,0 @@
|
||||
import { Node, Value } from 'slate'
|
||||
import { atob, btoa } from 'isomorphic-base64'
|
||||
|
||||
/**
|
||||
* Encode a JSON `object` as base-64 `string`.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
function encode(object) {
|
||||
const string = JSON.stringify(object)
|
||||
const encoded = btoa(encodeURIComponent(string))
|
||||
return encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a base-64 `string` to a JSON `object`.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function decode(string) {
|
||||
const decoded = decodeURIComponent(atob(string))
|
||||
const object = JSON.parse(decoded)
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Value `string`.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
function deserialize(string, options) {
|
||||
const raw = decode(string)
|
||||
const value = Value.fromJSON(raw, options)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Node `string`.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {Node}
|
||||
*/
|
||||
|
||||
function deserializeNode(string, options) {
|
||||
const raw = decode(string)
|
||||
const node = Node.fromJSON(raw, options)
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a `value`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
function serialize(value, options) {
|
||||
const raw = value.toJSON(options)
|
||||
const encoded = encode(raw)
|
||||
return encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
function serializeNode(node, options) {
|
||||
const raw = node.toJSON(options)
|
||||
const encoded = encode(raw)
|
||||
return encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default {
|
||||
deserialize,
|
||||
deserializeNode,
|
||||
serialize,
|
||||
serializeNode,
|
||||
}
|
@@ -1 +0,0 @@
|
||||
This package contains 'Suite' and 'Bench' that Slate uses to compare computation efficiency under nodeJS.
|
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"name": "slate-dev-benchmark",
|
||||
"description": "INTERNAL: A development-only benchmark tool for Slate's core.",
|
||||
"version": "0.0.5",
|
||||
"license": "MIT",
|
||||
"repository": "git://github.com/ianstormtaylor/slate.git",
|
||||
"main": "src/index.js",
|
||||
"devDependencies": {
|
||||
"mocha": "^2.5.3"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist ./lib ./node_modules"
|
||||
}
|
||||
}
|
@@ -1,227 +0,0 @@
|
||||
/* global Promise */
|
||||
const { BenchType } = require('./types')
|
||||
const { makeOptions } = require('./makeOptions')
|
||||
const { Timer } = require('./Timer')
|
||||
const { logger } = require('./logger')
|
||||
|
||||
const errorReport = {
|
||||
cycles: NaN,
|
||||
user: NaN,
|
||||
system: NaN,
|
||||
all: NaN,
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task and calculate the time consuming of tasks
|
||||
*/
|
||||
|
||||
class Bench {
|
||||
/**
|
||||
* Construct a bench and register it to a Suite
|
||||
* @param {Suite} suite
|
||||
* @param {string} name
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
constructor(suite, name, options = {}) {
|
||||
this.name = name
|
||||
this.options = makeOptions({ ...suite.options, ...options })
|
||||
this.isFinished = false
|
||||
this.inputter = () => undefined
|
||||
this.runner = () => {}
|
||||
this.report = { ...errorReport }
|
||||
suite.addBench(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a Bench?
|
||||
* @param {any} obj
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
isBench(obj) {
|
||||
return obj && obj[BenchType]
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method to generate (different} inputs for each run
|
||||
* @param {Array|Function|Scalar} inputter
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
input(inputter) {
|
||||
if (Array.isArray(inputter)) {
|
||||
this.inputter = index => inputter[index % inputter.length]
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof inputter === 'function') {
|
||||
this.inputter = inputter
|
||||
return
|
||||
}
|
||||
|
||||
this.inputter = () => inputter
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the task runner
|
||||
* @param {Function} runner
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
run(runner) {
|
||||
this.runner = runner
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to run tasks in `times`, if the time consuming excedes the max-time, then stop;
|
||||
* After run, generate report and return
|
||||
* If initial is the initial index to run the task, for continueing a task in adaptive mode
|
||||
* @param {number} times
|
||||
* @param {number} initial
|
||||
*/
|
||||
|
||||
async compose(times, initial) {
|
||||
times = Math.floor(times)
|
||||
const isAsync = this.options.async
|
||||
const { runner, inputter } = this
|
||||
|
||||
const { maxTime } = this.options
|
||||
let seq = Number.isFinite(this.options.maxTries) ? 1 : NaN
|
||||
let nextCheckIndex = seq
|
||||
const hrStart = process.hrtime()
|
||||
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
|
||||
const report = { user: 0, system: 0, all: 0, hr: 0, cycles: 0 }
|
||||
|
||||
for (
|
||||
let initialIndex = initial;
|
||||
initialIndex < times;
|
||||
initialIndex += this.options.allocationTries
|
||||
) {
|
||||
const tries = Math.min(times - initialIndex, this.options.allocationTries)
|
||||
const thisTryReport = await runBundleTasks.call(this, tries, initialIndex)
|
||||
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
|
||||
for (const key in report) {
|
||||
report[key] += thisTryReport[key]
|
||||
}
|
||||
}
|
||||
return report
|
||||
|
||||
/**
|
||||
* Run a bundle of tasks;
|
||||
* the Bench estimate the time consuming of every `tries` tasks, then explictly run gc, and caculate the time consuming of next bundle tasks
|
||||
* @param {number} tries
|
||||
* @param {number} initialIndex
|
||||
* @return {Promise< Object , *>}
|
||||
*/
|
||||
|
||||
function runBundleTasks(tries, initialIndex) {
|
||||
const inputs = Array.from({ length: tries }).map(index =>
|
||||
inputter(index + initialIndex)
|
||||
)
|
||||
const timer = new Timer()
|
||||
timer.start()
|
||||
return runFrom(0).then(cycles => {
|
||||
timer.end()
|
||||
const { elapsed } = timer
|
||||
return { ...elapsed, cycles }
|
||||
})
|
||||
|
||||
/**
|
||||
* Run a single task run; If the task is end, return a Promise with the index when the task ends
|
||||
* @param {number} index
|
||||
* @return {Promise<number, *>}
|
||||
*/
|
||||
|
||||
function runFrom(index) {
|
||||
if (index === tries) return Promise.resolve(tries)
|
||||
|
||||
if (index === nextCheckIndex) {
|
||||
const hrEnd = process.hrtime(hrStart)
|
||||
const elapsed = hrEnd[0] * 1e3 + hrEnd[1] / 1e6
|
||||
|
||||
if (elapsed > maxTime) {
|
||||
return Promise.resolve(index)
|
||||
} else {
|
||||
if (elapsed < maxTime / 20) {
|
||||
seq *= 2
|
||||
}
|
||||
|
||||
nextCheckIndex = seq + nextCheckIndex
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAsync) {
|
||||
const inputVar = inputs[index]
|
||||
runner(inputVar)
|
||||
return runFrom(index + 1)
|
||||
} else {
|
||||
return Promise.resolve(runner(inputs[index])).then(() =>
|
||||
runFrom(index + 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Run the bench
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
makeRun() {
|
||||
if (this.isFinished) return true
|
||||
logger(this)
|
||||
const { options } = this
|
||||
const { minTries, maxTime, maxTries } = options
|
||||
|
||||
let { minTime } = options
|
||||
if (minTime > maxTime) minTime = maxTime
|
||||
|
||||
return this.compose(minTries, 0)
|
||||
.then(report => {
|
||||
if (this.options.mode === 'static') return report
|
||||
const { all } = report
|
||||
if (all > minTime) return report
|
||||
const times = (minTime / all - 1) * minTries
|
||||
return this.compose(Math.min(times, maxTries), minTries).then(
|
||||
newReport => {
|
||||
return mergeResults(report, newReport)
|
||||
}
|
||||
)
|
||||
})
|
||||
.then(report => {
|
||||
this.report = report
|
||||
this.isFinished = true
|
||||
logger(this)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Bench.prototype[BenchType] = true
|
||||
|
||||
/*
|
||||
* Merge two different report
|
||||
* @param {Object} res1
|
||||
* @param {Object} res2
|
||||
*/
|
||||
|
||||
function mergeResults(res1, res2) {
|
||||
const result = {}
|
||||
|
||||
for (const key in res1) {
|
||||
result[key] = res1[key] + res2[key]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports = { Bench }
|
@@ -1,73 +0,0 @@
|
||||
/* global Promise */
|
||||
const { RepositoryType } = require('./types')
|
||||
const { logger } = require('./logger')
|
||||
const { compose } = require('./compose')
|
||||
|
||||
/**
|
||||
* Repository Class for holding Suite
|
||||
*/
|
||||
|
||||
class Repository {
|
||||
/**
|
||||
* Construct a Repository with a name
|
||||
* @param {string} name
|
||||
*/
|
||||
|
||||
constructor(name = 'default') {
|
||||
this.name = name
|
||||
this.suites = []
|
||||
this.report = {}
|
||||
this.isFinished = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether {obj} is repository
|
||||
* @param {any} obj
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
isRepository(obj) {
|
||||
return obj && obj[RepositoryType]
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a suite to the repository
|
||||
* @param {Suite} suite
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
addSuite(suite) {
|
||||
this.isFinished = false
|
||||
this.suites.push(suite)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all suites (and all benches under suites) and generate a report
|
||||
* @return {Promise<Object, *>}
|
||||
*/
|
||||
|
||||
run() {
|
||||
if (this.isFinished) return Promise.resolve(this.report)
|
||||
logger(this)
|
||||
return compose(this.suites).then(() => {
|
||||
this.isFinished = true
|
||||
const report = {}
|
||||
|
||||
for (const suite of this.suites) {
|
||||
report[suite.name] = suite.report
|
||||
}
|
||||
|
||||
this.report = report
|
||||
return report
|
||||
})
|
||||
}
|
||||
}
|
||||
Repository.prototype[RepositoryType] = true
|
||||
|
||||
/**
|
||||
* By default, all suites are registers to the following {repo}
|
||||
*/
|
||||
|
||||
const repo = new Repository()
|
||||
|
||||
module.exports = { Repository, repo }
|
@@ -1,85 +0,0 @@
|
||||
/* global Promise */
|
||||
const { repo } = require('./Repository.js')
|
||||
const { SuiteType } = require('./types')
|
||||
const { logger } = require('./logger')
|
||||
const { compose } = require('./compose')
|
||||
const { makeOptions } = require('./makeOptions')
|
||||
|
||||
/**
|
||||
* Suite is for holding Benches
|
||||
*/
|
||||
|
||||
class Suite {
|
||||
/**
|
||||
* Construct a Suite and regiester it to repository
|
||||
* @param {string} name
|
||||
* @param {Object} options
|
||||
* @property {void|Repository} repository
|
||||
* @property {any} ...rest
|
||||
*/
|
||||
|
||||
constructor(name, options = {}) {
|
||||
const { repository = repo } = options
|
||||
|
||||
if (repository[name]) {
|
||||
throw Error(`The suite name ${name} has benn occupied in repository`)
|
||||
}
|
||||
|
||||
if (typeof name !== 'string') {
|
||||
throw Error(`The suite name must be a string`)
|
||||
}
|
||||
|
||||
this.name = name
|
||||
this.options = makeOptions(options)
|
||||
this.isFinished = false
|
||||
this.benches = []
|
||||
this.report = {}
|
||||
repository.addSuite(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether it is a Suite
|
||||
* @param {any} obj
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
isSuite(obj) {
|
||||
return obj && obj[SuiteType]
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an bench to the repository
|
||||
* @param {Bench} bench
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
addBench(bench) {
|
||||
this.isFinished = false
|
||||
this.benches.push(bench)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all benches, and generate report for consumed time
|
||||
* @return {Promise<Object, *>
|
||||
*/
|
||||
|
||||
makeRun() {
|
||||
if (this.isFinished) return Promise.resolve(this.report)
|
||||
logger(this)
|
||||
return compose(this.benches).then(() => {
|
||||
this.isFinished = true
|
||||
const report = {}
|
||||
|
||||
for (const bench of this.benches) {
|
||||
report[bench.name] = bench.report
|
||||
}
|
||||
|
||||
this.report = report
|
||||
return report
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Suite.prototype[SuiteType] = true
|
||||
|
||||
module.exports = { Suite }
|
@@ -1,66 +0,0 @@
|
||||
const { TimerType } = require('./types')
|
||||
|
||||
class Timer {
|
||||
constructor() {
|
||||
this.cpuStartTime = {}
|
||||
this.hrStartTime = null
|
||||
this.isStopped = false
|
||||
this.elapsed = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether it is a Timer
|
||||
* @param {any} obj
|
||||
*/
|
||||
|
||||
isTimer(obj) {
|
||||
return obj && obj[TimerType]
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the timer
|
||||
* @return {void}
|
||||
*/
|
||||
|
||||
start() {
|
||||
this.isStopped = false
|
||||
this.cpuStartTime = process.cpuUsage()
|
||||
this.hrStartTime = process.hrtime()
|
||||
this.elapsed = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the timer and store restore in this.elapsed
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
end() {
|
||||
if (this.isStopped) return this.elapsed
|
||||
const cpuElapsed = process.cpuUsage(this.cpuStartTime)
|
||||
const hrElapsed = process.hrtime(this.hrStartTime)
|
||||
const { user, system } = cpuElapsed
|
||||
const hr = hrElapsed[0] * 1000 + hrElapsed[1] / 1e6
|
||||
|
||||
/**
|
||||
* user: cpu time consumed in user space
|
||||
* system: cpu time consumed in system space
|
||||
* all: user+system
|
||||
* hr: real world time
|
||||
* (unit): ms
|
||||
*/
|
||||
|
||||
this.elapsed = {
|
||||
user: user / 1000,
|
||||
system: system / 1000,
|
||||
all: (user + system) / 1000,
|
||||
hr,
|
||||
}
|
||||
|
||||
this.isStopped = true
|
||||
return this.elapsed
|
||||
}
|
||||
}
|
||||
|
||||
Timer.prototype[TimerType] = true
|
||||
|
||||
module.exports = { Timer }
|
@@ -1,22 +0,0 @@
|
||||
/* global Promise */
|
||||
const { errorLog } = require('./logger')
|
||||
|
||||
/**
|
||||
* Run all benches/suites with Promise; Ensure an error would not block the whole process
|
||||
* @param {Array<Suite>|Array<Bench>}
|
||||
* @param {string} name; where to call the run method
|
||||
*/
|
||||
|
||||
function compose(list, name = 'makeRun') {
|
||||
return dispatch(0)
|
||||
|
||||
function dispatch(index) {
|
||||
if (index === list.length) return Promise.resolve(true)
|
||||
const node = list[index]
|
||||
return new Promise(resolve => resolve(node[name]()))
|
||||
.catch(err => errorLog(err))
|
||||
.then(() => dispatch(index + 1))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { compose }
|
@@ -1,5 +0,0 @@
|
||||
const { Repository, repo } = require('./Repository')
|
||||
const { Suite } = require('./Suite')
|
||||
const { Bench } = require('./Bench')
|
||||
|
||||
module.exports = { Repository, Suite, Bench, repo }
|
@@ -1,74 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
/**
|
||||
* IS in test
|
||||
*/
|
||||
|
||||
const IS_TEST =
|
||||
typeof process !== 'undefined' &&
|
||||
process.env &&
|
||||
process.env.BABEL_ENV === 'test'
|
||||
|
||||
/**
|
||||
* Log a `message`
|
||||
*
|
||||
* @param {String} message
|
||||
* @param {Any} ...args
|
||||
* @retrun {void}
|
||||
*/
|
||||
|
||||
function log(message, ...args) {
|
||||
if (IS_TEST) return
|
||||
|
||||
return console.log(message, ...args)
|
||||
}
|
||||
|
||||
/*
|
||||
* Log a error `message`
|
||||
*/
|
||||
|
||||
function errorLog(message, ...args) {
|
||||
console.error(message, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logging benchmark result
|
||||
*/
|
||||
|
||||
function logger(obj) {
|
||||
const prefix = ' '
|
||||
|
||||
if (obj.isRepository) {
|
||||
return log(`Repository ${obj.name} is running`)
|
||||
}
|
||||
|
||||
if (obj.isSuite) {
|
||||
return log(`${prefix}- Suite ${obj.name} is running`)
|
||||
}
|
||||
|
||||
if (obj.isBench) {
|
||||
if (!obj.isFinished) {
|
||||
return log(`${prefix + prefix}- Bench ${obj.name} is running`)
|
||||
}
|
||||
|
||||
const { report } = obj
|
||||
const { cycles } = report
|
||||
|
||||
const header = {
|
||||
user: 'user:',
|
||||
hr: 'real:',
|
||||
}
|
||||
|
||||
for (const key of ['user', 'hr']) {
|
||||
log(
|
||||
`${prefix + prefix + prefix}${header[key]} * ${cycles} cycles: ${
|
||||
report[key]
|
||||
} ms; ( ${cycles * 1000 / report[key]} ops/sec)`
|
||||
)
|
||||
}
|
||||
return log(`${prefix + prefix + prefix}cycles: ${cycles}`)
|
||||
}
|
||||
return log(obj)
|
||||
}
|
||||
|
||||
module.exports = { logger, errorLog, log }
|
@@ -1,40 +0,0 @@
|
||||
const defaultOptions = {
|
||||
minTime: 1000,
|
||||
maxTime: 2000,
|
||||
minTries: 100,
|
||||
maxTries: Infinity,
|
||||
allocationTries: 1000,
|
||||
async: false,
|
||||
mode: 'adaptive',
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two options for configuring a bench run
|
||||
* @param {Object} options
|
||||
* @returns {Object}
|
||||
* @property {number} minTime
|
||||
* @property {number} maxTime
|
||||
* @property {number} minTries
|
||||
* @property {number} maxTries
|
||||
* @property {number} allocationTries
|
||||
* @property {boolean} async
|
||||
* @property {"static"|"adaptive"} mode
|
||||
*/
|
||||
|
||||
function makeOptions(options) {
|
||||
const result = { ...defaultOptions, ...options }
|
||||
|
||||
for (const key in defaultOptions) {
|
||||
const shallType = typeof defaultOptions[key]
|
||||
const inputType = typeof result[key]
|
||||
|
||||
if (shallType !== inputType) {
|
||||
throw TypeError(
|
||||
`Wrong Input in Config Suite, options[${key}] should be ${shallType}, but the input type is ${inputType}`
|
||||
)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports = { makeOptions }
|
@@ -1,11 +0,0 @@
|
||||
const RepositoryType = '@@__SLATE_REPOSITORY__@@'
|
||||
const SuiteType = '@@__SLATE_SUITE__@@'
|
||||
const BenchType = '@@__SLATE_BENCH__@@'
|
||||
const TimerType = '@@__SLATE_BENCH_TIMER_@@'
|
||||
|
||||
module.exports = {
|
||||
RepositoryType,
|
||||
SuiteType,
|
||||
BenchType,
|
||||
TimerType,
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Dependencies.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
|
||||
describe('slate-dev-benchmark', () => {
|
||||
// require('./tries/')
|
||||
// require('./time/')
|
||||
})
|
@@ -1,22 +0,0 @@
|
||||
import { repo, Suite } from '../..'
|
||||
import fs from 'fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
describe('time', async () => {
|
||||
const suite = new Suite('tries')
|
||||
const testDir = resolve(__dirname)
|
||||
const files = fs
|
||||
.readdirSync(testDir)
|
||||
.filter(x => x[0] !== '.' && x !== 'index.js')
|
||||
|
||||
for (const file of files) {
|
||||
const module = require(`./${file}`)
|
||||
|
||||
it(module.experiment, () => {
|
||||
module.default(suite)
|
||||
const { expected } = module
|
||||
repo.isFinished = false
|
||||
return repo.run().then(() => expected())
|
||||
})
|
||||
}
|
||||
})
|
@@ -1,31 +0,0 @@
|
||||
/* global Promise */
|
||||
import { Bench } from '../..'
|
||||
import assert from 'assert'
|
||||
|
||||
export const experiment = 'max-time-async'
|
||||
|
||||
let index = 0
|
||||
|
||||
// A wider range than sync, becuase Promise intialization, babel-node takes time
|
||||
export function expected() {
|
||||
assert(
|
||||
index > 5 && index < 12,
|
||||
`index should be 10, but is actually ${index}`
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
export default function(suite) {
|
||||
const bench = new Bench(suite, experiment, {
|
||||
mode: 'adaptive',
|
||||
minTries: 100,
|
||||
maxTries: 200,
|
||||
minTime: 1,
|
||||
maxTime: 100,
|
||||
async: true,
|
||||
})
|
||||
|
||||
bench.run(
|
||||
() => new Promise(resolve => setTimeout(() => resolve(index++), 10))
|
||||
)
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
import { Bench } from '../..'
|
||||
import { syncSleep } from '../utils/sleep'
|
||||
import assert from 'assert'
|
||||
|
||||
export const experiment = 'max-time'
|
||||
|
||||
let index = 0
|
||||
|
||||
export function expected() {
|
||||
assert(
|
||||
index > 85 && index < 115,
|
||||
`index should be around 100, but is actually ${index}`
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
export default function(suite) {
|
||||
const bench = new Bench(suite, experiment, {
|
||||
mode: 'adaptive',
|
||||
minTries: 1000,
|
||||
maxTries: 2000,
|
||||
minTime: 1,
|
||||
maxTime: 1000,
|
||||
async: false,
|
||||
})
|
||||
|
||||
bench.run(() => {
|
||||
syncSleep(10)
|
||||
index++
|
||||
})
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
import { Bench } from '../..'
|
||||
import { syncSleep } from '../utils/sleep'
|
||||
import assert from 'assert'
|
||||
|
||||
export const experiment = 'max-time'
|
||||
|
||||
let index = 0
|
||||
|
||||
export function expected() {
|
||||
assert(index === 10, `index should be 10, but is actually ${index}`)
|
||||
return true
|
||||
}
|
||||
|
||||
export default function(suite) {
|
||||
const bench = new Bench(suite, experiment, {
|
||||
mode: 'adaptive',
|
||||
minTries: 100,
|
||||
maxTries: 200,
|
||||
minTime: 1,
|
||||
maxTime: 100,
|
||||
async: false,
|
||||
})
|
||||
|
||||
bench.run(() => {
|
||||
syncSleep(10)
|
||||
index++
|
||||
})
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
import assert from 'assert'
|
||||
import { repo, Suite } from '../..'
|
||||
import fs from 'fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
describe('tries', async () => {
|
||||
const suite = new Suite('tries')
|
||||
const testDir = resolve(__dirname)
|
||||
const files = fs
|
||||
.readdirSync(testDir)
|
||||
.filter(x => x[0] !== '.' && x !== 'index.js')
|
||||
|
||||
for (const file of files) {
|
||||
const module = require(`./${file}`)
|
||||
|
||||
it(module.experiment, () => {
|
||||
module.default(suite)
|
||||
const { actual, expected } = module
|
||||
repo.isFinished = false
|
||||
return repo.run().then(() => {
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
@@ -1,19 +0,0 @@
|
||||
import { Bench } from '../..'
|
||||
|
||||
export const experiment = 'max-tries adaptive mode'
|
||||
export const actual = { index: 0 }
|
||||
export const expected = { index: 200 }
|
||||
|
||||
export default function(suite) {
|
||||
const bench = new Bench(suite, experiment, {
|
||||
mode: 'adaptive',
|
||||
minTries: 100,
|
||||
maxTries: 200,
|
||||
minTime: 100,
|
||||
maxTime: Infinity,
|
||||
})
|
||||
|
||||
bench.run(() => {
|
||||
actual.index++
|
||||
})
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
import { Bench } from '../..'
|
||||
|
||||
export const experiment = 'min-tries static mode'
|
||||
export const actual = { index: 0 }
|
||||
export const expected = { index: 100 }
|
||||
|
||||
export default function(suite) {
|
||||
const bench = new Bench(suite, experiment, {
|
||||
mode: 'static',
|
||||
minTries: 100,
|
||||
maxTries: 200,
|
||||
minTime: 100,
|
||||
})
|
||||
|
||||
bench.run(() => {
|
||||
actual.index++
|
||||
})
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
function syncSleep(ms) {
|
||||
const start = process.hrtime()
|
||||
|
||||
while (true) {
|
||||
const end = process.hrtime(start)
|
||||
if (end[0] * 1000 + end[1] / 1e6 > ms) return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export { syncSleep }
|
@@ -1 +0,0 @@
|
||||
This package can be used within core Slate packages to detect browser and OS environments.
|
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "slate-dev-environment",
|
||||
"description": "INTERNAL: A set of environment-related constants for Slate's core.",
|
||||
"version": "0.2.2",
|
||||
"license": "MIT",
|
||||
"repository": "git://github.com/ianstormtaylor/slate.git",
|
||||
"main": "lib/slate-dev-environment.js",
|
||||
"module": "lib/slate-dev-environment.es.js",
|
||||
"umd": "dist/slate-dev-environment.js",
|
||||
"umdMin": "dist/slate-dev-environment.min.js",
|
||||
"files": [
|
||||
"dist/",
|
||||
"lib/"
|
||||
],
|
||||
"dependencies": {
|
||||
"is-in-browser": "^1.1.3"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist ./lib ./node_modules"
|
||||
}
|
||||
}
|
@@ -1,154 +0,0 @@
|
||||
import isBrowser from 'is-in-browser'
|
||||
|
||||
/**
|
||||
* Browser matching rules.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const BROWSER_RULES = [
|
||||
['edge', /Edge\/([0-9\._]+)/],
|
||||
['chrome', /(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],
|
||||
['firefox', /Firefox\/([0-9\.]+)(?:\s|$)/],
|
||||
['opera', /Opera\/([0-9\.]+)(?:\s|$)/],
|
||||
['opera', /OPR\/([0-9\.]+)(:?\s|$)$/],
|
||||
['ie', /Trident\/7\.0.*rv\:([0-9\.]+)\).*Gecko$/],
|
||||
['ie', /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/],
|
||||
['ie', /MSIE\s(7\.0)/],
|
||||
['android', /Android\s([0-9\.]+)/],
|
||||
['safari', /Version\/([0-9\._]+).*Safari/],
|
||||
]
|
||||
|
||||
let browser
|
||||
|
||||
if (isBrowser) {
|
||||
for (const [name, regexp] of BROWSER_RULES) {
|
||||
if (regexp.test(window.navigator.userAgent)) {
|
||||
browser = name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Operating system matching rules.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const OS_RULES = [
|
||||
['ios', /os ([\.\_\d]+) like mac os/i], // must be before the macos rule
|
||||
['macos', /mac os x/i],
|
||||
['android', /android/i],
|
||||
['firefoxos', /mozilla\/[a-z\.\_\d]+ \((?:mobile)|(?:tablet)/i],
|
||||
['windows', /windows\s*(?:nt)?\s*([\.\_\d]+)/i],
|
||||
]
|
||||
|
||||
let os
|
||||
|
||||
if (isBrowser) {
|
||||
for (const [name, regexp] of OS_RULES) {
|
||||
if (regexp.test(window.navigator.userAgent)) {
|
||||
os = name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feature matching rules.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const FEATURE_RULES = [
|
||||
[
|
||||
'inputeventslevel1',
|
||||
window => {
|
||||
const event = window.InputEvent ? new window.InputEvent('input') : {}
|
||||
const support = 'inputType' in event
|
||||
return support
|
||||
},
|
||||
],
|
||||
[
|
||||
'inputeventslevel2',
|
||||
window => {
|
||||
const element = window.document.createElement('div')
|
||||
element.contentEditable = true
|
||||
const support = 'onbeforeinput' in element
|
||||
return support
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
const features = []
|
||||
|
||||
if (isBrowser) {
|
||||
for (const [name, test] of FEATURE_RULES) {
|
||||
if (test(window)) {
|
||||
features.push(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Array of regular expression matchers and their API version
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const ANDROID_API_VERSIONS = [
|
||||
[/^9([.]0|)/, 28],
|
||||
[/^8[.]1/, 27],
|
||||
[/^8([.]0|)/, 26],
|
||||
[/^7[.]1/, 25],
|
||||
[/^7([.]0|)/, 24],
|
||||
[/^6([.]0|)/, 23],
|
||||
[/^5[.]1/, 22],
|
||||
[/^5([.]0|)/, 21],
|
||||
[/^4[.]4/, 20],
|
||||
]
|
||||
|
||||
/**
|
||||
* get the Android API version from the userAgent
|
||||
*
|
||||
* @return {number} version
|
||||
*/
|
||||
|
||||
function getAndroidApiVersion() {
|
||||
if (os !== 'android') return null
|
||||
const { userAgent } = window.navigator
|
||||
const matchData = userAgent.match(/Android\s([0-9\.]+)/)
|
||||
if (matchData == null) return null
|
||||
const versionString = matchData[1]
|
||||
|
||||
for (const [regex, version] of ANDROID_API_VERSIONS) {
|
||||
if (versionString.match(regex)) return version
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
|
||||
export const IS_CHROME = browser === 'chrome'
|
||||
export const IS_OPERA = browser === 'opera'
|
||||
export const IS_FIREFOX = browser === 'firefox'
|
||||
export const IS_SAFARI = browser === 'safari'
|
||||
export const IS_IE = browser === 'ie'
|
||||
export const IS_EDGE = browser === 'edge'
|
||||
|
||||
export const IS_ANDROID = os === 'android'
|
||||
export const IS_IOS = os === 'ios'
|
||||
export const IS_MAC = os === 'macos'
|
||||
export const IS_WINDOWS = os === 'windows'
|
||||
|
||||
export const ANDROID_API_VERSION = getAndroidApiVersion()
|
||||
|
||||
export const HAS_INPUT_EVENTS_LEVEL_1 = features.includes('inputeventslevel1')
|
||||
export const HAS_INPUT_EVENTS_LEVEL_2 =
|
||||
features.includes('inputeventslevel2') ||
|
||||
(IS_ANDROID && (ANDROID_API_VERSION === 28 || ANDROID_API_VERSION === null))
|
@@ -1 +0,0 @@
|
||||
This package contains a set of testing utilities used by Slate's other core packages for writing their tests.
|
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "slate-dev-test-utils",
|
||||
"description": "INTERNAL: A set of utils for Slate's core tests.",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"repository": "git://github.com/ianstormtaylor/slate.git",
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
"dist/",
|
||||
"lib/"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.35.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist ./lib ./node_modules"
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import { basename, extname, resolve } from 'path'
|
||||
import { KeyUtils } from 'slate'
|
||||
|
||||
export const fixtures = (...args) => {
|
||||
let fn = args.pop()
|
||||
let options = { skip: false }
|
||||
|
||||
if (typeof fn !== 'function') {
|
||||
options = fn
|
||||
fn = args.pop()
|
||||
}
|
||||
|
||||
const path = resolve(...args)
|
||||
const files = fs.readdirSync(path)
|
||||
const dir = basename(path)
|
||||
const d = options.skip ? describe.skip : describe
|
||||
|
||||
d(dir, () => {
|
||||
for (const file of files) {
|
||||
const p = resolve(path, file)
|
||||
const stat = fs.statSync(p)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
fixtures(path, file, fn)
|
||||
}
|
||||
|
||||
if (
|
||||
stat.isFile() &&
|
||||
file.endsWith('.js') &&
|
||||
!file.startsWith('.') &&
|
||||
// Ignoring `index.js` files allows us to use the fixtures directly
|
||||
// from the top-level directory itself, instead of only children.
|
||||
file !== 'index.js'
|
||||
) {
|
||||
const name = basename(file, extname(file))
|
||||
|
||||
// This needs to be a non-arrow function to use `this.skip()`.
|
||||
it(name, function() {
|
||||
// Ensure that the key generator is reset. We have to do this here
|
||||
// because the `require` call will create the Slate objects.
|
||||
KeyUtils.resetGenerator()
|
||||
const module = require(p)
|
||||
|
||||
if (module.skip) {
|
||||
this.skip()
|
||||
return
|
||||
}
|
||||
|
||||
fn({ name, path, module })
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fixtures.skip = (...args) => {
|
||||
fixtures(...args, { skip: true })
|
||||
}
|
13
packages/slate-history/Changelog.md
Normal file
13
packages/slate-history/Changelog.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
A list of changes to the `slate-history` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes won't be accounted for since the library is moving quickly.
|
||||
|
||||
---
|
||||
|
||||
### `0.50.0` — November 27, 2019
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**A complete overhaul.** The Slate codebase has had a complete overhaul and many pieces of its core architecture have been reconsidered from the ground up. There are lots of changes. We recommend re-reading the [Walkthroughs](https://docs.slatejs.org/walkthroughs) and [Concepts](https://docs.slatejs.org/concepts) documentation and the [Examples](../../site/examples) to get a sense for everything that has changed. As well as the [Migration](https://docs.slatejs.org/concepts/XX-migrating) writeup for what the major changes are.
|
||||
|
||||
---
|
1
packages/slate-history/Readme.md
Normal file
1
packages/slate-history/Readme.md
Normal file
@@ -0,0 +1 @@
|
||||
This package contains the core logic of Slate. Feel free to poke around to learn more!
|
41
packages/slate-history/package.json
Normal file
41
packages/slate-history/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "slate-history",
|
||||
"type": "module",
|
||||
"description": "An operation-based history implementation for Slate editors.",
|
||||
"version": "0.47.8",
|
||||
"license": "MIT",
|
||||
"repository": "git://github.com/ianstormtaylor/slate.git",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.es.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"umd": "dist/slate-history.js",
|
||||
"umdMin": "dist/slate-history.min.js",
|
||||
"files": [
|
||||
"dist/",
|
||||
"lib/"
|
||||
],
|
||||
"dependencies": {
|
||||
"immer": "^5.0.0",
|
||||
"is-plain-object": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"slate": "^0.47.8",
|
||||
"slate-hyperscript": "^0.13.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.50.0"
|
||||
},
|
||||
"umdGlobals": {
|
||||
"slate": "Slate"
|
||||
},
|
||||
"keywords": [
|
||||
"editor",
|
||||
"history",
|
||||
"operation",
|
||||
"redo",
|
||||
"save",
|
||||
"slate",
|
||||
"stack",
|
||||
"undo"
|
||||
]
|
||||
}
|
39
packages/slate-history/src/history-command.ts
Normal file
39
packages/slate-history/src/history-command.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Command } from 'slate'
|
||||
|
||||
export interface RedoCommand {
|
||||
type: 'redo'
|
||||
}
|
||||
|
||||
export interface UndoCommand {
|
||||
type: 'undo'
|
||||
}
|
||||
|
||||
export type HistoryCommand = RedoCommand | UndoCommand
|
||||
|
||||
export const HistoryCommand = {
|
||||
/**
|
||||
* Check if a value is a `HistoryCommand` object.
|
||||
*/
|
||||
|
||||
isHistoryCommand(value: any): value is HistoryCommand {
|
||||
return (
|
||||
HistoryCommand.isRedoCommand(value) || HistoryCommand.isUndoCommand(value)
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is a `RedoCommand` object.
|
||||
*/
|
||||
|
||||
isRedoCommand(value: any): value is RedoCommand {
|
||||
return Command.isCommand(value) && value.type === 'redo'
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is an `UndoCommand` object.
|
||||
*/
|
||||
|
||||
isUndoCommand(value: any): value is UndoCommand {
|
||||
return Command.isCommand(value) && value.type === 'undo'
|
||||
},
|
||||
}
|
68
packages/slate-history/src/history-editor.ts
Normal file
68
packages/slate-history/src/history-editor.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Editor } from 'slate'
|
||||
import { History } from './history'
|
||||
|
||||
/**
|
||||
* Weakmaps for attaching state to the editor.
|
||||
*/
|
||||
|
||||
export const HISTORY = new WeakMap<Editor, History>()
|
||||
export const SAVING = new WeakMap<Editor, boolean | undefined>()
|
||||
export const MERGING = new WeakMap<Editor, boolean | undefined>()
|
||||
|
||||
/**
|
||||
* `HistoryEditor` contains helpers for history-enabled editors.
|
||||
*/
|
||||
|
||||
export interface HistoryEditor extends Editor {
|
||||
history: History
|
||||
}
|
||||
|
||||
export const HistoryEditor = {
|
||||
/**
|
||||
* Check if a value is a `HistoryEditor` object.
|
||||
*/
|
||||
|
||||
isHistoryEditor(value: any): value is HistoryEditor {
|
||||
return Editor.isEditor(value) && History.isHistory(value.history)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the merge flag's current value.
|
||||
*/
|
||||
|
||||
isMerging(editor: Editor): boolean | undefined {
|
||||
return MERGING.get(editor)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the saving flag's current value.
|
||||
*/
|
||||
|
||||
isSaving(editor: Editor): boolean | undefined {
|
||||
return SAVING.get(editor)
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply a series of changes inside a synchronous `fn`, without merging any of
|
||||
* the new operations into previous save point in the history.
|
||||
*/
|
||||
|
||||
withoutMerging(editor: Editor, fn: () => void): void {
|
||||
const prev = HistoryEditor.isMerging(editor)
|
||||
MERGING.set(editor, false)
|
||||
fn()
|
||||
MERGING.set(editor, prev)
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply a series of changes inside a synchronous `fn`, without saving any of
|
||||
* their operations into the history.
|
||||
*/
|
||||
|
||||
withoutSaving(editor: Editor, fn: () => void): void {
|
||||
const prev = HistoryEditor.isSaving(editor)
|
||||
SAVING.set(editor, false)
|
||||
fn()
|
||||
SAVING.set(editor, prev)
|
||||
},
|
||||
}
|
28
packages/slate-history/src/history.ts
Normal file
28
packages/slate-history/src/history.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Operation } from 'slate'
|
||||
|
||||
/**
|
||||
* `History` objects hold all of the operations that are applied to a value, so
|
||||
* they can be undone or redone as necessary.
|
||||
*/
|
||||
|
||||
export interface History {
|
||||
redos: Operation[][]
|
||||
undos: Operation[][]
|
||||
}
|
||||
|
||||
export const History = {
|
||||
/**
|
||||
* Check if a value is a `History` object.
|
||||
*/
|
||||
|
||||
isHistory(value: any): value is History {
|
||||
return (
|
||||
isPlainObject(value) &&
|
||||
Array.isArray(value.redos) &&
|
||||
Array.isArray(value.undos) &&
|
||||
(value.redos.length === 0 || Operation.isOperationList(value.redos[0])) &&
|
||||
(value.undos.length === 0 || Operation.isOperationList(value.undos[0]))
|
||||
)
|
||||
},
|
||||
}
|
4
packages/slate-history/src/index.ts
Normal file
4
packages/slate-history/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './history'
|
||||
export * from './history-command'
|
||||
export * from './history-editor'
|
||||
export * from './with-history'
|
174
packages/slate-history/src/with-history.ts
Normal file
174
packages/slate-history/src/with-history.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { Editor, Command, Operation, Path } from 'slate'
|
||||
|
||||
import { HistoryCommand } from './history-command'
|
||||
import { HistoryEditor } from './history-editor'
|
||||
|
||||
/**
|
||||
* The `withHistory` plugin keeps track of the operation history of a Slate
|
||||
* editor as operations are applied to it, using undo and redo stacks.
|
||||
*/
|
||||
|
||||
export const withHistory = (editor: Editor): Editor => {
|
||||
const { apply, exec } = editor
|
||||
editor.history = { undos: [], redos: [] }
|
||||
|
||||
editor.exec = (command: Command) => {
|
||||
if (HistoryEditor.isHistoryEditor(editor)) {
|
||||
const { history } = editor
|
||||
const { undos, redos } = history
|
||||
|
||||
if (redos.length > 0 && HistoryCommand.isRedoCommand(command)) {
|
||||
const batch = redos[redos.length - 1]
|
||||
|
||||
HistoryEditor.withoutSaving(editor, () => {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
for (const op of batch) {
|
||||
editor.apply(op)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
history.redos.pop()
|
||||
history.undos.push(batch)
|
||||
return
|
||||
}
|
||||
|
||||
if (undos.length > 0 && HistoryCommand.isUndoCommand(command)) {
|
||||
const batch = undos[undos.length - 1]
|
||||
|
||||
HistoryEditor.withoutSaving(editor, () => {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const inverseOps = batch.map(Operation.inverse).reverse()
|
||||
|
||||
for (const op of inverseOps) {
|
||||
// If the final operation is deselecting the editor, skip it. This is
|
||||
if (
|
||||
op === inverseOps[inverseOps.length - 1] &&
|
||||
op.type === 'set_selection' &&
|
||||
op.newProperties == null
|
||||
) {
|
||||
continue
|
||||
} else {
|
||||
editor.apply(op)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
history.redos.push(batch)
|
||||
history.undos.pop()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
exec(command)
|
||||
}
|
||||
|
||||
editor.apply = (op: Operation) => {
|
||||
if (HistoryEditor.isHistoryEditor(editor)) {
|
||||
const { operations, history } = editor
|
||||
const { undos } = history
|
||||
const lastBatch = undos[undos.length - 1]
|
||||
const lastOp = lastBatch && lastBatch[lastBatch.length - 1]
|
||||
const overwrite = shouldOverwrite(op, lastOp)
|
||||
let save = HistoryEditor.isSaving(editor)
|
||||
let merge = HistoryEditor.isMerging(editor)
|
||||
|
||||
if (save == null) {
|
||||
save = shouldSave(op, lastOp)
|
||||
}
|
||||
|
||||
if (save) {
|
||||
if (merge == null) {
|
||||
if (lastBatch == null) {
|
||||
merge = false
|
||||
} else if (operations.length !== 0) {
|
||||
merge = true
|
||||
} else {
|
||||
merge = shouldMerge(op, lastOp) || overwrite
|
||||
}
|
||||
}
|
||||
|
||||
if (lastBatch && merge) {
|
||||
if (overwrite) {
|
||||
lastBatch.pop()
|
||||
}
|
||||
|
||||
lastBatch.push(op)
|
||||
} else {
|
||||
const batch = [op]
|
||||
undos.push(batch)
|
||||
}
|
||||
|
||||
while (undos.length > 100) {
|
||||
undos.shift()
|
||||
}
|
||||
|
||||
history.redos = []
|
||||
}
|
||||
}
|
||||
|
||||
apply(op)
|
||||
}
|
||||
|
||||
return editor
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether to merge an operation into the previous operation.
|
||||
*/
|
||||
|
||||
const shouldMerge = (op: Operation, prev: Operation | undefined): boolean => {
|
||||
if (op.type === 'set_selection') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
prev &&
|
||||
op.type === 'insert_text' &&
|
||||
prev.type === 'insert_text' &&
|
||||
op.offset === prev.offset + prev.text.length &&
|
||||
Path.equals(op.path, prev.path)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
prev &&
|
||||
op.type === 'remove_text' &&
|
||||
prev.type === 'remove_text' &&
|
||||
op.offset + op.text.length === prev.offset &&
|
||||
Path.equals(op.path, prev.path)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an operation needs to be saved to the history.
|
||||
*/
|
||||
|
||||
const shouldSave = (op: Operation, prev: Operation | undefined): boolean => {
|
||||
if (op.type === 'set_selection' && op.newProperties == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an operation should overwrite the previous one.
|
||||
*/
|
||||
|
||||
const shouldOverwrite = (
|
||||
op: Operation,
|
||||
prev: Operation | undefined
|
||||
): boolean => {
|
||||
if (prev && op.type === 'set_selection' && prev.type === 'set_selection') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
35
packages/slate-history/test/index.js
Normal file
35
packages/slate-history/test/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import assert from 'assert'
|
||||
import { fixtures } from '../../../support/fixtures'
|
||||
import { createHyperscript } from 'slate-hyperscript'
|
||||
import { withHistory } from '..'
|
||||
|
||||
describe('slate-history', () => {
|
||||
fixtures(__dirname, 'undo', ({ module }) => {
|
||||
const { input, run, output } = module
|
||||
const editor = withTest(withHistory(input))
|
||||
run(editor)
|
||||
editor.exec({ type: 'undo' })
|
||||
assert.deepEqual(editor.children, output.children)
|
||||
})
|
||||
})
|
||||
|
||||
export const jsx = createHyperscript({
|
||||
elements: {
|
||||
block: {},
|
||||
inline: { inline: true },
|
||||
},
|
||||
})
|
||||
|
||||
const withTest = editor => {
|
||||
const { isInline, isVoid } = editor
|
||||
|
||||
editor.isInline = element => {
|
||||
return element.inline === true ? true : isInline(element)
|
||||
}
|
||||
|
||||
editor.isVoid = element => {
|
||||
return element.void === true ? true : isVoid(element)
|
||||
}
|
||||
|
||||
return editor
|
||||
}
|
22
packages/slate-history/test/undo/add_mark/block-across.js
Normal file
22
packages/slate-history/test/undo/add_mark/block-across.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
o<anchor />
|
||||
ne
|
||||
</block>
|
||||
<block>
|
||||
tw
|
||||
<focus />o
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
20
packages/slate-history/test/undo/add_mark/mark-across.js
Normal file
20
packages/slate-history/test/undo/add_mark/mark-across.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<mark key="b">
|
||||
w<anchor />o
|
||||
</mark>
|
||||
r<focus />d
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
20
packages/slate-history/test/undo/add_mark/mark-from.js
Normal file
20
packages/slate-history/test/undo/add_mark/mark-from.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<mark key="a">
|
||||
w<anchor />o
|
||||
</mark>
|
||||
r<focus />d
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
20
packages/slate-history/test/undo/add_mark/text.js
Normal file
20
packages/slate-history/test/undo/add_mark/text.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<anchor />
|
||||
wo
|
||||
<focus />
|
||||
rd
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
@@ -0,0 +1,27 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'delete_backward' })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>Hello</block>
|
||||
<block>
|
||||
<cursor />
|
||||
world!
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<editor>
|
||||
<block>Hello</block>
|
||||
<block>
|
||||
<cursor />
|
||||
world!
|
||||
</block>
|
||||
</editor>
|
||||
)
|
@@ -0,0 +1,31 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'delete_backward' })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>Hello</block>
|
||||
<block>
|
||||
<block>
|
||||
<cursor />
|
||||
world!
|
||||
</block>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<editor>
|
||||
<block>Hello</block>
|
||||
<block>
|
||||
<block>
|
||||
<cursor />
|
||||
world!
|
||||
</block>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
@@ -0,0 +1,22 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.delete()
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
wo
|
||||
<cursor />
|
||||
rd
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
||||
|
||||
export const skip = true
|
@@ -0,0 +1,25 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.delete()
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block a>
|
||||
o<anchor />
|
||||
ne
|
||||
</block>
|
||||
<block b>
|
||||
tw
|
||||
<focus />o
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
||||
|
||||
export const skip = true
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.delete()
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<text />
|
||||
<inline a>
|
||||
o<anchor />
|
||||
ne
|
||||
</inline>
|
||||
<text />
|
||||
</block>
|
||||
<block>
|
||||
<text />
|
||||
<inline b>
|
||||
tw
|
||||
<focus />o
|
||||
</inline>
|
||||
<text />
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
||||
|
||||
export const skip = true
|
@@ -0,0 +1,27 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.delete()
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<mark key="a">
|
||||
on
|
||||
<anchor />e
|
||||
</mark>
|
||||
<mark key="c">
|
||||
tw
|
||||
<focus />o
|
||||
</mark>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
||||
|
||||
export const skip = true
|
22
packages/slate-history/test/undo/insert_break/basic.js
Normal file
22
packages/slate-history/test/undo/insert_break/basic.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_break' })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<block>
|
||||
on
|
||||
<cursor />e
|
||||
</block>
|
||||
<block>two</block>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
42
packages/slate-history/test/undo/insert_fragment/basic.js
Normal file
42
packages/slate-history/test/undo/insert_fragment/basic.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
const fragment = (
|
||||
<block type="d">
|
||||
<block>A</block>
|
||||
<block type="c">
|
||||
<block type="d">
|
||||
<block>B</block>
|
||||
<block>
|
||||
<block type="d">
|
||||
<block>C</block>
|
||||
</block>
|
||||
</block>
|
||||
</block>
|
||||
<block type="d">
|
||||
<block>D</block>
|
||||
</block>
|
||||
</block>
|
||||
</block>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_fragment', fragment })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block type="d">
|
||||
<block>
|
||||
<text>
|
||||
<cursor />
|
||||
</text>
|
||||
</block>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
||||
|
||||
export const skip = true
|
18
packages/slate-history/test/undo/insert_text/basic.js
Normal file
18
packages/slate-history/test/undo/insert_text/basic.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_text', text: 'text' })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
one
|
||||
<cursor />
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
20
packages/slate-history/test/undo/insert_text/contiguous.js
Normal file
20
packages/slate-history/test/undo/insert_text/contiguous.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_text', text: 't' })
|
||||
editor.exec({ type: 'insert_text', text: 'w' })
|
||||
editor.exec({ type: 'insert_text', text: 'o' })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
one
|
||||
<cursor />
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
@@ -0,0 +1,31 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_text', text: 't' })
|
||||
// editor.move({ reverse: true })
|
||||
editor.exec({ type: 'insert_text', text: 'w' })
|
||||
// editor.move({ reverse: true })
|
||||
editor.exec({ type: 'insert_text', text: 'o' })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
one
|
||||
<cursor />
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<editor>
|
||||
<block>
|
||||
onew
|
||||
<cursor />t
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const skip = true
|
21
packages/slate-history/test/undo/remove_mark/basic.js
Normal file
21
packages/slate-history/test/undo/remove_mark/basic.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'remove_mark', mark: { key: true } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<mark key>
|
||||
<anchor />
|
||||
one
|
||||
<focus />
|
||||
</mark>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
10
packages/slate-history/tsconfig.json
Normal file
10
packages/slate-history/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../config/typescript/tsconfig.json",
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"composite": true
|
||||
},
|
||||
"references": []
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This document maintains a list of changes to the `slate-hotkeys` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes won't be accounted for since the library is moving quickly.
|
||||
|
||||
---
|
||||
|
||||
### `0.2.0` — August 7, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Some hotkey checkers have changed or been removed.** Please check the source to see the changes, sorry for the hassle. This was needed to cleanup the behavior and get this package to not be leaky in terms of what checkers it exposed.
|
||||
|
||||
---
|
||||
|
||||
### `0.1.0` — April 5, 2018
|
||||
|
||||
:tada:
|
@@ -1 +0,0 @@
|
||||
This package contains functions that detect common keypresses in a platform-agnostic way.
|
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "slate-hotkeys",
|
||||
"description": "A set of function to detect common keypresses in a platform-agnostic way",
|
||||
"version": "0.2.9",
|
||||
"license": "MIT",
|
||||
"repository": "git://github.com/ianstormtaylor/slate.git",
|
||||
"main": "lib/slate-hotkeys.js",
|
||||
"module": "lib/slate-hotkeys.es.js",
|
||||
"umd": "dist/slate-hotkeys.js",
|
||||
"umdMin": "dist/slate-hotkeys.min.js",
|
||||
"files": [
|
||||
"dist/",
|
||||
"lib/"
|
||||
],
|
||||
"dependencies": {
|
||||
"is-hotkey": "0.1.4",
|
||||
"slate-dev-environment": "^0.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist ./lib ./node_modules"
|
||||
},
|
||||
"keywords": [
|
||||
"contenteditable",
|
||||
"editor",
|
||||
"keybinding",
|
||||
"keypress",
|
||||
"slate",
|
||||
"text"
|
||||
]
|
||||
}
|
@@ -1,90 +0,0 @@
|
||||
import { isKeyHotkey } from 'is-hotkey'
|
||||
import { IS_IOS, IS_MAC } from 'slate-dev-environment'
|
||||
|
||||
/**
|
||||
* Hotkey mappings for each platform.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const HOTKEYS = {
|
||||
bold: 'mod+b',
|
||||
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
||||
moveBackward: 'left',
|
||||
moveForward: 'right',
|
||||
moveWordBackward: 'ctrl+left',
|
||||
moveWordForward: 'ctrl+right',
|
||||
deleteBackward: 'shift?+backspace',
|
||||
deleteForward: 'shift?+delete',
|
||||
extendBackward: 'shift+left',
|
||||
extendForward: 'shift+right',
|
||||
italic: 'mod+i',
|
||||
splitBlock: 'shift?+enter',
|
||||
undo: 'mod+z',
|
||||
}
|
||||
|
||||
const APPLE_HOTKEYS = {
|
||||
moveLineBackward: 'opt+up',
|
||||
moveLineForward: 'opt+down',
|
||||
moveWordBackward: 'opt+left',
|
||||
moveWordForward: 'opt+right',
|
||||
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
||||
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
||||
deleteLineBackward: 'cmd+shift?+backspace',
|
||||
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
||||
deleteWordBackward: 'opt+shift?+backspace',
|
||||
deleteWordForward: 'opt+shift?+delete',
|
||||
extendLineBackward: 'opt+shift+up',
|
||||
extendLineForward: 'opt+shift+down',
|
||||
redo: 'cmd+shift+z',
|
||||
transposeCharacter: 'ctrl+t',
|
||||
}
|
||||
|
||||
const WINDOWS_HOTKEYS = {
|
||||
deleteWordBackward: 'ctrl+shift?+backspace',
|
||||
deleteWordForward: 'ctrl+shift?+delete',
|
||||
redo: ['ctrl+y', 'ctrl+shift+z'],
|
||||
}
|
||||
|
||||
/**
|
||||
* Hotkeys.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const Hotkeys = {}
|
||||
|
||||
const IS_APPLE = IS_IOS || IS_MAC
|
||||
const IS_WINDOWS = !IS_APPLE
|
||||
const KEYS = []
|
||||
.concat(Object.keys(HOTKEYS))
|
||||
.concat(Object.keys(APPLE_HOTKEYS))
|
||||
.concat(Object.keys(WINDOWS_HOTKEYS))
|
||||
|
||||
KEYS.forEach(key => {
|
||||
const method = `is${key[0].toUpperCase()}${key.slice(1)}`
|
||||
if (Hotkeys[method]) return
|
||||
|
||||
const generic = HOTKEYS[key]
|
||||
const apple = APPLE_HOTKEYS[key]
|
||||
const windows = WINDOWS_HOTKEYS[key]
|
||||
|
||||
const isGeneric = generic && isKeyHotkey(generic)
|
||||
const isApple = apple && isKeyHotkey(apple)
|
||||
const isWindows = windows && isKeyHotkey(windows)
|
||||
|
||||
Hotkeys[method] = event => {
|
||||
if (isGeneric && isGeneric(event)) return true
|
||||
if (IS_APPLE && isApple && isApple(event)) return true
|
||||
if (IS_WINDOWS && isWindows && isWindows(event)) return true
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default Hotkeys
|
@@ -1,67 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This document maintains a list of changes to the `slate-html-serializer` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes won't be accounted for since the library is moving quickly.
|
||||
|
||||
---
|
||||
|
||||
### `0.8.0` — May 1, 2019
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Updated to work with `slate@0.46`.** The HTML serializer has been updated to work alongside the new text data model in the latest version of slate. For serializing it requires you pass in the new format for text nodes. And for deserializing it will return the new format.
|
||||
|
||||
---
|
||||
|
||||
### `0.7.0` — August 22, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
### `0.6.0` — March 22, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Returning `null` now ignores the node.** Previously it would be treated the same as `undefined`, which will move on to the next rule in the stack. Now it ignores the node and moves onto the next node instead.
|
||||
|
||||
---
|
||||
|
||||
### `0.5.0` — January 4, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
|
||||
**Serializing with `parse5` is no longer possible.** The codebase previously made concessions to allow this, but it was never a good idea because `parse5` does not match the `DOMParser` behavior exactly. Instead, you should use `jsdom` to get a matching behavior, otherwise your serialization rules need to account for two slightly different syntax trees.
|
||||
|
||||
---
|
||||
|
||||
### `0.4.0` — October 27, 2017
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
### `0.3.0` — October 27, 2017
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Updated to work with `slate@0.29.0`.** This is required because `slate-html-serializer` needs access to the new `Value` model.
|
||||
|
||||
---
|
||||
|
||||
### `0.2.0` — October 14, 2017
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
|
||||
---
|
||||
|
||||
### `0.1.0` — September 17, 2017
|
||||
|
||||
:tada:
|
@@ -1 +0,0 @@
|
||||
This package contains an HTML serializer for Slate documents, that you can configure depending on your custom schema.
|
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "slate-html-serializer",
|
||||
"description": "An HTML serializer for Slate editors.",
|
||||
"version": "0.8.11",
|
||||
"license": "MIT",
|
||||
"repository": "git://github.com/ianstormtaylor/slate.git",
|
||||
"main": "lib/slate-html-serializer.js",
|
||||
"module": "lib/slate-html-serializer.es.js",
|
||||
"umd": "dist/slate-html-serializer.js",
|
||||
"umdMin": "dist/slate-html-serializer.min.js",
|
||||
"files": [
|
||||
"dist/",
|
||||
"lib/"
|
||||
],
|
||||
"dependencies": {
|
||||
"type-of": "^2.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"immutable": ">=3.8.1 || >4.0.0-rc",
|
||||
"react": ">=0.14.0",
|
||||
"react-dom": ">=0.14.0",
|
||||
"slate": ">=0.36.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^2.5.3",
|
||||
"slate": "^0.47.9",
|
||||
"slate-hyperscript": "^0.13.9"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist ./lib ./node_modules"
|
||||
},
|
||||
"umdGlobals": {
|
||||
"immutable": "Immutable",
|
||||
"react": "React",
|
||||
"react-dom": "ReactDOM",
|
||||
"react-dom/server": "ReactDOMServer",
|
||||
"slate": "Slate"
|
||||
},
|
||||
"keywords": [
|
||||
"deserialize",
|
||||
"editor",
|
||||
"html",
|
||||
"serialize",
|
||||
"serializer",
|
||||
"slate",
|
||||
"xml"
|
||||
]
|
||||
}
|
@@ -1,413 +0,0 @@
|
||||
import React from 'react'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import typeOf from 'type-of'
|
||||
import { Node, Value } from 'slate'
|
||||
import { Record } from 'immutable'
|
||||
|
||||
/**
|
||||
* String.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const String = new Record({
|
||||
object: 'string',
|
||||
text: '',
|
||||
})
|
||||
|
||||
/**
|
||||
* A rule to (de)serialize text nodes. This is automatically added to the HTML
|
||||
* serializer so that users don't have to worry about text-level serialization.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const TEXT_RULE = {
|
||||
deserialize(el) {
|
||||
if (el.tagName && el.tagName.toLowerCase() === 'br') {
|
||||
return {
|
||||
object: 'text',
|
||||
text: '\n',
|
||||
marks: [],
|
||||
}
|
||||
}
|
||||
|
||||
if (el.nodeName === '#text') {
|
||||
if (el.nodeValue && el.nodeValue.match(/<!--.*?-->/)) return
|
||||
|
||||
return {
|
||||
object: 'text',
|
||||
text: el.nodeValue,
|
||||
marks: [],
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
serialize(obj, children) {
|
||||
if (obj.object === 'string') {
|
||||
return children.split('\n').reduce((array, text, i) => {
|
||||
if (i !== 0) array.push(<br key={i} />)
|
||||
array.push(text)
|
||||
return array
|
||||
}, [])
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* A default `parseHtml` function that returns the `<body>` using `DOMParser`.
|
||||
*
|
||||
* @param {String} html
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function defaultParseHtml(html) {
|
||||
if (typeof DOMParser == 'undefined') {
|
||||
throw new Error(
|
||||
'The native `DOMParser` global which the `Html` serializer uses by default is not present in this environment. You must supply the `options.parseHtml` function instead.'
|
||||
)
|
||||
}
|
||||
|
||||
const parsed = new DOMParser().parseFromString(html, 'text/html')
|
||||
const { body } = parsed
|
||||
// COMPAT: in IE 11 body is null if html is an empty string
|
||||
return body || window.document.createElement('body')
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML serializer.
|
||||
*
|
||||
* @type {Html}
|
||||
*/
|
||||
|
||||
class Html {
|
||||
/**
|
||||
* Create a new serializer with `rules`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @property {Array} rules
|
||||
* @property {String|Object|Block} defaultBlock
|
||||
* @property {Function} parseHtml
|
||||
*/
|
||||
|
||||
constructor(options = {}) {
|
||||
let {
|
||||
defaultBlock = 'paragraph',
|
||||
parseHtml = defaultParseHtml,
|
||||
rules = [],
|
||||
} = options
|
||||
|
||||
defaultBlock = Node.createProperties(defaultBlock)
|
||||
|
||||
this.rules = [...rules, TEXT_RULE]
|
||||
this.defaultBlock = defaultBlock
|
||||
this.parseHtml = parseHtml
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize pasted HTML.
|
||||
*
|
||||
* @param {String} html
|
||||
* @param {Object} options
|
||||
* @property {Boolean} toRaw
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
deserialize = (html, options = {}) => {
|
||||
const { toJSON = false } = options
|
||||
const { defaultBlock, parseHtml } = this
|
||||
const fragment = parseHtml(html)
|
||||
const children = Array.from(fragment.childNodes)
|
||||
let nodes = this.deserializeElements(children)
|
||||
|
||||
// COMPAT: ensure that all top-level inline nodes are wrapped into a block.
|
||||
nodes = nodes.reduce((memo, node, i, original) => {
|
||||
if (node.object === 'block') {
|
||||
memo.push(node)
|
||||
return memo
|
||||
}
|
||||
|
||||
if (i > 0 && original[i - 1].object !== 'block') {
|
||||
const block = memo[memo.length - 1]
|
||||
block.nodes.push(node)
|
||||
return memo
|
||||
}
|
||||
|
||||
const block = {
|
||||
object: 'block',
|
||||
data: {},
|
||||
...defaultBlock,
|
||||
nodes: [node],
|
||||
}
|
||||
|
||||
memo.push(block)
|
||||
return memo
|
||||
}, [])
|
||||
|
||||
// TODO: pretty sure this is no longer needed.
|
||||
if (nodes.length === 0) {
|
||||
nodes = [
|
||||
{
|
||||
object: 'block',
|
||||
data: {},
|
||||
...defaultBlock,
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const json = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
data: {},
|
||||
nodes,
|
||||
},
|
||||
}
|
||||
|
||||
const ret = toJSON ? json : Value.fromJSON(json)
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize an array of DOM elements.
|
||||
*
|
||||
* @param {Array} elements
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
deserializeElements = (elements = []) => {
|
||||
let nodes = []
|
||||
|
||||
elements.filter(this.cruftNewline).forEach(element => {
|
||||
const node = this.deserializeElement(element)
|
||||
|
||||
switch (typeOf(node)) {
|
||||
case 'array':
|
||||
nodes = nodes.concat(node)
|
||||
break
|
||||
case 'object':
|
||||
nodes.push(node)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a DOM element.
|
||||
*
|
||||
* @param {Object} element
|
||||
* @return {Any}
|
||||
*/
|
||||
|
||||
deserializeElement = element => {
|
||||
let node
|
||||
|
||||
if (!element.tagName) {
|
||||
element.tagName = ''
|
||||
}
|
||||
|
||||
const next = elements => {
|
||||
if (Object.prototype.toString.call(elements) === '[object NodeList]') {
|
||||
elements = Array.from(elements)
|
||||
}
|
||||
|
||||
switch (typeOf(elements)) {
|
||||
case 'array':
|
||||
return this.deserializeElements(elements)
|
||||
case 'object':
|
||||
return this.deserializeElement(elements)
|
||||
case 'null':
|
||||
case 'undefined':
|
||||
return
|
||||
default:
|
||||
throw new Error(
|
||||
`The \`next\` argument was called with invalid children: "${elements}".`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (const rule of this.rules) {
|
||||
if (!rule.deserialize) continue
|
||||
const ret = rule.deserialize(element, next)
|
||||
const type = typeOf(ret)
|
||||
|
||||
if (
|
||||
type !== 'array' &&
|
||||
type !== 'object' &&
|
||||
type !== 'null' &&
|
||||
type !== 'undefined'
|
||||
) {
|
||||
throw new Error(
|
||||
`A rule returned an invalid deserialized representation: "${node}".`
|
||||
)
|
||||
}
|
||||
|
||||
if (ret === undefined) {
|
||||
continue
|
||||
} else if (ret === null) {
|
||||
return null
|
||||
} else if (ret.object === 'mark') {
|
||||
node = this.deserializeMark(ret)
|
||||
} else {
|
||||
node = ret
|
||||
}
|
||||
|
||||
if (node.object === 'block' || node.object === 'inline') {
|
||||
node.data = node.data || {}
|
||||
node.nodes = node.nodes || []
|
||||
} else if (node.object === 'text') {
|
||||
node.marks = node.marks || []
|
||||
node.text = node.text || ''
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return node || next(element.childNodes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a `mark` object.
|
||||
*
|
||||
* @param {Object} mark
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
deserializeMark = mark => {
|
||||
const { type, data } = mark
|
||||
|
||||
const applyMark = node => {
|
||||
if (node.object === 'mark') {
|
||||
const ret = this.deserializeMark(node)
|
||||
return ret
|
||||
} else if (node.object === 'text') {
|
||||
node.marks = node.marks || []
|
||||
node.marks.push({ type, data })
|
||||
} else if (node.nodes) {
|
||||
node.nodes = node.nodes.map(applyMark)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
return mark.nodes.reduce((nodes, node) => {
|
||||
const ret = applyMark(node)
|
||||
if (Array.isArray(ret)) return nodes.concat(ret)
|
||||
nodes.push(ret)
|
||||
return nodes
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a `value` object into an HTML string.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Object} options
|
||||
* @property {Boolean} render
|
||||
* @return {String|Array}
|
||||
*/
|
||||
|
||||
serialize = (value, options = {}) => {
|
||||
const { document } = value
|
||||
const elements = document.nodes.map(this.serializeNode).filter(el => el)
|
||||
if (options.render === false) return elements
|
||||
|
||||
const html = renderToStaticMarkup(<body>{elements}</body>)
|
||||
const inner = html.slice(6, -7)
|
||||
return inner
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
serializeNode = node => {
|
||||
if (node.object === 'text') {
|
||||
const string = new String({ text: node.text })
|
||||
const text = this.serializeString(string)
|
||||
|
||||
return node.marks.reduce((children, mark) => {
|
||||
for (const rule of this.rules) {
|
||||
if (!rule.serialize) continue
|
||||
const ret = rule.serialize(mark, children)
|
||||
if (ret === null) return
|
||||
if (ret) return addKey(ret)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`No serializer defined for mark of type "${mark.type}".`
|
||||
)
|
||||
}, text)
|
||||
}
|
||||
|
||||
const children = node.nodes.map(this.serializeNode)
|
||||
|
||||
for (const rule of this.rules) {
|
||||
if (!rule.serialize) continue
|
||||
const ret = rule.serialize(node, children)
|
||||
if (ret === null) return
|
||||
if (ret) return addKey(ret)
|
||||
}
|
||||
|
||||
throw new Error(`No serializer defined for node of type "${node.type}".`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a `string`.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
serializeString = string => {
|
||||
for (const rule of this.rules) {
|
||||
if (!rule.serialize) continue
|
||||
const ret = rule.serialize(string, string.text)
|
||||
if (ret) return ret
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out cruft newline nodes inserted by the DOM parser.
|
||||
*
|
||||
* @param {Object} element
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
cruftNewline = element => {
|
||||
return !(element.nodeName === '#text' && element.nodeValue === '\n')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a unique key to a React `element`.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
let key = 0
|
||||
|
||||
function addKey(element) {
|
||||
return React.cloneElement(element, { key: key++ })
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Html}
|
||||
*/
|
||||
|
||||
export default Html
|
@@ -1,42 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'blockquote': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'quote',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<blockquote><p>one</p></blockquote>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph>one</paragraph>
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,33 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph />
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,34 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
data: { thing: 'value' },
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>one</p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph thing="value">one</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,32 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'img': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'image',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<img/>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<image />
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,33 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>one</p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>one</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,43 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultBlock: {
|
||||
type: 'default',
|
||||
data: {
|
||||
thing: 'value',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>one</p>
|
||||
<div>two</div>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>one</paragraph>
|
||||
<block type="default" data={{ thing: 'value' }}>
|
||||
two
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,17 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {}
|
||||
|
||||
export const input = ''
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<text />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,34 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<!-- This comment should be ignored -->
|
||||
<p>one</p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>one</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,51 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'a': {
|
||||
return {
|
||||
object: 'inline',
|
||||
type: 'link',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'span': {
|
||||
return {
|
||||
object: 'inline',
|
||||
type: 'hashtag',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p><a><span>one</span></a></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<link>
|
||||
<hashtag>one</hashtag>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,42 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'a': {
|
||||
return {
|
||||
object: 'inline',
|
||||
type: 'link',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p><a></a></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<link />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,43 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'a': {
|
||||
return {
|
||||
object: 'inline',
|
||||
type: 'link',
|
||||
data: { thing: 'value' },
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p><a>one</a></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<link thing="value">one</link>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,42 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'img': {
|
||||
return {
|
||||
object: 'inline',
|
||||
type: 'emoji',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p><img/></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<emoji />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,42 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'a': {
|
||||
return {
|
||||
object: 'inline',
|
||||
type: 'link',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p><a>one</a></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<link>one</link>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,51 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'strong': {
|
||||
return {
|
||||
object: 'mark',
|
||||
type: 'bold',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'em': {
|
||||
return {
|
||||
object: 'mark',
|
||||
type: 'italic',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>o<strong>n</strong><strong>e</strong></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<text>o</text>
|
||||
<b>n</b>
|
||||
<b>e</b>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,57 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'strong': {
|
||||
return {
|
||||
object: 'mark',
|
||||
type: 'bold',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'em': {
|
||||
return {
|
||||
object: 'mark',
|
||||
type: 'italic',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>o<em>n<strong>e</strong></em></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<text>o</text>
|
||||
<text>
|
||||
<i>n</i>
|
||||
</text>
|
||||
<text>
|
||||
<i>
|
||||
<b>e</b>
|
||||
</i>
|
||||
</text>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,54 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'strong': {
|
||||
return {
|
||||
object: 'mark',
|
||||
type: 'bold',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'br': {
|
||||
return {
|
||||
object: 'inline',
|
||||
type: 'linebreak',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p><strong>one<br/>two</strong></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<text>
|
||||
<b>one</b>
|
||||
</text>
|
||||
<linebreak />
|
||||
<text>
|
||||
<b>two</b>
|
||||
</text>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,46 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'strong': {
|
||||
return {
|
||||
object: 'mark',
|
||||
type: 'bold',
|
||||
data: { thing: 'value' },
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>on<strong>e</strong></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<text>on</text>
|
||||
<text>
|
||||
<b thing="value">e</b>
|
||||
</text>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,45 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'strong': {
|
||||
return {
|
||||
object: 'mark',
|
||||
type: 'bold',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>on<strong>e</strong></p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<text>on</text>
|
||||
<text>
|
||||
<b>e</b>
|
||||
</text>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,36 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
deserialize(el, next) {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'quote',
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>one</p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph />
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,33 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>one</p>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph />
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,45 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'div': {
|
||||
return null
|
||||
}
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
case 'img': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'image',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p><img/></p>
|
||||
<div><img/></div>
|
||||
`.trim()
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<image />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,47 +0,0 @@
|
||||
export const config = {
|
||||
rules: [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case 'p': {
|
||||
return {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.childNodes),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const input = `
|
||||
<p>one</p>
|
||||
`.trim()
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const options = {
|
||||
toJSON: true,
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
import { createHyperscript } from 'slate-hyperscript'
|
||||
|
||||
/**
|
||||
* Define a hyperscript.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
const h = createHyperscript({
|
||||
blocks: {
|
||||
line: 'line',
|
||||
paragraph: 'paragraph',
|
||||
quote: 'quote',
|
||||
code: 'code',
|
||||
image: 'image',
|
||||
},
|
||||
inlines: {
|
||||
link: 'link',
|
||||
hashtag: 'hashtag',
|
||||
comment: 'comment',
|
||||
emoji: 'emoji',
|
||||
linebreak: 'linebreak',
|
||||
},
|
||||
marks: {
|
||||
b: 'bold',
|
||||
i: 'italic',
|
||||
u: 'underline',
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default h
|
@@ -1,25 +0,0 @@
|
||||
import Html from 'slate-html-serializer'
|
||||
import assert from 'assert'
|
||||
import { JSDOM } from 'jsdom'
|
||||
import { Value } from 'slate'
|
||||
import { fixtures } from 'slate-dev-test-utils'
|
||||
|
||||
describe('slate-html-serializer', () => {
|
||||
fixtures(__dirname, 'deserialize', ({ module }) => {
|
||||
const { input, output, config, options } = module
|
||||
const html = new Html({ parseHtml: JSDOM.fragment, ...config })
|
||||
const value = html.deserialize(input, options)
|
||||
const actual = Value.isValue(value) ? value.toJSON() : value
|
||||
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
fixtures(__dirname, 'serialize', ({ module }) => {
|
||||
const { input, output, rules, options } = module
|
||||
const html = new Html({ rules, parseHtml: JSDOM.fragment })
|
||||
const string = html.serialize(input, options)
|
||||
const actual = string
|
||||
const expected = output
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
})
|
@@ -1,33 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const rules = [
|
||||
{
|
||||
serialize(obj, children) {
|
||||
if (obj.object !== 'block') return
|
||||
|
||||
switch (obj.type) {
|
||||
case 'paragraph':
|
||||
return React.createElement('p', {}, children)
|
||||
case 'quote':
|
||||
return React.createElement('blockquote', {}, children)
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph>one</paragraph>
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<blockquote><p>one</p></blockquote>
|
||||
`.trim()
|
@@ -1,30 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const rules = [
|
||||
{
|
||||
serialize(obj, children) {
|
||||
if (obj.object === 'block' && obj.type === 'paragraph') {
|
||||
return React.createElement(
|
||||
'p',
|
||||
{ 'data-thing': obj.data.get('thing') },
|
||||
children
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph thing="value">one</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<p data-thing="value">one</p>
|
||||
`.trim()
|
@@ -1,26 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const rules = [
|
||||
{
|
||||
serialize(obj, children) {
|
||||
if (obj.object === 'block' && obj.type === 'image') {
|
||||
return React.createElement('img')
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<image />
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<img/>
|
||||
`.trim()
|
@@ -1,32 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const rules = [
|
||||
{
|
||||
serialize(obj, children) {
|
||||
if (obj.object === 'block' && obj.type === 'paragraph') {
|
||||
return React.createElement('p', {}, children)
|
||||
}
|
||||
|
||||
if (obj.object === 'mark' && obj.type === 'bold') {
|
||||
return React.createElement('strong', {}, children)
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
on<b>e</b>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<p>on<strong>e</strong></p>
|
||||
`.trim()
|
@@ -1,26 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const rules = [
|
||||
{
|
||||
serialize(obj, children) {
|
||||
if (obj.object === 'block' && obj.type === 'paragraph') {
|
||||
return React.createElement('p', {}, children)
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>one</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<p>one</p>
|
||||
`.trim()
|
@@ -1,38 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const rules = [
|
||||
{
|
||||
serialize(obj, children) {
|
||||
if (obj.object === 'block' && obj.type === 'paragraph') {
|
||||
return React.createElement('p', {}, children)
|
||||
}
|
||||
|
||||
if (obj.object === 'inline' && obj.type === 'link') {
|
||||
return React.createElement('a', {}, children)
|
||||
}
|
||||
|
||||
if (obj.object === 'inline' && obj.type === 'hashtag') {
|
||||
return React.createElement('span', {}, children)
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<link>
|
||||
<hashtag>one</hashtag>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<p><a><span>one</span></a></p>
|
||||
`.trim()
|
@@ -1,36 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const rules = [
|
||||
{
|
||||
serialize(obj, children) {
|
||||
if (obj.object === 'block' && obj.type === 'paragraph') {
|
||||
return React.createElement('p', {}, children)
|
||||
}
|
||||
|
||||
if (obj.object === 'inline' && obj.type === 'link') {
|
||||
return React.createElement(
|
||||
'a',
|
||||
{ href: obj.data.get('href') },
|
||||
children
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<link href="https://google.com">one</link>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<p><a href="https://google.com">one</a></p>
|
||||
`.trim()
|
@@ -1,32 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../helpers/h'
|
||||
|
||||
export const rules = [
|
||||
{
|
||||
serialize(obj, children) {
|
||||
if (obj.object === 'block' && obj.type === 'paragraph') {
|
||||
return React.createElement('p', {}, children)
|
||||
}
|
||||
|
||||
if (obj.object === 'inline' && obj.type === 'emoji') {
|
||||
return React.createElement('img')
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<emoji />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<p><img/></p>
|
||||
`.trim()
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user