diff --git a/node/handler/ExportHandler.js b/node/handler/ExportHandler.js index a87219bb7..4453e3c2e 100644 --- a/node/handler/ExportHandler.js +++ b/node/handler/ExportHandler.js @@ -19,6 +19,7 @@ */ var exporthtml = require("../utils/ExportHtml"); +var exportdokuwiki = require("../utils/ExportDokuWiki"); var padManager = require("../db/PadManager"); var async = require("async"); var fs = require("fs"); @@ -56,6 +57,26 @@ exports.doExport = function(req, res, padId, type) res.send(pad.text()); }); } + else if(type == 'dokuwiki') + { + var randNum; + var srcFile, destFile; + + async.series([ + //render the dokuwiki document + function(callback) + { + exportdokuwiki.getPadDokuWikiDocument(padId, null, function(err, dokuwiki) + { + res.send(dokuwiki); + callback("stop"); + }); + }, + ], function(err) + { + if(err && err != "stop") throw err; + }); + } else { var html; diff --git a/node/server.js b/node/server.js index 2bebe6a24..9e7613a15 100644 --- a/node/server.js +++ b/node/server.js @@ -237,7 +237,7 @@ async.waterfall([ return; } - var types = ["pdf", "doc", "txt", "html", "odt"]; + var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"]; //send a 404 if we don't support this filetype if(types.indexOf(req.params.type) == -1) { @@ -246,7 +246,8 @@ async.waterfall([ } //if abiword is disabled, and this is a format we only support with abiword, output a message - if(settings.abiword == null && req.params.type != "html" && req.params.type != "txt" ) + if(settings.abiword == null && + ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) { res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature"); return; diff --git a/node/utils/ExportDokuWiki.js b/node/utils/ExportDokuWiki.js new file mode 100644 index 000000000..48e4b2915 --- /dev/null +++ b/node/utils/ExportDokuWiki.js @@ -0,0 +1,344 @@ +/** + * Copyright 2011 Adrian Lang + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var async = require("async"); +var Changeset = require("./Changeset"); +var padManager = require("../db/PadManager"); + +function getPadDokuWiki(pad, revNum, callback) +{ + var atext = pad.atext; + var dokuwiki; + async.waterfall([ + // fetch revision atext + + + function (callback) + { + if (revNum != undefined) + { + pad.getInternalRevisionAText(revNum, function (err, revisionAtext) + { + atext = revisionAtext; + callback(err); + }); + } + else + { + callback(null); + } + }, + + // convert atext to dokuwiki text + + function (callback) + { + dokuwiki = getDokuWikiFromAtext(pad, atext); + callback(null); + }], + // run final callback + + + function (err) + { + callback(err, dokuwiki); + }); +} + +function getDokuWikiFromAtext(pad, atext) +{ + var apool = pad.apool(); + var textLines = atext.text.slice(0, -1).split('\n'); + var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); + + var tags = ['======', '=====', '**', '//', '__', 'del>']; + var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; + var anumMap = {}; + + props.forEach(function (propName, i) + { + var propTrueNum = apool.putAttrib([propName, true], true); + if (propTrueNum >= 0) + { + anumMap[propTrueNum] = i; + } + }); + + function getLineDokuWiki(text, attribs) + { + var propVals = [false, false, false]; + var ENTER = 1; + var STAY = 2; + var LEAVE = 0; + + // Use order of tags (b/i/u) as order of nesting, for simplicity + // and decent nesting. For example, + // Just bold Bold and italics Just italics + // becomes + // Just bold Bold and italics Just italics + var taker = Changeset.stringIterator(text); + var assem = Changeset.stringAssembler(); + + function emitOpenTag(i) + { + if (tags[i].indexOf('>') !== -1) { + assem.append('<'); + } + assem.append(tags[i]); + } + + function emitCloseTag(i) + { + if (tags[i].indexOf('>') !== -1) { + assem.append(' bold, etc. + if (!propVals[i]) + { + propVals[i] = ENTER; + propChanged = true; + } + else + { + propVals[i] = STAY; + } + } + }); + for (var i = 0; i < propVals.length; i++) + { + if (propVals[i] === true) + { + propVals[i] = LEAVE; + propChanged = true; + } + else if (propVals[i] === STAY) + { + propVals[i] = true; // set it back + } + } + // now each member of propVal is in {false,LEAVE,ENTER,true} + // according to what happens at start of span + if (propChanged) + { + // leaving bold (e.g.) also leaves italics, etc. + var left = false; + for (var i = 0; i < propVals.length; i++) + { + var v = propVals[i]; + if (!left) + { + if (v === LEAVE) + { + left = true; + } + } + else + { + if (v === true) + { + propVals[i] = STAY; // tag will be closed and re-opened + } + } + } + + for (var i = propVals.length - 1; i >= 0; i--) + { + if (propVals[i] === LEAVE) + { + emitCloseTag(i); + propVals[i] = false; + } + else if (propVals[i] === STAY) + { + emitCloseTag(i); + } + } + for (var i = 0; i < propVals.length; i++) + { + if (propVals[i] === ENTER || propVals[i] === STAY) + { + emitOpenTag(i); + propVals[i] = true; + } + } + // propVals is now all {true,false} again + } // end if (propChanged) + var chars = o.chars; + if (o.lines) + { + chars--; // exclude newline at end of line, if present + } + var s = taker.take(chars); + + assem.append(_escapeDokuWiki(s)); + } // end iteration over spans in line + for (var i = propVals.length - 1; i >= 0; i--) + { + if (propVals[i]) + { + emitCloseTag(i); + propVals[i] = false; + } + } + } // end processNextChars + if (urls) + { + urls.forEach(function (urlData) + { + var startIndex = urlData[0]; + var url = urlData[1]; + var urlLength = url.length; + processNextChars(startIndex - idx); + assem.append('[['); + + // Do not use processNextChars since a link does not contain syntax and + // needs no escaping + var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + urlLength)); + idx += urlLength; + assem.append(taker.take(iter.next().chars)); + + assem.append(']]'); + }); + } + processNextChars(text.length - idx); + + return assem.toString() + "\n"; + } // end getLineDokuWiki + var pieces = []; + + for (var i = 0; i < textLines.length; i++) + { + var line = _analyzeLine(textLines[i], attribLines[i], apool); + var lineContent = getLineDokuWiki(line.text, line.aline); + + if (line.listLevel && lineContent) + { + pieces.push(new Array(line.listLevel + 1).join(' ') + '* '); + } + pieces.push(lineContent); + } + + return pieces.join(''); +} + +function _analyzeLine(text, aline, apool) +{ + var line = {}; + + // identify list + var lineMarker = 0; + line.listLevel = 0; + if (aline) + { + var opIter = Changeset.opIterator(aline); + if (opIter.hasNext()) + { + var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); + if (listType) + { + lineMarker = 1; + listType = /([a-z]+)([12345678])/.exec(listType); + if (listType) + { + line.listTypeName = listType[1]; + line.listLevel = Number(listType[2]); + } + } + } + } + if (lineMarker) + { + line.text = text.substring(1); + line.aline = Changeset.subattribution(aline, 1); + } + else + { + line.text = text; + line.aline = aline; + } + + return line; +} + +exports.getPadDokuWikiDocument = function (padId, revNum, callback) +{ + padManager.getPad(padId, function (err, pad) + { + if (err) + { + callback(err); + return; + } + + getPadDokuWiki(pad, revNum, callback); + }); +} + +function _escapeDokuWiki(s) +{ + s = s.replace(/(\/\/|\*\*|__)/g, '%%$1%%'); + return s; +} + +// copied from ACE +var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; +var _REGEX_SPACE = /\s/; +var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); +var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); + +// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] + + +function _findURLs(text) +{ + _REGEX_URL.lastIndex = 0; + var urls = null; + var execResult; + while ((execResult = _REGEX_URL.exec(text))) + { + urls = (urls || []); + var startIndex = execResult.index; + var url = execResult[0]; + urls.push([startIndex, url]); + } + + return urls; +} diff --git a/static/css/pad.css b/static/css/pad.css index 45385a5b7..f6edefef4 100644 --- a/static/css/pad.css +++ b/static/css/pad.css @@ -1066,6 +1066,10 @@ position: relative; background-position: 2px -49px; } +#exportdokuwiki{ + background-position: 2px -144px; +} + #export a{ text-decoration: none; } @@ -1146,4 +1150,4 @@ width:33px !important; #editbar ul li { padding: 4px 1px; } -} \ No newline at end of file +} diff --git a/static/img/fileicons.gif b/static/img/fileicons.gif index 26f63882b..c03b6031a 100644 Binary files a/static/img/fileicons.gif and b/static/img/fileicons.gif differ diff --git a/static/js/pad_impexp.js b/static/js/pad_impexp.js index 8224fce56..fe1d28fbe 100644 --- a/static/js/pad_impexp.js +++ b/static/js/pad_impexp.js @@ -234,6 +234,7 @@ var padimpexp = (function() $("#exporthtmla").attr("href", document.location.pathname + "/export/html"); $("#exportplaina").attr("href", document.location.pathname + "/export/txt"); $("#exportwordlea").attr("href", document.location.pathname + "/export/wordle"); + $("#exportdokuwikia").attr("href", document.location.pathname + "/export/dokuwiki"); //hide stuff thats not avaible if abiword is disabled if(clientVars.abiwordAvailable == "no") @@ -241,8 +242,8 @@ var padimpexp = (function() $("#exportworda").remove(); $("#exportpdfa").remove(); $("#exportopena").remove(); - $("#importexport").css({"height":"95px"}); - $("#importexportline").css({"height":"95px"}); + $("#importexport").css({"height":"115px"}); + $("#importexportline").css({"height":"115px"}); $("#import").html("Import is not available"); } else if(clientVars.abiwordAvailable == "withoutPDF") diff --git a/static/pad.html b/static/pad.html index 980905e99..154cbe1fd 100644 --- a/static/pad.html +++ b/static/pad.html @@ -208,6 +208,7 @@
Microsoft Word
PDF
OpenDocument
+
DokuWiki text
Wordle