import visit from './visit';
/** Return a new {@link Node} */
class Node {
/**
* Position of the current {@link Node} from its parent.
* @returns {number}
* @example
* node.index // returns the index of the node or -1
*/
get index () {
if (this.parent === Object(this.parent) && this.parent.nodes && this.parent.nodes.length) {
return this.parent.nodes.indexOf(this);
}
return -1;
}
/**
* Next {@link Node} after the current {@link Node}, or `null` if there is none.
* @returns {Node|null}
* @example
* node.next // returns null
*/
get next () {
const index = this.index;
if (index !== -1) {
return this.parent.nodes[index + 1] || null;
}
return null;
}
/**
* Next {@link Element} after the current {@link Node}, or `null` if there is none.
* @returns {Element|null}
* @example
* node.nextElement // returns an element or null
*/
get nextElement () {
const index = this.index;
if (index !== -1) {
return this.parent.nodes.slice(index).find(hasNodes);
}
return null;
}
/**
* Previous {@link Node} before the current {@link Node}, or `null` if there is none.
* @returns {Node|null}
* @example
* node.previous // returns a node or null
*/
get previous () {
const index = this.index;
if (index !== -1) {
return this.parent.nodes[index - 1] || null;
}
return null;
}
/**
* Previous {@link Element} before the current {@link Node}, or `null` if there is none.
* @returns {Element|null}
* @example
* node.previousElement // returns an element or null
*/
get previousElement () {
const index = this.index;
if (index !== -1) {
return this.parent.nodes.slice(0, index).reverse().find(hasNodes);
}
return null;
}
/**
* Top-most ancestor from the current {@link Node}.
* @returns {Node}
* @example
* node.root // returns the top-most node or the current node itself
*/
get root () {
let parent = this;
while (parent.parent) {
parent = parent.parent;
}
return parent;
}
/**
* Insert one or more {@link Node}s after the current {@link Node}, returning the current {@link Node}.
* @param {...Node|string} nodes - Any nodes to be inserted after the current {@link Node}.
* @example
* node.after(new Text({ data: 'Hello World' }))
*/
after (...nodes) {
if (nodes.length) {
const index = this.index;
if (index !== -1) {
this.parent.nodes.splice(index + 1, 0, ...nodes);
}
}
return this;
}
/**
* Append Nodes or new Text Nodes to the current {@link Node}, returning the current {@link Node}.
* @param {...Node|string} nodes - Any nodes to be inserted after the last child of the current {@link Node}.
* @example
* node.append(someOtherNode)
*/
append (...nodes) {
if (this.nodes) {
this.nodes.splice(this.nodes.length, 0, ...nodes);
}
return this;
}
/**
* Append the current {@link Node} to another Node, returning the current {@link Node}.
* @param {Container} parent - {@link Container} for the current {@link Node}.
*/
appendTo (parent) {
if (parent && parent.nodes) {
parent.nodes.splice(parent.nodes.length, 0, this);
}
return this;
}
/**
* Insert Nodes or new Text Nodes before the Node if it has a parent, returning the current {@link Node}.
* @param {...Node|string} nodes - Any nodes to be inserted before the current {@link Node}.
* @example
* node.before(new Text({ data: 'Hello World' })) // returns the current node
*/
before (...nodes) {
if (nodes.length) {
const index = this.index;
if (index !== -1) {
this.parent.nodes.splice(index, 0, ...nodes);
}
}
return this;
}
/**
* Prepend Nodes or new Text Nodes to the current {@link Node}, returning the current {@link Node}.
* @param {...Node|string} nodes - Any nodes inserted before the first child of the current {@link Node}.
* @example
* node.prepend(someOtherNode)
*/
prepend (...nodes) {
if (this.nodes) {
this.nodes.splice(0, 0, ...nodes);
}
return this;
}
/**
* Remove the current {@link Node} from its parent, returning the current {@link Node}.
* @example
* node.remove() // returns the current node
*/
remove () {
const index = this.index;
if (index !== -1) {
this.parent.nodes.splice(index, 1);
}
return this;
}
/**
* Replace the current {@link Node} with another Node or Nodes, returning the current {@link Node}.
* @param {...Node} nodes - Any nodes replacing the current {@link Node}.
* @example
* node.replaceWith(someOtherNode) // returns the current node
*/
replaceWith (...nodes) {
const index = this.index;
if (index !== -1) {
this.parent.nodes.splice(index, 1, ...nodes);
}
return this;
}
/**
* Transform the current {@link Node} and any descendants using visitors.
* @param {Result} result - {@link Result} to be used by visitors.
* @param {Object} [overrideVisitors] - Alternative visitors to be used in place of {@link Result} visitors.
* @returns {ResultPromise}
* @example
* await node.visit(result)
* @example
* await node.visit() // visit using the result of the current node
* @example
* await node.visit(result, {
* Element () {
* // do something to an element
* }
* })
*/
visit (result, overrideVisitors) {
const resultToUse = 0 in arguments ? result : this.result;
return visit(this, resultToUse, overrideVisitors);
}
/**
* Add a warning from the current {@link Node}.
* @param {Result} result - {@link Result} the warning is being added to.
* @param {string} text - Message being sent as the warning.
* @param {Object} [opts] - Additional information about the warning.
* @example
* node.warn(result, 'Something went wrong')
* @example
* node.warn(result, 'Something went wrong', {
* node: someOtherNode,
* plugin: someOtherPlugin
* })
*/
warn (result, text, opts) {
const data = Object.assign({ node: this }, opts);
return result.warn(text, data);
}
}
function hasNodes (node) {
return node.nodes;
}
export default Node;