internal static ParagraphsToMerge create(IWindowSelection windowSelection) { //multiple ranges when several (separate) cells are selected //could support this scenario later but it's uncessarily //complicated for a demo so for now simply disable the //'merge paragraphs' feature when the selection includes table cells if (windowSelection.rangeCount != 1) { return(null); } try { //get the selected range using (IDomRange domRange = windowSelection.getRangeAt(0)) { return(create(domRange)); } } catch { return(null); } }
static ParagraphsToMerge create(IDomRange domRange) { //find the block-level element associated with the start of the range IDomNode startBlock = findBlock(domRange.startContainer, domRange.startOffset, true); //find the block-level element associated with the end of the range IDomNode endBlock = findBlock(domRange.endContainer, domRange.endOffset, false); //block may be null if it's positioned directly under a <td> //which (removing things from a table) isn't a scenario supported by this class if ((startBlock == null) || (endBlock == null)) { return(null); } //iterate the inline-level elements to be moved List <List <IDomNode> > selected = new List <List <IDomNode> >(); IDomNode currentBlock = startBlock; for (; ;) { //get the inline elements inside this block List <IDomNode> children = null; bool childIsBlock = false; foreach (IDomNode child in currentBlock.childNodes.elements) { if (!isBlock(child)) { //found an inline child if (children == null) { children = new List <IDomNode>(); } children.Add(child); } else { //child of this block is itself a block currentBlock = child; childIsBlock = true; break; } } if (children != null) { //found some inline children selected.Add(children); } if (childIsBlock) { //descend: iterate this child block continue; } //else finished iterating children of currentBlock if (ReferenceEquals(currentBlock, endBlock)) { //currentBlock is the last block we wanted to iterate break; } //find the next block to iterate for (; ;) { IDomNode currentParent = currentBlock.parentNode; int currentIndex = currentParent.childNodes.indexOf(currentBlock); if (currentIndex < (currentParent.childNodes.count - 1)) { //next we'll iterate the next sibling after currentBlock currentBlock = currentParent.childNodes.itemAt(currentIndex + 1); break; } else { //no next sibling after currentBlock //so iterate next sibling after parent block currentBlock = currentParent; continue; } } if (!isBlock(currentBlock)) { //this can happen if an 'anonymous block' is not the first block of a parent block //which isn't supported by the current version of the renderer throw new ApplicationException("unexpected"); } } //find the block before the selected block //into which we'll merge the inline elements currentBlock = startBlock; Location previous; for (; ;) { //find the block before the first block IDomNode parent = currentBlock.parentNode; int index = parent.childNodes.indexOf(currentBlock); if (index > 0) { //found a previous element IDomNode previousNode = parent.childNodes.itemAt(index - 1); if (isBlock(previousNode)) { //previous element is a block //but it might have block-level descendents while ((previousNode.childNodes.count > 0) && isBlock(previousNode.childNodes.itemAt(previousNode.childNodes.count - 1))) { previousNode = previousNode.childNodes.itemAt(previousNode.childNodes.count - 1); } //want to insert at the end (after the last child) of the previousNode previous = new Location(previousNode, null); break; } else { //previous is not a block, //so it's an anonymous block //near the start of a list item or table cell previous = new Location(parent, previousNode); break; } } if (parent.nodeName == "body") { //startBlock is the first block in the body //there is no previous block //so don't merge return(null); } currentBlock = parent; continue; } //search up to find the <body> element IDomNode body = startBlock; while (body.nodeName != "body") { body = body.parentNode; } //remember whether the selection range starts and ends //in order to restore it after we edit the DOM OffsetInElement start = new OffsetInElement(body, domRange.startContainer, domRange.startOffset); OffsetInElement end = new OffsetInElement(body, domRange.endContainer, domRange.endOffset); return(new ParagraphsToMerge(previous, selected, start, end)); }
void onInsertHyperlink() { //get the document fragment which the user has currently selected using mouse and/or cursor IWindowSelection windowSelection = modelEdit.windowSelection; //verify that there's only one selection if (windowSelection.rangeCount != 1) { //this can happen when the user has selected several cell in a table, //in which case each cell is a separate selection/range MessageBox.Show("Can't insert a hyperlink when more than one range in the document is selected"); return; } using (IDomRange domRange = windowSelection.getRangeAt(0)) { //verify that only one node is selected if (!domRange.startContainer.isSameNode(domRange.endContainer)) { //this can happen for example when the selection spans multiple paragraphs MessageBox.Show("Can't insert a hyperlink when more than one node in the document is selected"); return; } IDomNode container = domRange.startContainer; //already just checked that this is the same as domRange.endContainer //read existing values from the current selection string url; string visibleText; IDomElement existingHyperlink; switch (container.nodeType) { case DomNodeType.Text: //selection is a text fragment visibleText = container.nodeValue.Substring(domRange.startOffset, domRange.endOffset - domRange.startOffset); IDomNode parentNode = container.parentNode; if ((parentNode.nodeType == DomNodeType.Element) && (parentNode.nodeName == "a")) { //parent of this text node is a <a> element existingHyperlink = parentNode as IDomElement; url = existingHyperlink.getAttribute("href"); visibleText = container.nodeValue; if ((existingHyperlink.childNodes.count != 1) || (existingHyperlink.childNodes.itemAt(0).nodeType != DomNodeType.Text)) { //this can happen when an anchor tag wraps more than just a single, simple text node //for example when it contains inline elements like <strong> MessageBox.Show("Can't edit a complex hyperlink"); return; } } else { existingHyperlink = null; url = null; } break; default: //unexpected MessageBox.Show("Can't insert a hyperlink when more than one node in the document is selected"); return; } //display the modal dialog box using (FormInsertHyperlink formInsertHyperlink = new FormInsertHyperlink()) { formInsertHyperlink.url = url; formInsertHyperlink.visibleText = visibleText; DialogResult dialogResult = formInsertHyperlink.ShowDialog(); if (dialogResult != DialogResult.OK) { //user cancelled return; } //get new values from the dialog box //the FormInsertHyperlink.onEditTextChanged method assures that both strings are non-empty url = formInsertHyperlink.url; visibleText = formInsertHyperlink.visibleText; } //need to change href, change text, and possibly delete existing text; //do this within the scope of a single IEditorTransaction instance so //that if the user does 'undo' then it will undo all these operations at once, instead of one at a time using (IEditorTransaction editorTransaction = modelEdit.createEditorTransaction()) { if (existingHyperlink != null) { //changing an existing hyperlink ... //... change the href attribute value existingHyperlink.setAttribute("href", url); //... change the text, by removing the old text node and inserting a new text node IDomText newDomText = modelEdit.domDocument.createTextNode(visibleText); IDomNode oldDomText = existingHyperlink.childNodes.itemAt(0); existingHyperlink.removeChild(oldDomText); existingHyperlink.insertBefore(newDomText, null); } else { //creating a new hyperlink IDomElement newHyperlink = modelEdit.domDocument.createElement("a"); IDomText newDomText = modelEdit.domDocument.createTextNode(visibleText); newHyperlink.insertBefore(newDomText, null); newHyperlink.setAttribute("href", url); //remove whatever was previously selected, if anything if (!domRange.collapsed) { domRange.deleteContents(); } //insert the new hyperlink domRange.insertNode(newHyperlink); } } } }