1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-31 19:01:54 +02:00
* 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:
Ian Storm Taylor
2019-11-27 20:54:42 -05:00
committed by GitHub
parent 02b87d5968
commit 4ff6972096
2367 changed files with 45706 additions and 80698 deletions

View File

@@ -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:

View File

@@ -1 +0,0 @@
This package contains a base 64 serializer for Slate documents.

View File

@@ -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"
]
}

View File

@@ -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,
}

View File

@@ -1 +0,0 @@
This package contains 'Suite' and 'Bench' that Slate uses to compare computation efficiency under nodeJS.

View File

@@ -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"
}
}

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -1,5 +0,0 @@
const { Repository, repo } = require('./Repository')
const { Suite } = require('./Suite')
const { Bench } = require('./Bench')
module.exports = { Repository, Suite, Bench, repo }

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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,
}

View File

@@ -1,12 +0,0 @@
/**
* Dependencies.
*/
/**
* Tests.
*/
describe('slate-dev-benchmark', () => {
// require('./tries/')
// require('./time/')
})

View File

@@ -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())
})
}
})

View File

@@ -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))
)
}

View File

@@ -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++
})
}

View File

@@ -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++
})
}

View File

@@ -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)
})
})
}
})

View File

@@ -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++
})
}

View File

@@ -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++
})
}

View File

@@ -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 }

View File

@@ -1 +0,0 @@
This package can be used within core Slate packages to detect browser and OS environments.

View File

@@ -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"
}
}

View File

@@ -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))

View File

@@ -1 +0,0 @@
This package contains a set of testing utilities used by Slate's other core packages for writing their tests.

View File

@@ -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"
}
}

View File

@@ -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 })
}

View 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.
---

View File

@@ -0,0 +1 @@
This package contains the core logic of Slate. Feel free to poke around to learn more!

View 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"
]
}

View 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'
},
}

View 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)
},
}

View 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]))
)
},
}

View File

@@ -0,0 +1,4 @@
export * from './history'
export * from './history-command'
export * from './history-editor'
export * from './with-history'

View 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
}

View 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
}

View 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

View 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

View 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

View 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

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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

View File

@@ -0,0 +1,10 @@
{
"extends": "../../config/typescript/tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"composite": true
},
"references": []
}

View File

@@ -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:

View File

@@ -1 +0,0 @@
This package contains functions that detect common keypresses in a platform-agnostic way.

View File

@@ -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"
]
}

View File

@@ -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

View File

@@ -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:

View File

@@ -1 +0,0 @@
This package contains an HTML serializer for Slate documents, that you can configure depending on your custom schema.

View File

@@ -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"
]
}

View File

@@ -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

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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)
})
})

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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