/** * Inserts a paragraph before or after the table containing the selection. * * <p>We decided to use Control/Meta+UpArrow for inserting a paragraph before a table and * Control/Meta+DownArrow for inserting a paragraph after a table. Here's the rationale: * * <ul> * <li>We can't reliably detect if the user can place the caret before of after a table. Our * playground is the DOM tree which we fully control but in the end the browser decides how * to render each node. The table can have previous siblings or previous nodes in the DOM * tree but they may not be rendered at all (as it happens with HTML garbage like empty * elements) or not rendered before/after the table (as it happens with absolute positioned * elements). So we have to insert the paragraph each time. * <li>We can't use the same key to insert a paragraph before and after the table because we can * have a table with just one empty cell. So we can't rely on the Enter key. * <li>We can't use just the navigation keys because they would insert a paragraph before/after * the table even when the user can navigate outside of the table. * </ul> * * We can replace the Ctrl with Alt. The idea is to use the Up/Down arrow keys with a modifier. * They will work form any table cell. * * @param event the native event that was fired * @param before {@code true} to insert a paragraph before the table, {@code false} to insert a * paragraph after the table */ protected void navigateOutsideTableCell(Event event, boolean before) { // Navigate only if the Control or Meta modifiers are pressed along with the Up/Down arrow keys. if (event.getAltKey() || event.getShiftKey() || !(event.getCtrlKey() ^ event.getMetaKey())) { return; } Selection selection = getTextArea().getDocument().getSelection(); if (selection.getRangeCount() == 0) { return; } else { selection.collapseToStart(); } Range range = selection.getRangeAt(0); Node ancestor = domUtils.getFirstAncestor(range.getStartContainer(), "table"); if (ancestor == null) { return; } event.xPreventDefault(); Document document = getTextArea().getDocument(); Node paragraph = document.createPElement(); paragraph.appendChild(document.createTextNode("")); if (before) { ancestor.getParentNode().insertBefore(paragraph, ancestor); } else { domUtils.insertAfter(paragraph, ancestor); } range.selectNodeContents(paragraph.getFirstChild()); selection.removeAllRanges(); selection.addRange(range); }
/** * Updates the meta data of the given anchor. * * @param anchor the anchor whose meta data will be updated * @param reference the new link reference * @param linkType the new link type */ private void updateMetaData(AnchorElement anchor, String reference, LinkType linkType) { Document document = (Document) anchor.getOwnerDocument(); DocumentFragment metaData = document.createDocumentFragment(); metaData.appendChild( document.createComment("startwikilink:" + EscapeUtils.escapeComment(reference))); metaData.appendChild(document.createSpanElement()); if (CLASS_NAME_MAPPING.containsKey(linkType)) { Element.as(metaData.getChild(1)).setClassName(CLASS_NAME_MAPPING.get(linkType)); } metaData.getChild(1).appendChild(document.createTextNode(Element.INNER_HTML_PLACEHOLDER)); metaData.appendChild(document.createComment("stopwikilink")); Element.as(anchor).setMetaData(metaData); }
/** * Create a table from TableConfig configuration. * * <p>We create the table using innerHTML instead of creating each DOM node in order to improve * the speed. In most of the browsers setting the innerHTML is faster than creating the DOM nodes * and appending them. * * @param doc currently edited document. * @param config table configuration (row number, etc). * @return the newly created table. */ public Element createTable(Document doc, TableConfig config) { StringBuffer table = new StringBuffer("<table>"); StringBuffer row = new StringBuffer("<tr>"); for (int i = 0; i < config.getColNumber(); i++) { row.append("<td>"); // The default cell content depends on the browser. In Firefox the best option is to use a BR. // Firefox // itself uses BRs in order to allow the user to place the caret inside empty block elements. // In Internet // Explorer the best option is to set the inner HTML of each cell to the empty string, after // creation. For // now lets keep the non-breaking space. At some point we should have browser specific // implementations for // FF and IE. Each will overwrite this method and add specific initialization. row.append(TableUtils.CELL_DEFAULTHTML); row.append("</td>"); } row.append("</tr>"); if (config.hasHeader()) { table.append("<thead>"); if (config.getRowNumber() > 0) { table.append(row.toString().replace("td", "th")); } table.append("</thead>"); } table.append("<tbody>"); for (int i = config.hasHeader() ? 1 : 0; i < config.getRowNumber(); i++) { table.append(row.toString()); } table.append("</tbody></table>"); Element container = doc.createDivElement().cast(); container.setInnerHTML(table.toString()); Element tableElement = (Element) container.getFirstChild(); container.removeChild(tableElement); return tableElement; }