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'); }