From 16faed0a836ded53c4806a5fecbbf62e9bbeb6ff Mon Sep 17 00:00:00 2001 From: Steve Howell Date: Thu, 13 Mar 2014 17:07:56 -0400 Subject: [PATCH] Create copy_and_paste.js (with code from ui.js). (imported from commit 37a06af5489e7da5196e5710f384d1f5a7ee7578) --- static/js/copy_and_paste.js | 142 ++++++++++++++++++++++++++++++++++++ static/js/ui.js | 134 ---------------------------------- tools/jslint/check-all.js | 1 + zproject/settings.py | 1 + 4 files changed, 144 insertions(+), 134 deletions(-) create mode 100644 static/js/copy_and_paste.js diff --git a/static/js/copy_and_paste.js b/static/js/copy_and_paste.js new file mode 100644 index 0000000000..2f9653b032 --- /dev/null +++ b/static/js/copy_and_paste.js @@ -0,0 +1,142 @@ +var copy_and_paste = (function () { + +var exports = {}; // we don't actually export anything yet, but that's ok + +function find_boundary_tr(initial_tr, iterate_row) { + var j, skip_same_td_check = false; + var tr = initial_tr; + + // If the selection boundary is somewhere that does not have a + // parent tr, we should let the browser handle the copy-paste + // entirely on its own + if (tr.length === 0) { + return undefined; + } + + // If the selection boundary is on a table row that does not have an + // associated message id (because the user clicked between messages), + // then scan downwards until we hit a table row with a message id. + // To ensure we can't enter an infinite loop, bail out (and let the + // browser handle the copy-paste on its own) if we don't hit what we + // are looking for within 10 rows. + for (j = 0; (!tr.is('.message_row')) && j < 10; j++) { + tr = iterate_row(tr); + } + if (j === 10) { + return undefined; + } else if (j !== 0) { + // If we updated tr, then we are not dealing with a selection + // that is entirely within one td, and we can skip the same td + // check (In fact, we need to because it won't work correctly + // in this case) + skip_same_td_check = true; + } + return [rows.id(tr), skip_same_td_check]; +} + + +function copy_handler(e) { + var selection = window.getSelection(); + var i, range, ranges = [], startc, endc, initial_end_tr, start_id, end_id, row, message; + var start_data, end_data; + var skip_same_td_check = false; + var div = $('
'), content; + for (i = 0; i < selection.rangeCount; i++) { + range = selection.getRangeAt(i); + ranges.push(range); + + startc = $(range.startContainer); + start_data = find_boundary_tr($(startc.parents('.selectable_row, .message_header')[0]), function (row) { + return row.next(); + }); + if (start_data === undefined) { + return; + } + start_id = start_data[0]; + + endc = $(range.endContainer); + // If the selection ends in the bottom whitespace, we should act as + // though the selection ends on the final message + // Chrome seems to like selecting the compose_close button + // when you go off the end of the last message + if (endc.attr('id') === "bottom_whitespace" || endc.attr('id') === "compose_close") { + initial_end_tr = $(".message_row:last"); + skip_same_td_check = true; + } else { + initial_end_tr = $(endc.parents('.selectable_row')[0]); + } + end_data = find_boundary_tr(initial_end_tr, function (row) { + return row.prev(); + }); + if (end_data === undefined) { + return; + } + end_id = end_data[0]; + + if (start_data[1] || end_data[1]) { + skip_same_td_check = true; + } + + // we should let the browser handle the copy-paste entirely on its own + // (In this case, there is no need for our special copy code) + if (!skip_same_td_check && + startc.parents('.selectable_row>div')[0] === endc.parents('.selectable_row>div')[0]) { + return; + } + else { + + // Construct a div for what we want to copy (div) + for (row = current_msg_list.get_row(start_id); + rows.id(row) <= end_id; + row = rows.next_visible(row)) + { + if (row.prev().hasClass("message_header")) { + content = $('
').text(row.prev().text() + .replace(/\s+/g, " ") + .replace(/^\s/, "").replace(/\s$/, "")); + div.append($('

').append($('').text(content.text()))); + } + + message = current_msg_list.get(rows.id(row)); + + var message_firstp = $(message.content).slice(0, 1); + message_firstp.prepend(message.sender_full_name + ": "); + div.append(message_firstp); + div.append($(message.content).slice(1)); + } + } + } + + if (window.bridge !== undefined) { + // If the user is running the desktop app, + // convert emoji images to plain text for + // copy-paste purposes. + ui.replace_emoji_with_text(div); + } + + // Select div so that the browser will copy it + // instead of copying the original selection + div.css({position: 'absolute', 'left': '-99999px'}) + .attr('id', 'copytempdiv'); + $('body').append(div); + selection.selectAllChildren(div[0]); + + // After the copy has happened, delete the div and + // change the selection back to the original selection + window.setTimeout(function () { + selection = window.getSelection(); + selection.removeAllRanges(); + _.each(ranges, function (range) { + selection.addRange(range); + }); + $('#copytempdiv').remove(); + },0); +} + +$(function () { + $(document).bind('copy', copy_handler); +}); + + +return exports; +}()); diff --git a/static/js/ui.js b/static/js/ui.js index f1e651ed37..021517f6e1 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -79,146 +79,12 @@ exports.page_down_the_right_amount = function () { viewport.scrollTop(viewport.scrollTop() + delta); }; -function find_boundary_tr(initial_tr, iterate_row) { - var j, skip_same_td_check = false; - var tr = initial_tr; - - // If the selection boundary is somewhere that does not have a - // parent tr, we should let the browser handle the copy-paste - // entirely on its own - if (tr.length === 0) { - return undefined; - } - - // If the selection bounary is on a table row that does not have an - // associated message id (because the user clicked between messages), - // then scan downwards until we hit a table row with a message id. - // To ensure we can't enter an infinite loop, bail out (and let the - // browser handle the copy-paste on its own) if we don't hit what we - // are looking for within 10 rows. - for (j = 0; (!tr.is('.message_row')) && j < 10; j++) { - tr = iterate_row(tr); - } - if (j === 10) { - return undefined; - } else if (j !== 0) { - // If we updated tr, then we are not dealing with a selection - // that is entirely within one td, and we can skip the same td - // check (In fact, we need to because it won't work correctly - // in this case) - skip_same_td_check = true; - } - return [rows.id(tr), skip_same_td_check]; -} - exports.replace_emoji_with_text = function (element) { element.find(".emoji").replaceWith(function () { return $(this).attr("alt"); }); }; -function copy_handler(e) { - var selection = window.getSelection(); - var i, range, ranges = [], startc, endc, initial_end_tr, start_id, end_id, row, message; - var start_data, end_data; - var skip_same_td_check = false; - var div = $('

'), content; - for (i = 0; i < selection.rangeCount; i++) { - range = selection.getRangeAt(i); - ranges.push(range); - - startc = $(range.startContainer); - start_data = find_boundary_tr($(startc.parents('.selectable_row, .message_header')[0]), function (row) { - return row.next(); - }); - if (start_data === undefined) { - return; - } - start_id = start_data[0]; - - endc = $(range.endContainer); - // If the selection ends in the bottom whitespace, we should act as - // though the selection ends on the final message - // Chrome seems to like selecting the compose_close button - // when you go off the end of the last message - if (endc.attr('id') === "bottom_whitespace" || endc.attr('id') === "compose_close") { - initial_end_tr = $(".message_row:last"); - skip_same_td_check = true; - } else { - initial_end_tr = $(endc.parents('.selectable_row')[0]); - } - end_data = find_boundary_tr(initial_end_tr, function (row) { - return row.prev(); - }); - if (end_data === undefined) { - return; - } - end_id = end_data[0]; - - if (start_data[1] || end_data[1]) { - skip_same_td_check = true; - } - - // we should let the browser handle the copy-paste entirely on its own - // (In this case, there is no need for our special copy code) - if (!skip_same_td_check && - startc.parents('.selectable_row>div')[0] === endc.parents('.selectable_row>div')[0]) { - return; - } - else { - - // Construct a div for what we want to copy (div) - for (row = current_msg_list.get_row(start_id); - rows.id(row) <= end_id; - row = rows.next_visible(row)) - { - if (row.prev().hasClass("message_header")) { - content = $('
').text(row.prev().text() - .replace(/\s+/g, " ") - .replace(/^\s/, "").replace(/\s$/, "")); - div.append($('

').append($('').text(content.text()))); - } - - message = current_msg_list.get(rows.id(row)); - - var message_firstp = $(message.content).slice(0, 1); - message_firstp.prepend(message.sender_full_name + ": "); - div.append(message_firstp); - div.append($(message.content).slice(1)); - } - } - } - - if (window.bridge !== undefined) { - // If the user is running the desktop app, - // convert emoji images to plain text for - // copy-paste purposes. - exports.replace_emoji_with_text(div); - } - - // Select div so that the browser will copy it - // instead of copying the original selection - div.css({position: 'absolute', 'left': '-99999px'}) - .attr('id', 'copytempdiv'); - $('body').append(div); - selection.selectAllChildren(div[0]); - - // After the copy has happened, delete the div and - // change the selection back to the original selection - window.setTimeout(function () { - selection = window.getSelection(); - selection.removeAllRanges(); - _.each(ranges, function (range) { - selection.addRange(range); - }); - $('#copytempdiv').remove(); - },0); -} - -$(function () { - $(document).bind('copy', copy_handler); -}); - /* We use 'visibility' rather than 'display' and jQuery's show() / hide(), because we want to reserve space for the email address. This avoids things jumping around slightly when the email address is shown. */ diff --git a/tools/jslint/check-all.js b/tools/jslint/check-all.js index 7cdebc8cbe..984f9bf0bf 100644 --- a/tools/jslint/check-all.js +++ b/tools/jslint/check-all.js @@ -29,6 +29,7 @@ var globals = + ' avatar feature_flags search_suggestion referral stream_color Dict' + ' Filter summary admin stream_data muting WinChan muting_ui Socket channel gear_menu' + ' message_flags bot_data loading favicon resize scroll_bar condense floating_recipient_bar' + + ' copy_and_paste' // colorspace.js + ' colorspace' diff --git a/zproject/settings.py b/zproject/settings.py index 34a6a948b6..e911bd64c3 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -548,6 +548,7 @@ JS_SPECS = { 'js/ui.js', 'js/scroll_bar.js', 'js/gear_menu.js', + 'js/copy_and_paste.js', 'js/popovers.js', 'js/typeahead_helper.js', 'js/search_suggestion.js',