mirror of
https://github.com/zulip/zulip.git
synced 2025-11-11 17:36:27 +00:00
Create copy_and_paste.js (with code from ui.js).
(imported from commit 37a06af5489e7da5196e5710f384d1f5a7ee7578)
This commit is contained in:
committed by
Leo Franchi
parent
fa440d0be2
commit
16faed0a83
142
static/js/copy_and_paste.js
Normal file
142
static/js/copy_and_paste.js
Normal file
@@ -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 = $('<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 = $('<div>').text(row.prev().text()
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.replace(/^\s/, "").replace(/\s$/, ""));
|
||||||
|
div.append($('<p>').append($('<strong>').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;
|
||||||
|
}());
|
||||||
134
static/js/ui.js
134
static/js/ui.js
@@ -79,146 +79,12 @@ exports.page_down_the_right_amount = function () {
|
|||||||
viewport.scrollTop(viewport.scrollTop() + delta);
|
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) {
|
exports.replace_emoji_with_text = function (element) {
|
||||||
element.find(".emoji").replaceWith(function () {
|
element.find(".emoji").replaceWith(function () {
|
||||||
return $(this).attr("alt");
|
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 = $('<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 = $('<div>').text(row.prev().text()
|
|
||||||
.replace(/\s+/g, " ")
|
|
||||||
.replace(/^\s/, "").replace(/\s$/, ""));
|
|
||||||
div.append($('<p>').append($('<strong>').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(),
|
/* We use 'visibility' rather than 'display' and jQuery's show() / hide(),
|
||||||
because we want to reserve space for the email address. This avoids
|
because we want to reserve space for the email address. This avoids
|
||||||
things jumping around slightly when the email address is shown. */
|
things jumping around slightly when the email address is shown. */
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var globals =
|
|||||||
+ ' avatar feature_flags search_suggestion referral stream_color Dict'
|
+ ' avatar feature_flags search_suggestion referral stream_color Dict'
|
||||||
+ ' Filter summary admin stream_data muting WinChan muting_ui Socket channel gear_menu'
|
+ ' 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'
|
+ ' message_flags bot_data loading favicon resize scroll_bar condense floating_recipient_bar'
|
||||||
|
+ ' copy_and_paste'
|
||||||
|
|
||||||
// colorspace.js
|
// colorspace.js
|
||||||
+ ' colorspace'
|
+ ' colorspace'
|
||||||
|
|||||||
@@ -548,6 +548,7 @@ JS_SPECS = {
|
|||||||
'js/ui.js',
|
'js/ui.js',
|
||||||
'js/scroll_bar.js',
|
'js/scroll_bar.js',
|
||||||
'js/gear_menu.js',
|
'js/gear_menu.js',
|
||||||
|
'js/copy_and_paste.js',
|
||||||
'js/popovers.js',
|
'js/popovers.js',
|
||||||
'js/typeahead_helper.js',
|
'js/typeahead_helper.js',
|
||||||
'js/search_suggestion.js',
|
'js/search_suggestion.js',
|
||||||
|
|||||||
Reference in New Issue
Block a user