Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

notes on the future namespace-based API #2759

Closed
ianstormtaylor opened this issue May 10, 2019 · 5 comments
Closed

notes on the future namespace-based API #2759

ianstormtaylor opened this issue May 10, 2019 · 5 comments

Comments

@ianstormtaylor
Copy link
Owner

ianstormtaylor commented May 10, 2019

In light of #2495 and switching to using an interface-based API with plain JavaScript objects and arrays, the core API will change. Using plain JavaScript data means that we can eliminate a decent amount of code I think, and it means we'll move to mimicking native namespaced utils like Object.entries(object).

This issue is just note to keep track of the "ideal" future interface-and-namespace-based API, as I think of improvements. We'll work towards it over a series of versions, since we want to keep things incremental and deprecation-friendly whenever possible.

But here's a sketch...

const Slate = {
  // Creators
  createAnnotation(props, root = null) {},
  createDecoration(props, root = null) {},
  createMark(props) {},
  createNode(props) {},
  createRange(props, root = null) {},
  createPath(array, root = null) {},
  createPoint(props, root = null) {},
  createSelection(props, root = null) {},
  createValue(props) {},

  // Producers
  produce() {}, // for producing new immutable values, delegates to immer
  apply(value, operation) {},
}
const Node = {
  // Getters
  get(root, path) {}, // lookup by path in a `root`
  path(root, node) {}, // lookup a path by `node` instance
  slice(root, range) {}, // slice out a fragment by range
  offset(root, path) {}, // compute the text offset by path
  text(root) {}, // get the concatenated text content of a node

  // Testers
  has(root, path) {}, // test if a path exists
  hasBlocks(root) {},
  hasInlines(root) {},
  isInVoid(root, path) {}, // test if a path is in a void
  isLeafBlock(root) {},
  isLeafInline(root) {},

  // Iterables
  ancestors(root, path) {},
  blocks(root) {},
  descendants(root) {},
  inlines(root) {},
  marks(root) {},
  siblings(root, path) {},
  texts(root) {},

  // Searching
  closest(root, path, predicate = identity) {},
  furthest(root, path, predicate = identity) {},

  // Convenience
  firstBlock(root) {},
  lastBlock(root) {},
  nextBlock(root, path) {},
  previousBlock(root, path) {},
  closestBlock(root, path) {},
  furthestBlock(root, path) {},
  firstInline(root) {},
  lastInline(root) {},
  nextInline(root, path) {},
  previousInline(root, path) {},
  closestInline(root, path) {},
  furthestInline(root, path) {},
  nextSibling(root, path) {},
  previousSibling(root, path) {},
  firstText(root) {},
  lastText(root) {},
  nextText(root, path) {},
  previousText(root, path) {},
  closestVoid(root, path) {},
  furthestVoid(root, path) {},

  // Producers
  addMark(root, path, mark) {},
  removeMark(root, path, mark) {},
  setMark(root, path, mark, newProperties) {},
  insertNode(root, path, node) {},
  removeNode(root, path) {},
  insertText(root, path, offset, text) {},
  removeText(root, path, offset, length) {},
  splitNode(root, path, position) {},
  mergeNode(root, path) {},
  setNode(root, path, newProperties) {},
  setData(node, data) {},
  setObject(node, object) {},
  setType(node, type) {},
  setNodes(node, nodes) {},
}
const Point = {
  // Testers
  isAfter(point, pointOrRange) {},
  isBefore(point, pointOrRange) {},
  isAtEndOf(point, range) {},
  isAtStartOf(point, range) {},
  isIn(point, range) {},

  // Producers
  moveBackward(point, n, root, options) {}, // can specify `options.unit` (char, word, …)
  moveForward(point, n, root, options) {}, // can specify `options.unit` (char, word, …)
  moveTo(point, path, offset, root) {},
  setPath(point, path) {},
  setOffset(point, offset) {},
}
const Range = {
  // Testers
  isBackward(range) {},
  isCollapsed(range) {},
  isExpanded(range) {},
  isForward(range) {},
  isSet(range) {},
  isUnset(range) {},

  // Getters 
  start(range) {},
  end(range) {},

  // Iterables
  points(range) {}, // returns `[start, end]` for convenience

  // Producers
  flip(range) {},
  unset(range) {},
  moveBackward(range, n, root, options) {}, // can specify `options.unit` (char, word, …)
  moveForward(range, n, root, options) {}, // can specify `options.unit` (char, word, …)
  moveTo(range, path, offset) {},
  setAnchor(range, fn) {},
  setFocus(range, fn) {},
  setStart(range, fn) {},
  setEnd(range, fn) {},
  setPoints(range, fn) {},
}
// TODO: the other interfaces...

It's funny, I thought it was going to make things more verbose, but in reality the before and after is pretty similar (and in some cases more clear) for many of them:

// Before...
const node = document.getNode(path)

// After...
const node = Node.get(document, path)

And the iterators are similar to how Object.entries() works:

for (const [block, path] of Node.blocks(document)) {
  // ...
}

Overall pretty excited for how it's shaking out. If you want to help make this happen, anything to further #2495 is the best way to do that! Thanks.

@steida
Copy link

steida commented May 10, 2019

Just an idea. Why do not have all functions in 'slate' with namespaces in their names?

import { createMark, getNode, getNodeAncestors, isPointAfter } from 'slate';

People using TypeScript just write 'isPointAfter' and VSCode will import automatically.
I suppose it would make a code a more readable. get, filter, find names are too much generic IMHO.

@ianstormtaylor
Copy link
Owner Author

ianstormtaylor commented May 11, 2019

@steida that’s a great question. And I think it’s still an option.

I lean towards the separate namespaces right now because I think it makes it a bit more clear as to what interface they accept. The first argument will always be one of the objects themselves.

@steida
Copy link

steida commented May 11, 2019

It can be both. Separate namespaces and full names. Node etc. can be re-exported from slate. And we can just write import * as s from 'slate' and have easy API discoverability through autocomplete. I like io-ts API https://github.com/gcanti/io-ts approach.

"a bit more clear as to what interface they accept" That's a task for VSCode. It shows types even in plain JavaScript.

Anyway, it's up to you. I am only thinking aloud. Thank you for your precious work.

Also: https://twitter.com/GiulioCanti/status/1127249761134948352

@ianstormtaylor
Copy link
Owner Author

@steida that's a good point, we could consider exporting both and just seeing which one is most useful over time if only one ends up being used.

@ianstormtaylor
Copy link
Owner Author

Fixed by #3093.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants