1// Dokuwiki table editing plugin 2// Author: Bohumir Zamecnik <bohumir|zamecnik|org> 3// Last modified: 22.11.2008 4// License: GNU GPL 2 5// Website: http://zamecnik.org/projekty/dokuwiki_tableedit 6// 7// Purpose: 8// The goal of this plugin is to simplify handling Dokuwiki table columns. 9// It's not easy to move some columns around, add them or remove them by hand. 10// This plugin will offer a friendly user iterface to underlying manipulation 11// with the table markup using regular expression. 12 13//function replaceColumns(inputText, replaceSpec, columnChar, defaultContent) { 14function replaceColumns(inputText, replaceSpec) { 15 var headChar = "^"; 16 var headDefault = ""; 17 var normalChar = "|"; 18 var normalDefault = " "; 19 var nColumns = countColumns(inputText); 20 21 for (var i = 0; i < replaceSpec.length; i++) { 22 if ((replaceSpec[i] <= 0) || (replaceSpec[i] > nColumns)) { 23 replaceSpec[i] = "&"; // will be replaced by a default char 24 } 25 replaceSpec[i] = "$" + replaceSpec[i]; 26 } 27 28 var pattern = "^" + multiple("#", nColumns) 29 .join("([^#]*)") + "[ \t]*$"; 30 31 var replacePattern = replaceSpec.join("#"); 32 if (replaceSpec.length > 0) { 33 replacePattern = "#" + replacePattern + "#"; 34 } 35 36 var currentPattern = new RegExp(); 37 38 currentPattern.compile(pattern.replace(/#/g, "\\"+headChar), "gm"); 39 currentReplacePattern = replacePattern.replace(/#/g, headChar).replace(/\$&/g, headDefault); 40 var text = inputText.replace(currentPattern, currentReplacePattern); 41 42 currentPattern.compile(pattern.replace(/#/g, "\\"+normalChar),"gm"); 43 currentReplacePattern = replacePattern.replace(/#/g, normalChar).replace(/\$&/g, normalDefault) 44 return text.replace(currentPattern, currentReplacePattern); 45} 46 47// n times text into an array 48function multiple(text, n) { 49 if (n <= 0) { return new Array(); } 50 var tmp = new Array(); 51 for (var i = 0; i < n; i++) { 52 tmp[i] = text; 53 } 54 return tmp; 55} 56 57// maximum number of columns in a table 58function countColumns(table) { 59 var maxColumns = 0; 60 var lines = table.split("\n"); 61 var pattern = new RegExp("[\\^\\|]","g"); 62 63 for (var i = 0; i < lines.length; i++) { 64 var result = lines[i].match(pattern); 65 var columns = (result != null ? result.length : 0); 66 maxColumns = (columns > maxColumns ? columns : maxColumns); 67 } 68 return maxColumns; 69} 70 71// add ^ or | symbols up to maxColumns 72function normalizeTable(table) { 73 var maxColumns = countColumns(table); 74 var lines = table.split("\n"); 75 var headPattern = new RegExp("\\^","g"); 76 var normalPattern = new RegExp("\\|","g"); 77 for (var i = 0; i < lines.length; i++) { 78 // resolve which character to add 79 var c = lines[i].charAt(0); // ...using the first character 80 // count column count on current line 81 var columns = 0; 82 if (c == "^") { 83 result = lines[i].match(headPattern); 84 columns = (result != null ? result.length : 0); 85 } else if (c == "|") { 86 result = lines[i].match(normalPattern); 87 columns = (result != null ? result.length : 0); 88 } else { 89 break; 90 } 91 92 //for (k = 0; k < lines[i].length; k++) { 93 // if (lines[i].charAt(k) == c) { 94 // columns++; 95 // } 96 //} 97 // add up to maxColumns count 98 for (var j = 0; j < maxColumns - columns; j++) { 99 lines[i] += " " + c; 100 } 101 } 102 return lines.join("\n"); 103} 104 105function parseColumnOrder(orderString) { 106 return (orderString != "" ? orderString.split(",") : new Array()); 107} 108 109// Get position of the caret (cursor) in a field with given id 110function caretPosition(field) { 111 //var field = document.getElementById(editId); 112 113 if (field != null) { 114 field.focus(); 115 if (document.selection) { 116 // IE 117 return getCaretPositionIE(field); 118 } else if (field.selectionStart || field.selectionStart == '0') { 119 // Mozilla / Netscape 120 return field.selectionStart; 121 } 122 } 123 return 0; 124} 125 126function getCaretPositionIE(textarea) { 127 // Source: http://linebyline.blogspot.com/2006/11/textarea-cursor-position-in-internet.html 128 // TODO: 129 // - simplify a bit, we need only to compute startPoint 130 // - find out what about the license... 131 132 133 //var textarea = document.getElementById("myTextArea"); 134 textarea.focus(); 135 var selection_range = document.selection.createRange().duplicate(); 136 137 if (selection_range.parentElement() != textarea) { 138 // Check that the selection is actually in our textarea 139 return 0; 140 } 141 // Create three ranges, one containing all the text before the selection, 142 // one containing all the text in the selection (this already exists), and one containing all 143 // the text after the selection. 144 var before_range = document.body.createTextRange(); 145 before_range.moveToElementText(textarea); // Selects all the text 146 before_range.setEndPoint("EndToStart", selection_range); // Moves the end where we need it 147 148 var after_range = document.body.createTextRange(); 149 after_range.moveToElementText(textarea); // Selects all the text 150 after_range.setEndPoint("StartToEnd", selection_range); // Moves the start where we need it 151 152 var before_finished = false, selection_finished = false, after_finished = false; 153 var before_text, untrimmed_before_text, selection_text, untrimmed_selection_text, after_text, untrimmed_after_text; 154 155 // Load the text values we need to compare 156 before_text = untrimmed_before_text = before_range.text; 157 selection_text = untrimmed_selection_text = selection_range.text; 158 after_text = untrimmed_after_text = after_range.text; 159 160 // Check each range for trimmed newlines by shrinking the range by 1 character and seeing 161 // if the text property has changed. If it has not changed then we know that IE has trimmed 162 // a \r\n from the end. 163 do { 164 if (!before_finished) { 165 if (before_range.compareEndPoints("StartToEnd", before_range) == 0) { 166 before_finished = true; 167 } else { 168 before_range.moveEnd("character", -1) 169 if (before_range.text == before_text) { 170 untrimmed_before_text += "\r\n"; 171 } else { 172 before_finished = true; 173 } 174 } 175 } 176 if (!selection_finished) { 177 if (selection_range.compareEndPoints("StartToEnd", selection_range) == 0) { 178 selection_finished = true; 179 } else { 180 selection_range.moveEnd("character", -1) 181 if (selection_range.text == selection_text) { 182 untrimmed_selection_text += "\r\n"; 183 } else { 184 selection_finished = true; 185 } 186 } 187 } 188 if (!after_finished) { 189 if (after_range.compareEndPoints("StartToEnd", after_range) == 0) { 190 after_finished = true; 191 } else { 192 after_range.moveEnd("character", -1) 193 if (after_range.text == after_text) { 194 untrimmed_after_text += "\r\n"; 195 } else { 196 after_finished = true; 197 } 198 } 199 } 200 201 } while ((!before_finished || !selection_finished || !after_finished)); 202 203 // Untrimmed success test to make sure our results match what is actually in the textarea 204 // This can be removed once you're confident it's working correctly 205 var untrimmed_text = untrimmed_before_text + untrimmed_selection_text + untrimmed_after_text; 206 var untrimmed_successful = false; 207 if (textarea.value == untrimmed_text) { 208 untrimmed_successful = true; 209 } 210 // ** END Untrimmed success test 211 212 var startPoint = untrimmed_before_text.length; 213 var endPoint = startPoint + untrimmed_selection_text.length; 214 var selected_text = untrimmed_selection_text; 215 216 //alert("Start Index: " + startPoint + "\nEnd Index: " + endPoint + "\nSelected Text\n'" + selected_text + "'"); 217 return startPoint; 218} 219 220// Count columns of table under cursor given a textarea 221function countTableColumns(fieldId) { 222 field = document.getElementById(fieldId); 223 if (field == null) { return 0; } 224 var parts = extractTable(field); 225 return countColumns(parts[1]); 226} 227 228// Prepare default value for order input field 229function defaultOrderValue(tableFieldId) { 230 if (tableFieldId == null) { 231 return ""; 232 } 233 var defaultOrder = new Array(); 234 var columns = countTableColumns(tableFieldId) - 1; 235 columns = (columns >= 0 ? columns : 0); 236 for (var i = 0; i < columns; i++) { 237 defaultOrder[i] = i + 1; 238 } 239 return defaultOrder.join(","); 240} 241 242// Find a continuous table which lies on "currentPosition" in "table". 243// Return array [before table, table, after table] (all could be empty). 244function extractTable(field) { 245 var table = field.value; 246 var currentPosition = caretPosition(field); 247 var emptyLine = "\n\n"; 248 if (/MSIE/.test(navigator.userAgent)) { 249 emptyLine = "\r\n\r\n"; 250 } 251 var startPos = table.lastIndexOf(emptyLine, currentPosition); 252 startPos = (startPos != -1 ? (startPos + emptyLine.length) : 0); // +2 for \n\n 253 var endPos = table.indexOf(emptyLine, currentPosition); 254 endPos = (endPos != -1 ? endPos : table.length); 255 var array = new Array(); 256 array[0] = table.substring(0, startPos); 257 array[1] = table.substring(startPos, endPos); 258 array[2] = table.substring(endPos, table.length); 259 return array; 260} 261 262// Main editing function 263function editTable(fieldId, order) { 264 inputField = document.getElementById(fieldId); 265 var parts = extractTable(inputField); 266 inputField.value = parts[0] + replaceColumns(normalizeTable(parts[1]), 267 parseColumnOrder(order)) + parts[2]; 268} 269 270// Callback for Dokuwiki toolbar button 271function addBtnActionTableedit(btn, props, edid) 272{ 273 eval("btn.onclick = function(){var order = defaultOrderValue('wiki__text');" 274 + "var result = prompt('"+jsEscape(props['prompt'])+"', order);" 275 + "if (result != null) {editTable('wiki__text', result)};return false;}"); 276 return true; 277} 278