1function tg_commentable(options){ 2 3 var pub = this; 4 var priv = { 5 6 }; 7 8 pub.object_name = 'tg_commentable'; 9 10 pub.s = { 11 class: 'tg_commentable', // the class which applies to the container you have the comments in 12 //commentable: null, // the element you are commenting on, 13 placeholder: 'Add Your Note / Comment', 14 insert_url: '/comments/comments/insert/', 15 update_url: '/comments/comments/update/', 16 edit_url: '/comments/comments/edit/', 17 delete_url: '/comments/comments/delete/', 18 archive_url: '/comments/comments/archive/', 19 unarchive_url: '/comments/comments/unarchive/', 20 load_url: '/comments/comments/get_comments_by_entity/', 21 update_relation_url: '/comments/comments/update_relation/', 22 init_tags_url: '/tags/tags/tree_list/', 23 init_tags_data: { 'tag_root': 183 }, 24 before_update: false, 25 $empty_message: $('<div class="text-muted">No notes/comments left yet.</div>'), 26 $edited_badge: $('<span class="label label-default tg_comment_edited_badge">Edited</span>'), 27 $new_badge: $('<span class="label label-success tg_comment_new_badge">New</span>'), 28 user_id: false, // user id in order to be able to tell what is new for this user 29 comment_entity_name_field: '', // can also be a function which returns the field's value instead of passing the field name 30 //$loading_icon: $('<span></span>'); 31 }; 32 33 pub.$container = null; 34 pub.tooltip = null; 35 pub.$content = null; 36 37 pub.entity_name = false; 38 pub.entity_id = false; 39 pub.entity = false; 40 41 pub.cog_tooltip = false; // the tooltip on the cog icon on every comment 42 pub.cog_tooltip_buttons = {}; // the buttons within the cog tooltip so we can hide/show them before opening tooltip 43 44 pub.tagger_tooltip = false; 45 pub.tagger = false; 46 47 pub.parsed_tags = {}; 48 pub.tagges_parsed_promise = false; 49 50 51 52 // the at.js settings 53 priv.atwho = { 54 at: "@", 55 searchKey: 'the_label', 56 callbacks: { 57 remoteFilter: function(query, callback) { 58 if(query.length > 2) { 59 $.getJSON("/users/users/typeahead/", { search: query }, function(data) { 60 callback(data); 61 }); 62 } 63 } 64 }, 65 displayTpl: '<li>${the_label}</li>', //<small>${the_value}</small> 66 insertTpl: '[@${the_value}::${the_label} ]', 67 }; 68 69 priv.ini = function(options){ 70 $.extend(pub.s, options || {}); 71
72 pub.$container = $('<div class="'+pub.s.class+'">'); 73 pub.$content = $('<div>'); 74 75 if($php && $php.user_id){ 76 pub.s.user_id = $php.user_id; 77 } 78 79 //var $add_a_commment = $('<button class="btn btn-primary"> Add a Note / Comment</button>'); 80 //$add_a_commment.on('click', function(){}); 81 82 pub.textarea = new tg_textarea({ 83 placeholder: pub.s.placeholder, 84 classes: 'tg_comment_main_post_field', 85 update_url: pub.s.insert_url, 86 update_dom: false, 87 auto_update: false, 88 column: 'comment_text', 89 after_init:function(p, pr){ 90 var $navbar = $('<div class="tg_comment_insert_navbar">'); 91 var $post = $('<button class="btn btn-sm btn-primary tg_comment_post_field_button">Post Your Note / Comment</button>'); 92 $post.on('click', function(){ 93 p.update(); 94 }); 95 96 $navbar.append($post); 97 p.container.append($navbar); 98 p.field.atwho(priv.atwho); 99 }, 100 before_update: function(data, p){ 101 if(!p.value()){ 102 editable_toast('comments_can_not_be_empty'); 103 return false; 104 } 105 106 data.comment_entity = pub.entity_name; 107 data.comment_entity_id = pub.entity_id; 108 109 if(typeof pub.s.comment_entity_name_field === 'function'){ 110 data.comment_entity_name = pub.s.comment_entity_name_field(pub.entity, pub, priv); 111 }else{ 112 data.comment_entity_name = pub.entity[pub.s.comment_entity_name_field]; 113 } 114 115 data.comment_parent = 0; 116 data.comment_root = 0; 117 118 if(typeof pub.s.before_insert === 'function'){ 119 var rs = pub.s.before_insert(data, pub); 120 if(rs === false){ 121 return false; 122 } 123 } 124 }, 125 onsuccess: function(p, res){ 126 //pub.parent = res.data; 127 //pub.root = res.data; 128 pub.load(); 129 pub.textarea.init(''); 130 } 131 }); 132 133 /* 134 pub.show_archived = new tg_input({ 135 boolean: true, 136 label: 'Show archived comments', 137 }); 138 //pub.show_archived.get() 139 */ 140 141 pub.$container.append(pub.textarea.get(), pub.$content); 142 143 // delete 144 pub.cog_tooltip_buttons.delete = $('<button class="btn btn-sm btn-danger"><span class="glyphicon glyphicon glyphicon-trash" title="Delete Comment"></span></button>'); 145 pub.cog_tooltip_buttons.delete.on('click', function(){ 146 pub.cog_tooltip.close(); 147 ask({ 148 message: 'Would you not prefer to archive it? If so click "Cancel". <br><br> Deleting a comment will delete all replies as well. This action can not be undone. <br><br> Are you sure you would like to go ahead and delete it?', 149 yes: function() { 150 var $comment = pub.cog_tooltip.$attached_to.closest('.tg_comment'); 151 pub.delete($comment); 152 } 153 }); 154 }); 155 156 // archive 157 pub.cog_tooltip_buttons.archive = $('<button class="btn btn-sm btn-warning"><span class="glyphicon glyphicon glyphicon-ok" title="Resolve Comment"></span></button>'); 158 pub.cog_tooltip_buttons.archive.on('click', function(){ 159 pub.cog_tooltip.close(); 160 ask({ 161 message: 'You are about to archive the comment. Please confirm.', 162 yes: function() { 163 var $comment = pub.cog_tooltip.$attached_to.closest('.tg_comment'); 164 pub.archive($comment); 165 } 166 }); 167 }); 168 169 // unarchive unused for now 170 pub.cog_tooltip_buttons.unarchive = $('<button class="btn btn-sm btn-success">Unarchive</button>'); 171 pub.cog_tooltip_buttons.unarchive.on('click', function(){ 172 pub.cog_tooltip.close(); 173 var $comment = pub.cog_tooltip.$attached_to.closest('.tg_comment'); 174 pub.unarchive($comment); 175 }); 176 177 // edit 178 pub.cog_tooltip_buttons.edit = $('<button class="btn btn-sm btn-success"><span class="glyphicon glyphicon glyphicon-pencil" title="Edit Comment"></span></button>'); 179 pub.cog_tooltip_buttons.edit.on('click', function(){ 180 pub.cog_tooltip.close(); 181 var $comment = pub.cog_tooltip.$attached_to.closest('.tg_comment'); 182 pub.edit($comment); 183 }); 184 185 var tags_data = {}; 186 var promise = action({ 187 url: pub.s.init_tags_url, 188 data: pub.s.init_tags_data, 189 onsuccess: function(res){ 190 tags_data = res.data.data; 191 } 192 }); 193 194 $.when(promise).then(function(){ 195 priv.parse_tags(tags_data); 196 pub.tagger = new tg_select_dropdown({ 197 the_name: 'comment_tagger', 198 options: tags_data,
199 container_class: 'tg_commentable_tagger_select', 200 actions_options: false, 201 relation: 'tags', 202 //remote:'/tags/tags/tree_list/', 203 label: 'tag_name', 204 value: 'tag_id', 205 multiple: true, 206 title: '<span class="glyphicon glyphicon glyphicon-tag" title="Tags"></span>', 207 data: { 'tag_root': 183 }, 208 tree: true, 209 selected_text_format: 'count > 0', 210 count_selected_text: function(){ return ''; }, 211 selected_class: 'comment_is_tagged', 212 style: 'btn btn-primary btn-sm', 213 on_change:function(val){ 214 var $el = pub.cog_tooltip.get_attached_to(); 215 216 if(!$el || $el.length != 1){ 217 console.log('element not found'); 218 } 219 var $comment = $el.closest('.tg_comment'); 220 if($comment.length != 1){ 221 console.log('comment not found'); 222 } 223 224 var comment = $comment.data('comment'); 225 var data = { 226 id: comment.comment_id, 227 relation_ids: val.val(), 228 relation_name: 'tags' 229 }; 230 231 action({ 232 url: pub.s.update_relation_url, 233 data: data, 234 onsuccess: function(){ 235 pub.load(); 236 } 237 }); 238 239 } 240 241 }); 242 243 var $tag = pub.tagger.get(); 244 pub.cog_tooltip = new tg_tooltip({ 245 position_on_open: false, 246 close_on_leave: false, 247 content: [ 248 pub.cog_tooltip_buttons.edit, 249 pub.cog_tooltip_buttons.delete, 250 pub.cog_tooltip_buttons.archive, 251 $tag 252 ], 253 classes: 'tg_comment_cog_tooltip', 254 on_click_outside_tooltip: function(event){ 255 // stop tooltip closing 256 var $t = $(event.target); 257 if($t.closest('.dropdown-menu').length > 0){ 258 return false; 259 } 260 }, 261 on_close: function(tooltip_pub, tooltip_priv){ 262 //var $el = tooltip_pub.$attached_to; 263 264 }, 265 on_attach: function(tooltip_pub, tooltip_priv){ 266 267 var $attached_to = tooltip_pub.get_attached_to(); 268 var comment = $attached_to.data('comment'); 269 if(comment && comment.tagids){ 270 var tagids_array = comment.tagids.split(','); 271 pub.tagger.value(tagids_array); 272 }else{ 273 pub.tagger.value([]); 274 } 275 }, 276 on_open:function(){ 277 pub.tagger.render(); 278 } 279 }); 280 281 }); 282 //out(window.location.pathname); 283 284 }; 285 286 priv.parse_tags = function(tags){ 287 288 if(tags && tags.length > 0){ 289 $.each(tags, function(i, row){ 290 var tag = row.tag_html ? row.tag_html : '<span class="label label-info label-sm tg_comment_tags_label" data-tag_id="'+row.tag_id+'">'+row.tag_name+'</span>'; 291 pub.parsed_tags[row.tag_id] = tag; 292 }); 293 } 294 }; 295 296 pub.edit = function($comment){ 297 298 var $body = $comment.find('.tg_comment_body').first(); 299 300 // see if the body is already contains the edit field 301 if($body.find('.tg_comment_inline_field_update').length > 0){ 302 return; 303 } 304 var comment = $comment.data('comment'); 305 var comment_text = comment.comment_text; 306 var comment_html = $body.html(); 307 308 var field = new tg_textarea({ 309 auto_update: false, 310 classes: 'tg_comment_inline_field_update', 311 update_url: pub.s.edit_url, 312 update_dom: false, 313 column: 'comment_text', 314 destroy_after_success: true, 315 destroy_on_esc: true, 316 on_escape: function(){ 317 $body.empty().append(comment_html); 318 }, 319 after_init:function(p, pr){ 320 p.init(comment_text); 321 p.set_primary_key('comment_id'); 322 p.set_entity($comment.data('comment')); 323 324 p.field.atwho(priv.atwho); 325
326 var $navbar = $('<div class="tg_comment_insert_navbar">'); 327 var $save = $('<button class="btn btn-sm btn-success tg_comment_post_field_button">Save</button>'); 328 $save.on('click', function(){ 329 p.update(); 330 }); 331 332 var $cancel = $('<button class="btn btn-sm btn-warning tg_comment_post_field_button">Cancel</button>'); 333 $cancel.on('click', function(){ 334 $body.empty().append(comment_html); 335 p.destroy(); 336 }); 337 338 $navbar.append($save, $cancel); 339 p.container.append($navbar); 340 }, 341 before_update: function(data, p){ 342 if(!p.value()){ 343 editable_toast('comments_can_not_be_empty'); 344 return false; 345 } 346 }, 347 onsuccess: function(p, res){ 348 $body.empty().append(res.data.comment_html); 349 350 // add edited label to it if it does not have it yet 351 if($comment.find('.tg_comment_head:first').find('.tg_comment_edited_badge').length === 0){ 352 $comment.find('.tg_comment_date:first').after(pub.s.$edited_badge); 353 } 354 355 // update the time on the field 356 var $date = priv.generate_date_badge(res.data); 357 $comment.find('.tg_comment_head:first').find('.tg_comment_date').replaceWith($date); 358 359 // update entity 360 361 comment.comment_html = res.data.comment_html; 362 comment.comment_text = res.data.comment_text; 363 comment.comment_last_updated = res.data.comment_last_updated; 364 } 365 }); 366 367 $body.empty().append(field.get()); 368 field.focus(); 369 //var comment = $comment.data('comment'); 370 //var selected = comment.comment_id; 371 372 373 374 375 }; 376 377 priv.generate_date_badge = function(comment){ 378 var ago = priv.format_ago(comment); 379 var $date = $('<span class="tg_comment_date" title="'+moment(comment.comment_last_updated).format('LT LL')+'"> - '+ago+'</span>'); 380 $date.data('comment_last_updated', comment.comment_last_updated); 381 return $date; 382 }; 383 384 pub.archive = function($comment){ 385 var comment = $comment.data('comment'); 386 var selected = comment.comment_id; 387 388 pub.$content.addClass('tg_loading_background'); 389 390 action({ 391 url: pub.s.archive_url, 392 data: { selected: selected }, 393 onsuccess: function(){ 394 pub.load(); 395 } 396 }); 397 }; 398 399 pub.unarchive = function($comment){ 400 var comment = $comment.data('comment'); 401 var selected = comment.comment_id; 402 403 pub.$content.addClass('tg_loading_background'); 404 action({ 405 url: pub.s.unarchive_url, 406 data: { selected: selected }, 407 onsuccess: function(){ 408 pub.load(); 409 } 410 }); 411 }; 412 413 pub.delete = function($comment){ 414 var comment = $comment.data('comment'); 415 var selected = comment.comment_id; 416 pub.$content.addClass('tg_loading_background'); 417 action({ 418 url: pub.s.delete_url, 419 data: { selected: selected }, 420 onsuccess: function(){ 421 pub.load(); 422 } 423 }); 424 }; 425 426 pub.get = function(){ 427 return pub.$container; 428 } 429 430 pub.load = function(){ 431 pub.$content.addClass('tg_loading_background'); 432 if(typeof pub.s.before_load === 'function'){ 433 var rs = pub.s.before_load(pub); 434 if(rs === false){ 435 pub.$content.removeClass('tg_loading_background'); 436 return; 437 } 438 } 439 440 var data = { 441 comment_entity_id: pub.entity_id, 442 comment_entity: pub.entity_name, 443 }; 444 445 var promise = action({ 446 url: pub.s.load_url, 447 data: data, 448 onsuccess: function(res){ 449 pub.render_loaded_comments(res.data); 450 } 451 }); 452 453 promise.always(function(){ 454 pub.$content.removeClass('tg_loading_background'); 455 }) 456 } 457 458 pub.comment_template = function(comment){ 459 460 var $comment = $('<div class="tg_comment tg_comment-'+comment.comment_id+' tg_comment_depth_'+comment.depth+'">'); 461 $comment.data('comment', comment); 462 463 var $head = $('<div class="tg_comment_head">'); 464 465 var username = comment.user_firstname + ' '+ comment.user_lastname; 466 username.trim(); 467 var $user = $('<span class="tg_comment_user">'+username+'<span>'); 468 469 var $date = priv.generate_date_badge(comment); 470 //$('<span class="tg_comment_date" title="'+moment(comment.comment_last_updated).format('LT LL')+'"> - '+ago+'</span>'); 471 472 var $deadline = ''; 473 if(comment.comment_deadline){ 474 $deadline = $('<span class="tg_comment_deadline">'); 475 } 476 //class="btn-group"
477 var $menu = $('<div class="tg_comments_menu">'); 478 var $cog = $('<span class="text-muted tg_comments_custom"><span class="glyphicon glyphicon-cog"></span>'); 479 $cog.data('comment', comment); 480 $cog.on('click', function(){ 481 var $button = $(this); 482 var $comment = $button.closest('.tg_comment'); 483 priv.open_cog_tooltip($comment, $button); 484 }); 485 486 487 var $tags = $('<span class="tg_comment_tags">'); 488 if(comment.tagids){ 489 var tag_ids_array = comment.tagids.split(','); 490 491 $.each(tag_ids_array, function(i, tag_id){ 492 if(pub.parsed_tags && pub.parsed_tags[tag_id]){ 493 $tags.append(pub.parsed_tags[tag_id]); 494 } 495 }) 496 } 497 498 $menu.append($cog); 499 500 501 if(comment.user_avatar){ 502 // gravatar 503 if(comment.user_avatar == 'G' && comment.user_email){ 504 var $thumbnail = $('<div class="tg_comment_thumbnail"><div class="tg_comment_thumbnail_wrapper"><img src="https://www.gravatar.com/avatar/'+md5(comment.user_email)+'?s=36" /></div></div>'); 505 }else{ 506 var $thumbnail = $('<div class="tg_comment_thumbnail"><div class="tg_comment_thumbnail_wrapper"><img src="'+comment.user_avatar+'" /></div></div>'); 507 } 508 }else{ 509 var $thumbnail = $('<div class="tg_comment_thumbnail"><span class="glyphicon glyphicon-user"></span></div>'); 510 } 511 512 var $body = $('<div class="tg_comment_body">'+comment.comment_html+'</div>'); 513 514 var $edited_badge = ''; 515 if(comment.comment_edited){ 516 $edited_badge = pub.s.$edited_badge.clone(); 517 } 518 519 $head.append($user, $date, $deadline, $edited_badge, $tags, $menu); 520 521 var $children = $('<div class="tg_comment_children">'); 522 var $footer = $('<div class="tg_comment_footer">'); 523 var $reply = $('<button class="btn btn-muted btn-xs tg_comment_reply">Reply</button>'); 524 525 $reply.on('click', function(event){ 526 var $this = $(this); 527 var $parent = $this.closest('.tg_comment'); 528 var parent = $parent.data('comment'); 529 if(!parent){ 530 throw 'Parent of this comment not found'; 531 } 532 533 if(!$this.data('tg_comment_temp_textfield')){ 534 var field = priv.create_comment_field(parent, $this); 535 $parent.find('.tg_comment_children:first').prepend(field.get()); 536 field.focus(); 537 } 538 539 }); 540 541 //var $reply = $('<span class="tg_comment_reply">Reply</span>'); 542 543 $footer.append($reply); 544 545 $comment.append($thumbnail, $head, $body, $footer, $children); 546 return $comment; 547 }; 548 549 pub.render_loaded_comments = function(comments){ 550 pub.$content.empty(); 551 552 if(comments){ 553 var parents = {}; 554 var comments_obj = {}; 555 556 var render = function(parent){ 557 $.each(parent, function(i, child){ 558 if(child.comment_parent == 0){ 559 pub.$content.append(pub.comment_template(child)); 560 }else{ 561 pub.$content.find('.tg_comment-'+child.comment_parent+' .tg_comment_children:first').append(pub.comment_template(child)); 562 } 563 564 if(parents[child.comment_id]){ 565 render(parents[child.comment_id]); 566 } 567 }); 568 }; 569 570 $.each(comments, function(i, comment){ 571 comments_obj[comment.comment_id] = comment; 572 if(!parents[comment.comment_parent]){ 573 parents[comment.comment_parent] = []; 574 } 575 576 parents[comment.comment_parent].push(comment); 577 }); 578 579 render(parents[0]); 580 581 }else{ 582 pub.$content.append(pub.s.$empty_message); 583 } 584 }; 585 586 priv.open_cog_tooltip = function($comment, $button){ 587 pub.cog_tooltip.open(); 588 pub.cog_tooltip.attach($button, true); 589 590 591 /* 592 $range.on( "mouseleave", function(event){ 593 // only close if tooltip is not hovered because of position collision this is needed 594 priv.range_hover_timeout = setTimeout(function(){ 595 if(pub.range_tooltip.hovering === false){ 596 pub.range_tooltip.close(); 597 priv.range_hover_timeout = null; 598 } 599 }, 200); 600 }) 601 */ 602 }; 603 604 priv.open_tagger_tooltip = function($comment, $button){ 605 pub.tagger_tooltip.open(); 606 pub.tagger_tooltip.attach($button, true); 607 }; 608 609 priv.create_comment_field = function(parent, $button){ 610 611 var field = new tg_textarea({ 612 placeholder: pub.s.placeholder, 613 auto_update: false, 614 classes: 'tg_comment_inline_field', 615 update_url: pub.s.insert_url, 616 update_dom: false, 617 column: 'comment_text', 618 destroy_after_success: true, 619 destroy_on_esc: true, 620 before_destroy: function(p){ 621 $button.removeData('tg_comment_temp_textfield'); 622 }, 623 after_init:function(p, pr){
624 var $navbar = $('<div class="tg_comment_insert_navbar">'); 625 var $post = $('<button class="btn btn-sm btn-success tg_comment_post_field_button">Post</button>'); 626 $post.on('click', function(){ 627 p.update(); 628 }); 629 630 p.field.atwho(priv.atwho); 631 632 var $cancel = $('<button class="btn btn-sm btn-warning tg_comment_post_field_button">Cancel</button>'); 633 $cancel.on('click', function(){ 634 p.destroy(); 635 }); 636 637 $navbar.append($post, $cancel); 638 p.container.append($navbar); 639 $button.data('tg_comment_temp_textfield', true); 640 }, 641 before_update: function(data, p){ 642 if(!p.value()){ 643 editable_toast('comments_can_not_be_empty'); 644 return false; 645 } 646 data.comment_entity = pub.entity_name; 647 data.comment_entity_id = pub.entity_id; 648 data.comment_parent = parent.comment_id; 649 650 if(typeof pub.s.comment_entity_name_field === 'function'){ 651 data.comment_entity_name = pub.s.comment_entity_name_field(pub.entity, pub, priv); 652 }else{ 653 data.comment_entity_name = pub.entity[pub.s.comment_entity_name_field]; 654 } 655 656 if(parent.comment_root){ 657 data.comment_root = parent.comment_root; 658 }else{ 659 data.comment_root = parent.comment_id; 660 } 661 662 if(typeof pub.s.before_insert === 'function'){ 663 var rs = pub.s.before_insert(data, pub); 664 if(rs === false){ 665 return false; 666 } 667 } 668 }, 669 onsuccess: function(p, res){ 670 pub.load(); 671 } 672 }); 673 674 return field; 675 }; 676 677 priv.format_ago = function(comment){ 678 return date_format_ago(comment.comment_last_updated); 679 } 680 681 pub.set_entity_name = function(v){ 682 pub.entity_name = v; 683 }; 684 685 pub.set_entity_id = function(v){ 686 pub.entity_id = v; 687 }; 688 689 pub.set_entity = function(v){ 690 pub.entity = v; 691 }; 692 693 priv.ini(options); 694 695 696};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.