bdb96f9fc6c54405c2cd033e1ac222a8764438c1
[moodle.git] / mod / assign / feedback / editpdf / yui / src / editor / js / comment.js
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/>.
16 /**
17  * Provides an in browser PDF editor.
18  *
19  * @module moodle-assignfeedback_editpdf-editor
20  */
22 /**
23  * Class representing a list of comments.
24  *
25  * @namespace M.assignfeedback_editpdf
26  * @class comment
27  * @param M.assignfeedback_editpdf.editor editor
28  * @param Int gradeid
29  * @param Int pageno
30  * @param Int x
31  * @param Int y
32  * @param Int width
33  * @param String colour
34  * @param String rawtext
35  */
36 var COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) {
38     /**
39      * Reference to M.assignfeedback_editpdf.editor.
40      * @property editor
41      * @type M.assignfeedback_editpdf.editor
42      * @public
43      */
44     this.editor = editor;
46     /**
47      * Grade id
48      * @property gradeid
49      * @type Int
50      * @public
51      */
52     this.gradeid = gradeid || 0;
54     /**
55      * X position
56      * @property x
57      * @type Int
58      * @public
59      */
60     this.x = parseInt(x, 10) || 0;
62     /**
63      * Y position
64      * @property y
65      * @type Int
66      * @public
67      */
68     this.y = parseInt(y, 10) || 0;
70     /**
71      * Comment width
72      * @property width
73      * @type Int
74      * @public
75      */
76     this.width = parseInt(width, 10) || 0;
78     /**
79      * Comment rawtext
80      * @property rawtext
81      * @type String
82      * @public
83      */
84     this.rawtext = rawtext || '';
86     /**
87      * Comment page number
88      * @property pageno
89      * @type Int
90      * @public
91      */
92     this.pageno = pageno || 0;
94     /**
95      * Comment background colour.
96      * @property colour
97      * @type String
98      * @public
99      */
100     this.colour = colour || 'yellow';
102     /**
103      * Reference to M.assignfeedback_editpdf.drawable
104      * @property drawable
105      * @type M.assignfeedback_editpdf.drawable
106      * @public
107      */
108     this.drawable = false;
110     /**
111      * Boolean used by a timeout to delete empty comments after a short delay.
112      * @property deleteme
113      * @type Boolean
114      * @public
115      */
116     this.deleteme = false;
118     /**
119      * Reference to the link that opens the menu.
120      * @property menulink
121      * @type Y.Node
122      * @public
123      */
124     this.menulink = null;
126     /**
127      * Reference to the dialogue that is the context menu.
128      * @property menu
129      * @type M.assignfeedback_editpdf.dropdown
130      * @public
131      */
132     this.menu = null;
134     /**
135      * Clean a comment record, returning an oject with only fields that are valid.
136      * @public
137      * @method clean
138      * @return {}
139      */
140     this.clean = function() {
141         return {
142             gradeid : this.gradeid,
143             x : parseInt(this.x, 10),
144             y : parseInt(this.y, 10),
145             width : parseInt(this.width, 10),
146             rawtext : this.rawtext,
147             pageno : this.currentpage,
148             colour : this.colour
149         };
150     };
152     /**
153      * Draw a comment.
154      * @public
155      * @method draw_comment
156      * @param boolean focus - Set the keyboard focus to the new comment if true
157      * @return M.assignfeedback_editpdf.drawable
158      */
159     this.draw = function(focus) {
160         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
161             node,
162             drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
163             container,
164             menu,
165             position,
166             scrollheight;
168         // Lets add a contenteditable div.
169         node = Y.Node.create('<textarea/>');
170         container = Y.Node.create('<div class="commentdrawable"/>');
171         menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
173         this.menulink = menu;
174         container.append(node);
176         if (!this.editor.get('readonly')) {
177             container.append(menu);
178         } else {
179             node.setAttribute('readonly', 'readonly');
180         }
181         if (this.width < 100) {
182             this.width = 100;
183         }
185         position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
186         node.setStyles({
187             width: this.width + 'px',
188             backgroundColor: COMMENTCOLOUR[this.colour],
189             color: COMMENTTEXTCOLOUR
190         });
192         drawingregion.append(container);
193         container.setStyle('position', 'absolute');
194         container.setX(position.x);
195         container.setY(position.y);
196         drawable.store_position(container, position.x, position.y);
197         drawable.nodes.push(container);
198         node.set('value', this.rawtext);
199         scrollheight = node.get('scrollHeight'),
200         node.setStyles({
201             'height' : scrollheight + 'px',
202             'overflow': 'hidden'
203         });
204         if (!this.editor.get('readonly')) {
205             this.attach_events(node, menu);
206         }
207         if (focus) {
208             node.focus();
209         }
210         this.drawable = drawable;
213         return drawable;
214     };
216     /**
217      * Delete an empty comment if it's menu hasn't been opened in time.
218      * @method delete_comment_later
219      */
220     this.delete_comment_later = function() {
221         if (this.deleteme) {
222             this.remove();
223         }
224     };
226     /**
227      * Comment nodes have a bunch of event handlers attached to them directly.
228      * This is all done here for neatness.
229      *
230      * @protected
231      * @method attach_comment_events
232      * @param node - The Y.Node representing the comment.
233      * @param menu - The Y.Node representing the menu.
234      */
235     this.attach_events = function(node, menu) {
236         // Save the text on blur.
237         node.on('blur', function() {
238             // Save the changes back to the comment.
239             this.rawtext = node.get('value');
240             this.width = parseInt(node.getStyle('width'), 10);
242             // Trim.
243             if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') {
244                 // Delete empty comments.
245                 this.deleteme = true;
246                 Y.later(400, this, this.delete_comment_later);
247             }
248             this.editor.save_current_page();
249             this.editor.editingcomment = false;
250         }, this);
252         // For delegated event handler.
253         menu.setData('comment', this);
255         node.on('keyup', function() {
256             var scrollheight = node.get('scrollHeight'),
257                 height = parseInt(node.getStyle('height'), 10);
259             // Webkit scrollheight fix.
260             if (scrollheight === height + 8) {
261                 scrollheight -= 8;
262             }
263             node.setStyle('height', scrollheight + 'px');
265         });
267         node.on('gesturemovestart', function(e) {
268             node.setData('dragging', true);
269             node.setData('offsetx', e.clientX - node.getX());
270             node.setData('offsety', e.clientY - node.getY());
271         });
272         node.on('gesturemoveend', function() {
273             node.setData('dragging', false);
274             this.editor.save_current_page();
275         }, null, this);
276         node.on('gesturemove', function(e) {
277             var x = e.clientX - node.getData('offsetx'),
278                 y = e.clientY - node.getData('offsety'),
279                 nodewidth,
280                 nodeheight,
281                 newlocation,
282                 windowlocation,
283                 bounds;
285             nodewidth = parseInt(node.getStyle('width'), 10);
286             nodeheight = parseInt(node.getStyle('height'), 10);
288             newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
289             bounds = this.editor.get_canvas_bounds(true);
290             bounds.x = 0;
291             bounds.y = 0;
293             bounds.width -= nodewidth + 42;
294             bounds.height -= nodeheight + 8;
295             // Clip to the window size - the comment size.
296             newlocation.clip(bounds);
298             this.x = newlocation.x;
299             this.y = newlocation.y;
301             windowlocation = this.editor.get_window_coordinates(newlocation);
302             node.ancestor().setX(windowlocation.x);
303             node.ancestor().setY(windowlocation.y);
304             this.drawable.store_position(node.ancestor(), windowlocation.x, windowlocation.y);
305         }, null, this);
307         this.menu = new M.assignfeedback_editpdf.commentmenu({
308             buttonNode: this.menulink,
309             comment: this
310         });
311     };
313     /**
314      * Delete a comment.
315      * @method remove
316      */
317     this.remove = function() {
318         var i = 0, comments;
320         comments = this.editor.pages[this.editor.currentpage].comments;
321         for (i = 0; i < comments.length; i++) {
322             if (comments[i] === this) {
323                 comments.splice(i, 1);
324                 this.drawable.erase();
325                 this.editor.save_current_page();
326                 return;
327             }
328         }
329     };
331     /**
332      * Event handler to remove a comment from the users quicklist.
333      *
334      * @protected
335      * @method remove_from_quicklist
336      */
337     this.remove_from_quicklist = function(e, quickcomment) {
338         e.preventDefault();
340         this.menu.hide();
342         this.editor.quicklist.remove(quickcomment);
343     };
345     /**
346      * A quick comment was selected in the list, update the active comment and redraw the page.
347      *
348      * @param Event e
349      * @protected
350      * @method set_from_quick_comment
351      */
352     this.set_from_quick_comment = function(e, quickcomment) {
353         e.preventDefault();
355         this.menu.hide();
357         this.rawtext = quickcomment.rawtext;
358         this.width = quickcomment.width;
359         this.colour = quickcomment.colour;
361         this.editor.save_current_page();
363         this.editor.redraw();
364     };
366     /**
367      * Event handler to add a comment to the users quicklist.
368      *
369      * @protected
370      * @method add_to_quicklist
371      */
372     this.add_to_quicklist = function(e) {
373         e.preventDefault();
374         this.menu.hide();
375         this.editor.quicklist.add(this);
376     };
378     /**
379      * Draw the in progress edit.
380      *
381      * @public
382      * @method draw_current_edit
383      * @param M.assignfeedback_editpdf.edit edit
384      */
385     this.draw_current_edit = function(edit) {
386         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
387             shape,
388             bounds;
390         bounds = new M.assignfeedback_editpdf.rect();
391         bounds.bound([edit.start, edit.end]);
393         // We will draw a box with the current background colour.
394         shape = this.editor.graphic.addShape({
395             type: Y.Rect,
396             width: bounds.width,
397             height: bounds.height,
398             fill: {
399                color: COMMENTCOLOUR[edit.commentcolour]
400             },
401             x: bounds.x,
402             y: bounds.y
403         });
405         drawable.shapes.push(shape);
407         return drawable;
408     };
410     /**
411      * Promote the current edit to a real comment.
412      *
413      * @public
414      * @method init_from_edit
415      * @param M.assignfeedback_editpdf.edit edit
416      * @return bool true if comment bound is more than min width/height, else false.
417      */
418     this.init_from_edit = function(edit) {
419         var bounds = new M.assignfeedback_editpdf.rect();
420         bounds.bound([edit.start, edit.end]);
422         // Minimum comment width.
423         if (bounds.width < 100) {
424             bounds.width = 100;
425         }
427         // Save the current edit to the server and the current page list.
429         this.gradeid = this.editor.get('gradeid');
430         this.pageno = this.editor.currentpage;
431         this.x = bounds.x;
432         this.y = bounds.y;
433         this.width = bounds.width;
434         this.colour = edit.commentcolour;
435         this.rawtext = '';
437         return (bounds.has_min_width() && bounds.has_min_height());
438     };
440 };
442 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
443 M.assignfeedback_editpdf.comment = COMMENT;