Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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>');
178
        menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
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;