wed
Version:
Wed is a schema-aware editor for XML documents.
631 lines (630 loc) • 24.5 kB
TypeScript
import { isAttr } from "./domtypeguards";
/**
* Search an array.
*
* @param a The array to search.
*
* @param target The target to find.
*
* @return -1 if the target is not found, or its index.
*/
export declare function indexOf(a: NodeList, target: Node): number;
export declare function indexOf<T>(a: T[], target: T): number;
/**
* Compare two positions in document order.
*
* This function relies on DOM's ``compareDocumentPosition`` function. Remember
* that calling that function with attributes can be problematic. (For instance,
* two attributes on the same element are not ordered.)
*
* @param firstNode Node of the first position.
*
* @param firstOffset Offset of the first position.
*
* @param secondNode Node of the second position.
*
* @param secondOffset Offset of the second position.
*
* @returns -1 if the first position comes before the second. 1 if the first
* position comes after the other. 0 if the two positions are equal.
*/
export declare function comparePositions(firstNode: Node, firstOffset: number, secondNode: Node, secondOffset: number): 1 | 0 | -1;
/**
* Gets the first range in the selection.
*
* @param win The window for which we want the selection.
*
* @returns The first range in the selection. Undefined if there is no selection
* or no range.
*/
export declare function getSelectionRange(win: Window): Range | undefined;
/**
* A range and a flag indicating whether it is a reversed range or not. Range
* objects themselves do not record how they were created. If the range was
* created from a starting point which is greater than the end point (in
* document order), then the range is "reversed".
*/
export declare type RangeInfo = {
range: Range;
reversed: boolean;
};
/**
* Creates a range from two points in a document.
*
* @returns The range information.
*/
export declare function rangeFromPoints(startContainer: Node, startOffset: number, endContainer: Node, endOffset: number): RangeInfo;
/**
* Focuses the node itself or if the node is a text node, focuses the parent.
*
* @param node The node to focus.
*
* @throws {Error} If the node is neither a text node nor an element. Trying to
* focus something other than these is almost certainly an algorithmic bug.
*/
export declare function focusNode(node: Node): void;
/**
* A caret position in the form of a pair of values. The caret we are talking
* about here roughly corresponds to the caret that a "contenteditable" element
* would present to the user. It can index in text nodes and element nodes but
* not in attributes.
*/
export declare type Caret = [Node, number];
/**
* This function determines the caret position if the caret was moved forward.
*
* This function does not fully emulate how a browser moves the caret. The sole
* emulation it performs is to check whether whitespace matters or not. It skips
* whitespace that does not matter.
*
* @param caret A caret position where the search starts. This should be an
* array of length two that has in first position the node where the caret is
* and in second position the offset in that node. This pair is to be
* interpreted in the same way node, offset pairs are interpreted in selection
* or range objects.
*
* @param container A DOM node which indicates the container within which caret
* movements must be contained.
*
* @param noText If true, and a text node would be returned, the function will
* instead return the parent of the text node.
*
* @returns The next caret position, or ``null`` if such position does not
* exist. The ``container`` parameter constrains movements to positions inside
* it.
*/
export declare function nextCaretPosition(caret: Caret, container: Node, noText: boolean): Caret | null;
/**
* This function determines the caret position if the caret was moved backwards.
*
* This function does not fully emulate how a browser moves the caret. The sole
* emulation it performs is to check whether whitespace matters or not. It skips
* whitespace that does not matter.
*
* @param caret A caret position where the search starts. This should be an
* array of length two that has in first position the node where the caret is
* and in second position the offset in that node. This pair is to be
* interpreted in the same way node, offset pairs are interpreted in selection
* or range objects.
*
* @param container A DOM node which indicates the container within which caret
* movements must be contained.
*
* @param noText If true, and a text node would be returned, the function will
* instead return the parent of the text node.
*
* @returns The previous caret position, or ``null`` if such position does not
* exist. The ``container`` parameter constrains movements to positions inside
* it.
*/
export declare function prevCaretPosition(caret: Caret, container: Node, noText: boolean): Caret | null;
/**
* Given two trees A and B of DOM nodes, this function finds the node in tree B
* which corresponds to a node in tree A. The two trees must be structurally
* identical. If tree B is cloned from tree A, it will satisfy this
* requirement. This function does not work with attribute nodes.
*
* @param treeA The root of the first tree.
*
* @param treeB The root of the second tree.
*
* @param nodeInA A node in the first tree.
*
* @returns The node which corresponds to ``nodeInA`` in ``treeB``.
*
* @throws {Error} If ``nodeInA`` is not ``treeA`` or a child of ``treeA``.
*/
export declare function correspondingNode(treeA: Node, treeB: Node, nodeInA: Node): Node;
/**
* Makes a placeholder element
*
* @param text The text to put in the placeholder.
*
* @returns A node.
*/
export declare function makePlaceholder(text?: string): HTMLElement;
export declare type InsertionBoundaries = [Caret, Caret];
export interface GenericInsertIntoTextContext {
insertNodeAt(into: Element, index: number, node: Node): void;
deleteNode(node: Node): void;
/**
* This function performs roughly the same as insertNodeAt but may be
* optimized to take care of fragment handling.
*/
insertFragAt?(into: Element, index: number, node: DocumentFragment): void;
}
/**
* Inserts an element into text, effectively splitting the text node in
* two. This function takes care to modify the DOM tree only once.
*
* @param textNode The text node that will be cut in two by the new element.
*
* @param index The offset into the text node where the new element is to be
* inserted.
*
* @param node The node to insert.
*
* @returns A pair containing a caret position marking the boundary between what
* comes before the material inserted and the material inserted, and a caret
* position marking the boundary between the material inserted and what comes
* after. If I insert "foo" at position 2 in "abcd", then the final result would
* be "abfoocd" and the first caret would mark the boundary between "ab" and
* "foo" and the second caret the boundary between "foo" and "cd".
*
* @throws {Error} If the node to insert is undefined or null.
*/
export declare function genericInsertIntoText(this: GenericInsertIntoTextContext, textNode: Text, index: number, node?: Node): InsertionBoundaries;
/**
* Records the results of inserting text into the tree.
*/
export interface TextInsertionResult {
/** The node that contains the added text. */
node: Text | undefined;
/** Whether [[node]] is a new node. If ``false``, it was modified. */
isNew: boolean;
/** The caret position after the insertion. */
caret: Caret;
}
export interface GenericInsertTextContext {
insertNodeAt(into: Element, index: number, node: Node): void;
setTextNodeValue(node: Text, value: string): void;
}
/**
* Inserts text into a node. This function will use already existing
* text nodes whenever possible rather than create a new text node.
*
* @param node The node where the text is to be inserted.
*
* @param index The location in the node where the text is
* to be inserted.
*
* @param text The text to insert.
*
* @param caretAtEnd Whether the caret position returned should be placed at the
* end of the inserted text.
*
* @returns The result of inserting the text.
*
* @throws {Error} If ``node`` is not an element or text Node type.
*/
export declare function genericInsertText(this: GenericInsertTextContext, node: Node, index: number, text: string, caretAtEnd?: boolean): TextInsertionResult;
/**
* Deletes text from a text node. If the text node becomes empty, it is deleted.
*
* @param node The text node from which to delete text.
*
* @param index The index at which to delete text.
*
* @param length The length of text to delete.
*
* @throws {Error} If ``node`` is not a text Node type.
*/
export declare function deleteText(node: Text, index: number, length: number): void;
/**
* This function recursively links two DOM trees through the jQuery ``.data()``
* method. For an element in the first tree the data item named
* "wed_mirror_node" points to the corresponding element in the second tree, and
* vice-versa. It is presumed that the two DOM trees are perfect mirrors of each
* other, although no test is performed to confirm this.
*/
export declare function linkTrees(rootA: Element, rootB: Element): void;
/**
* This function recursively unlinks a DOM tree though the jQuery ``.data()``
* method.
*
* @param root A DOM node.
*
*/
export declare function unlinkTree(root: Element): void;
/**
* Returns the first descendant or the node passed to the function if the node
* happens to not have a descendant. The function searches in document order.
*
* When passed ``<p><b>A</b><b><q>B</q></b></p>`` this code would return the
* text node "A" because it has no children and is first.
*
* @param node The node to search.
*
* @returns The first node which is both first in its parent and has no
* children.
*/
export declare function firstDescendantOrSelf(node: Node | null | undefined): Node | null;
/**
* Returns the last descendant or the node passed to the function if the node
* happens to not have a descendant. The function searches in reverse document
* order.
*
* When passed ``<p><b>A</b><b><q>B</q></b></p>`` this code would return the
* text node "B" because it has no children and is last.
*
* @param node The node to search.
*
* @returns The last node which is both last in its parent and has no
* children.
*/
export declare function lastDescendantOrSelf(node: Node | null | undefined): Node | null;
/**
* Removes the node. Mainly for use with the generic functions defined here.
*
* @param node The node to remove.
*/
export declare function deleteNode(node: Node): void;
/**
* Inserts text into a node. This function will use already existing text nodes
* whenever possible rather than create a new text node.
*
* @function
*
* @param node The node where the text is to be inserted.
*
* @param index The location in the node where the text is to be inserted.
*
* @param text The text to insert.
*
* @param caretAtEnd Whether to return the caret position at the end of the
* inserted text or at the beginning. Default to ``true``.
*
* @returns The result of inserting the text.
*
* @throws {Error} If ``node`` is not an element or text Node type.
*/
export declare function insertText(node: Node, index: number, text: string, caretAtEnd?: boolean): TextInsertionResult;
/**
* Inserts an element into text, effectively splitting the text node in
* two. This function takes care to modify the DOM tree only once.
*
* @param textNode The text node that will be cut in two by the new element.
*
* @param index The offset into the text node where the new element is to be
* inserted.
*
* @param node The node to insert.
*
* @returns A pair containing a caret position marking the boundary between what
* comes before the material inserted and the material inserted, and a caret
* position marking the boundary between the material inserted and what comes
* after. If I insert "foo" at position 2 in "abcd", then the final result would
* be "abfoocd" and the first caret would mark the boundary between "ab" and
* "foo" and the second caret the boundary between "foo" and "cd".
*/
export declare function insertIntoText(textNode: Text, index: number, node: Node): InsertionBoundaries;
export declare type SplitResult = [Node, Node];
/**
* Splits a text node into two nodes. This function takes care to modify the DOM
* tree only once.
*
* @param textNode The text node to split into two text nodes.
*
* @param index The offset into the text node where to split.
*
* @returns The first element is the node before index after split and the
* second element is the node after the index after split.
*/
export declare function splitTextNode(textNode: Text, index: number): SplitResult;
/**
* Merges a text node with the next text node, if present. When called on
* something which is not a text node or if the next node is not text, does
* nothing. Mainly for use with the generic functions defined here.
*
* @param node The node to merge with the next node.
*
* @returns A caret position between the two parts that were merged, or between
* the two nodes that were not merged (because they were not both text).
*/
export declare function mergeTextNodes(node: Node): Caret;
export interface RangeLike {
startContainer: Node;
startOffset: number;
endContainer: Node;
endOffset: number;
}
export declare type ElementPair = [Element, Element];
/**
* Determines whether a range is well-formed. A well-formed range is one which
* starts and ends in the same element.
*
* @param range An object which has the ``startContainer``,
* ``startOffset``, ``endContainer``, ``endOffset`` attributes set. The
* interpretation of these values is the same as for DOM ``Range``
* objects. Therefore, the object passed can be a DOM range.
*
* @returns ``true`` if the range is well-formed. ``false`` if not.
*/
export declare function isWellFormedRange(range: RangeLike): boolean;
export interface GenericCutContext {
deleteText(node: Text, index: number, length: number): void;
deleteNode(node: Node): void;
mergeTextNodes(node: Node): Caret;
}
export declare type CutResult = [Caret, Node[]];
/**
* Removes the contents between the start and end carets from the DOM tree. If
* two text nodes become adjacent, they are merged.
*
* @param startCaret Start caret position.
*
* @param endCaret Ending caret position.
*
* @returns The first item is the caret position indicating where the cut
* happened. The second item is a list of nodes, the cut contents.
*
* @throws {Error} If Nodes in the range are not in the same element.
*/
export declare function genericCutFunction(this: GenericCutContext, startCaret: Caret, endCaret: Caret): CutResult;
/**
* Copies a well formed region of the DOM tree.
*
* @param startCaret Start caret position.
*
* @param endCaret Ending caret position.
*
* @returns A copy of the contents.
*
* @throws {Error} If Nodes in the range are not in the same element.
*/
export declare function copy(startCaret: Caret, endCaret: Caret): Node[];
/**
* Dumps a range to the console.
*
* @param msg A message to output in front of the range information.
*
* @param range The range.
*/
export declare function dumpRange(msg: string, range?: RangeLike): void;
/**
* Dumps the current selection to the console.
*
* @param msg A message to output in front of the range information.
*
* @param win The window for which to dump selection information.
*/
export declare function dumpCurrentSelection(msg: string, win: Window): void;
/**
* Dumps a range to a string.
*
* @param msg A message to output in front of the range information.
*
* @param range The range.
*/
export declare function dumpRangeToString(msg: string, range?: RangeLike): string;
/**
* Checks whether a point is in the element's contents. This means inside the
* element and **not** inside one of the scrollbars that the element may
* have. The coordinates passed must be **relative to the document.** If the
* coordinates are taken from an event, this means passing ``pageX`` and
* ``pageY``.
*
* @param element The element to check.
*
* @param x The x coordinate **relative to the document.**
*
* @param y The y coordinate **relative to the document.**
*
* @returns ``true`` if inside, ``false`` if not.
*/
export declare function pointInContents(element: Element, x: number, y: number): boolean;
/**
* Starting with the node passed, and walking up the node's
* parents, returns the first node that matches the selector.
*
* @param node The node to start with.
*
* @param selector The selector to use for matches.
*
* @param limit The algorithm will search up to this limit, inclusively.
*
* @returns The first element that matches the selector, or ``null`` if nothing
* matches.
*/
export declare function closest(node: Node | undefined | null, selector: string, limit?: Element | Document): Element | null;
/**
* Starting with the node passed, and walking up the node's parents, returns the
* first element that matches the class.
*
* @param node The node to start with.
*
* @param cl The class to use for matches.
*
* @param limit The algorithm will search up to this limit, inclusively.
*
* @returns The first element that matches the class, or ``null`` if nothing
* matches.
*/
export declare function closestByClass(node: Node | undefined | null, cl: string, limit?: Element | Document): Element | null;
/**
* Find a sibling matching the class.
*
* @param node The element whose sibling we are looking for.
*
* @param cl The class to use for matches.
*
* @returns The first sibling (in document order) that matches the class, or
* ``null`` if nothing matches.
*/
export declare function siblingByClass(node: Node | null, cl: string): Element | null;
/**
* Find children matching the class.
*
* @param node The element whose children we are looking for.
*
* @param cl The class to use for matches.
*
* @returns The children (in document order) that match the class.
*/
export declare function childrenByClass(node: Node | null, cl: string): Element[];
/**
* Find child matching the class.
*
* @param node The element whose child we are looking for.
*
* @param cl The class to use for matches.
*
* @returns The first child (in document order) that matches the class, or
* ``null`` if nothing matches.
*/
export declare function childByClass(node: Node | null, cl: string): Element | null;
/**
* Convert a string to HTML encoding. For instance if you want to have the
* less-than symbol be part of the contents of a ``span`` element, it would have
* to be escaped to ``<`` otherwise it would be interpreted as the beginning of
* a tag. This function does this kind of escaping.
*
* @param text The text to convert.
*
* @returns The converted text.
*/
export declare function textToHTML(text: string): string;
/**
* Converts a CSS selector written as if it were run against the XML document
* being edited by wed into a selector that will match the corresponding items
* in the GUI tree. This implementation is extremely naive and likely to break
* on complex selectors. Some specific things it cannot do:
*
* - Match attributes.
*
* - Match pseudo-elements.
*
* @param selector The selector to convert.
*
* @param namespaces The namespaces that are known. This is used to convert
* element name prefixes to namespace URIs.
*
* @returns The converted selector.
*/
export declare function toGUISelector(selector: string, namespaces: Record<string, string>): string;
/**
* Allows applying simple CSS selectors on the data tree as if it were an HTML
* tree. This is necessary because the current browsers are unable to handle tag
* prefixes or namespaces in selectors passed to ``matches``, ``querySelector``
* and related functions.
*
* The steps are:
*
* 1. Convert ``selector`` with [[toGUISelector]] into a selector that can be
* applied to the GUI tree.
*
* 2. Convert ``node`` to a GUI node.
*
* 3. Apply the converted selector to the GUI node.
*
* 4. Convert the resulting node to a data node.
*
* @param node The element to use as the starting point of the query.
*
* @param selector The selector to use.
*
* @param namespaces The namespaces that are known. This is used to convert
* element name prefixes to namespace URIs.
*
* @returns The resulting data node.
*/
export declare function dataFind(node: Element, selector: string, namespaces: Record<string, string>): Element | null;
/**
* Allows applying simple CSS selectors on the data tree as if it were an HTML
* tree. Operates like [[dataFind]] but returns an array of nodes.
*
* @param node The data node to use as the starting point of the query.
*
* @param selector The selector to use.
*
* @param namespaces The namespaces that are known. This is used to convert
* element name prefixes to namespace URIs.
*
* @returns The resulting data nodes.
*/
export declare function dataFindAll(node: Element, selector: string, namespaces: Record<string, string>): Element[];
/**
* Converts an HTML string to an array of DOM nodes. **This function is not
* responsible for checking the HTML for security holes it is the responsibility
* of the calling code to ensure the HTML passed is clean.**
*
* @param html The HTML to convert.
*
* @param document The document for which to create the nodes. If not specified,
* the document will be the global ``document``.
*
* @returns The resulting nodes.
*/
export declare function htmlToElements(html: string, document?: Document): Node[];
/**
* Gets the character immediately before the caret. The word "immediately" here
* means that this function does not walk the DOM. If the caret is pointing into
* an element node, it will check whether the node before the offset is a text
* node and use it. That's the extent to which it walks the DOM.
*
* @param caret The caret position.
*
* @return The character, if it exists.
*/
export declare function getCharacterImmediatelyBefore(caret: Caret): string | undefined;
/**
* Gets the character immediately at the caret. The word "immediately" here
* means that this function does not walk the DOM. If the caret is pointing into
* an element node, it will check whether the node at the offset is a text
* node and use it. That's the extent to which it walks the DOM.
*
* @param caret The caret position.
*
* @return The character, if it exists.
*/
export declare function getCharacterImmediatelyAt(caret: Caret): string | undefined;
/**
* Determine whether an element is displayed. This function is designed to
* handle checks in wed's GUI tree, and not as a general purpose solution. It
* only checks whether the element or its parents have ``display`` set to
* ``"none"``.
*
* @param el The DOM element for which we want to check whether it is displayed
* or not.
*
* @param root The parent of ``el`` beyond which we do not search.
*
* @returns ``true`` if the element or any of its parents is not
* displayed. ``false`` otherwise. If the search up the DOM tree hits ``root``,
* then the value returned is ``false``.
*/
export declare function isNotDisplayed(el: HTMLElement, root: HTMLElement | HTMLDocument): boolean;
/**
* A ``contains`` function that handles attributes. Attributes are not part of
* the node tree and performing a ``contains`` test on them is always ``false``.
*
* Yet it makes sense to say that an element A contains its own attributes and
* thus by transitivity if element A is contained by element B, then all
* attributes of A are contained by B. This function supports the contention
* just described.
*
* Usage note: this function is typically not *needed* when doing tests in the
* GUI tree because we do not address attributes in that tree. There is,
* however, no harm in using it where it is not strictly needed. In the data
* tree, however, we do address attributes. Code that works with either tree
* (e.g. the [["wed/dloc"]] module) should use this function as a general rule
* so that it can work with either tree.
*
* @param container The thing which should contain in the test.
*
* @param contained The thing which should be contained in the test.
*
* @returns Whether ``container`` contains ``contained``.
*/
export declare function contains(container: Node, contained: Node): boolean;
export { isAttr };