From 5d1bf5f624d861b8ef2ee0463bf3baedc2de7c82 Mon Sep 17 00:00:00 2001 From: or-else Date: Tue, 15 Aug 2017 20:54:59 -0700 Subject: [PATCH] first pass at defining rich text formatting --- drafty.js | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 drafty.js diff --git a/drafty.js b/drafty.js new file mode 100644 index 00000000..722ab0a3 --- /dev/null +++ b/drafty.js @@ -0,0 +1,148 @@ +// Basic parser and formatter for simple rich text. +// JSON data representation is similar to Draft.js +(function(environment) { // closure for web browsers + 'use strict'; + + var Drafty = (function() { + + return { + parse: function(content) { + // Regular expressions for parsing inline formats + var inlineStyles = [ + // Bold, *bold text* + {name: "BO", re: /(\*(?:[^\s*]|(?:[^\s*]*[^*]*[^\s*]))\*)/g}, + // Italic, _italic text_ + {name: "IT", re: /(_(?:[^\s_]|(?:[^\s_]*[^_]*[^\s_]))_)/g}, + // Strikethough, ~strike this though~ + {name: "ST", re: /(~(?:[^\s~]|(?:[^\s~]*[^~]*[^\s~]))~)/g}, + // Code block `this is monospace` + {name: "CO", re: /(`[^`]+`)/g} + ]; + + var entities = [ + // URLs + {name: "LN", re: /(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/}, + // Mentions @user + {name: "MN", re: /@[\w]+/g}, + // Hashtags #hashtag + {name: "HT", re: /#[\w]+/g} + ]; + + // Split text into lines. It makes formatting easier + var lines = content.split('\n'); + // Holds formatted lines + var blocks = []; + // Holds entities referenced from text + var entityMap = {}; + + // + lines.map(function(line) { + console.log("processing line: '" + line + "'"); + var spans = []; + // Find formatted spans in the string. + // Try to match each style. + inlineStyles.map(function(style) { + var match; + // Each style could be matched multiple times. + while ((match = style.re.exec(line)) !== null) { + // match[0]; // match, like '*abc*' + // match[1]; // matched text in parenthesis, like 'abc' + // match['index']; // offset where the match started. + spans.push({ + match: match[1], + text: match[1].substring(1, match[1].length-1), + offset: match['index'], + len: match[1].length, + type: style.name + }); + } + }); + + var ranges = []; + if (spans.length == 0) { + blocks.push({text: line}); + } else { + console.log(JSON.stringify(spans)); + + // Sort spans by style occurence early -> late + spans.sort(function(a,b) { + return a.offset - b.offset; + }); + + // Clear formatting characters from the string, leave just the plain text. + // Save formatting to inlineStylesRanges array + var chunks = []; + var start = 0; + for (var i = 0; i < spans.length; i++) { + // Skip empty or invalid (overlapping) chunk. + if (start > spans[i].offset) { + continue; + } + + // Grab the initial unstyled chunk + if (start < spans[i].offset) { + chunks.push({text: line.slice(start, spans[i].offset)}); + start = spans[i].offset; + console.log(i + ": " + chunks[chunks.length-1].text); + } + + // Grab the styled chunk + chunks.push({text: line.slice(start, start + spans[i].len), style: spans[i]}); + console.log(i + ": " + chunks[chunks.length-1].text); + // And grab the unstyled span till the next chunk + start = start + spans[i].len; + var end = (i < spans.length - 1) ? spans[i+1].offset : undefined; + chunks.push({text: line.slice(start, end)}); + console.log(i + ": " + chunks[chunks.length-1].text); + start = end; // does not matter if undefined. + } + console.log("CHUNKS"); + console.log(JSON.stringify(chunks)); + /* + var offset = spans[i].offset - shiftBy; + line = line.slice(0, offset) + + spans[i].text + + line.slice(offset + spans[i].len); + console.log(line); + shiftBy += 2; + // Save style + ranges.push({offset: offset, length: spans[i].len - 2, type: spans[i].type}); + } + */ + blocks.push({text: line, styleRanges: ranges}); + } + }); + + return {blocks: blocks, entityMap: entityMap}; + }, + + format: function(json) { + var TAGS = { + BO: ['', '', 7], + IT: ['', '', 7], + ST: ['', '', 11], + CO: ['', '', 9] + }; + var {blocks} = json; + var text = ""; + for (var i in blocks) { + var shiftBy = 0; + var line = blocks[i].text; + if (blocks[i].styleRanges) { + for (var j in blocks[i].styleRanges) { + var range = blocks[i].styleRanges[j]; + line = line.slice(0, range.offset + shiftBy); + } + } + text += line + "
"; + } + return text; + } + }; + }); + + // Export for the window object or node; Check that is not already defined. + if (typeof(environment.Drafty) === 'undefined') { + environment.Drafty = Drafty(); + } +})(this); // 'this' will be window object for the browsers.