Browse files

Allow copy and pasting of Perseus widgets using localStorage

Before, copying widgets between sections would create entirely new widgets on a
paste. For example, if I had an image widget with a image URL and caption when
I copied the `[[☃ image 1]]` and pasted it the image URL and caption would be

Now widgets can be directly cut/copied/pasted between sections and even
in between different Perseus exercises and articles on different pages!

This is done by:

- Listening for the cut/copy/paste events in jQuery in `componentDidMount`.
- If widgets are being cut/copied, grab the JSON of those widgets and throw
  them in `localStorage.perseusLastCopiedWidgets`
- On a paste, grab the widget data from `localStorage` and use it to update the
  state; if there's a widget name collision, don't override widgets already in
  the section we're pasting into.

Test Plan:
Visit [this Perseus link](http://localhost:9000/ and verify that copying and pasting of widgets works when:

- There are no widgets being pasted
- There is one widget being pasted
- There are multiple widgets being pasted

Also try opening a new Perseus tab and check that copying and pasting
in between Perseus editors works.

Reviewers: alex

Subscribers: aria

Differential Revision:
  • Loading branch information...
SamLau95 committed Jul 15, 2015
1 parent 0ecb6ee commit e693b679fd799845da47ed8d6d5b04c6e2e4a0b2
Showing with 42 additions and 0 deletions.
  1. +42 −0 src/editor.jsx
@@ -1,4 +1,5 @@
var React = require('react');
var $ = require('jquery');
var _ = require("underscore");
var ApiOptions = require("./perseus-api.jsx").Options;
@@ -495,6 +496,10 @@ var Editor = React.createClass({
// this.props.onChange during that, since it calls our parent's
// setState
.on('copy cut', this._maybeCopyWidgets)
.on('paste', this._maybePasteWidgets);
componentDidUpdate: function(prevProps) {
@@ -579,6 +584,8 @@ var Editor = React.createClass({
_handleKeyDown: function(e) {
// Tab-completion of widgets. For example, to insert an image:
// type `[[im`, then tab.
if (e.key === "Tab") {
var textarea = this.refs.textarea.getDOMNode();
@@ -607,6 +614,41 @@ var Editor = React.createClass({
_maybeCopyWidgets: function(e) {
// If there are widgets being cut/copied, put the widget JSON in
// localStorage.perseusLastCopiedWidgets to allow copy-pasting of
// widgets between Editors.
var textarea =;
var selectedText = textarea.value.substring(
var widgetNames =, (syntax) => {
return Util.rWidgetParts.exec(syntax)[1];
var widgetData = _.pick(this.props.widgets, widgetNames);
localStorage.perseusLastCopiedWidgets = JSON.stringify(widgetData);
`Widgets copied: ${localStorage.perseusLastCopiedWidgets}`);
_maybePasteWidgets: function() {
// Use the data from localStorage to paste any widgets we copied
// before. If there is a widget name conflict, don't override the
// widgets in the Editor we're pasting into.
var widgetJSON = localStorage.perseusLastCopiedWidgets;
if (widgetJSON) {
var widgetData = JSON.parse(widgetJSON);
var newWidgets = _.extend(widgetData, this.props.widgets);
this.props.onChange({widgets: newWidgets});
_addWidgetToContent: function(oldContent, cursorRange, widgetType) {
var textarea = this.refs.textarea.getDOMNode();

0 comments on commit e693b67

Please sign in to comment.