adding rich formatting to messags

This commit is contained in:
or-else
2017-08-19 20:40:25 -07:00
parent fd8c58e837
commit 0c7549819a
3 changed files with 56 additions and 36 deletions
+4
View File
@@ -825,6 +825,10 @@ table.permission-editor td.checkbox {
height: 4rem; height: 4rem;
} }
#sendMessage {
overflow: hidden;
}
/* A static panel shown when no topic is selected */ /* A static panel shown when no topic is selected */
#dummy-view { #dummy-view {
width: 100%; width: 100%;
+33 -32
View File
@@ -29,7 +29,7 @@ Sample JSON representation of the text above:
{ {
"at":8, "at":8,
"len":4, "len":4,
"tp":"BO" "tp":"ST"
}, },
{ {
"at":14, "at":14,
@@ -39,12 +39,12 @@ Sample JSON representation of the text above:
{ {
"at":20, "at":20,
"len":6, "len":6,
"tp":"IT" "tp":"EM"
}, },
{ {
"at":31, "at":31,
"len":7, "len":7,
"tp":"ST" "tp":"DL"
} }
] ]
}, },
@@ -54,12 +54,12 @@ Sample JSON representation of the text above:
{ {
"at":25, "at":25,
"len":11, "len":11,
"tp":"IT" "tp":"EM"
}, },
{ {
"at":15, "at":15,
"len":21, "len":21,
"tp":"BO" "tp":"ST"
} }
] ]
}, },
@@ -69,7 +69,7 @@ Sample JSON representation of the text above:
{ {
"at":73, "at":73,
"len":13, "len":13,
"tp":"BO" "tp":"ST"
} }
], ],
"ent":[ "ent":[
@@ -106,7 +106,7 @@ Sample JSON representation of the text above:
{ {
"at":4, "at":4,
"len":8, "len":8,
"tp":"IT" "tp":"EM"
} }
], ],
"ent":[ "ent":[
@@ -153,12 +153,12 @@ Sample JSON representation of the text above:
// Regular expressions for parsing inline formats. Javascript does not support lookbehind, // Regular expressions for parsing inline formats. Javascript does not support lookbehind,
// so it's a bit messy. // so it's a bit messy.
var INLINE_STYLES = [ var INLINE_STYLES = [
// Bold, *bold text* // Strong = bold, *bold text*
{name: "BO", start: /(?:^|\W)(\*)[^\s*]/, end: /[^\s*](\*)(?=$|\W)/}, {name: "ST", start: /(?:^|\W)(\*)[^\s*]/, end: /[^\s*](\*)(?=$|\W)/},
// Italic, _italic text_ // Emphesized = italic, _italic text_
{name: "IT", start: /(?:^|[\W_])(_)[^\s_]/, end: /[^\s_](_)(?=$|[\W_])/}, {name: "EM", start: /(?:^|[\W_])(_)[^\s_]/, end: /[^\s_](_)(?=$|[\W_])/},
// Strikethough, ~strike this though~ // Deleted, ~strike this though~
{name: "ST", start: /(?:^|\W)(~)[^\s~]/g, end: /[^\s~](~)(?=$|\W)/}, {name: "DL", start: /(?:^|\W)(~)[^\s~]/g, end: /[^\s~](~)(?=$|\W)/},
// Code block `this is monospace` // Code block `this is monospace`
{name: "CO", start: /(?:^|\W)(`)[^`]/, end: /[^`](`)(?=$|\W)/} {name: "CO", start: /(?:^|\W)(`)[^`]/, end: /[^`](`)(?=$|\W)/}
]; ];
@@ -168,7 +168,7 @@ Sample JSON representation of the text above:
// URLs // URLs
{name: "LN", dataName: "url", {name: "LN", dataName: "url",
pack: function(val) { pack: function(val) {
// Check if the protocol is pecified, if not use http // Check if the protocol is specified, if not use http
if (!/^[a-z]+:\/\//i.test(val)) { if (!/^[a-z]+:\/\//i.test(val)) {
val = 'http://' + val; val = 'http://' + val;
} }
@@ -182,14 +182,14 @@ Sample JSON representation of the text above:
// Hashtags #hashtag // Hashtags #hashtag
{name: "HT", dataName: "val", {name: "HT", dataName: "val",
pack: function(val) { return {val: val.slice(1)}; }, pack: function(val) { return {val: val.slice(1)}; },
re: /\B#(\w\w+)/g} re: /\b#(\w\w+)/g}
]; ];
// Formatting HTML tags // Formatting HTML tags
var STYLE_DECOR = { var STYLE_DECOR = {
BO: ['<b>', '</b>'], ST: ['<b>', '</b>'],
IT: ['<i>', '</i>'], EM: ['<i>', '</i>'],
ST: ['<del>', '</del>'], DL: ['<del>', '</del>'],
CO: ['<tt>', '</tt>'] CO: ['<tt>', '</tt>']
}; };
@@ -253,7 +253,7 @@ Sample JSON representation of the text above:
} }
// Detect starts and ends of formatting spans. Unformatted spans are // Detect starts and ends of formatting spans. Unformatted spans are
// ignored at this time. // ignored at this stage.
function spannify(original, re_start, re_end, type) { function spannify(original, re_start, re_end, type) {
var result = []; var result = [];
var index = 0; var index = 0;
@@ -269,8 +269,8 @@ Sample JSON representation of the text above:
if (start == null) { if (start == null) {
break; break;
} }
// Because javascript RegExp does not support lookbehind, the actual offset may not be at the beginning of // Because javascript RegExp does not support lookbehind, the actual offset may not point
// the match. Find it in the matched string. // at the markup character. Find it in the matched string.
var start_offset = start['index'] + start[0].lastIndexOf(start[1]); var start_offset = start['index'] + start[0].lastIndexOf(start[1]);
// Clip the processed part of the string. // Clip the processed part of the string.
line = line.slice(start_offset + 1); line = line.slice(start_offset + 1);
@@ -337,11 +337,11 @@ Sample JSON representation of the text above:
} }
// Get a list of entities from a text. // Get a list of entities from a text.
function extractEntities(line, re, name) { function extractEntities(line) {
var match; var match;
var extracted = []; var extracted = [];
ENTITY_TYPES.map(function(ent) { ENTITY_TYPES.map(function(ent) {
while((match = ent.re.exec(line)) !== null) { while ((match = ent.re.exec(line)) !== null) {
extracted.push({ extracted.push({
offset: match['index'], offset: match['index'],
len: match[0].length, len: match[0].length,
@@ -371,13 +371,13 @@ Sample JSON representation of the text above:
} }
// Convert the chunks into format suitable for serialization. // Convert the chunks into format suitable for serialization.
function toDrafty(chunks, startAt) { function draftify(chunks, startAt) {
var plain = ""; var plain = "";
var ranges = []; var ranges = [];
for (var i in chunks) { for (var i in chunks) {
var chunk = chunks[i]; var chunk = chunks[i];
if (!chunk.text) { if (!chunk.text) {
var drafty = toDrafty(chunk.children, plain.length + startAt); var drafty = draftify(chunk.children, plain.length + startAt);
chunk.text = drafty.txt; chunk.text = drafty.txt;
ranges = ranges.concat(drafty.fmt); ranges = ranges.concat(drafty.fmt);
} }
@@ -401,8 +401,8 @@ Sample JSON representation of the text above:
* Parse text into structured representation. * Parse text into structured representation.
*/ */
parse: function(content) { parse: function(content) {
// Split text into lines. It makes formatting easier // Split text into lines. It makes further processing easier.
var lines = content.split('\n'); var lines = content.split(/\r?\n/);
// Holds formatted lines // Holds formatted lines
var blocks = []; var blocks = [];
// Holds entities referenced from text // Holds entities referenced from text
@@ -432,10 +432,11 @@ Sample JSON representation of the text above:
// Convert an array of possibly overlapping spans into a tree // Convert an array of possibly overlapping spans into a tree
spans = toTree(spans); spans = toTree(spans);
// Build tree representation of the format. // Build a tree representation of the entire string, not
// just the formatted parts.
var chunks = chunkify(line, 0, line.length, spans); var chunks = chunkify(line, 0, line.length, spans);
var drafty = toDrafty(chunks, 0); var drafty = draftify(chunks, 0);
block = {txt: drafty.txt, fmt: drafty.fmt}; block = {txt: drafty.txt, fmt: drafty.fmt};
} }
@@ -475,7 +476,7 @@ Sample JSON representation of the text above:
var {blocks} = content; var {blocks} = content;
var {refs} = content; var {refs} = content;
var text = ""; var text = [];
for (var i in blocks) { for (var i in blocks) {
var line = blocks[i].txt; var line = blocks[i].txt;
var markup = []; var markup = [];
@@ -505,9 +506,9 @@ Sample JSON representation of the text above:
line = splice(line, markup[j].idx, markup[j].what); line = splice(line, markup[j].idx, markup[j].what);
} }
} }
text += line + "<br>"; text.push(line);
} }
return text; return text.join("<br/>");
}, },
/* /*
+19 -4
View File
@@ -19,6 +19,7 @@
<script src="https://unpkg.com/react-dom@15.4/dist/react-dom.js"></script> <script src="https://unpkg.com/react-dom@15.4/dist/react-dom.js"></script>
<script src="https://unpkg.com/babel-standalone@6.24.0/babel.min.js"></script> <script src="https://unpkg.com/babel-standalone@6.24.0/babel.min.js"></script>
<script src="tinode.js"></script> <script src="tinode.js"></script>
<script src="drafty.js"></script>
</head> </head>
<body> <body>
<div id="mountPoint"></div> <div id="mountPoint"></div>
@@ -2318,9 +2319,15 @@
}, },
/* Send on Enter key */ /* Send on Enter key */
handleKeyPress: function(e) { handleKeyPress: function(e) {
/* Remove this if you don't want Enter to trigger send */ // Remove this if you don't want Enter to trigger send
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.handleSend(); // Have Shift-Enter insert a line break instead
if (!e.shiftKey) {
e.preventDefault();
e.stopPropagation();
this.handleSend();
}
} }
}, },
handleMessageTyping: function(e) { handleMessageTyping: function(e) {
@@ -2338,7 +2345,7 @@
render: function() { render: function() {
return ( return (
<div id="send-message-panel"> <div id="send-message-panel">
<input id="sendMessage" type="text" placeholder="New message" <textarea id="sendMessage" placeholder="New message"
value={this.state.message} onChange={this.handleMessageTyping} onKeyPress={this.handleKeyPress} /> value={this.state.message} onChange={this.handleMessageTyping} onKeyPress={this.handleKeyPress} />
<a href="#" onClick={this.handleSend}> <a href="#" onClick={this.handleSend}>
<i className="material-icons">send</i> <i className="material-icons">send</i>
@@ -2613,7 +2620,15 @@
// User clicked on a message send button. // User clicked on a message send button.
handleSendMessage: function(msg) { handleSendMessage: function(msg) {
var topic = Tinode.getTopic(this.state.topicSelected); var topic = Tinode.getTopic(this.state.topicSelected);
topic.publish(msg); // Check if the message is formatted.
var dft = Drafty.parse(msg);
if (Drafty.isPlain(dft)) {
// The message is simple text. Sned if as text/plain.
topic.publish(msg);
} else {
// Formatted message. Send it as a Drafty object.
topic.publish(dft, undefined, "text/x-drafty+json");
}
}, },
// User chose a Sign Up menu item. // User chose a Sign Up menu item.
handleNewAccount: function() { handleNewAccount: function() {