PageSourceSearch

https://scotlandsgardens.org/framework/public/js/tg_commentable.js?v=2.21

js scotlandsgardens.org collected 2026-09-24 10:23:52 UTC 24,968 bytes, 696 lines download raw bytes

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.