diff --git a/client/html/post_edit_sidebar.tpl b/client/html/post_edit_sidebar.tpl
index 4283c251..0fb878bf 100644
--- a/client/html/post_edit_sidebar.tpl
+++ b/client/html/post_edit_sidebar.tpl
@@ -66,6 +66,12 @@
Add a note
<%= ctx.makeTextarea({disabled: true, text: 'Content (supports Markdown)', rows: '8'}) %>
Delete selected note
+ <% if (ctx.hasClipboard) { %>
+
+ Export notes to clipboard
+
+ Import notes from clipboard
+ <% } %>
<% } %>
diff --git a/client/js/controls/post_edit_sidebar_control.js b/client/js/controls/post_edit_sidebar_control.js
index 3c3f59d2..72cabc9c 100644
--- a/client/js/controls/post_edit_sidebar_control.js
+++ b/client/js/controls/post_edit_sidebar_control.js
@@ -5,6 +5,8 @@ const config = require('../config.js');
const events = require('../events.js');
const misc = require('../util/misc.js');
const views = require('../util/views.js');
+const Note = require('../models/note.js');
+const Point = require('../models/point.js');
const TagInputControl = require('./tag_input_control.js');
const ExpanderControl = require('../controls/expander_control.js');
const FileDropperControl = require('../controls/file_dropper_control.js');
@@ -25,6 +27,7 @@ class PostEditSidebarControl extends events.EventTarget {
views.replaceContent(this._hostNode, template({
post: this._post,
enableSafety: config.enableSafety,
+ hasClipboard: document.queryCommandSupported('copy'),
canEditPostSafety: api.hasPrivilege('posts:edit:safety'),
canEditPostSource: api.hasPrivilege('posts:edit:source'),
canEditPostTags: api.hasPrivilege('posts:edit:tags'),
@@ -107,6 +110,16 @@ class PostEditSidebarControl extends events.EventTarget {
'click', e => this._evtAddNoteClick(e));
}
+ if (this._copyNotesLinkNode) {
+ this._copyNotesLinkNode.addEventListener(
+ 'click', e => this._evtCopyNotesClick(e));
+ }
+
+ if (this._pasteNotesLinkNode) {
+ this._pasteNotesLinkNode.addEventListener(
+ 'click', e => this._evtPasteNotesClick(e));
+ }
+
if (this._deleteNoteLinkNode) {
this._deleteNoteLinkNode.addEventListener(
'click', e => this._evtDeleteNoteClick(e));
@@ -252,6 +265,50 @@ class PostEditSidebarControl extends events.EventTarget {
this._postNotesOverlayControl.switchToDrawing();
}
+ _evtCopyNotesClick(e) {
+ e.preventDefault();
+ let textarea = document.createElement('textarea');
+ textarea.style.position = 'fixed';
+ textarea.style.opacity = '0';
+ textarea.value = JSON.stringify([...this._post.notes].map(note => ({
+ polygon: [...note.polygon].map(
+ point => [point.x, point.y]),
+ text: note.text,
+ })));
+ document.body.appendChild(textarea);
+ textarea.select();
+
+ let success = false;
+ try {
+ success = document.execCommand('copy');
+ } catch (err) {
+ }
+ textarea.blur();
+ document.body.removeChild(textarea);
+ alert(success
+ ? 'Notes copied to clipboard.'
+ : 'Failed to copy the text to clipboard. Sorry.');
+ }
+
+ _evtPasteNotesClick(e) {
+ e.preventDefault();
+ const text = window.prompt(
+ 'Please enter the exported notes snapshot:');
+ if (!text) {
+ return;
+ }
+ const notesObj = JSON.parse(text);
+ this._post.notes.clear();
+ for (let noteObj of notesObj) {
+ let note = new Note();
+ for (let pointObj of noteObj.polygon) {
+ note.polygon.add(new Point(pointObj[0], pointObj[1]));
+ }
+ note.text = noteObj.text;
+ this._post.notes.add(note);
+ }
+ }
+
_evtDeleteNoteClick(e) {
e.preventDefault();
if (e.target.classList.contains('inactive')) {
@@ -350,6 +407,14 @@ class PostEditSidebarControl extends events.EventTarget {
return this._formNode.querySelector('.notes .add');
}
+ get _copyNotesLinkNode() {
+ return this._formNode.querySelector('.notes .copy');
+ }
+
+ get _pasteNotesLinkNode() {
+ return this._formNode.querySelector('.notes .paste');
+ }
+
get _deleteNoteLinkNode() {
return this._formNode.querySelector('.notes .delete');
}