MDL-55459 assignfeedback_editpdf: Add linked comments and markers to PDF
[moodle.git] / mod / assign / feedback / editpdf / yui / src / editor / js / comment.js
CommitLineData
5c386472
DW
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
ad3f8cd1 15/* global SELECTOR, COMMENTCOLOUR, COMMENTTEXTCOLOUR */
5c386472
DW
16
17/**
1f777e5c 18 * Provides an in browser PDF editor.
5c386472
DW
19 *
20 * @module moodle-assignfeedback_editpdf-editor
21 */
22
23/**
1f777e5c 24 * Class representing a list of comments.
5c386472
DW
25 *
26 * @namespace M.assignfeedback_editpdf
27 * @class comment
28 * @param M.assignfeedback_editpdf.editor editor
29 * @param Int gradeid
30 * @param Int pageno
31 * @param Int x
32 * @param Int y
33 * @param Int width
34 * @param String colour
35 * @param String rawtext
36 */
557f44d9 37var COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) {
5c386472
DW
38
39 /**
40 * Reference to M.assignfeedback_editpdf.editor.
41 * @property editor
42 * @type M.assignfeedback_editpdf.editor
43 * @public
44 */
45 this.editor = editor;
46
47 /**
48 * Grade id
49 * @property gradeid
50 * @type Int
51 * @public
52 */
53 this.gradeid = gradeid || 0;
54
55 /**
56 * X position
57 * @property x
58 * @type Int
59 * @public
60 */
61 this.x = parseInt(x, 10) || 0;
62
63 /**
64 * Y position
65 * @property y
66 * @type Int
67 * @public
68 */
69 this.y = parseInt(y, 10) || 0;
70
71 /**
72 * Comment width
73 * @property width
74 * @type Int
75 * @public
76 */
77 this.width = parseInt(width, 10) || 0;
78
79 /**
80 * Comment rawtext
81 * @property rawtext
82 * @type String
83 * @public
84 */
85 this.rawtext = rawtext || '';
86
87 /**
88 * Comment page number
89 * @property pageno
90 * @type Int
91 * @public
92 */
93 this.pageno = pageno || 0;
94
95 /**
96 * Comment background colour.
97 * @property colour
98 * @type String
99 * @public
100 */
101 this.colour = colour || 'yellow';
102
103 /**
104 * Reference to M.assignfeedback_editpdf.drawable
105 * @property drawable
106 * @type M.assignfeedback_editpdf.drawable
107 * @public
108 */
109 this.drawable = false;
110
111 /**
112 * Boolean used by a timeout to delete empty comments after a short delay.
113 * @property deleteme
114 * @type Boolean
115 * @public
116 */
117 this.deleteme = false;
118
119 /**
120 * Reference to the link that opens the menu.
121 * @property menulink
122 * @type Y.Node
123 * @public
124 */
125 this.menulink = null;
126
127 /**
128 * Reference to the dialogue that is the context menu.
129 * @property menu
130 * @type M.assignfeedback_editpdf.dropdown
131 * @public
132 */
133 this.menu = null;
134
135 /**
136 * Clean a comment record, returning an oject with only fields that are valid.
137 * @public
138 * @method clean
139 * @return {}
140 */
141 this.clean = function() {
142 return {
5bb4f444
DP
143 gradeid: this.gradeid,
144 x: parseInt(this.x, 10),
145 y: parseInt(this.y, 10),
146 width: parseInt(this.width, 10),
147 rawtext: this.rawtext,
148 pageno: this.currentpage,
149 colour: this.colour
5c386472
DW
150 };
151 };
152
153 /**
154 * Draw a comment.
155 * @public
156 * @method draw_comment
157 * @param boolean focus - Set the keyboard focus to the new comment if true
158 * @return M.assignfeedback_editpdf.drawable
159 */
160 this.draw = function(focus) {
161 var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
162 node,
667cec9b 163 drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
5c386472
DW
164 container,
165 menu,
166 position,
167 scrollheight;
168
169 // Lets add a contenteditable div.
170 node = Y.Node.create('<textarea/>');
171 container = Y.Node.create('<div class="commentdrawable"/>');
172 menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
173
174 this.menulink = menu;
175 container.append(node);
176
177 if (!this.editor.get('readonly')) {
178 container.append(menu);
179 } else {
180 node.setAttribute('readonly', 'readonly');
181 }
182 if (this.width < 100) {
183 this.width = 100;
184 }
185
186 position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
187 node.setStyles({
188 width: this.width + 'px',
1d38083c
DW
189 backgroundColor: COMMENTCOLOUR[this.colour],
190 color: COMMENTTEXTCOLOUR
5c386472
DW
191 });
192
667cec9b 193 drawingregion.append(container);
d79d1bd8 194 container.setStyle('position', 'absolute');
5c386472
DW
195 container.setX(position.x);
196 container.setY(position.y);
50c12f01 197 drawable.store_position(container, position.x, position.y);
5c386472
DW
198 drawable.nodes.push(container);
199 node.set('value', this.rawtext);
8b766dd1 200 scrollheight = node.get('scrollHeight');
5c386472 201 node.setStyles({
5bb4f444 202 'height': scrollheight + 'px',
5c386472
DW
203 'overflow': 'hidden'
204 });
205 if (!this.editor.get('readonly')) {
206 this.attach_events(node, menu);
207 }
208 if (focus) {
209 node.focus();
210 }
211 this.drawable = drawable;
212
213
214 return drawable;
215 };
216
217 /**
218 * Delete an empty comment if it's menu hasn't been opened in time.
219 * @method delete_comment_later
220 */
221 this.delete_comment_later = function() {
222 if (this.deleteme) {
223 this.remove();
224 }
225 };
226
227 /**
228 * Comment nodes have a bunch of event handlers attached to them directly.
229 * This is all done here for neatness.
230 *
231 * @protected
232 * @method attach_comment_events
233 * @param node - The Y.Node representing the comment.
234 * @param menu - The Y.Node representing the menu.
235 */
236 this.attach_events = function(node, menu) {
237 // Save the text on blur.
238 node.on('blur', function() {
239 // Save the changes back to the comment.
240 this.rawtext = node.get('value');
241 this.width = parseInt(node.getStyle('width'), 10);
242
243 // Trim.
244 if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') {
245 // Delete empty comments.
246 this.deleteme = true;
247 Y.later(400, this, this.delete_comment_later);
248 }
249 this.editor.save_current_page();
60b87080 250 this.editor.editingcomment = false;
5c386472
DW
251 }, this);
252
253 // For delegated event handler.
254 menu.setData('comment', this);
255
256 node.on('keyup', function() {
257 var scrollheight = node.get('scrollHeight'),
258 height = parseInt(node.getStyle('height'), 10);
259
260 // Webkit scrollheight fix.
261 if (scrollheight === height + 8) {
262 scrollheight -= 8;
263 }
264 node.setStyle('height', scrollheight + 'px');
265
266 });
267
268 node.on('gesturemovestart', function(e) {
b37b6cd8 269 if (editor.currentedit.tool === 'select') {
243ddbcf 270 e.preventDefault();
b37b6cd8
TB
271 node.setData('dragging', true);
272 node.setData('offsetx', e.clientX - node.getX());
273 node.setData('offsety', e.clientY - node.getY());
274 }
5c386472
DW
275 });
276 node.on('gesturemoveend', function() {
b37b6cd8
TB
277 if (editor.currentedit.tool === 'select') {
278 node.setData('dragging', false);
279 this.editor.save_current_page();
280 }
5c386472
DW
281 }, null, this);
282 node.on('gesturemove', function(e) {
b37b6cd8
TB
283 if (editor.currentedit.tool === 'select') {
284 var x = e.clientX - node.getData('offsetx'),
285 y = e.clientY - node.getData('offsety'),
286 nodewidth,
287 nodeheight,
288 newlocation,
289 windowlocation,
290 bounds;
291
292 nodewidth = parseInt(node.getStyle('width'), 10);
293 nodeheight = parseInt(node.getStyle('height'), 10);
294
295 newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
296 bounds = this.editor.get_canvas_bounds(true);
297 bounds.x = 0;
298 bounds.y = 0;
299
300 bounds.width -= nodewidth + 42;
301 bounds.height -= nodeheight + 8;
302 // Clip to the window size - the comment size.
303 newlocation.clip(bounds);
304
305 this.x = newlocation.x;
306 this.y = newlocation.y;
307
308 windowlocation = this.editor.get_window_coordinates(newlocation);
309 node.ancestor().setX(windowlocation.x);
310 node.ancestor().setY(windowlocation.y);
311 this.drawable.store_position(node.ancestor(), windowlocation.x, windowlocation.y);
312 }
5c386472
DW
313 }, null, this);
314
315 this.menu = new M.assignfeedback_editpdf.commentmenu({
316 buttonNode: this.menulink,
317 comment: this
318 });
319 };
320
321 /**
322 * Delete a comment.
323 * @method remove
324 */
325 this.remove = function() {
bc8b6dc6
DP
326 var i = 0;
327 var comments;
5c386472
DW
328
329 comments = this.editor.pages[this.editor.currentpage].comments;
330 for (i = 0; i < comments.length; i++) {
331 if (comments[i] === this) {
332 comments.splice(i, 1);
333 this.drawable.erase();
334 this.editor.save_current_page();
335 return;
336 }
337 }
338 };
339
340 /**
341 * Event handler to remove a comment from the users quicklist.
342 *
343 * @protected
344 * @method remove_from_quicklist
345 */
346 this.remove_from_quicklist = function(e, quickcomment) {
114913e3
DS
347 e.preventDefault();
348
5c386472
DW
349 this.menu.hide();
350
351 this.editor.quicklist.remove(quickcomment);
352 };
353
354 /**
355 * A quick comment was selected in the list, update the active comment and redraw the page.
356 *
357 * @param Event e
358 * @protected
359 * @method set_from_quick_comment
360 */
361 this.set_from_quick_comment = function(e, quickcomment) {
114913e3
DS
362 e.preventDefault();
363
5c386472
DW
364 this.menu.hide();
365
366 this.rawtext = quickcomment.rawtext;
367 this.width = quickcomment.width;
368 this.colour = quickcomment.colour;
369
370 this.editor.save_current_page();
371
372 this.editor.redraw();
373 };
374
375 /**
376 * Event handler to add a comment to the users quicklist.
377 *
378 * @protected
379 * @method add_to_quicklist
380 */
114913e3
DS
381 this.add_to_quicklist = function(e) {
382 e.preventDefault();
5c386472
DW
383 this.menu.hide();
384 this.editor.quicklist.add(this);
385 };
386
387 /**
388 * Draw the in progress edit.
389 *
390 * @public
391 * @method draw_current_edit
392 * @param M.assignfeedback_editpdf.edit edit
393 */
394 this.draw_current_edit = function(edit) {
395 var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
396 shape,
397 bounds;
398
399 bounds = new M.assignfeedback_editpdf.rect();
400 bounds.bound([edit.start, edit.end]);
401
402 // We will draw a box with the current background colour.
403 shape = this.editor.graphic.addShape({
404 type: Y.Rect,
405 width: bounds.width,
406 height: bounds.height,
407 fill: {
408 color: COMMENTCOLOUR[edit.commentcolour]
409 },
410 x: bounds.x,
411 y: bounds.y
412 });
413
414 drawable.shapes.push(shape);
415
416 return drawable;
417 };
418
419 /**
420 * Promote the current edit to a real comment.
421 *
422 * @public
423 * @method init_from_edit
424 * @param M.assignfeedback_editpdf.edit edit
baf881b8 425 * @return bool true if comment bound is more than min width/height, else false.
5c386472
DW
426 */
427 this.init_from_edit = function(edit) {
428 var bounds = new M.assignfeedback_editpdf.rect();
429 bounds.bound([edit.start, edit.end]);
430
431 // Minimum comment width.
432 if (bounds.width < 100) {
433 bounds.width = 100;
434 }
435
436 // Save the current edit to the server and the current page list.
437
438 this.gradeid = this.editor.get('gradeid');
439 this.pageno = this.editor.currentpage;
440 this.x = bounds.x;
441 this.y = bounds.y;
442 this.width = bounds.width;
443 this.colour = edit.commentcolour;
444 this.rawtext = '';
baf881b8
RT
445
446 return (bounds.has_min_width() && bounds.has_min_height());
5c386472
DW
447 };
448
449};
450
451M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
452M.assignfeedback_editpdf.comment = COMMENT;