mirror of
https://github.com/jupyter/notebook.git
synced 2025-03-01 12:56:54 +08:00
Merge pull request #1286 from takluyver/system-clipboard
Copy and paste cells with system clipboard
This commit is contained in:
commit
eb01a6eef0
132
notebook/static/notebook/js/clipboard.js
Normal file
132
notebook/static/notebook/js/clipboard.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright (c) Jupyter Development Team.
|
||||||
|
// Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
define([
|
||||||
|
'base/js/namespace',
|
||||||
|
'base/js/utils',
|
||||||
|
'base/js/dialog'
|
||||||
|
], function(Jupyter, utils, dialog) {
|
||||||
|
|
||||||
|
var jcbprefix = '<pre class="jupyter-nb-cells-json">';
|
||||||
|
var jcbsuffix = '</pre>';
|
||||||
|
|
||||||
|
function store_json(cells, clipboard) {
|
||||||
|
// Firefox ignores application/json mime type, so put it in HTML as well.
|
||||||
|
// We also copy a text version so you can paste cell sources into a text editor
|
||||||
|
var j = JSON.stringify(cells);
|
||||||
|
var t = cells.map(function(c) {return c.source;}).join('\n\n');
|
||||||
|
clipboard.setData('text/plain', t);
|
||||||
|
clipboard.setData('text/html', jcbprefix + j + jcbsuffix);
|
||||||
|
clipboard.setData('application/json', j);
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_json(clipboard) {
|
||||||
|
var s = clipboard.getData('text/html');
|
||||||
|
// System/browsers may add some more stuff before/after our content, so
|
||||||
|
// find where our prefix and suffix are.
|
||||||
|
var pix = s.indexOf(jcbprefix);
|
||||||
|
var six = s.lastIndexOf(jcbsuffix);
|
||||||
|
if (pix === -1 || six === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.parse(s.slice(pix + jcbprefix.length, six));
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy(event) {
|
||||||
|
if (Jupyter.notebook.mode !== 'command') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var selecn = Jupyter.notebook.get_selected_cells().map(
|
||||||
|
function(c) { return c.toJSON();});
|
||||||
|
store_json(selecn, event.clipboardData);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function paste(event) {
|
||||||
|
if (Jupyter.notebook.mode !== 'command') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Clipboard types: ' + event.clipboardData.types);
|
||||||
|
cells = load_json(event.clipboardData);
|
||||||
|
// console.log(cells);
|
||||||
|
// Does this JSON look like cells?
|
||||||
|
if (Array.isArray(cells) && (cells.length > 0) &&
|
||||||
|
cells[0].cell_type && cells[0].source) {
|
||||||
|
var first_inserted = null;
|
||||||
|
for (var i=0; i < cells.length; i++) {
|
||||||
|
var cell_data = cells[i];
|
||||||
|
var new_cell = Jupyter.notebook.insert_cell_above(cell_data.cell_type);
|
||||||
|
new_cell.fromJSON(cell_data);
|
||||||
|
if (first_inserted === null) {
|
||||||
|
first_inserted = new_cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first_inserted.focus_cell();
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function needs_text_box_for_paste_event() {
|
||||||
|
// I know this is bad, but I don't know a better way to check this
|
||||||
|
return navigator.userAgent.indexOf('Firefox') !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_paste_dialog() {
|
||||||
|
// Firefox only fires a paste event if the cursor is in a text input. So, on
|
||||||
|
// Ctrl-V, bring up a dialog with an invisible text box and catch the
|
||||||
|
// second Ctrl-V
|
||||||
|
var action = {
|
||||||
|
icon: 'fa-clipboard', // a font-awesome class used on buttons, etc
|
||||||
|
help : 'Dialog for paste from system clipboard',
|
||||||
|
help_index : 'zz',
|
||||||
|
handler : function () {
|
||||||
|
var entry_box = $('<input type="text"/>');
|
||||||
|
entry_box.css('opacity', 0);
|
||||||
|
function paste_close_dlg(e) {
|
||||||
|
paste(e);
|
||||||
|
// There must be a better way to do this, but it's not any of:
|
||||||
|
// .hide(), .remove() or .dialog('close')
|
||||||
|
paste_dlg.find('.close').click();
|
||||||
|
document.removeEventListener('paste', paste_close_dlg);
|
||||||
|
}
|
||||||
|
document.addEventListener('paste', paste_close_dlg);
|
||||||
|
var cmdtrl = 'Ctrl';
|
||||||
|
if (utils.platform === 'MacOS') {
|
||||||
|
cmdtrl = 'Cmd';
|
||||||
|
}
|
||||||
|
var dialog_body = $("<div/>").append("<p>Press "+cmdtrl+"-V again to paste")
|
||||||
|
.append("<br/>")
|
||||||
|
.append("<p><b>Why is this needed?</b> We can't get paste events in this browser without a text box. "+
|
||||||
|
"There's an invisible text box focused in this dialog.")
|
||||||
|
.append($("<form/>").append(entry_box));
|
||||||
|
|
||||||
|
var paste_dlg = dialog.modal({
|
||||||
|
notebook: Jupyter.notebook,
|
||||||
|
keyboard_manager: Jupyter.keyboard_manager,
|
||||||
|
title : cmdtrl+"-V to paste",
|
||||||
|
body : dialog_body,
|
||||||
|
open: function() {
|
||||||
|
entry_box.focus();
|
||||||
|
},
|
||||||
|
buttons : {
|
||||||
|
"Cancel" : {
|
||||||
|
// click : function() { reject("Dialog cancelled"); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var full_action_name = Jupyter.actions.register(action, 'paste-dialog', 'system-clipboard');
|
||||||
|
Jupyter.keyboard_manager.command_shortcuts.add_shortcut('Cmdtrl-V', full_action_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set clipboard event listeners on the document.
|
||||||
|
return {setup_clipboard_events: function() {
|
||||||
|
document.addEventListener('copy', copy);
|
||||||
|
if (needs_text_box_for_paste_event()) {
|
||||||
|
setup_paste_dialog();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('paste', paste);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
});
|
@ -23,7 +23,8 @@ require([
|
|||||||
'notebook/js/kernelselector',
|
'notebook/js/kernelselector',
|
||||||
'codemirror/lib/codemirror',
|
'codemirror/lib/codemirror',
|
||||||
'notebook/js/about',
|
'notebook/js/about',
|
||||||
'notebook/js/searchandreplace'
|
'notebook/js/searchandreplace',
|
||||||
|
'notebook/js/clipboard'
|
||||||
], function(
|
], function(
|
||||||
IPython,
|
IPython,
|
||||||
$,
|
$,
|
||||||
@ -44,7 +45,8 @@ require([
|
|||||||
kernelselector,
|
kernelselector,
|
||||||
CodeMirror,
|
CodeMirror,
|
||||||
about,
|
about,
|
||||||
searchandreplace
|
searchandreplace,
|
||||||
|
clipboard
|
||||||
) {
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@ -177,6 +179,8 @@ require([
|
|||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: false
|
configurable: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clipboard.setup_clipboard_events();
|
||||||
|
|
||||||
// Now actually load nbextensionsload_extensions_from_config
|
// Now actually load nbextensionsload_extensions_from_config
|
||||||
Promise.all([
|
Promise.all([
|
||||||
|
Loading…
Reference in New Issue
Block a user