| 1 | efrain | 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/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * Provides an in browser PDF editor.
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * @module moodle-assignfeedback_editpdf-editor
 | 
        
           |  |  | 20 |  */
 | 
        
           |  |  | 21 |   | 
        
           |  |  | 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) {
 | 
        
           |  |  | 37 |   | 
        
           |  |  | 38 |     /**
 | 
        
           |  |  | 39 |      * Reference to M.assignfeedback_editpdf.editor.
 | 
        
           |  |  | 40 |      * @property editor
 | 
        
           |  |  | 41 |      * @type M.assignfeedback_editpdf.editor
 | 
        
           |  |  | 42 |      * @public
 | 
        
           |  |  | 43 |      */
 | 
        
           |  |  | 44 |     this.editor = editor;
 | 
        
           |  |  | 45 |   | 
        
           |  |  | 46 |     /**
 | 
        
           |  |  | 47 |      * Grade id
 | 
        
           |  |  | 48 |      * @property gradeid
 | 
        
           |  |  | 49 |      * @type Int
 | 
        
           |  |  | 50 |      * @public
 | 
        
           |  |  | 51 |      */
 | 
        
           |  |  | 52 |     this.gradeid = gradeid || 0;
 | 
        
           |  |  | 53 |   | 
        
           |  |  | 54 |     /**
 | 
        
           |  |  | 55 |      * X position
 | 
        
           |  |  | 56 |      * @property x
 | 
        
           |  |  | 57 |      * @type Int
 | 
        
           |  |  | 58 |      * @public
 | 
        
           |  |  | 59 |      */
 | 
        
           |  |  | 60 |     this.x = parseInt(x, 10) || 0;
 | 
        
           |  |  | 61 |   | 
        
           |  |  | 62 |     /**
 | 
        
           |  |  | 63 |      * Y position
 | 
        
           |  |  | 64 |      * @property y
 | 
        
           |  |  | 65 |      * @type Int
 | 
        
           |  |  | 66 |      * @public
 | 
        
           |  |  | 67 |      */
 | 
        
           |  |  | 68 |     this.y = parseInt(y, 10) || 0;
 | 
        
           |  |  | 69 |   | 
        
           |  |  | 70 |     /**
 | 
        
           |  |  | 71 |      * Comment width
 | 
        
           |  |  | 72 |      * @property width
 | 
        
           |  |  | 73 |      * @type Int
 | 
        
           |  |  | 74 |      * @public
 | 
        
           |  |  | 75 |      */
 | 
        
           |  |  | 76 |     this.width = parseInt(width, 10) || 0;
 | 
        
           |  |  | 77 |   | 
        
           |  |  | 78 |     /**
 | 
        
           |  |  | 79 |      * Comment rawtext
 | 
        
           |  |  | 80 |      * @property rawtext
 | 
        
           |  |  | 81 |      * @type String
 | 
        
           |  |  | 82 |      * @public
 | 
        
           |  |  | 83 |      */
 | 
        
           |  |  | 84 |     this.rawtext = rawtext || '';
 | 
        
           |  |  | 85 |   | 
        
           |  |  | 86 |     /**
 | 
        
           |  |  | 87 |      * Comment page number
 | 
        
           |  |  | 88 |      * @property pageno
 | 
        
           |  |  | 89 |      * @type Int
 | 
        
           |  |  | 90 |      * @public
 | 
        
           |  |  | 91 |      */
 | 
        
           |  |  | 92 |     this.pageno = pageno || 0;
 | 
        
           |  |  | 93 |   | 
        
           |  |  | 94 |     /**
 | 
        
           |  |  | 95 |      * Comment background colour.
 | 
        
           |  |  | 96 |      * @property colour
 | 
        
           |  |  | 97 |      * @type String
 | 
        
           |  |  | 98 |      * @public
 | 
        
           |  |  | 99 |      */
 | 
        
           |  |  | 100 |     this.colour = colour || 'yellow';
 | 
        
           |  |  | 101 |   | 
        
           |  |  | 102 |     /**
 | 
        
           |  |  | 103 |      * Reference to M.assignfeedback_editpdf.drawable
 | 
        
           |  |  | 104 |      * @property drawable
 | 
        
           |  |  | 105 |      * @type M.assignfeedback_editpdf.drawable
 | 
        
           |  |  | 106 |      * @public
 | 
        
           |  |  | 107 |      */
 | 
        
           |  |  | 108 |     this.drawable = false;
 | 
        
           |  |  | 109 |   | 
        
           |  |  | 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;
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 |     /**
 | 
        
           |  |  | 119 |      * Reference to the link that opens the menu.
 | 
        
           |  |  | 120 |      * @property menulink
 | 
        
           |  |  | 121 |      * @type Y.Node
 | 
        
           |  |  | 122 |      * @public
 | 
        
           |  |  | 123 |      */
 | 
        
           |  |  | 124 |     this.menulink = null;
 | 
        
           |  |  | 125 |   | 
        
           |  |  | 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;
 | 
        
           |  |  | 133 |   | 
        
           |  |  | 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: parseInt(this.pageno, 10),
 | 
        
           |  |  | 148 |             colour: this.colour
 | 
        
           |  |  | 149 |         };
 | 
        
           |  |  | 150 |     };
 | 
        
           |  |  | 151 |   | 
        
           |  |  | 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 |             drawingcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
 | 
        
           |  |  | 163 |             container,
 | 
        
           |  |  | 164 |             label,
 | 
        
           |  |  | 165 |             marker,
 | 
        
           |  |  | 166 |             menu,
 | 
        
           |  |  | 167 |             position,
 | 
        
           |  |  | 168 |             scrollheight;
 | 
        
           |  |  | 169 |   | 
        
           |  |  | 170 |         // Lets add a contenteditable div.
 | 
        
           |  |  | 171 |         node = Y.Node.create('<textarea/>');
 | 
        
           |  |  | 172 |         container = Y.Node.create('<div class="commentdrawable"/>');
 | 
        
           |  |  | 173 |         label = Y.Node.create('<label/>');
 | 
        
           |  |  | 174 |         marker = Y.Node.create('<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.5 -0.5 13 13" ' +
 | 
        
           |  |  | 175 |                 'preserveAspectRatio="xMinYMin meet">' +
 | 
        
           |  |  | 176 |                 '<path d="M11 0H1C.4 0 0 .4 0 1v6c0 .6.4 1 1 1h1v4l4-4h5c.6 0 1-.4 1-1V1c0-.6-.4-1-1-1z" ' +
 | 
        
           |  |  | 177 |                 'fill="currentColor" opacity="0.9" stroke="rgb(153, 153, 153)" stroke-width="0.5"/></svg>');
 | 
        
           | 1441 | ariadna | 178 |         menu = Y.Node.create('<a href="#"><img class="icon" src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
 | 
        
           | 1 | efrain | 179 |   | 
        
           |  |  | 180 |         this.menulink = menu;
 | 
        
           |  |  | 181 |         container.append(label);
 | 
        
           |  |  | 182 |         label.append(node);
 | 
        
           |  |  | 183 |         container.append(marker);
 | 
        
           |  |  | 184 |         container.setAttribute('tabindex', '-1');
 | 
        
           |  |  | 185 |         label.setAttribute('tabindex', '0');
 | 
        
           |  |  | 186 |         node.setAttribute('tabindex', '-1');
 | 
        
           |  |  | 187 |         menu.setAttribute('tabindex', '0');
 | 
        
           |  |  | 188 |   | 
        
           |  |  | 189 |         if (!this.editor.get('readonly')) {
 | 
        
           |  |  | 190 |             container.append(menu);
 | 
        
           |  |  | 191 |         } else {
 | 
        
           |  |  | 192 |             node.setAttribute('readonly', 'readonly');
 | 
        
           |  |  | 193 |         }
 | 
        
           |  |  | 194 |         if (this.width < 100) {
 | 
        
           |  |  | 195 |             this.width = 100;
 | 
        
           |  |  | 196 |         }
 | 
        
           |  |  | 197 |   | 
        
           |  |  | 198 |         position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
 | 
        
           |  |  | 199 |         node.setStyles({
 | 
        
           |  |  | 200 |             width: this.width + 'px',
 | 
        
           |  |  | 201 |             backgroundColor: COMMENTCOLOUR[this.colour],
 | 
        
           |  |  | 202 |             color: COMMENTTEXTCOLOUR
 | 
        
           |  |  | 203 |         });
 | 
        
           |  |  | 204 |   | 
        
           |  |  | 205 |         drawingcanvas.append(container);
 | 
        
           |  |  | 206 |         container.setStyle('position', 'absolute');
 | 
        
           |  |  | 207 |         container.setX(position.x);
 | 
        
           |  |  | 208 |         container.setY(position.y);
 | 
        
           |  |  | 209 |         drawable.store_position(container, position.x, position.y);
 | 
        
           |  |  | 210 |         drawable.nodes.push(container);
 | 
        
           |  |  | 211 |         node.set('value', this.rawtext);
 | 
        
           |  |  | 212 |         scrollheight = node.get('scrollHeight');
 | 
        
           |  |  | 213 |         node.setStyles({
 | 
        
           |  |  | 214 |             'height': scrollheight + 'px',
 | 
        
           |  |  | 215 |             'overflow': 'hidden'
 | 
        
           |  |  | 216 |         });
 | 
        
           |  |  | 217 |         marker.setStyle('color', COMMENTCOLOUR[this.colour]);
 | 
        
           |  |  | 218 |         this.attach_events(node, menu);
 | 
        
           |  |  | 219 |         if (focus) {
 | 
        
           |  |  | 220 |             node.focus();
 | 
        
           |  |  | 221 |         } else if (editor.collapsecomments) {
 | 
        
           |  |  | 222 |             container.addClass('commentcollapsed');
 | 
        
           |  |  | 223 |         }
 | 
        
           |  |  | 224 |         this.drawable = drawable;
 | 
        
           |  |  | 225 |   | 
        
           |  |  | 226 |   | 
        
           |  |  | 227 |         return drawable;
 | 
        
           |  |  | 228 |     };
 | 
        
           |  |  | 229 |   | 
        
           |  |  | 230 |     /**
 | 
        
           |  |  | 231 |      * Delete an empty comment if it's menu hasn't been opened in time.
 | 
        
           |  |  | 232 |      * @method delete_comment_later
 | 
        
           |  |  | 233 |      */
 | 
        
           |  |  | 234 |     this.delete_comment_later = function() {
 | 
        
           |  |  | 235 |         if (this.deleteme && !this.is_menu_active()) {
 | 
        
           |  |  | 236 |             this.remove();
 | 
        
           |  |  | 237 |         }
 | 
        
           |  |  | 238 |     };
 | 
        
           |  |  | 239 |   | 
        
           |  |  | 240 |     /**
 | 
        
           |  |  | 241 |      * Returns true if the menu is active, false otherwise.
 | 
        
           |  |  | 242 |      *
 | 
        
           |  |  | 243 |      * @return bool true if menu is active, else false.
 | 
        
           |  |  | 244 |      */
 | 
        
           |  |  | 245 |     this.is_menu_active = function() {
 | 
        
           |  |  | 246 |         return this.menu !== null && this.menu.get('visible');
 | 
        
           |  |  | 247 |     };
 | 
        
           |  |  | 248 |   | 
        
           |  |  | 249 |     /**
 | 
        
           |  |  | 250 |      * Comment nodes have a bunch of event handlers attached to them directly.
 | 
        
           |  |  | 251 |      * This is all done here for neatness.
 | 
        
           |  |  | 252 |      *
 | 
        
           |  |  | 253 |      * @protected
 | 
        
           |  |  | 254 |      * @method attach_comment_events
 | 
        
           |  |  | 255 |      * @param node - The Y.Node representing the comment.
 | 
        
           |  |  | 256 |      * @param menu - The Y.Node representing the menu.
 | 
        
           |  |  | 257 |      */
 | 
        
           |  |  | 258 |     this.attach_events = function(node, menu) {
 | 
        
           |  |  | 259 |         var container = node.ancestor('div'),
 | 
        
           |  |  | 260 |             label = node.ancestor('label'),
 | 
        
           |  |  | 261 |             marker = label.next('svg');
 | 
        
           |  |  | 262 |   | 
        
           |  |  | 263 |         // Function to collapse a comment to a marker icon.
 | 
        
           |  |  | 264 |         node.collapse = function(delay) {
 | 
        
           |  |  | 265 |             node.collapse.delay = Y.later(delay, node, function() {
 | 
        
           |  |  | 266 |                 if (editor.collapsecomments && !this.is_menu_active()) {
 | 
        
           |  |  | 267 |                     container.addClass('commentcollapsed');
 | 
        
           |  |  | 268 |                 }
 | 
        
           |  |  | 269 |             }.bind(this));
 | 
        
           |  |  | 270 |         }.bind(this);
 | 
        
           |  |  | 271 |   | 
        
           |  |  | 272 |         // Function to expand a comment.
 | 
        
           |  |  | 273 |         node.expand = function() {
 | 
        
           |  |  | 274 |             if (node.getData('dragging') !== true) {
 | 
        
           |  |  | 275 |                 if (node.collapse.delay) {
 | 
        
           |  |  | 276 |                     node.collapse.delay.cancel();
 | 
        
           |  |  | 277 |                 }
 | 
        
           |  |  | 278 |                 container.removeClass('commentcollapsed');
 | 
        
           |  |  | 279 |             }
 | 
        
           |  |  | 280 |         };
 | 
        
           |  |  | 281 |   | 
        
           |  |  | 282 |         // Expand comment on mouse over (under certain conditions) or click/tap.
 | 
        
           |  |  | 283 |         container.on('mouseenter', function() {
 | 
        
           |  |  | 284 |             if (editor.currentedit.tool === 'comment' || editor.currentedit.tool === 'select' || this.editor.get('readonly')) {
 | 
        
           |  |  | 285 |                 node.expand();
 | 
        
           |  |  | 286 |             }
 | 
        
           |  |  | 287 |         }, this);
 | 
        
           |  |  | 288 |         container.on('click|tap', function() {
 | 
        
           |  |  | 289 |             node.expand();
 | 
        
           |  |  | 290 |             node.focus();
 | 
        
           |  |  | 291 |         }, this);
 | 
        
           |  |  | 292 |   | 
        
           |  |  | 293 |         // Functions to capture reverse tabbing events.
 | 
        
           |  |  | 294 |         node.on('keyup', function(e) {
 | 
        
           |  |  | 295 |             if (e.keyCode === 9 && e.shiftKey && menu.getAttribute('tabindex') === '0') {
 | 
        
           |  |  | 296 |                 // User landed here via Shift+Tab (but not from this comment's menu).
 | 
        
           |  |  | 297 |                 menu.focus();
 | 
        
           |  |  | 298 |             }
 | 
        
           |  |  | 299 |             menu.setAttribute('tabindex', '0');
 | 
        
           |  |  | 300 |         }, this);
 | 
        
           |  |  | 301 |         menu.on('keydown', function(e) {
 | 
        
           |  |  | 302 |             if (e.keyCode === 9 && e.shiftKey) {
 | 
        
           |  |  | 303 |                 // User is tabbing back to the comment node from its own menu.
 | 
        
           |  |  | 304 |                 menu.setAttribute('tabindex', '-1');
 | 
        
           |  |  | 305 |             }
 | 
        
           |  |  | 306 |         }, this);
 | 
        
           |  |  | 307 |   | 
        
           |  |  | 308 |         // Comment becomes "active" on label or menu focus.
 | 
        
           |  |  | 309 |         label.on('focus', function() {
 | 
        
           |  |  | 310 |             node.active = true;
 | 
        
           |  |  | 311 |             if (node.collapse.delay) {
 | 
        
           |  |  | 312 |                 node.collapse.delay.cancel();
 | 
        
           |  |  | 313 |             }
 | 
        
           |  |  | 314 |             // Give comment a tabindex to prevent focus outline being suppressed.
 | 
        
           |  |  | 315 |             node.setAttribute('tabindex', '0');
 | 
        
           |  |  | 316 |             // Expand comment and pass focus to it.
 | 
        
           |  |  | 317 |             node.expand();
 | 
        
           |  |  | 318 |             node.focus();
 | 
        
           |  |  | 319 |             // Now remove label tabindex so user can reverse tab past it.
 | 
        
           |  |  | 320 |             label.setAttribute('tabindex', '-1');
 | 
        
           |  |  | 321 |         }, this);
 | 
        
           |  |  | 322 |         menu.on('focus', function() {
 | 
        
           |  |  | 323 |             node.active = true;
 | 
        
           |  |  | 324 |             if (node.collapse.delay) {
 | 
        
           |  |  | 325 |                 node.collapse.delay.cancel();
 | 
        
           |  |  | 326 |             }
 | 
        
           |  |  | 327 |             this.deleteme = false;
 | 
        
           |  |  | 328 |             // Restore label tabindex so user can tab back to it from menu.
 | 
        
           |  |  | 329 |             label.setAttribute('tabindex', '0');
 | 
        
           |  |  | 330 |         }, this);
 | 
        
           |  |  | 331 |   | 
        
           |  |  | 332 |         // Always restore the default tabindex states when moving away.
 | 
        
           |  |  | 333 |         node.on('blur', function() {
 | 
        
           |  |  | 334 |             node.setAttribute('tabindex', '-1');
 | 
        
           |  |  | 335 |         }, this);
 | 
        
           |  |  | 336 |         label.on('blur', function() {
 | 
        
           |  |  | 337 |             label.setAttribute('tabindex', '0');
 | 
        
           |  |  | 338 |         }, this);
 | 
        
           |  |  | 339 |   | 
        
           |  |  | 340 |         // Collapse comment on mouse out if not currently active.
 | 
        
           |  |  | 341 |         container.on('mouseleave', function() {
 | 
        
           |  |  | 342 |             if (editor.collapsecomments && node.active !== true) {
 | 
        
           |  |  | 343 |                 node.collapse(400);
 | 
        
           |  |  | 344 |             }
 | 
        
           |  |  | 345 |         }, this);
 | 
        
           |  |  | 346 |   | 
        
           |  |  | 347 |         // Collapse comment on blur.
 | 
        
           |  |  | 348 |         container.on('blur', function() {
 | 
        
           |  |  | 349 |             node.active = false;
 | 
        
           |  |  | 350 |             node.collapse(800);
 | 
        
           |  |  | 351 |         }, this);
 | 
        
           |  |  | 352 |   | 
        
           |  |  | 353 |         if (!this.editor.get('readonly')) {
 | 
        
           |  |  | 354 |             // Save the text on blur.
 | 
        
           |  |  | 355 |             node.on('blur', function() {
 | 
        
           |  |  | 356 |                 // Save the changes back to the comment.
 | 
        
           |  |  | 357 |                 this.rawtext = node.get('value');
 | 
        
           |  |  | 358 |                 this.width = parseInt(node.getStyle('width'), 10);
 | 
        
           |  |  | 359 |   | 
        
           |  |  | 360 |                 // Trim.
 | 
        
           |  |  | 361 |                 if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') {
 | 
        
           |  |  | 362 |                     // Delete empty comments.
 | 
        
           |  |  | 363 |                     this.deleteme = true;
 | 
        
           |  |  | 364 |                     Y.later(400, this, this.delete_comment_later);
 | 
        
           |  |  | 365 |                 }
 | 
        
           |  |  | 366 |                 this.editor.save_current_page();
 | 
        
           |  |  | 367 |                 this.editor.editingcomment = false;
 | 
        
           |  |  | 368 |             }, this);
 | 
        
           |  |  | 369 |   | 
        
           |  |  | 370 |             // For delegated event handler.
 | 
        
           |  |  | 371 |             menu.setData('comment', this);
 | 
        
           |  |  | 372 |   | 
        
           |  |  | 373 |             node.on('keyup', function() {
 | 
        
           |  |  | 374 |                 node.setStyle('height', 'auto');
 | 
        
           |  |  | 375 |                 var scrollheight = node.get('scrollHeight'),
 | 
        
           |  |  | 376 |                     height = parseInt(node.getStyle('height'), 10);
 | 
        
           |  |  | 377 |   | 
        
           |  |  | 378 |                 // Webkit scrollheight fix.
 | 
        
           |  |  | 379 |                 if (scrollheight === height + 8) {
 | 
        
           |  |  | 380 |                     scrollheight -= 8;
 | 
        
           |  |  | 381 |                 }
 | 
        
           |  |  | 382 |                 node.setStyle('height', scrollheight + 'px');
 | 
        
           |  |  | 383 |             });
 | 
        
           |  |  | 384 |   | 
        
           |  |  | 385 |             node.on('gesturemovestart', function(e) {
 | 
        
           |  |  | 386 |                 if (editor.currentedit.tool === 'select') {
 | 
        
           |  |  | 387 |                     e.preventDefault();
 | 
        
           |  |  | 388 |                     if (editor.collapsecomments) {
 | 
        
           |  |  | 389 |                         node.setData('offsetx', 8);
 | 
        
           |  |  | 390 |                         node.setData('offsety', 8);
 | 
        
           |  |  | 391 |                     } else {
 | 
        
           |  |  | 392 |                         node.setData('offsetx', e.clientX - container.getX());
 | 
        
           |  |  | 393 |                         node.setData('offsety', e.clientY - container.getY());
 | 
        
           |  |  | 394 |                     }
 | 
        
           |  |  | 395 |                 }
 | 
        
           |  |  | 396 |             });
 | 
        
           |  |  | 397 |             node.on('gesturemove', function(e) {
 | 
        
           |  |  | 398 |                 if (editor.currentedit.tool === 'select') {
 | 
        
           |  |  | 399 |                     var x = e.clientX - node.getData('offsetx'),
 | 
        
           |  |  | 400 |                         y = e.clientY - node.getData('offsety'),
 | 
        
           |  |  | 401 |                         newlocation,
 | 
        
           |  |  | 402 |                         windowlocation,
 | 
        
           |  |  | 403 |                         bounds;
 | 
        
           |  |  | 404 |   | 
        
           |  |  | 405 |                     if (node.getData('dragging') !== true) {
 | 
        
           |  |  | 406 |                         // Collapse comment during move.
 | 
        
           |  |  | 407 |                         node.collapse(0);
 | 
        
           |  |  | 408 |                         node.setData('dragging', true);
 | 
        
           |  |  | 409 |                     }
 | 
        
           |  |  | 410 |   | 
        
           |  |  | 411 |                     newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
 | 
        
           |  |  | 412 |                     bounds = this.editor.get_canvas_bounds(true);
 | 
        
           |  |  | 413 |                     bounds.x = 0;
 | 
        
           |  |  | 414 |                     bounds.y = 0;
 | 
        
           |  |  | 415 |   | 
        
           |  |  | 416 |                     bounds.width -= 24;
 | 
        
           |  |  | 417 |                     bounds.height -= 24;
 | 
        
           |  |  | 418 |                     // Clip to the window size - the comment icon size.
 | 
        
           |  |  | 419 |                     newlocation.clip(bounds);
 | 
        
           |  |  | 420 |   | 
        
           |  |  | 421 |                     this.x = newlocation.x;
 | 
        
           |  |  | 422 |                     this.y = newlocation.y;
 | 
        
           |  |  | 423 |   | 
        
           |  |  | 424 |                     windowlocation = this.editor.get_window_coordinates(newlocation);
 | 
        
           |  |  | 425 |                     container.setX(windowlocation.x);
 | 
        
           |  |  | 426 |                     container.setY(windowlocation.y);
 | 
        
           |  |  | 427 |                     this.drawable.store_position(container, windowlocation.x, windowlocation.y);
 | 
        
           |  |  | 428 |                 }
 | 
        
           |  |  | 429 |             }, null, this);
 | 
        
           |  |  | 430 |             node.on('gesturemoveend', function() {
 | 
        
           |  |  | 431 |                 if (editor.currentedit.tool === 'select') {
 | 
        
           |  |  | 432 |                     if (node.getData('dragging') === true) {
 | 
        
           |  |  | 433 |                         node.setData('dragging', false);
 | 
        
           |  |  | 434 |                     }
 | 
        
           |  |  | 435 |                     this.editor.save_current_page();
 | 
        
           |  |  | 436 |                 }
 | 
        
           |  |  | 437 |             }, null, this);
 | 
        
           |  |  | 438 |             marker.on('gesturemovestart', function(e) {
 | 
        
           |  |  | 439 |                 if (editor.currentedit.tool === 'select') {
 | 
        
           |  |  | 440 |                     e.preventDefault();
 | 
        
           |  |  | 441 |                     node.setData('offsetx', e.clientX - container.getX());
 | 
        
           |  |  | 442 |                     node.setData('offsety', e.clientY - container.getY());
 | 
        
           |  |  | 443 |                     node.expand();
 | 
        
           |  |  | 444 |                 }
 | 
        
           |  |  | 445 |             });
 | 
        
           |  |  | 446 |             marker.on('gesturemove', function(e) {
 | 
        
           |  |  | 447 |                 if (editor.currentedit.tool === 'select') {
 | 
        
           |  |  | 448 |                     var x = e.clientX - node.getData('offsetx'),
 | 
        
           |  |  | 449 |                         y = e.clientY - node.getData('offsety'),
 | 
        
           |  |  | 450 |                         newlocation,
 | 
        
           |  |  | 451 |                         windowlocation,
 | 
        
           |  |  | 452 |                         bounds;
 | 
        
           |  |  | 453 |   | 
        
           |  |  | 454 |                     if (node.getData('dragging') !== true) {
 | 
        
           |  |  | 455 |                         // Collapse comment during move.
 | 
        
           |  |  | 456 |                         node.collapse(100);
 | 
        
           |  |  | 457 |                         node.setData('dragging', true);
 | 
        
           |  |  | 458 |                     }
 | 
        
           |  |  | 459 |   | 
        
           |  |  | 460 |                     newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
 | 
        
           |  |  | 461 |                     bounds = this.editor.get_canvas_bounds(true);
 | 
        
           |  |  | 462 |                     bounds.x = 0;
 | 
        
           |  |  | 463 |                     bounds.y = 0;
 | 
        
           |  |  | 464 |   | 
        
           |  |  | 465 |                     bounds.width -= 24;
 | 
        
           |  |  | 466 |                     bounds.height -= 24;
 | 
        
           |  |  | 467 |                     // Clip to the window size - the comment icon size.
 | 
        
           |  |  | 468 |                     newlocation.clip(bounds);
 | 
        
           |  |  | 469 |   | 
        
           |  |  | 470 |                     this.x = newlocation.x;
 | 
        
           |  |  | 471 |                     this.y = newlocation.y;
 | 
        
           |  |  | 472 |   | 
        
           |  |  | 473 |                     windowlocation = this.editor.get_window_coordinates(newlocation);
 | 
        
           |  |  | 474 |                     container.setX(windowlocation.x);
 | 
        
           |  |  | 475 |                     container.setY(windowlocation.y);
 | 
        
           |  |  | 476 |                     this.drawable.store_position(container, windowlocation.x, windowlocation.y);
 | 
        
           |  |  | 477 |                 }
 | 
        
           |  |  | 478 |             }, null, this);
 | 
        
           |  |  | 479 |             marker.on('gesturemoveend', function() {
 | 
        
           |  |  | 480 |                 if (editor.currentedit.tool === 'select') {
 | 
        
           |  |  | 481 |                     if (node.getData('dragging') === true) {
 | 
        
           |  |  | 482 |                         node.setData('dragging', false);
 | 
        
           |  |  | 483 |                     }
 | 
        
           |  |  | 484 |                     this.editor.save_current_page();
 | 
        
           |  |  | 485 |                 }
 | 
        
           |  |  | 486 |             }, null, this);
 | 
        
           |  |  | 487 |   | 
        
           |  |  | 488 |             this.menu = new M.assignfeedback_editpdf.commentmenu({
 | 
        
           |  |  | 489 |                 buttonNode: this.menulink,
 | 
        
           |  |  | 490 |                 comment: this
 | 
        
           |  |  | 491 |             });
 | 
        
           |  |  | 492 |         }
 | 
        
           |  |  | 493 |     };
 | 
        
           |  |  | 494 |   | 
        
           |  |  | 495 |     /**
 | 
        
           |  |  | 496 |      * Delete a comment.
 | 
        
           |  |  | 497 |      * @method remove
 | 
        
           |  |  | 498 |      */
 | 
        
           |  |  | 499 |     this.remove = function() {
 | 
        
           |  |  | 500 |         var i = 0;
 | 
        
           |  |  | 501 |         var comments;
 | 
        
           |  |  | 502 |   | 
        
           |  |  | 503 |         comments = this.editor.pages[this.editor.currentpage].comments;
 | 
        
           |  |  | 504 |         for (i = 0; i < comments.length; i++) {
 | 
        
           |  |  | 505 |             if (comments[i] === this) {
 | 
        
           |  |  | 506 |                 comments.splice(i, 1);
 | 
        
           |  |  | 507 |                 this.drawable.erase();
 | 
        
           |  |  | 508 |                 this.editor.save_current_page();
 | 
        
           |  |  | 509 |                 return;
 | 
        
           |  |  | 510 |             }
 | 
        
           |  |  | 511 |         }
 | 
        
           |  |  | 512 |     };
 | 
        
           |  |  | 513 |   | 
        
           |  |  | 514 |     /**
 | 
        
           |  |  | 515 |      * Event handler to remove a comment from the users quicklist.
 | 
        
           |  |  | 516 |      *
 | 
        
           |  |  | 517 |      * @protected
 | 
        
           |  |  | 518 |      * @method remove_from_quicklist
 | 
        
           |  |  | 519 |      */
 | 
        
           |  |  | 520 |     this.remove_from_quicklist = function(e, quickcomment) {
 | 
        
           |  |  | 521 |         e.preventDefault();
 | 
        
           |  |  | 522 |         e.stopPropagation();
 | 
        
           |  |  | 523 |   | 
        
           |  |  | 524 |         this.menu.hide();
 | 
        
           |  |  | 525 |   | 
        
           |  |  | 526 |         this.editor.quicklist.remove(quickcomment);
 | 
        
           |  |  | 527 |     };
 | 
        
           |  |  | 528 |   | 
        
           |  |  | 529 |     /**
 | 
        
           |  |  | 530 |      * A quick comment was selected in the list, update the active comment and redraw the page.
 | 
        
           |  |  | 531 |      *
 | 
        
           |  |  | 532 |      * @param Event e
 | 
        
           |  |  | 533 |      * @protected
 | 
        
           |  |  | 534 |      * @method set_from_quick_comment
 | 
        
           |  |  | 535 |      */
 | 
        
           |  |  | 536 |     this.set_from_quick_comment = function(e, quickcomment) {
 | 
        
           |  |  | 537 |         e.preventDefault();
 | 
        
           |  |  | 538 |   | 
        
           |  |  | 539 |         this.menu.hide();
 | 
        
           |  |  | 540 |         this.deleteme = false;
 | 
        
           |  |  | 541 |   | 
        
           |  |  | 542 |         this.rawtext = quickcomment.rawtext;
 | 
        
           |  |  | 543 |         this.width = quickcomment.width;
 | 
        
           |  |  | 544 |         this.colour = quickcomment.colour;
 | 
        
           |  |  | 545 |   | 
        
           |  |  | 546 |         this.editor.save_current_page();
 | 
        
           |  |  | 547 |   | 
        
           |  |  | 548 |         this.editor.redraw();
 | 
        
           |  |  | 549 |   | 
        
           |  |  | 550 |         this.node = this.drawable.nodes[0].one('textarea');
 | 
        
           |  |  | 551 |         this.node.ancestor('div').removeClass('commentcollapsed');
 | 
        
           |  |  | 552 |         this.node.focus();
 | 
        
           |  |  | 553 |     };
 | 
        
           |  |  | 554 |   | 
        
           |  |  | 555 |     /**
 | 
        
           |  |  | 556 |      * Event handler to add a comment to the users quicklist.
 | 
        
           |  |  | 557 |      *
 | 
        
           |  |  | 558 |      * @protected
 | 
        
           |  |  | 559 |      * @method add_to_quicklist
 | 
        
           |  |  | 560 |      */
 | 
        
           |  |  | 561 |     this.add_to_quicklist = function(e) {
 | 
        
           |  |  | 562 |         e.preventDefault();
 | 
        
           |  |  | 563 |         this.menu.hide();
 | 
        
           |  |  | 564 |         this.editor.quicklist.add(this);
 | 
        
           |  |  | 565 |     };
 | 
        
           |  |  | 566 |   | 
        
           |  |  | 567 |     /**
 | 
        
           |  |  | 568 |      * Draw the in progress edit.
 | 
        
           |  |  | 569 |      *
 | 
        
           |  |  | 570 |      * @public
 | 
        
           |  |  | 571 |      * @method draw_current_edit
 | 
        
           |  |  | 572 |      * @param M.assignfeedback_editpdf.edit edit
 | 
        
           |  |  | 573 |      */
 | 
        
           |  |  | 574 |     this.draw_current_edit = function(edit) {
 | 
        
           |  |  | 575 |         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
 | 
        
           |  |  | 576 |             shape,
 | 
        
           |  |  | 577 |             bounds;
 | 
        
           |  |  | 578 |   | 
        
           |  |  | 579 |         bounds = new M.assignfeedback_editpdf.rect();
 | 
        
           |  |  | 580 |         bounds.bound([edit.start, edit.end]);
 | 
        
           |  |  | 581 |   | 
        
           |  |  | 582 |         // We will draw a box with the current background colour.
 | 
        
           |  |  | 583 |         shape = this.editor.graphic.addShape({
 | 
        
           |  |  | 584 |             type: Y.Rect,
 | 
        
           |  |  | 585 |             width: bounds.width,
 | 
        
           |  |  | 586 |             height: bounds.height,
 | 
        
           |  |  | 587 |             fill: {
 | 
        
           |  |  | 588 |                color: COMMENTCOLOUR[edit.commentcolour]
 | 
        
           |  |  | 589 |             },
 | 
        
           |  |  | 590 |             x: bounds.x,
 | 
        
           |  |  | 591 |             y: bounds.y
 | 
        
           |  |  | 592 |         });
 | 
        
           |  |  | 593 |   | 
        
           |  |  | 594 |         drawable.shapes.push(shape);
 | 
        
           |  |  | 595 |   | 
        
           |  |  | 596 |         return drawable;
 | 
        
           |  |  | 597 |     };
 | 
        
           |  |  | 598 |   | 
        
           |  |  | 599 |     /**
 | 
        
           |  |  | 600 |      * Promote the current edit to a real comment.
 | 
        
           |  |  | 601 |      *
 | 
        
           |  |  | 602 |      * @public
 | 
        
           |  |  | 603 |      * @method init_from_edit
 | 
        
           |  |  | 604 |      * @param M.assignfeedback_editpdf.edit edit
 | 
        
           |  |  | 605 |      * @return bool true if comment bound is more than min width/height, else false.
 | 
        
           |  |  | 606 |      */
 | 
        
           |  |  | 607 |     this.init_from_edit = function(edit) {
 | 
        
           |  |  | 608 |         var bounds = new M.assignfeedback_editpdf.rect();
 | 
        
           |  |  | 609 |         bounds.bound([edit.start, edit.end]);
 | 
        
           |  |  | 610 |   | 
        
           |  |  | 611 |         // Minimum comment width.
 | 
        
           |  |  | 612 |         if (bounds.width < 100) {
 | 
        
           |  |  | 613 |             bounds.width = 100;
 | 
        
           |  |  | 614 |         }
 | 
        
           |  |  | 615 |   | 
        
           |  |  | 616 |         // Save the current edit to the server and the current page list.
 | 
        
           |  |  | 617 |   | 
        
           |  |  | 618 |         this.gradeid = this.editor.get('gradeid');
 | 
        
           |  |  | 619 |         this.pageno = this.editor.currentpage;
 | 
        
           |  |  | 620 |         this.x = bounds.x;
 | 
        
           |  |  | 621 |         this.y = bounds.y;
 | 
        
           |  |  | 622 |         this.width = bounds.width;
 | 
        
           |  |  | 623 |         this.colour = edit.commentcolour;
 | 
        
           |  |  | 624 |         this.rawtext = '';
 | 
        
           |  |  | 625 |   | 
        
           |  |  | 626 |         return (bounds.has_min_width() && bounds.has_min_height());
 | 
        
           |  |  | 627 |     };
 | 
        
           |  |  | 628 |   | 
        
           |  |  | 629 |     /**
 | 
        
           |  |  | 630 |      * Update comment position when rotating page.
 | 
        
           |  |  | 631 |      * @public
 | 
        
           |  |  | 632 |      * @method updatePosition
 | 
        
           |  |  | 633 |      */
 | 
        
           |  |  | 634 |     this.updatePosition = function() {
 | 
        
           |  |  | 635 |         var node = this.drawable.nodes[0].one('textarea');
 | 
        
           |  |  | 636 |         var container = node.ancestor('div');
 | 
        
           |  |  | 637 |   | 
        
           |  |  | 638 |         var newlocation = new M.assignfeedback_editpdf.point(this.x, this.y);
 | 
        
           |  |  | 639 |         var windowlocation = this.editor.get_window_coordinates(newlocation);
 | 
        
           |  |  | 640 |   | 
        
           |  |  | 641 |         container.setX(windowlocation.x);
 | 
        
           |  |  | 642 |         container.setY(windowlocation.y);
 | 
        
           |  |  | 643 |         this.drawable.store_position(container, windowlocation.x, windowlocation.y);
 | 
        
           |  |  | 644 |     };
 | 
        
           |  |  | 645 |   | 
        
           |  |  | 646 | };
 | 
        
           |  |  | 647 |   | 
        
           |  |  | 648 | M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 | 
        
           |  |  | 649 | M.assignfeedback_editpdf.comment = COMMENT;
 |