diff options
-rw-r--r-- | LICENSE | 19 | ||||
-rw-r--r-- | README.org | 14 | ||||
-rw-r--r-- | index.html | 35 | ||||
-rw-r--r-- | static/app.js | 55 | ||||
-rw-r--r-- | static/org.js | 1816 | ||||
-rw-r--r-- | static/styles.css | 62 |
6 files changed, 2001 insertions, 0 deletions
@@ -0,0 +1,19 @@ +Copyright (c) 2024 Christian Cleberg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.
\ No newline at end of file diff --git a/README.org b/README.org new file mode 100644 index 0000000..62f970c --- /dev/null +++ b/README.org @@ -0,0 +1,14 @@ +* README + +[[https://org.cleberg.net][org-live] is a live environment for writing and editing [[https://orgmode.org/][org-mode]]. + +** Functions + +- Plain text editor with capitalization, autocorrect, and spellcheck support. +- Show HTML output of the written org-mode. +- Download the org-mode content to a local file. +- Download the parsed HTML output to a local file. + +** TODO Further Development + +- Explore feasability of parsing org-mode to HTML constantly and sending parsed HTML output to a "viewing" pane. diff --git a/index.html b/index.html new file mode 100644 index 0000000..02a784f --- /dev/null +++ b/index.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>org-live</title> + <link rel="stylesheet" href="./static/styles.css" /> + </head> + <body> + <form autocomplete> + <div> + <label for="editor">org-live</label> + <span class="spacer"></span> + <button type="button" onclick="showHTML();">Show HTML</button> + <button type="button" onclick="saveHTML();">Save HTML</button> + <button type="button" onclick="saveOrg();">Save Org</button> + </div> + <textarea + id="editor" + name="editor" + autocapitalize="on" + autocorrect="on" + spellcheck="on" + placeholder="* Lorem Ipsum" + cols="80" + wrap="hard" + autofocus + required + ></textarea> + </form> + <dialog id="modal"></dialog> + <script src="./static/org.js"></script> + <script src="./static/app.js"></script> + </body> +</html> diff --git a/static/app.js b/static/app.js new file mode 100644 index 0000000..7da442b --- /dev/null +++ b/static/app.js @@ -0,0 +1,55 @@ +function parseOrg() { + var orgCode = document.getElementById("editor").value; + var orgParser = new Org.Parser(); + var orgDocument = orgParser.parse(orgCode); + var orgHTMLDocument = orgDocument.convert(Org.ConverterHTML, { + headerOffset: 1, + exportFromLineNumber: false, + suppressSubScriptHandling: false, + suppressAutoLink: false, + }); + + // console.dir(orgHTMLDocument); // => { title, contentHTML, tocHTML, toc } + // console.log(orgHTMLDocument.toString()) // => Rendered HTML + return orgHTMLDocument; +} + +function saveOrg() { + const filename = new Date().toISOString(); + var data = document.getElementById("editor").value; + var file = new Blob([data], { type: Text }); + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename + ".org"; + document.body.appendChild(a); + a.click(); + setTimeout(function () { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); +} + +function saveHTML() { + const filename = new Date().toISOString(); + var data = parseOrg(); + var file = new Blob([data], { type: Text }); + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename + ".html"; + document.body.appendChild(a); + a.click(); + setTimeout(function () { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); +} + +function showHTML() { + var data = parseOrg(); + data += "<form method='dialog'><button>Close dialog</button></form>"; + var element = document.getElementById("modal"); + element.innerHTML = data.toString(); + element.showModal(); +} diff --git a/static/org.js b/static/org.js new file mode 100644 index 0000000..8819c69 --- /dev/null +++ b/static/org.js @@ -0,0 +1,1816 @@ +// Generated by export.rb at Sat Feb 21 07:44:29 UTC 2015 +/* + Copyright (c) 2014 Masafumi Oyamada + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +var Org = (function () { + var exports = {}; + + // ------------------------------------------------------------ + // Syntax + // ------------------------------------------------------------ + + var Syntax = { + rules: {}, + + define: function (name, syntax) { + this.rules[name] = syntax; + var methodName = "is" + name.substring(0, 1).toUpperCase() + name.substring(1); + this[methodName] = function (line) { + return this.rules[name].exec(line); + }; + } + }; + + Syntax.define("header", /^(\*+)\s+(.*)$/); // m[1] => level, m[2] => content + Syntax.define("preformatted", /^(\s*):(?: (.*)$|$)/); // m[1] => indentation, m[2] => content + Syntax.define("unorderedListElement", /^(\s*)(?:-|\+|\s+\*)\s+(.*)$/); // m[1] => indentation, m[2] => content + Syntax.define("orderedListElement", /^(\s*)(\d+)(?:\.|\))\s+(.*)$/); // m[1] => indentation, m[2] => number, m[3] => content + Syntax.define("tableSeparator", /^(\s*)\|((?:\+|-)*?)\|?$/); // m[1] => indentation, m[2] => content + Syntax.define("tableRow", /^(\s*)\|(.*?)\|?$/); // m[1] => indentation, m[2] => content + Syntax.define("blank", /^$/); + Syntax.define("horizontalRule", /^(\s*)-{5,}$/); // + Syntax.define("directive", /^(\s*)#\+(?:(begin|end)_)?(.*)$/i); // m[1] => indentation, m[2] => type, m[3] => content + Syntax.define("comment", /^(\s*)#(.*)$/); + Syntax.define("line", /^(\s*)(.*)$/); + + // ------------------------------------------------------------ + // Token + // ------------------------------------------------------------ + + function Token() { + } + + Token.prototype = { + isListElement: function () { + return this.type === Lexer.tokens.orderedListElement || + this.type === Lexer.tokens.unorderedListElement; + }, + + isTableElement: function () { + return this.type === Lexer.tokens.tableSeparator || + this.type === Lexer.tokens.tableRow; + } + }; + + // ------------------------------------------------------------ + // Lexer + // ------------------------------------------------------------ + + function Lexer(stream) { + this.stream = stream; + this.tokenStack = []; + } + + Lexer.prototype = { + tokenize: function (line) { + var token = new Token(); + token.fromLineNumber = this.stream.lineNumber; + + if (Syntax.isHeader(line)) { + token.type = Lexer.tokens.header; + token.indentation = 0; + token.content = RegExp.$2; + // specific + token.level = RegExp.$1.length; + } else if (Syntax.isPreformatted(line)) { + token.type = Lexer.tokens.preformatted; + token.indentation = RegExp.$1.length; + token.content = RegExp.$2; + } else if (Syntax.isUnorderedListElement(line)) { + token.type = Lexer.tokens.unorderedListElement; + token.indentation = RegExp.$1.length; + token.content = RegExp.$2; + } else if (Syntax.isOrderedListElement(line)) { + token.type = Lexer.tokens.orderedListElement; + token.indentation = RegExp.$1.length; + token.content = RegExp.$3; + // specific + token.number = RegExp.$2; + } else if (Syntax.isTableSeparator(line)) { + token.type = Lexer.tokens.tableSeparator; + token.indentation = RegExp.$1.length; + token.content = RegExp.$2; + } else if (Syntax.isTableRow(line)) { + token.type = Lexer.tokens.tableRow; + token.indentation = RegExp.$1.length; + token.content = RegExp.$2; + } else if (Syntax.isBlank(line)) { + token.type = Lexer.tokens.blank; + token.indentation = 0; + token.content = null; + } else if (Syntax.isHorizontalRule(line)) { + token.type = Lexer.tokens.horizontalRule; + token.indentation = RegExp.$1.length; + token.content = null; + } else if (Syntax.isDirective(line)) { + token.type = Lexer.tokens.directive; + token.indentation = RegExp.$1.length; + token.content = RegExp.$3; + // decide directive type (begin, end or oneshot) + var directiveTypeString = RegExp.$2; + if (/^begin/i.test(directiveTypeString)) + token.beginDirective = true; + else if (/^end/i.test(directiveTypeString)) + token.endDirective = true; + else + token.oneshotDirective = true; + } else if (Syntax.isComment(line)) { + token.type = Lexer.tokens.comment; + token.indentation = RegExp.$1.length; + token.content = RegExp.$2; + } else if (Syntax.isLine(line)) { + token.type = Lexer.tokens.line; + token.indentation = RegExp.$1.length; + token.content = RegExp.$2; + } else { + throw new Error("SyntaxError: Unknown line: " + line); + } + + return token; + }, + + pushToken: function (token) { + this.tokenStack.push(token); + }, + + pushDummyTokenByType: function (type) { + var token = new Token(); + token.type = type; + this.tokenStack.push(token); + }, + + peekStackedToken: function () { + return this.tokenStack.length > 0 ? + this.tokenStack[this.tokenStack.length - 1] : null; + }, + + getStackedToken: function () { + return this.tokenStack.length > 0 ? + this.tokenStack.pop() : null; + }, + + peekNextToken: function () { + return this.peekStackedToken() || + this.tokenize(this.stream.peekNextLine()); + }, + + getNextToken: function () { + return this.getStackedToken() || + this.tokenize(this.stream.getNextLine()); + }, + + hasNext: function () { + return this.stream.hasNext(); + }, + + getLineNumber: function () { + return this.stream.lineNumber; + } + }; + + Lexer.tokens = {}; + [ + "header", + "orderedListElement", + "unorderedListElement", + "tableRow", + "tableSeparator", + "preformatted", + "line", + "horizontalRule", + "blank", + "directive", + "comment" + ].forEach(function (tokenName, i) { + Lexer.tokens[tokenName] = i; + }); + + // ------------------------------------------------------------ + // Exports + // ------------------------------------------------------------ + + if (typeof exports !== "undefined") + exports.Lexer = Lexer; + + function PrototypeNode(type, children) { + this.type = type; + this.children = []; + + if (children) { + for (var i = 0, len = children.length; i < len; ++i) { + this.appendChild(children[i]); + } + } + } + PrototypeNode.prototype = { + previousSibling: null, + parent: null, + get firstChild() { + return this.children.length < 1 ? + null : this.children[0]; + }, + get lastChild() { + return this.children.length < 1 ? + null : this.children[this.children.length - 1]; + }, + appendChild: function (newChild) { + var previousSibling = this.children.length < 1 ? + null : this.lastChild; + this.children.push(newChild); + newChild.previousSibling = previousSibling; + newChild.parent = this; + }, + toString: function () { + var string = "<" + this.type + ">"; + + if (typeof this.value !== "undefined") { + string += " " + this.value; + } else if (this.children) { + string += "\n" + this.children.map(function (child, idx) { + return "#" + idx + " " + child.toString(); + }).join("\n").split("\n").map(function (line) { + return " " + line; + }).join("\n"); + } + + return string; + } + }; + + var Node = { + types: {}, + + define: function (name, postProcess) { + this.types[name] = name; + + var methodName = "create" + name.substring(0, 1).toUpperCase() + name.substring(1); + var postProcessGiven = typeof postProcess === "function"; + + this[methodName] = function (children, options) { + var node = new PrototypeNode(name, children); + + if (postProcessGiven) + postProcess(node, options || {}); + + return node; + }; + } + }; + + Node.define("text", function (node, options) { + node.value = options.value; + }); + Node.define("header", function (node, options) { + node.level = options.level; + }); + Node.define("orderedList"); + Node.define("unorderedList"); + Node.define("definitionList"); + Node.define("listElement"); + Node.define("paragraph"); + Node.define("preformatted"); + Node.define("table"); + Node.define("tableRow"); + Node.define("tableCell"); + Node.define("horizontalRule"); + Node.define("directive"); + + // Inline + Node.define("inlineContainer"); + + Node.define("bold"); + Node.define("italic"); + Node.define("underline"); + Node.define("code"); + Node.define("verbatim"); + Node.define("dashed"); + Node.define("link", function (node, options) { + node.src = options.src; + }); + + if (typeof exports !== "undefined") + exports.Node = Node; + + function Stream(sequence) { + this.sequences = sequence.split(/\r?\n/); + this.totalLines = this.sequences.length; + this.lineNumber = 0; + } + + Stream.prototype.peekNextLine = function () { + return this.hasNext() ? this.sequences[this.lineNumber] : null; + }; + + Stream.prototype.getNextLine = function () { + return this.hasNext() ? this.sequences[this.lineNumber++] : null; + }; + + Stream.prototype.hasNext = function () { + return this.lineNumber < this.totalLines; + }; + + if (typeof exports !== "undefined") { + exports.Stream = Stream; + } + + // var Stream = require("./stream.js").Stream; + // var Lexer = require("./lexer.js").Lexer; + // var Node = require("./node.js").Node; + + function Parser() { + this.inlineParser = new InlineParser(); + } + + Parser.parseStream = function (stream, options) { + var parser = new Parser(); + parser.initStatus(stream, options); + parser.parseNodes(); + return parser.nodes; + }; + + Parser.prototype = { + initStatus: function (stream, options) { + if (typeof stream === "string") + stream = new Stream(stream); + this.lexer = new Lexer(stream); + this.nodes = []; + this.options = { + toc: true, + num: true, + "^": "{}", + multilineCell: false + }; + // Override option values + if (options && typeof options === "object") { + for (var key in options) { + this.options[key] = options[key]; + } + } + this.document = { + options: this.options, + directiveValues: {}, + convert: function (ConverterClass, exportOptions) { + var converter = new ConverterClass(this, exportOptions); + return converter.result; + } + }; + }, + + parse: function (stream, options) { + this.initStatus(stream, options); + this.parseDocument(); + this.document.nodes = this.nodes; + return this.document; + }, + + createErrorReport: function (message) { + return new Error(message + " at line " + this.lexer.getLineNumber()); + }, + + skipBlank: function () { + var blankToken = null; + while (this.lexer.peekNextToken().type === Lexer.tokens.blank) + blankToken = this.lexer.getNextToken(); + return blankToken; + }, + + setNodeOriginFromToken: function (node, token) { + node.fromLineNumber = token.fromLineNumber; + return node; + }, + + appendNode: function (newNode) { + var previousSibling = this.nodes.length > 0 ? this.nodes[this.nodes.length - 1] : null; + this.nodes.push(newNode); + newNode.previousSibling = previousSibling; + }, + + // ------------------------------------------------------------ + // <Document> ::= <Element>* + // ------------------------------------------------------------ + + parseDocument: function () { + this.parseTitle(); + this.parseNodes(); + }, + + parseNodes: function () { + while (this.lexer.hasNext()) { + var element = this.parseElement(); + if (element) this.appendNode(element); + } + }, + + parseTitle: function () { + this.skipBlank(); + + if (this.lexer.hasNext() && + this.lexer.peekNextToken().type === Lexer.tokens.line) + this.document.title = this.createTextNode(this.lexer.getNextToken().content); + else + this.document.title = null; + + this.lexer.pushDummyTokenByType(Lexer.tokens.blank); + }, + + // ------------------------------------------------------------ + // <Element> ::= (<Header> | <List> + // | <Preformatted> | <Paragraph> + // | <Table>)* + // ------------------------------------------------------------ + + parseElement: function () { + var element = null; + + switch (this.lexer.peekNextToken().type) { + case Lexer.tokens.header: + element = this.parseHeader(); + break; + case Lexer.tokens.preformatted: + element = this.parsePreformatted(); + break; + case Lexer.tokens.orderedListElement: + case Lexer.tokens.unorderedListElement: + element = this.parseList(); + break; + case Lexer.tokens.line: + element = this.parseText(); + break; + case Lexer.tokens.tableRow: + case Lexer.tokens.tableSeparator: + element = this.parseTable(); + break; + case Lexer.tokens.blank: + this.skipBlank(); + if (this.lexer.hasNext()) { + if (this.lexer.peekNextToken().type === Lexer.tokens.line) + element = this.parseParagraph(); + else + element = this.parseElement(); + } + break; + case Lexer.tokens.horizontalRule: + this.lexer.getNextToken(); + element = Node.createHorizontalRule(); + break; + case Lexer.tokens.directive: + element = this.parseDirective(); + break; + case Lexer.tokens.comment: + // Skip + this.lexer.getNextToken(); + break; + default: + throw this.createErrorReport("Unhandled token: " + this.lexer.peekNextToken().type); + } + + return element; + }, + + parseElementBesidesDirectiveEnd: function () { + try { + // Temporary, override the definition of `parseElement` + this.parseElement = this.parseElementBesidesDirectiveEndBody; + return this.parseElement(); + } finally { + this.parseElement = this.originalParseElement; + } + }, + + parseElementBesidesDirectiveEndBody: function () { + if (this.lexer.peekNextToken().type === Lexer.tokens.directive && + this.lexer.peekNextToken().endDirective) { + return null; + } + + return this.originalParseElement(); + }, + + // ------------------------------------------------------------ + // <Header> + // + // : preformatted + // : block + // ------------------------------------------------------------ + + parseHeader: function () { + var headerToken = this.lexer.getNextToken(); + var header = Node.createHeader([ + this.createTextNode(headerToken.content) // TODO: Parse inline markups + ], { level: headerToken.level }); + this.setNodeOriginFromToken(header, headerToken); + + return header; + }, + + // ------------------------------------------------------------ + // <Preformatted> + // + // : preformatted + // : block + // ------------------------------------------------------------ + + parsePreformatted: function () { + var preformattedFirstToken = this.lexer.peekNextToken(); + var preformatted = Node.createPreformatted([]); + this.setNodeOriginFromToken(preformatted, preformattedFirstToken); + + var textContents = []; + + while (this.lexer.hasNext()) { + var token = this.lexer.peekNextToken(); + if (token.type !== Lexer.tokens.preformatted || + token.indentation < preformattedFirstToken.indentation) + break; + this.lexer.getNextToken(); + textContents.push(token.content); + } + + preformatted.appendChild(this.createTextNode(textContents.join("\n"), true /* no emphasis */)); + + return preformatted; + }, + + // ------------------------------------------------------------ + // <List> + // + // - foo + // 1. bar + // 2. baz + // ------------------------------------------------------------ + + // XXX: not consider codes (e.g., =Foo::Bar=) + definitionPattern: /^(.*?) :: *(.*)$/, + + parseList: function () { + var rootToken = this.lexer.peekNextToken(); + var list; + var isDefinitionList = false; + + if (this.definitionPattern.test(rootToken.content)) { + list = Node.createDefinitionList([]); + isDefinitionList = true; + } else { + list = rootToken.type === Lexer.tokens.unorderedListElement ? + Node.createUnorderedList([]) : Node.createOrderedList([]); + } + this.setNodeOriginFromToken(list, rootToken); + + while (this.lexer.hasNext()) { + var nextToken = this.lexer.peekNextToken(); + if (!nextToken.isListElement() || nextToken.indentation !== rootToken.indentation) + break; + list.appendChild(this.parseListElement(rootToken.indentation, isDefinitionList)); + } + + return list; + }, + + unknownDefinitionTerm: "???", + + parseListElement: function (rootIndentation, isDefinitionList) { + var listElementToken = this.lexer.getNextToken(); + var listElement = Node.createListElement([]); + this.setNodeOriginFromToken(listElement, listElementToken); + + listElement.isDefinitionList = isDefinitionList; + + if (isDefinitionList) { + var match = this.definitionPattern.exec(listElementToken.content); + listElement.term = [ + this.createTextNode(match && match[1] ? match[1] : this.unknownDefinitionTerm) + ]; + listElement.appendChild(this.createTextNode(match ? match[2] : listElementToken.content)); + } else { + listElement.appendChild(this.createTextNode(listElementToken.content)); + } + + while (this.lexer.hasNext()) { + var blankToken = this.skipBlank(); + if (!this.lexer.hasNext()) + break; + + var notBlankNextToken = this.lexer.peekNextToken(); + if (blankToken && !notBlankNextToken.isListElement()) + this.lexer.pushToken(blankToken); // Recover blank token only when next line is not listElement. + if (notBlankNextToken.indentation <= rootIndentation) + break; // end of the list + + var element = this.parseElement(); // recursive + if (element) + listElement.appendChild(element); + } + + return listElement; + }, + + // ------------------------------------------------------------ + // <Table> ::= <TableRow>+ + // ------------------------------------------------------------ + + parseTable: function () { + var nextToken = this.lexer.peekNextToken(); + var table = Node.createTable([]); + this.setNodeOriginFromToken(table, nextToken); + var sawSeparator = false; + + var allowMultilineCell = nextToken.type === Lexer.tokens.tableSeparator && this.options.multilineCell; + + while (this.lexer.hasNext() && + (nextToken = this.lexer.peekNextToken()).isTableElement()) { + if (nextToken.type === Lexer.tokens.tableRow) { + var tableRow = this.parseTableRow(allowMultilineCell); + table.appendChild(tableRow); + } else { + // Lexer.tokens.tableSeparator + sawSeparator = true; + this.lexer.getNextToken(); + } + } + + if (sawSeparator && table.children.length) { + table.children[0].children.forEach(function (cell) { + cell.isHeader = true; + }); + } + + return table; + }, + + // ------------------------------------------------------------ + // <TableRow> ::= <TableCell>+ + // ------------------------------------------------------------ + + parseTableRow: function (allowMultilineCell) { + var tableRowTokens = []; + + while (this.lexer.peekNextToken().type === Lexer.tokens.tableRow) { + tableRowTokens.push(this.lexer.getNextToken()); + if (!allowMultilineCell) { + break; + } + } + + if (!tableRowTokens.length) { + throw this.createErrorReport("Expected table row"); + } + + var firstTableRowToken = tableRowTokens.shift(); + var tableCellTexts = firstTableRowToken.content.split("|"); + + tableRowTokens.forEach(function (rowToken) { + rowToken.content.split("|").forEach(function (cellText, cellIdx) { + tableCellTexts[cellIdx] = (tableCellTexts[cellIdx] || "") + "\n" + cellText; + }); + }); + + // TODO: Prepare two pathes: (1) + var tableCells = tableCellTexts.map( + // TODO: consider '|' escape? + function (text) { + return Node.createTableCell(Parser.parseStream(text)); + }, this); + + return this.setNodeOriginFromToken(Node.createTableRow(tableCells), firstTableRowToken); + }, + + // ------------------------------------------------------------ + // <Directive> ::= "#+.*" + // ------------------------------------------------------------ + + parseDirective: function () { + var directiveToken = this.lexer.getNextToken(); + var directiveNode = this.createDirectiveNodeFromToken(directiveToken); + + if (directiveToken.endDirective) + throw this.createErrorReport("Unmatched 'end' directive for " + directiveNode.directiveName); + + if (directiveToken.oneshotDirective) { + this.interpretDirective(directiveNode); + return directiveNode; + } + + if (!directiveToken.beginDirective) + throw this.createErrorReport("Invalid directive " + directiveNode.directiveName); + + // Parse begin ~ end + directiveNode.children = []; + if (this.isVerbatimDirective(directiveNode)) + return this.parseDirectiveBlockVerbatim(directiveNode); + else + return this.parseDirectiveBlock(directiveNode); + }, + + createDirectiveNodeFromToken: function (directiveToken) { + var matched = /^[ ]*([^ ]*)[ ]*(.*)[ ]*$/.exec(directiveToken.content); + + var directiveNode = Node.createDirective(null); + this.setNodeOriginFromToken(directiveNode, directiveToken); + directiveNode.directiveName = matched[1].toLowerCase(); + directiveNode.directiveArguments = this.parseDirectiveArguments(matched[2]); + directiveNode.directiveOptions = this.parseDirectiveOptions(matched[2]); + directiveNode.directiveRawValue = matched[2]; + + return directiveNode; + }, + + isVerbatimDirective: function (directiveNode) { + var directiveName = directiveNode.directiveName; + return directiveName === "src" || directiveName === "example" || directiveName === "html"; + }, + + parseDirectiveBlock: function (directiveNode, verbatim) { + this.lexer.pushDummyTokenByType(Lexer.tokens.blank); + + while (this.lexer.hasNext()) { + var nextToken = this.lexer.peekNextToken(); + if (nextToken.type === Lexer.tokens.directive && + nextToken.endDirective && + this.createDirectiveNodeFromToken(nextToken).directiveName === directiveNode.directiveName) { + // Close directive + this.lexer.getNextToken(); + return directiveNode; + } + var element = this.parseElementBesidesDirectiveEnd(); + if (element) + directiveNode.appendChild(element); + } + + throw this.createErrorReport("Unclosed directive " + directiveNode.directiveName); + }, + + parseDirectiveBlockVerbatim: function (directiveNode) { + var textContent = []; + + while (this.lexer.hasNext()) { + var nextToken = this.lexer.peekNextToken(); + if (nextToken.type === Lexer.tokens.directive && + nextToken.endDirective && + this.createDirectiveNodeFromToken(nextToken).directiveName === directiveNode.directiveName) { + this.lexer.getNextToken(); + directiveNode.appendChild(this.createTextNode(textContent.join("\n"), true)); + return directiveNode; + } + textContent.push(this.lexer.stream.getNextLine()); + } + + throw this.createErrorReport("Unclosed directive " + directiveNode.directiveName); + }, + + parseDirectiveArguments: function (parameters) { + return parameters.split(/[ ]+/).filter(function (param) { + return param.length && param[0] !== "-"; + }); + }, + + parseDirectiveOptions: function (parameters) { + return parameters.split(/[ ]+/).filter(function (param) { + return param.length && param[0] === "-"; + }); + }, + + interpretDirective: function (directiveNode) { + // http://orgmode.org/manual/Export-options.html + switch (directiveNode.directiveName) { + case "options:": + this.interpretOptionDirective(directiveNode); + break; + case "title:": + this.document.title = directiveNode.directiveRawValue; + break; + case "author:": + this.document.author = directiveNode.directiveRawValue; + break; + case "email:": + this.document.email = directiveNode.directiveRawValue; + break; + default: + this.document.directiveValues[directiveNode.directiveName] = directiveNode.directiveRawValue; + break; + } + }, + + interpretOptionDirective: function (optionDirectiveNode) { + optionDirectiveNode.directiveArguments.forEach(function (pairString) { + var pair = pairString.split(":"); + this.options[pair[0]] = this.convertLispyValue(pair[1]); + }, this); + }, + + convertLispyValue: function (lispyValue) { + switch (lispyValue) { + case "t": + return true; + case "nil": + return false; + default: + if (/^[0-9]+$/.test(lispyValue)) + return parseInt(lispyValue); + return lispyValue; + } + }, + + // ------------------------------------------------------------ + // <Paragraph> ::= <Blank> <Line>* + // ------------------------------------------------------------ + + parseParagraph: function () { + var paragraphFisrtToken = this.lexer.peekNextToken(); + var paragraph = Node.createParagraph([]); + this.setNodeOriginFromToken(paragraph, paragraphFisrtToken); + + var textContents = []; + + while (this.lexer.hasNext()) { + var nextToken = this.lexer.peekNextToken(); + if (nextToken.type !== Lexer.tokens.line + || nextToken.indentation < paragraphFisrtToken.indentation) + break; + this.lexer.getNextToken(); + textContents.push(nextToken.content); + } + + paragraph.appendChild(this.createTextNode(textContents.join("\n"))); + + return paragraph; + }, + + parseText: function (noEmphasis) { + var lineToken = this.lexer.getNextToken(); + return this.createTextNode(lineToken.content, noEmphasis); + }, + + // ------------------------------------------------------------ + // <Text> (DOM Like) + // ------------------------------------------------------------ + + createTextNode: function (text, noEmphasis) { + return noEmphasis ? Node.createText(null, { value: text }) + : this.inlineParser.parseEmphasis(text); + } + }; + Parser.prototype.originalParseElement = Parser.prototype.parseElement; + + // ------------------------------------------------------------ + // Parser for Inline Elements + // + // @refs org-emphasis-regexp-components + // ------------------------------------------------------------ + + function InlineParser() { + this.preEmphasis = " \t\\('\""; + this.postEmphasis = "- \t.,:!?;'\"\\)"; + this.borderForbidden = " \t\r\n,\"'"; + this.bodyRegexp = "[\\s\\S]*?"; + this.markers = "*/_=~+"; + + this.emphasisPattern = this.buildEmphasisPattern(); + this.linkPattern = /\[\[([^\]]*)\](?:\[([^\]]*)\])?\]/g; // \1 => link, \2 => text + } + + InlineParser.prototype = { + parseEmphasis: function (text) { + var emphasisPattern = this.emphasisPattern; + emphasisPattern.lastIndex = 0; + + var result = [], + match, + previousLast = 0, + savedLastIndex; + + while ((match = emphasisPattern.exec(text))) { + var whole = match[0]; + var pre = match[1]; + var marker = match[2]; + var body = match[3]; + var post = match[4]; + + { + // parse links + var matchBegin = emphasisPattern.lastIndex - whole.length; + var beforeContent = text.substring(previousLast, matchBegin + pre.length); + savedLastIndex = emphasisPattern.lastIndex; + result.push(this.parseLink(beforeContent)); + emphasisPattern.lastIndex = savedLastIndex; + } + + var bodyNode = [Node.createText(null, { value: body })]; + var bodyContainer = this.emphasizeElementByMarker(bodyNode, marker); + result.push(bodyContainer); + + previousLast = emphasisPattern.lastIndex - post.length; + } + + if (emphasisPattern.lastIndex === 0 || + emphasisPattern.lastIndex !== text.length - 1) + result.push(this.parseLink(text.substring(previousLast))); + + if (result.length === 1) { + // Avoid duplicated inline container wrapping + return result[0]; + } else { + return Node.createInlineContainer(result); + } + }, + + depth: 0, + parseLink: function (text) { + var linkPattern = this.linkPattern; + linkPattern.lastIndex = 0; + + var match, + result = [], + previousLast = 0, + savedLastIndex; + + while ((match = linkPattern.exec(text))) { + var whole = match[0]; + var src = match[1]; + var title = match[2]; + + // parse before content + var matchBegin = linkPattern.lastIndex - whole.length; + var beforeContent = text.substring(previousLast, matchBegin); + result.push(Node.createText(null, { value: beforeContent })); + + // parse link + var link = Node.createLink([]); + link.src = src; + if (title) { + savedLastIndex = linkPattern.lastIndex; + link.appendChild(this.parseEmphasis(title)); + linkPattern.lastIndex = savedLastIndex; + } else { + link.appendChild(Node.createText(null, { value: src })); + } + result.push(link); + + previousLast = linkPattern.lastIndex; + } + + if (linkPattern.lastIndex === 0 || + linkPattern.lastIndex !== text.length - 1) + result.push(Node.createText(null, { value: text.substring(previousLast) })); + + return Node.createInlineContainer(result); + }, + + emphasizeElementByMarker: function (element, marker) { + switch (marker) { + case "*": + return Node.createBold(element); + case "/": + return Node.createItalic(element); + case "_": + return Node.createUnderline(element); + case "=": + case "~": + return Node.createCode(element); + case "+": + return Node.createDashed(element); + } + }, + + buildEmphasisPattern: function () { + return new RegExp( + "([" + this.preEmphasis + "]|^|\r?\n)" + // \1 => pre + "([" + this.markers + "])" + // \2 => marker + "([^" + this.borderForbidden + "]|" + // \3 => body + "[^" + this.borderForbidden + "]" + + this.bodyRegexp + + "[^" + this.borderForbidden + "])" + + "\\2" + + "([" + this.postEmphasis +"]|$|\r?\n)", // \4 => post + // flags + "g" + ); + } + }; + + if (typeof exports !== "undefined") { + exports.Parser = Parser; + exports.InlineParser = InlineParser; + } + + // var Node = require("../node.js").Node; + + function Converter() { + } + + Converter.prototype = { + exportOptions: { + headerOffset: 1, + exportFromLineNumber: false, + suppressSubScriptHandling: false, + suppressAutoLink: false, + // HTML + translateSymbolArrow: false, + suppressCheckboxHandling: false, + // { "directive:": function (node, childText, auxData) {} } + customDirectiveHandler: null, + // e.g., "org-js-" + htmlClassPrefix: null, + htmlIdPrefix: null + }, + + untitled: "Untitled", + result: null, + + // TODO: Manage TODO lists + + initialize: function (orgDocument, exportOptions) { + this.orgDocument = orgDocument; + this.documentOptions = orgDocument.options || {}; + this.exportOptions = exportOptions || {}; + + this.headers = []; + this.headerOffset = + typeof this.exportOptions.headerOffset === "number" ? this.exportOptions.headerOffset : 1; + this.sectionNumbers = [0]; + }, + + createTocItem: function (headerNode, parentTocs) { + var childTocs = []; + childTocs.parent = parentTocs; + var tocItem = { headerNode: headerNode, childTocs: childTocs }; + return tocItem; + }, + + computeToc: function (exportTocLevel) { + if (typeof exportTocLevel !== "number") + exportTocLevel = Infinity; + + var toc = []; + toc.parent = null; + + var previousLevel = 1; + var currentTocs = toc; // first + + for (var i = 0; i < this.headers.length; ++i) { + var headerNode = this.headers[i]; + + if (headerNode.level > exportTocLevel) + continue; + + var levelDiff = headerNode.level - previousLevel; + if (levelDiff > 0) { + for (var j = 0; j < levelDiff; ++j) { + if (currentTocs.length === 0) { + // Create a dummy tocItem + var dummyHeader = Node.createHeader([], { + level: previousLevel + j + }); + dummyHeader.sectionNumberText = ""; + currentTocs.push(this.createTocItem(dummyHeader, currentTocs)); + } + currentTocs = currentTocs[currentTocs.length - 1].childTocs; + } + } else if (levelDiff < 0) { + levelDiff = -levelDiff; + for (var k = 0; k < levelDiff; ++k) { + currentTocs = currentTocs.parent; + } + } + + currentTocs.push(this.createTocItem(headerNode, currentTocs)); + + previousLevel = headerNode.level; + } + + return toc; + }, + + convertNode: function (node, recordHeader, insideCodeElement) { + if (!insideCodeElement) { + if (node.type === Node.types.directive) { + if (node.directiveName === "example" || + node.directiveName === "src") { + insideCodeElement = true; + } + } else if (node.type === Node.types.preformatted) { + insideCodeElement = true; + } + } + + if (typeof node === "string") { + node = Node.createText(null, { value: node }); + } + + var childText = node.children ? this.convertNodesInternal(node.children, recordHeader, insideCodeElement) : ""; + var text; + + var auxData = this.computeAuxDataForNode(node); + + switch (node.type) { + case Node.types.header: + // Parse task status + var taskStatus = null; + if (childText.indexOf("TODO ") === 0) + taskStatus = "todo"; + else if (childText.indexOf("DONE ") === 0) + taskStatus = "done"; + + // Compute section number + var sectionNumberText = null; + if (recordHeader) { + var thisHeaderLevel = node.level; + var previousHeaderLevel = this.sectionNumbers.length; + if (thisHeaderLevel > previousHeaderLevel) { + // Fill missing section number + var levelDiff = thisHeaderLevel - previousHeaderLevel; + for (var j = 0; j < levelDiff; ++j) { + this.sectionNumbers[thisHeaderLevel - 1 - j] = 0; // Extend + } + } else if (thisHeaderLevel < previousHeaderLevel) { + this.sectionNumbers.length = thisHeaderLevel; // Collapse + } + this.sectionNumbers[thisHeaderLevel - 1]++; + sectionNumberText = this.sectionNumbers.join("."); + node.sectionNumberText = sectionNumberText; // Can be used in ToC + } + + text = this.convertHeader(node, childText, auxData, + taskStatus, sectionNumberText); + + if (recordHeader) + this.headers.push(node); + break; + case Node.types.orderedList: + text = this.convertOrderedList(node, childText, auxData); + break; + case Node.types.unorderedList: + text = this.convertUnorderedList(node, childText, auxData); + break; + case Node.types.definitionList: + text = this.convertDefinitionList(node, childText, auxData); + break; + case Node.types.listElement: + if (node.isDefinitionList) { + var termText = this.convertNodesInternal(node.term, recordHeader, insideCodeElement); + text = this.convertDefinitionItem(node, childText, auxData, + termText, childText); + } else { + text = this.convertListItem(node, childText, auxData); + } + break; + case Node.types.paragraph: + text = this.convertParagraph(node, childText, auxData); + break; + case Node.types.preformatted: + text = this.convertPreformatted(node, childText, auxData); + break; + case Node.types.table: + text = this.convertTable(node, childText, auxData); + break; + case Node.types.tableRow: + text = this.convertTableRow(node, childText, auxData); + break; + case Node.types.tableCell: + if (node.isHeader) + text = this.convertTableHeader(node, childText, auxData); + else + text = this.convertTableCell(node, childText, auxData); + break; + case Node.types.horizontalRule: + text = this.convertHorizontalRule(node, childText, auxData); + break; + // ============================================================ // + // Inline + // ============================================================ // + case Node.types.inlineContainer: + text = this.convertInlineContainer(node, childText, auxData); + break; + case Node.types.bold: + text = this.convertBold(node, childText, auxData); + break; + case Node.types.italic: + text = this.convertItalic(node, childText, auxData); + break; + case Node.types.underline: + text = this.convertUnderline(node, childText, auxData); + break; + case Node.types.code: + text = this.convertCode(node, childText, auxData); + break; + case Node.types.dashed: + text = this.convertDashed(node, childText, auxData); + break; + case Node.types.link: + text = this.convertLink(node, childText, auxData); + break; + case Node.types.directive: + switch (node.directiveName) { + case "quote": + text = this.convertQuote(node, childText, auxData); + break; + case "example": + text = this.convertExample(node, childText, auxData); + break; + case "src": + text = this.convertSrc(node, childText, auxData); + break; + case "html": + case "html:": + text = this.convertHTML(node, childText, auxData); + break; + default: + if (this.exportOptions.customDirectiveHandler && + this.exportOptions.customDirectiveHandler[node.directiveName]) { + text = this.exportOptions.customDirectiveHandler[node.directiveName]( + node, childText, auxData + ); + } else { + text = childText; + } + } + break; + case Node.types.text: + text = this.convertText(node.value, insideCodeElement); + break; + default: + throw Error("Unknown node type: " + node.type); + } + + if (typeof this.postProcess === "function") { + text = this.postProcess(node, text, insideCodeElement); + } + + return text; + }, + + convertText: function (text, insideCodeElement) { + var escapedText = this.escapeSpecialChars(text, insideCodeElement); + + if (!this.exportOptions.suppressSubScriptHandling && !insideCodeElement) { + escapedText = this.makeSubscripts(escapedText, insideCodeElement); + } + if (!this.exportOptions.suppressAutoLink) { + escapedText = this.linkURL(escapedText); + } + + return escapedText; + }, + + // By default, ignore html + convertHTML: function (node, childText, auxData) { + return childText; + }, + + convertNodesInternal: function (nodes, recordHeader, insideCodeElement) { + var nodesTexts = []; + for (var i = 0; i < nodes.length; ++i) { + var node = nodes[i]; + var nodeText = this.convertNode(node, recordHeader, insideCodeElement); + nodesTexts.push(nodeText); + } + return this.combineNodesTexts(nodesTexts); + }, + + convertHeaderBlock: function (headerBlock, recordHeader) { + throw Error("convertHeaderBlock is not implemented"); + }, + + convertHeaderTree: function (headerTree, recordHeader) { + return this.convertHeaderBlock(headerTree, recordHeader); + }, + + convertNodesToHeaderTree: function (nodes, nextBlockBegin, blockHeader) { + var childBlocks = []; + var childNodes = []; + + if (typeof nextBlockBegin === "undefined") { + nextBlockBegin = 0; + } + if (typeof blockHeader === "undefined") { + blockHeader = null; + } + + for (var i = nextBlockBegin; i < nodes.length;) { + var node = nodes[i]; + + var isHeader = node.type === Node.types.header; + + if (!isHeader) { + childNodes.push(node); + i = i + 1; + continue; + } + + // Header + if (blockHeader && node.level <= blockHeader.level) { + // Finish Block + break; + } else { + // blockHeader.level < node.level + // Begin child block + var childBlock = this.convertNodesToHeaderTree(nodes, i + 1, node); + childBlocks.push(childBlock); + i = childBlock.nextIndex; + } + } + + // Finish block + return { + header: blockHeader, + childNodes: childNodes, + nextIndex: i, + childBlocks: childBlocks + }; + }, + + convertNodes: function (nodes, recordHeader, insideCodeElement) { + return this.convertNodesInternal(nodes, recordHeader, insideCodeElement); + }, + + combineNodesTexts: function (nodesTexts) { + return nodesTexts.join(""); + }, + + getNodeTextContent: function (node) { + if (node.type === Node.types.text) + return this.escapeSpecialChars(node.value); + else + return node.children ? node.children.map(this.getNodeTextContent, this).join("") : ""; + }, + + // @Override + escapeSpecialChars: function (text) { + throw Error("Implement escapeSpecialChars"); + }, + + // http://daringfireball.net/2010/07/improved_regex_for_matching_urls + urlPattern: /\b(?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’])/ig, + + // @Override + linkURL: function (text) { + var self = this; + return text.replace(this.urlPattern, function (matched) { + if (matched.indexOf("://") < 0) + matched = "http://" + matched; + return self.makeLink(matched); + }); + }, + + makeLink: function (url) { + throw Error("Implement makeLink"); + }, + + makeSubscripts: function (text) { + if (this.documentOptions["^"] === "{}") + return text.replace(/\b([^_ \t]*)_{([^}]*)}/g, + this.makeSubscript); + else if (this.documentOptions["^"]) + return text.replace(/\b([^_ \t]*)_([^_]*)\b/g, + this.makeSubscript); + else + return text; + }, + + makeSubscript: function (match, body, subscript) { + throw Error("Implement makeSubscript"); + }, + + stripParametersFromURL: function (url) { + return url.replace(/\?.*$/, ""); + }, + + imageExtensionPattern: new RegExp("(" + [ + "bmp", "png", "jpeg", "jpg", "gif", "tiff", + "tif", "xbm", "xpm", "pbm", "pgm", "ppm", "svg" + ].join("|") + ")$", "i") + }; + + if (typeof exports !== "undefined") + exports.Converter = Converter; + + // var Converter = require("./converter.js").Converter; + // var Node = require("../node.js").Node; + + function ConverterHTML(orgDocument, exportOptions) { + this.initialize(orgDocument, exportOptions); + this.result = this.convert(); + } + + ConverterHTML.prototype = { + __proto__: Converter.prototype, + + convert: function () { + var title = this.orgDocument.title ? this.convertNode(this.orgDocument.title) : this.untitled; + var titleHTML = this.tag("h" + Math.max(Number(this.headerOffset), 1), title); + var contentHTML = this.convertNodes(this.orgDocument.nodes, true /* record headers */); + var toc = this.computeToc(this.documentOptions["toc"]); + var tocHTML = this.tocToHTML(toc); + + return { + title: title, + titleHTML: titleHTML, + contentHTML: contentHTML, + tocHTML: tocHTML, + toc: toc, + toString: function () { + return titleHTML + tocHTML + "\n" + contentHTML; + } + }; + }, + + tocToHTML: function (toc) { + function tocToHTMLFunction(tocList) { + var html = ""; + for (var i = 0; i < tocList.length; ++i) { + var tocItem = tocList[i]; + var sectionNumberText = tocItem.headerNode.sectionNumberText; + var sectionNumber = this.documentOptions.num ? + this.inlineTag("span", sectionNumberText, { + "class": "section-number" + }) : ""; + var header = this.getNodeTextContent(tocItem.headerNode); + var headerLink = this.inlineTag("a", sectionNumber + header, { + href: "#header-" + sectionNumberText.replace(/\./g, "-") + }); + var subList = tocItem.childTocs.length ? tocToHTMLFunction.call(this, tocItem.childTocs) : ""; + html += this.tag("li", headerLink + subList); + } + return this.tag("ul", html); + } + + return tocToHTMLFunction.call(this, toc); + }, + + computeAuxDataForNode: function (node) { + while (node.parent && + node.parent.type === Node.types.inlineContainer) { + node = node.parent; + } + var attributesNode = node.previousSibling; + var attributesText = ""; + while (attributesNode && + attributesNode.type === Node.types.directive && + attributesNode.directiveName === "attr_html:") { + attributesText += attributesNode.directiveRawValue + " "; + attributesNode = attributesNode.previousSibling; + } + return attributesText; + }, + + // Method to construct org-js generated class + orgClassName: function (className) { + return this.exportOptions.htmlClassPrefix ? + this.exportOptions.htmlClassPrefix + className + : className; + }, + + // Method to construct org-js generated id + orgId: function (id) { + return this.exportOptions.htmlIdPrefix ? + this.exportOptions.htmlIdPrefix + id + : id; + }, + + // ---------------------------------------------------- + // Node conversion + // ---------------------------------------------------- + + convertHeader: function (node, childText, auxData, + taskStatus, sectionNumberText) { + var headerAttributes = {}; + + if (taskStatus) { + childText = this.inlineTag("span", childText.substring(0, 4), { + "class": "task-status " + taskStatus + }) + childText.substring(5); + } + + if (sectionNumberText) { + childText = this.inlineTag("span", sectionNumberText, { + "class": "section-number" + }) + childText; + headerAttributes["id"] = "header-" + sectionNumberText.replace(/\./g, "-"); + } + + if (taskStatus) + headerAttributes["class"] = "task-status " + taskStatus; + + return this.tag("h" + (this.headerOffset + node.level), + childText, headerAttributes, auxData); + }, + + convertOrderedList: function (node, childText, auxData) { + return this.tag("ol", childText, null, auxData); + }, + + convertUnorderedList: function (node, childText, auxData) { + return this.tag("ul", childText, null, auxData); + }, + + convertDefinitionList: function (node, childText, auxData) { + return this.tag("dl", childText, null, auxData); + }, + + convertDefinitionItem: function (node, childText, auxData, + term, definition) { + return this.tag("dt", term) + this.tag("dd", definition); + }, + + convertListItem: function (node, childText, auxData) { + if (this.exportOptions.suppressCheckboxHandling) { + return this.tag("li", childText, null, auxData); + } else { + var listItemAttributes = {}; + var listItemText = childText; + // Embed checkbox + if (/^\s*\[(X| |-)\]([\s\S]*)/.exec(listItemText)) { + listItemText = RegExp.$2 ; + var checkboxIndicator = RegExp.$1; + + var checkboxAttributes = { type: "checkbox" }; + switch (checkboxIndicator) { + case "X": + checkboxAttributes["checked"] = "true"; + listItemAttributes["data-checkbox-status"] = "done"; + break; + case "-": + listItemAttributes["data-checkbox-status"] = "intermediate"; + break; + default: + listItemAttributes["data-checkbox-status"] = "undone"; + break; + } + + listItemText = this.inlineTag("input", null, checkboxAttributes) + listItemText; + } + + return this.tag("li", listItemText, listItemAttributes, auxData); + } + }, + + convertParagraph: function (node, childText, auxData) { + return this.tag("p", childText, null, auxData); + }, + + convertPreformatted: function (node, childText, auxData) { + return this.tag("pre", childText, null, auxData); + }, + + convertTable: function (node, childText, auxData) { + return this.tag("table", this.tag("tbody", childText), null, auxData); + }, + + convertTableRow: function (node, childText, auxData) { + return this.tag("tr", childText); + }, + + convertTableHeader: function (node, childText, auxData) { + return this.tag("th", childText); + }, + + convertTableCell: function (node, childText, auxData) { + return this.tag("td", childText); + }, + + convertHorizontalRule: function (node, childText, auxData) { + return this.tag("hr", null, null, auxData); + }, + + convertInlineContainer: function (node, childText, auxData) { + return childText; + }, + + convertBold: function (node, childText, auxData) { + return this.inlineTag("b", childText); + }, + + convertItalic: function (node, childText, auxData) { + return this.inlineTag("i", childText); + }, + + convertUnderline: function (node, childText, auxData) { + return this.inlineTag("span", childText, { + style: "text-decoration:underline;" + }); + }, + + convertCode: function (node, childText, auxData) { + return this.inlineTag("code", childText); + }, + + convertDashed: function (node, childText, auxData) { + return this.inlineTag("del", childText); + }, + + convertLink: function (node, childText, auxData) { + var srcParameterStripped = this.stripParametersFromURL(node.src); + if (this.imageExtensionPattern.exec(srcParameterStripped)) { + var imgText = this.getNodeTextContent(node); + return this.inlineTag("img", null, { + src: node.src, + alt: imgText, + title: imgText + }, auxData); + } else { + return this.inlineTag("a", childText, { href: node.src }); + } + }, + + convertQuote: function (node, childText, auxData) { + return this.tag("blockquote", childText, null, auxData); + }, + + convertExample: function (node, childText, auxData) { + return this.tag("pre", childText, null, auxData); + }, + + convertSrc: function (node, childText, auxData) { + var codeLanguage = node.directiveArguments.length + ? node.directiveArguments[0] + : "unknown"; + childText = this.tag("code", childText, { + "class": "language-" + codeLanguage + }, auxData); + return this.tag("pre", childText, { + "class": "prettyprint" + }); + }, + + // @override + convertHTML: function (node, childText, auxData) { + if (node.directiveName === "html:") { + return node.directiveRawValue; + } else if (node.directiveName === "html") { + return node.children.map(function (textNode) { + return textNode.value; + }).join("\n"); + } else { + return childText; + } + }, + + // @implement + convertHeaderBlock: function (headerBlock, level, index) { + level = level || 0; + index = index || 0; + + var contents = []; + + var headerNode = headerBlock.header; + if (headerNode) { + contents.push(this.convertNode(headerNode)); + } + + var blockContent = this.convertNodes(headerBlock.childNodes); + contents.push(blockContent); + + var childBlockContent = headerBlock.childBlocks + .map(function (block, idx) { + return this.convertHeaderBlock(block, level + 1, idx); + }, this) + .join("\n"); + contents.push(childBlockContent); + + var contentsText = contents.join("\n"); + + if (headerNode) { + return this.tag("section", "\n" + contents.join("\n"), { + "class": "block block-level-" + level + }); + } else { + return contentsText; + } + }, + + // ---------------------------------------------------- + // Supplemental methods + // ---------------------------------------------------- + + replaceMap: { + // [replacing pattern, predicate] + "&": ["&", null], + "<": ["<", null], + ">": [">", null], + '"': [""", null], + "'": ["'", null], + "->": ["➔", function (text, insideCodeElement) { + return this.exportOptions.translateSymbolArrow && !insideCodeElement; + }] + }, + + replaceRegexp: null, + + // @implement @override + escapeSpecialChars: function (text, insideCodeElement) { + if (!this.replaceRegexp) { + this.replaceRegexp = new RegExp(Object.keys(this.replaceMap).join("|"), "g"); + } + + var replaceMap = this.replaceMap; + var self = this; + return text.replace(this.replaceRegexp, function (matched) { + if (!replaceMap[matched]) { + throw Error("escapeSpecialChars: Invalid match"); + } + + var predicate = replaceMap[matched][1]; + if (typeof predicate === "function" && + !predicate.call(self, text, insideCodeElement)) { + // Not fullfill the predicate + return matched; + } + + return replaceMap[matched][0]; + }); + }, + + // @implement + postProcess: function (node, currentText, insideCodeElement) { + if (this.exportOptions.exportFromLineNumber && + typeof node.fromLineNumber === "number") { + // Wrap with line number information + currentText = this.inlineTag("div", currentText, { + "data-line-number": node.fromLineNumber + }); + } + return currentText; + }, + + // @implement + makeLink: function (url) { + return "<a href=\"" + url + "\">" + decodeURIComponent(url) + "</a>"; + }, + + // @implement + makeSubscript: function (match, body, subscript) { + return "<span class=\"org-subscript-parent\">" + + body + + "</span><span class=\"org-subscript-child\">" + + subscript + + "</span>"; + }, + + // ---------------------------------------------------- + // Specific methods + // ---------------------------------------------------- + + attributesObjectToString: function (attributesObject) { + var attributesString = ""; + for (var attributeName in attributesObject) { + if (attributesObject.hasOwnProperty(attributeName)) { + var attributeValue = attributesObject[attributeName]; + // To avoid id/class name conflicts with other frameworks, + // users can add arbitrary prefix to org-js generated + // ids/classes via exportOptions. + if (attributeName === "class") { + attributeValue = this.orgClassName(attributeValue); + } else if (attributeName === "id") { + attributeValue = this.orgId(attributeValue); + } + attributesString += " " + attributeName + "=\"" + attributeValue + "\""; + } + } + return attributesString; + }, + + inlineTag: function (name, innerText, attributesObject, auxAttributesText) { + attributesObject = attributesObject || {}; + + var htmlString = "<" + name; + // TODO: check duplicated attributes + if (auxAttributesText) + htmlString += " " + auxAttributesText; + htmlString += this.attributesObjectToString(attributesObject); + + if (innerText === null) + return htmlString + "/>"; + + htmlString += ">" + innerText + "</" + name + ">"; + + return htmlString; + }, + + tag: function (name, innerText, attributesObject, auxAttributesText) { + return this.inlineTag(name, innerText, attributesObject, auxAttributesText) + "\n"; + } + }; + + if (typeof exports !== "undefined") + exports.ConverterHTML = ConverterHTML; + + return exports; + })();
\ No newline at end of file diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..be2d307 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,62 @@ +body { + font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, + segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, + arial, sans-serif; + overflow-x: hidden; + margin: 0; + padding: 0; +} + +form { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +div { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 100vw; +} + +label { + text-align: center; + width: 20%; +} + +.spacer { + width: 60%; +} + +button { + border: none; + border-radius: 0; + padding: 0.75rem; + width: 20%; +} + +textarea { + background-color: inherit; + border: none; + border-radius: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + resize: none; + outline: none; + + height: 100vh; + width: 100vw; + max-height: 100vw; + max-width: 90vw; + margin: 0 auto; + padding: 1.5rem; +} + +:modal { + height: 90vh; + width: 90vw; +} |