PageSourceSearch

https://collaborase.com/assets/best_in_place.js?body=1

js collaborase.com collected 2026-10-04 23:17:15 UTC 25,752 bytes, 741 lines download raw bytes

1/*
2        BestInPlace (for jQuery)
3        version: 0.1.0 (01/01/2011)
4        @requires jQuery >= v1.4
5        @requires jQuery.purr to display pop-up windows
6
7        By Bernat Farrero based on the work of Jan Varwig.
8        Examples at http://bernatfarrero.com
9
10        Licensed under the MIT:
11          http://www.opensource.org/licenses/mit-license.php
12
13        Usage:
14
15        Attention.
16        The format of the JSON object given to the select inputs is the following:
17        [["key", "value"],["key", "value"]]
18        The format of the JSON object given to the checkbox inputs is the following:
19        ["falseValue", "trueValue"]
20*/
21
22
23function BestInPlaceEditor(e) {
24  this.element = e;
25  this.initOptions();
26  this.bindForm();
27  this.initNil();
28  jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
29}
30
31BestInPlaceEditor.prototype = {
32  // Public Interface Functions //////////////////////////////////////////////
33
34  activate : function() {
35    var to_display = "";
36    if (this.isNil()) {
37      to_display = "";
38    }
39    else if (this.original_content) {
40      to_display = this.original_content;
41    }
42    else {
43      if (this.sanitize) {
44        to_display = this.element.text();
45      } else {
46        to_display = this.element.html();
47      }
48    }
49
50    this.oldValue = this.isNil() ? "" : this.element.html();
51    this.display_value = to_display;
52    jQuery(this.activator).unbind("click", this.clickHandler);
53    this.activateForm();
54    this.element.trigger(jQuery.Event("best_in_place:activate"));
55  },
56
57  abort : function() {
58    this.activateText(this.oldValue);
59    jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
60    this.element.trigger(jQuery.Event("best_in_place:abort"));
61    this.element.trigger(jQuery.Event("best_in_place:deactivate"));
62  },
63
64  abortIfConfirm : function () {
65    if (!this.useConfirm) {
66      this.abort();
67      return;
68    }
69
70    if (confirm("Are you sure you want to discard your changes?")) {
71      this.abort();
72    }
73  },
74
75  update : function() {
76    var editor = this;
77    if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
78    { // Avoid request if no change is made
79      this.abort();
80      return true;
81    }
82    editor.ajax({
83      "type"       : "post",
84      "dataType"   : "text",
85      "data"       : editor.requestData(),
86      "success"    : function(data){ editor.loadSuccessCallback(data); },
87      "error"      : function(request, error){ editor.loadErrorCallback(request, error); }
88    });
89    if (this.formType == "select") {
90      var value = this.getValue();
91      this.previousCollectionValue = value;
92
93      jQuery.each(this.values, function(i, v) {
94        if (value == v[0]) {
95          editor.element.html(v[1]);
96        }
97      }
98    );
99    } else if (this.formType == "checkbox") {
100      editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
101    } else {
102      if (this.getValue() !== "") {
103        editor.element.text(this.getValue());
104      } else {
105        editor.element.html(this.nil);
106      }
107    }
108    editor.element.trigger(jQuery.Event("best_in_place:update"));
109  },
110
111  activateForm : function() {
112    alert("The form was not properly initialized. activateForm is unbound");
113  },
114
115  activateText : function(value){
116    this.element.html(value);
117    if(this.isNil()) this.element.html(this.nil);
118  },
119
120  // Helper Functions ////////////////////////////////////////////////////////
121
122  initOptions : function() {
123    // Try parent supplied info
124    var self = this;
125    self.element.parents().each(function(){
126      $parent = jQuery(this);
127      self.url               = self.url               || $parent.attr("data-url");
128      self.collection        = self.collection        || $parent.attr("data-collection");
129      self.formType          = self.formType          || $parent.attr("data-type");
130      self.objectName        = self.objectName        || $parent.attr("data-object");
131      self.attributeName     = self.attributeName     || $parent.attr("data-attribute");
132      self.activator         = self.activator         || $parent.attr("data-activator");
133      self.okButton          = self.okButton          || $parent.attr("data-ok-button");
134      self.okButtonClass     = self.okButtonClass     || $parent.attr("data-ok-button-class");
135      self.cancelButton      = self.cancelButton      || $parent.attr("data-cancel-button");
136      self.cancelButtonClass = self.cancelButtonClass || $parent.attr("data-cancel-button-class");
137      self.nil               = self.nil               || $parent.attr("data-nil");
138      self.inner_class       = self.inner_class       || $parent.attr("data-inner-class");
139      self.html_attrs        = self.html_attrs        || $parent.attr("data-html-attrs");
140      self.original_content  = self.original_content  || $parent.attr("data-original-content");
141      self.collectionValue   = self.collectionValue   || $parent.attr("data-value");
142    });
143
144    // Try Rails-id based if parents did not explicitly supply something
145    self.element.parents().each(function(){
146      var res = this.id.match(/^(\w+)_(\d+)$/i);
147      if (res) {
148        self.objectName = self.objectName || res[1];
149      }
150    });
151
152    // Load own attributes (overrides all others)
153    self.url               = self.element.attr("data-url")                 || self.url      || document.location.pathname;
154    self.collection        = self.element.attr("data-collection")          || self.collection;
155    self.formType          = self.element.attr("data-type")                || self.formtype || "input";
156    self.objectName        = self.element.attr("data-object")              || self.objectName;
157    self.attributeName     = self.element.attr("data-attribute")           || self.attributeName;
158    self.activator         = self.element.attr("data-activator")           || self.element;
159    self.okButton          = self.element.attr("data-ok-button")           || self.okButton;
160    self.okButtonClass     = self.element.attr("data-ok-button-class")     || self.okButtonClass || "";
161    self.cancelButton      = self.element.attr("data-cancel-button")       || self.cancelButton;
162    self.cancelButtonClass = self.element.attr("data-cancel-button-class") || self.cancelButtonClass || "";
163    self.nil               = self.element.attr("data-nil")                 || self.nil      || "—";
164    self.inner_class       = self.element.attr("data-inner-class")         || self.inner_class   || null;
165    self.html_attrs        = self.element.attr("data-html-attrs")          || self.html_attrs;
166    self.original_content  = self.element.attr("data-original-content")    || self.original_content;
167    self.collectionValue   = self.element.attr("data-value")               || self.collectionValue;
168
169    if (!self.element.attr("data-sanitize")) {
170      self.sanitize = true;
171    }
172    else {
173      self.sanitize = (self.element.attr("data-sanitize") == "true");
174    }
175
176    if (!self.element.attr("data-use-confirm")) {
177      self.useConfirm = true;
178    } else {
179      self.useConfirm = (self.element.attr("data-use-confirm") != "false");
180    }
181
182    if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
183    {
184      self.values = jQuery.parseJSON(self.collection);
185    }
186
187  },
188
189  bindForm : function() {
190    this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
191    this.getValue     = BestInPlaceEditor.forms[this.formType].getValue;
192  },
193
194  initNil: function() {
195    if (this.element.html() === "")
196    {
197      this.element.html(this.nil);
198    }
199  },
200
201  isNil: function() {
202    // TODO: It only work when form is deactivated.
203    //       Condition will fail when form is activated
204    return this.element.html() === "" || this.element.html() === this.nil;
205  },
206
207  getValue : function() {
208    alert("The form was not properly initialized. getValue is unbound");
209  },
210
211  // Trim and Strips HTML from text
212  sanitizeValue : function(s) {
213   return jQuery.trim(s);
214  },
215
216  /* Generate the data sent in the POST request */
217  requestData : function() {
218    // To prevent xss attacks, a csrf token must be defined as a meta attribute
219    csrf_token = jQuery('meta[name=csrf-token]').attr('content');
220    csrf_param = jQuery('meta[name=csrf-param]').attr('content');
221
222    var data = "_method=put";
223    data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
224
225    if (csrf_param !== undefined && csrf_token !== undefined) {
226      data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
227    }
228    return data;
229  },
230
231  ajax : function(options) {
232    options.url = this.url;
233    options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
234    return jQuery.ajax(options);
235  },
236
237  // Handlers ////////////////////////////////////////////////////////////////
238
239  loadSuccessCallback : function(data) {
240    data = jQuery.trim(data);
241
242    if(data && data!=""){
243      var response = jQuery.parseJSON(jQuery.trim(data));
244      if (response !== null && response.hasOwnProperty("display_as")) {
245        this.element.attr("data-original-content", this.element.text());
246        this.original_content = this.element.text();
247        this.element.html(response["display_as"]);
248      }
249
250      this.element.trigger(jQuery.Event("best_in_place:success"), data);
251      this.element.trigger(jQuery.Event("ajax:success"), data);
252    } else {
253      this.element.trigger(jQuery.Event("best_in_place:success"));
254      this.element.trigger(jQuery.Event("ajax:success"));
255    }
256
257    // Binding back after being clicked
258    jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
259    this.element.trigger(jQuery.Event("best_in_place:deactivate"));
260
261    if (this.collectionValue !== null && this.formType == "select") {
262      this.collectionValue = this.previousCollectionValue;
263      this.previousCollectionValue = null;
264    }
265  },
266
267  loadErrorCallback : function(request, error) {
268    this.activateText(this.oldValue);
269
270    this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
271    this.element.trigger(jQuery.Event("ajax:error"), request, error);
272
273    // Binding back after being clicked
274    jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
275    this.element.trigger(jQuery.Event("best_in_place:deactivate"));
276  },
277
278  clickHandler : function(event) {
279    event.preventDefault();
280    event.data.editor.activate();
281  },
282
283  setHtmlAttributes : function() {
284    var formField = this.element.find(this.formType);
285
286    if(this.html_attrs){
287      var attrs = jQuery.parseJSON(this.html_attrs);
288      for(var key in attrs){
289        formField.attr(key, attrs[key]);
290      }
291    }
292  }
293};
294
295
296// Button cases:
297// If no buttons, then blur saves, ESC cancels
298// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
299// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
300// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
301BestInPlaceEditor.forms = {
302  "input" : {
303    activateForm : function() {
304      var output = jQuery(document.createElement('form'))
305                   .addClass('form_in_place')
306                   .attr('action', 'javascript:void(0);')
307                   .attr('style', 'display:inline');
308      var input_elt = jQuery(document.createElement('input'))
309                      .attr('type', 'text')
310                      .attr('name', this.attributeName)
311                      .val(this.display_value);
312      if(this.inner_class !== null) {
313        input_elt.addClass(this.inner_class);
314      }
315      output.append(input_elt);
316      if(this.okButton) {
317        output.append(
318          jQuery(document.createElement('input'))
319          .attr('type', 'submit')
320          .attr('class', this.okButtonClass)
321          .attr('value', this.okButton)
322        )
323      }
324      if(this.cancelButton) {
325        output.append(
326          jQuery(document.createElement('input'))
327          .attr('type', 'button')
328          .attr('class', this.cancelButtonClass)
329          .attr('value', this.cancelButton)
330        )
331      }
332
333      this.element.html(output);
334      this.setHtmlAttributes();
335      this.element.find("input[type='text']")[0].select();
336      this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
337      if (this.cancelButton) {
338        this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
339      }
340      this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
341      this.element.find("input[type='text']").bind('keyup', {editor: this}
341, BestInPlaceEditor.forms.input.keyupHandler);
342      this.blurTimer = null;
343      this.userClicked = false;
344    },
345
346    getValue : function() {
347      return this.sanitizeValue(this.element.find("input").val());
348    },
349
350    // When buttons are present, use a timer on the blur event to give precedence to clicks
351    inputBlurHandler : function(event) {
352      if (event.data.editor.okButton) {
353        event.data.editor.blurTimer = setTimeout(function () {
354          if (!event.data.editor.userClicked) {
355            event.data.editor.abort();
356          }
357        }, 500);
358      } else {
359        if (event.data.editor.cancelButton) {
360          event.data.editor.blurTimer = setTimeout(function () {
361            if (!event.data.editor.userClicked) {
362              event.data.editor.update();
363            }
364          }, 500);
365        } else {
366          event.data.editor.update();
367        }
368      }
369    },
370
371    submitHandler : function(event) {
372      event.data.editor.userClicked = true;
373      clearTimeout(event.data.editor.blurTimer);
374      event.data.editor.update();
375    },
376
377    cancelButtonHandler : function(event) {
378      event.data.editor.userClicked = true;
379      clearTimeout(event.data.editor.blurTimer);
380      event.data.editor.abort();
381      event.stopPropagation(); // Without this, click isn't handled
382    },
383
384    keyupHandler : function(event) {
385      if (event.keyCode == 27) {
386        event.data.editor.abort();
387      }
388    }
389  },
390
391  "date" : {
392    activateForm : function() {
393      var that      = this,
394          output    = jQuery(document.createElement('form'))
395                      .addClass('form_in_place')
396                      .attr('action', 'javascript:void(0);')
397                      .attr('style', 'display:inline'),
398          input_elt = jQuery(document.createElement('input'))
399                      .attr('type', 'text')
400                      .attr('name', this.attributeName)
401                      .attr('value', this.sanitizeValue(this.display_value));
402      if(this.inner_class !== null) {
403        input_elt.addClass(this.inner_class);
404      }
405      output.append(input_elt)
406
407      this.element.html(output);
408      this.setHtmlAttributes();
409      this.element.find('input')[0].select();
410      this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
411      this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
412
413      this.element.find('input')
414        .datepicker({
415            onClose: function() {
416              that.update();
417            }
418          })
419        .datepicker('show');
420    },
421
422    getValue :  function() {
423      return this.sanitizeValue(this.element.find("input").val());
424    },
425
426    submitHandler : function(event) {
427      event.data.editor.update();
428    },
429
430    keyupHandler : function(event) {
431      if (event.keyCode == 27) {
432        event.data.editor.abort();
433      }
434    }
435  },
436
437  "select" : {
438    activateForm : function() {
439      var output     = jQuery(document.createElement('form'))
440                       .attr('action', 'javascript:void(0)')
441                       .attr('style', 'display:inline');
442          selected   = '',
443          oldValue   = this.oldValue,
444          select_elt = jQuery(document.createElement('select'))
445                      .attr('class', this.inned_class !== null ? this.inner_class : '' ),
446          currentCollectionValue = this.collectionValue;
447
448      jQuery.each(this.values, function (index, value) {
449        var option_elt = jQuery(document.createElement('option'))
450                         // .attr('value', value[0])
451                         .val(value[0])
452                         .html(value[1]);
453        if(value[0] == currentCollectionValue) {
454          option_elt.attr('selected', 'selected');
455        }
456        select_elt.append(option_elt);
457      });
458      output.append(select_elt);
459
460      this.element.html(output);
461      this.setHtmlAttributes();
462      this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
463      this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
464      this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
465      this.element.find("select")[0].focus();
466    },
467
468    getValue : function() {
469      return this.sanitizeValue(this.element.find("select").val());
470      // return this.element.find("select").val();
471    },
472
473    blurHandler : function(event) {
474      event.data.editor.update();
475    },
476
477    keyupHandler : function(event) {
478      if (event.keyCode == 27) event.data.editor.abort();
479    }
480  },
481
482  "checkbox" : {
483    activateForm : function() {
484      this.collectionValue = !this.getValue();
485      this.setHtmlAttributes();
486      this.update();
487    },
488
489    getValue : function() {
490      return this.collectionValue;
491    }
492  },
493
494  "textarea" : {
495    activateForm : function() {
496      // grab width and height of text
497      width = this.element.css('width');
498      height = this.element.css('height');
499
500      // construct form
501      var output   = jQuery(document.createElement('form'))
502                     .attr('action', 'javascript:void(0)')
503                     .attr('style', 'display:inline')
504                     .append(jQuery(document.createElement('textarea'))
505                             .val(this.sanitizeValue(this.display_value)));
506      if(this.okButton) {
507        output.append(
508          jQuery(document.createElement('input'))
509          .attr('type', 'submit')
510          .attr('value', this.okButton)
511        );
512      }
513      if(this.cancelButton) {
514        output.append(
515          jQuery(document.createElement('input'))
516          .attr('type', 'button')
517          .attr('value', this.cancelButton)
518        )
519      }
520
521      this.element.html(output);
522      this.setHtmlAttributes();
523
524      // set width and height of textarea
525      jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
526      jQuery(this.element.find("textarea")[0]).elastic();
527
528      this.element.find("textarea")[0].focus();
529      this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
530      if (this.cancelButton) {
531        this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
532      }
533      this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
534      this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
535      this.blurTimer = null;
536      this.userClicked = false;
537    },
538
539    getValue :  function() {
540      return this.sanitizeValue(this.element.find("textarea").val());
541    },
542
543    // When buttons are present, use a timer on the blur event to give precedence to clicks
544    blurHandler : function(event) {
545      if (event.data.editor.okButton) {
546        event.data.editor.blurTimer = setTimeout(function () {
547          if (!event.data.editor.userClicked) {
548            event.data.editor.abortIfConfirm();
549          }
550        }, 500);
551      } else {
552        if (event.data.editor.cancelButton) {
553          event.data.editor.blurTimer = setTimeout(function () {
554            if (!event.data.editor.userClicked) {
555              event.data.editor.update();
556            }
557          }, 500);
558        } else {
559          event.data.editor.update();
560        }
561      }
562    },
563
564    submitHandler : function(event) {
565      event.data.editor.userClicked = true;
566      clearTimeout(event.data.editor.blurTimer);
567      event.data.editor.update();
568    },
569
570    cancelButtonHandler : function(event) {
571      event.data.editor.userClicked = true;
572      clearTimeout(event.data.editor.blurTimer);
573      event.data.editor.abortIfConfirm();
574      event.stopPropagation(); // Without this, click isn't handled
575    },
576
577    keyupHandler : function(event) {
578      if (event.keyCode == 27) {
579        event.data.editor.abortIfConfirm();
580      }
581    }
582  }
583};
584
585jQuery.fn.best_in_place = function() {
586
587  function setBestInPlace(element) {
588    if (!element.data('bestInPlaceEditor')) {
589      element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
590      return true;
591    }
592  }
593
594  jQuery(this.context).delegate(this.selector, 'click', function () {
595    var el = jQuery(this);
596    if (setBestInPlace(el))
597      el.click();
598  });
599
600  this.each(function () {
601    setBestInPlace(jQuery(this));
602  });
603
604  return this;
605};
606
607
608
609/**
610* @name             Elastic
611* @descripton           Elastic is Jquery plugin that grow and shrink y
611our textareas automaticliy
612* @version            1.6.5
613* @requires           Jquery 1.2.6+
614*
615* @author             Jan Jarfalk
616* @author-email         [email protected]
617* @author-website         http://www.unwrongest.com
618*
619* @licens             MIT License - http://www.opensource.org/licenses/mit-license.php
620*/
621
622(function(jQuery){
623  if (typeof jQuery.fn.elastic !== 'undefined') return;
624
625  jQuery.fn.extend({
626    elastic: function() {
627      //  We will create a div clone of the textarea
628      //  by copying these attributes from the textarea to the div.
629      var mimics = [
630        'paddingTop',
631        'paddingRight',
632        'paddingBottom',
633        'paddingLeft',
634        'fontSize',
635        'lineHeight',
636        'fontFamily',
637        'width',
638        'fontWeight'];
639
640      return this.each( function() {
641
642        // Elastic only works on textareas
643        if ( this.type != 'textarea' ) {
644          return false;
645        }
646
647        var $textarea = jQuery(this),
648          $twin   = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
649          lineHeight  = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
650          minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
651          maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
652          goalheight  = 0,
653          i       = 0;
654
655        // Opera returns max-height of -1 if not set
656        if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
657
658        // Append the twin to the DOM
659        // We are going to meassure the height of this, not the textarea.
660        $twin.appendTo($textarea.parent());
661
662        // Copy the essential styles (mimics) from the textarea to the twin
663        i = mimics.length;
664        while(i--){
665          $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
666        }
667
668
669        // Sets a given height and overflow state on the textarea
670        function setHeightAndOverflow(height, overflow){
671          curratedHeight = Math.floor(parseInt(height,10));
672          if($textarea.height() != curratedHeight){
673            $textarea.css({'height': curratedHeight + 'px','overflow':overflow});
674
675          }
676        }
677
678
679        // This function will update the height of the textarea if necessary
680        function update() {
681
682          // Get curated content from the textarea.
683          var textareaContent = $textarea.val().replace(/&/g,'&amp;').replace(/  /g, '&nbsp;').replace(/<|>/g, '&gt;').replace(/\n/g, '<br />');
684
685          // Compare curated content with curated twin.
686          var twinContent = $twin.html().replace(/<br>/ig,'<br />');
687
688          if(textareaContent+'&nbsp;' != twinContent){
689
690            // Add an extra white space so new rows are added when you are at the end of a row.
691            $twin.html(textareaContent+'&nbsp;');
692
693            // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
694            if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
695
696              var goalheight = $twin.height()+lineHeight;
697              if(goalheight >= maxheight) {
698                setHeightAndOverflow(maxheight,'auto');
699              } else if(goalheight <= minheight) {
700                setHeightAndOverflow(minheight,'hidden');
701              } else {
702                setHeightAndOverflow(goalheight,'hidden');
703              }
704
705            }
706
707          }
708
709        }
710
711        // Hide scrollbars
712        $textarea.css({'overflow':'hidden'});
713
714        // Update textarea size on keyup, change, cut and paste
715        $textarea.bind('keyup change cut paste', function(){
716          update();
717        });
718
719        // Compact textarea on blur
720        // Lets animate this....
721        $textarea.bind('blur',function(){
722          if($twin.height() < maxheight){
723            if($twin.height() > minheight) {
724              $textarea.height($twin.height());
725            } else {
726              $textarea.height(minheight);
727            }
728          }
729        });
730
731        // And this line is to catch the browser paste event
732        $textarea.on("input paste", function(e){ setTimeout( update, 250); });
733
734        // Run update once when elastic is initialized
735        update();
736
737      });
738
739        }
740    });
741})(jQuery);

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.