From e70fd4a07b3e35f5bda57d7112e721a2290082c6 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Sun, 22 Nov 2020 03:18:13 +0100 Subject: [PATCH] Add clickable URL links to web client. --- rogue_chat_nocanvas_monochrome.html | 154 +++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 25 deletions(-) diff --git a/rogue_chat_nocanvas_monochrome.html b/rogue_chat_nocanvas_monochrome.html index c2c6bc7..9db72e4 100644 --- a/rogue_chat_nocanvas_monochrome.html +++ b/rogue_chat_nocanvas_monochrome.html @@ -122,7 +122,7 @@ let mode_helps = { 'long': 'Waiting for a server response.' }, 'password': { - 'short': 'password input', + 'short': 'map edit password', 'long': 'This mode allows you to change the password that you send to authorize yourself for editing password-protected map tiles. Hit return to confirm and leave.' }, 'admin': { @@ -180,12 +180,39 @@ let terminal = { }, 100); }, refresh: function() { - let pre_string = ''; + function escapeHTML(str) { + return str. + replace(/&/g, '&'). + replace(//g, '>'). + replace(/'/g, '''). + replace(/"/g, '"'); + }; + let pre_content = ''; for (let y = 0; y < this.rows; y++) { let line = this.content[y].join(''); - pre_string += line + '\n'; + let chunks = []; + if (y in tui.links) { + let start_x = 0; + for (let span of tui.links[y]) { + chunks.push(escapeHTML(line.slice(start_x, span[0]))); + chunks.push(''); + chunks.push(escapeHTML(line.slice(span[0], span[1]))); + chunks.push(''); + start_x = span[1]; + } + chunks.push(escapeHTML(line.slice(start_x))); + } else { + chunks = [escapeHTML(line)]; + } + for (const chunk of chunks) { + pre_content += chunk; + } + pre_content += '\n'; } - this.pre_el.textContent = pre_string; + this.pre_el.innerHTML = pre_content; }, write: function(start_y, start_x, msg) { for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) { @@ -438,6 +465,7 @@ class Mode { } } let tui = { + links: {}, log: [], input_prompt: '> ', input_lines: [], @@ -602,6 +630,18 @@ let tui = { } this.full_refresh(); }, + offset_links: function(offset, links) { + for (let y in links) { + let real_y = offset[0] + parseInt(y); + if (!this.links[real_y]) { + this.links[real_y] = []; + } + for (let link of links[y]) { + const offset_link = [link[0] + offset[1], link[1] + offset[1], link[2]]; + this.links[real_y].push(offset_link); + } + } + }, restore_input_values: function() { if (this.mode.name == 'annotate' && explorer.position in explorer.info_db) { let info = explorer.info_db[explorer.position]; @@ -627,24 +667,64 @@ let tui = { } }, recalc_input_lines: function() { - this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width); + let _ = null; + [this.input_lines, _] = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width); this.height_input = this.input_lines.length; }, msg_into_lines_of_width: function(msg, width) { - let chunk = ""; - let lines = []; - for (let i = 0, x = 0; i < msg.length; i++, x++) { - if (x >= width || msg[i] == "\n") { - lines.push(chunk); - chunk = ""; - x = 0; + function push_inner_link(y, end_x) { + if (!inner_links[y]) { + inner_links[y] = []; + }; + inner_links[y].push([url_start_x, end_x, url]); }; - if (msg[i] != "\n") { - chunk += msg[i]; + const matches = msg.matchAll(/https?:\/\/[^\s]+/g) + let link_data = {}; + let url_ends = []; + for (const match of matches) { + const url = match[0]; + const url_start = match.index; + const url_end = match.index + match[0].length; + link_data[url_start] = url; + url_ends.push(url_end); } - } - lines.push(chunk); - return lines; + let url_start_x = 0; + let url = ''; + let inner_links = {}; + let in_link = false; + let chunk = ""; + let lines = []; + for (let i = 0, x = 0, y = 0; i < msg.length; i++, x++) { + if (x >= width || msg[i] == "\n") { + if (in_link) { + push_inner_link(y, chunk.length); + url_start_x = 0; + }; + lines.push(chunk); + chunk = ""; + x = 0; + if (msg[i] == "\n") { + x -= 1; + }; + y += 1; + }; + if (msg[i] != "\n") { + chunk += msg[i]; + }; + if (i in link_data) { + url_start_x = x; + url = link_data[i]; + in_link = true; + } else if (url_ends.includes(i)) { + push_inner_link(y, x); + in_link = false; + } + } + lines.push(chunk); + if (in_link) { + push_inner_link(lines.length - 1, chunk.length); + } + return [lines, inner_links]; }, log_msg: function(msg) { this.log.push(msg); @@ -745,21 +825,44 @@ let tui = { }, draw_history: function() { let log_display_lines = []; + let log_links = {}; + let y_offset_in_log = 0; for (let line of this.log) { - log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width)); + let [new_lines, link_data] = this.msg_into_lines_of_width(line, + this.window_width) + log_display_lines = log_display_lines.concat(new_lines); + for (const y in link_data) { + const rel_y = y_offset_in_log + parseInt(y); + log_links[rel_y] = []; + for (let link of link_data[y]) { + log_links[rel_y].push(link); + } + } + y_offset_in_log += new_lines.length; }; - for (let y = terminal.rows - 1 - this.height_input, - i = log_display_lines.length - 1; + let i = log_display_lines.length - 1; + for (let y = terminal.rows - 1 - this.height_input; y >= this.height_header && i >= 0; y--, i--) { terminal.write(y, this.window_width, log_display_lines[i]); } + for (const key of Object.keys(log_links)) { + if (parseInt(key) <= i) { + delete log_links[key]; + } + } + let offset = [terminal.rows - this.height_input - log_display_lines.length, + this.window_width]; + this.offset_links(offset, log_links); }, draw_info: function() { - let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width); - for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) { - terminal.write(y, this.window_width, lines[i]); - } + let [lines, link_data] = this.msg_into_lines_of_width(explorer.get_info(), + this.window_width); + let offset = [this.height_header, this.window_width]; + for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) { + terminal.write(y, offset[1], lines[i]); + } + this.offset_links(offset, link_data); }, draw_input: function() { if (this.mode.has_input_prompt) { @@ -803,12 +906,13 @@ let tui = { start_x = this.window_width } terminal.drawBox(0, start_x, terminal.rows, this.window_width); - let lines = this.msg_into_lines_of_width(content, this.window_width); + let [lines, _] = this.msg_into_lines_of_width(content, this.window_width); for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) { terminal.write(y, start_x, lines[i]); } }, full_refresh: function() { + this.links = {}; terminal.drawBox(0, 0, terminal.rows, terminal.cols); if (this.mode.is_intro) { this.draw_history(); -- 2.30.2