1;(function ($, window, document, undefined) { 2 var pluginName = "ErratumReporter"; 3 4 var defaults = { 5 charsAround: 60, 6 maxTextLen: 220, 7 sendUrl: '/ajax/erratum_report/', 8 modalSelector: '#erratum', 9 messages: { 10 title: 'Ñîîáùèòü îá îøèáêå', 11 description: 'Âûäåëåííûé òåêñò â ñòàòüå:', 12 commentInfo: 'Êîììåíòàðèé (íå îáÿçàòåëüíî):', 13 sendButton: 'Îòïðàâèòü', 14 cancelButton: 'Çàêðûòü', 15 warning: 'Ïðåäóïðåæäåíèå', 16 info: 'Èíôîðìàöèÿ', 17 error: 'Îøèáêà', 18 tooBig: 'Âûäåëåííûé òåêñò ñëèøêîì áîëüøîé äëÿ îòïðàâêè', 19 sendError: 'Íå óäàëîñü îòïðàâèòü ñîîáùåíèå. Ïîæàëóéñòà, ïîïðîáóéòå ïîçæå.', 20 thanks: 'Ñïàñèáî! Âàøå ñîîáùåíèå îòïðàâëåíî.' 21 }, 22 onSuccess: $.noop, 23 onError: $.noop, 24 transformReport: function(report) { 25 return report; 26 } 27 }; 28 29 function Plugin(options) { 30 this.options = $.extend(true, {}, defaults, options); 31 this.init(); 32 } 33 34 Plugin.prototype = { 35 init: function() { 36 var self = this; 37 // keypress óñòàðåë, èñïîëüçóåì keydown 38 $(document).on('keydown.erratum', function(event) { 39 var call = false; 40 if (window.event) { 41 call = window.event.keyCode == 10 || (window.event.keyCode == 13 && window.event.ctrlKey); 42 } else if (event) { 43 call = (event.which == 10 && event.modifiers == 2) || 44 (event.keyCode == 0 && event.charCode == 106 && event.ctrlKey) || 45 (event.keyCode == 13 && event.ctrlKey); 46 } 47 if (call) { 48 self.process(); 49 } 50 }); 51 }, 52 53 process: function() { 54 var selection = this.getSelection(); 55 if (!selection) return; 56 57 this.selection = selection; 58 this.showDialog(); 59 }, 60 61 _getVisibleTextSimple: function(range) { 62 if (!range || range.collapsed) return ''; 63 64 // Êëîíèðóåì ñîäåðæèìîå äèàïàçîíà â DocumentFragment 65 var fragment = range.cloneContents(); 66 var tempDiv = document.createElement('div'); 67 tempDiv.appendChild(fragment); 68 69 // Ðåêóðñèâíî óäàëÿåì ñêðûòûå ýëåìåíòû 70 this._removeHiddenNodes(tempDiv); 71 72 // Èçâëåêàåì î÷èùåííûé òåêñò 73 return tempDiv.textContent.replace(/\s+/g, ' ').trim(); 74 }, 75 76 _removeHiddenNodes: function(element) { 77 // Îáðàáàòûâàåì ñíèçó ââåðõ, ÷òîáû íå ïðîïóñòèòü âëîæåííûå ñêðûòûå ýëåìåíòû 78 var nodes = element.querySelectorAll('*'); 79 for (var i = nodes.length - 1; i >= 0; i--) { 80 var node = nodes[i]; 81 var style = window.getComputedStyle(node); 82 var tag = node.tagName.toLowerCase(); 83 84 // Óäàëÿåì ñëóæåáíûå òåãè è ñêðûòûå ýëåìåíòû 85 if (['script', 'style', 'noscript', 'meta', 'link', 'head'].includes(tag) || 86 style.display === 'none' || 87 style.visibility === 'hidden') { 88 node.remove(); 89 } 90 } 91 }, 92 93 getSelection: function() { 94 try { 95 var selection = window.getSelection ? window.getSelection() : (document.selection ? document.selection : null); 96 if (!selection || selection.isCollapsed) return null; 97 98 var textBefore = '', text = '', textAfter = ''; 99 var rangeBefore, range, rangeAfter; 100 101 if (selection.getRangeAt) { 102 range = selection.getRangeAt(0); 103 text = this._getVisibleTextSimple(range); 104 105 rangeBefore = document.createRange(); 106 rangeBefore.setStartBefore(document.body); 107 rangeBefore.setEnd(range.startContainer, range.startOffset); 108 textBefore = this._getVisibleTextSimple(rangeBefore); 109 textBefore = textBefore.substring(textBefore.length - this.options.charsAround, textBefore.length); 110 111 rangeAfter = document.createRange(); 112 rangeAfter.setStart(range.endContainer, range.endOffset); 113 rangeAfter.setEndAfter(document.body); 114 textAfter = this._getVisibleTextSimple(rangeAfter); 115 textAfter = textAfter.substring(0, this.options.charsAround); 116 } else if (selection.createRange) { // Legacy IE 117 range = selection.createRange(); 118 text = range.text; 119 120 rangeBefore = selection.createRange(); 121 rangeBefore.moveStart('character', -this.options.charsAround); 122 rangeBefore.moveEnd('character', -text.length); 123 textBefore = rangeBefore.text; 124 125 rangeAfter = selection.createRange(); 126 rangeAfter.moveStart('character', text.length); 127 rangeAfter.moveEnd('character', this.options.charsAround); 128 textAfter = rangeAfter.text; 129 } 130 131 if (text.replace(/\s/g, '').length === 0) return null; 132 if (text.length > this.options.maxTextLen) { 133 this.showWarning(this.options.messages.tooBig); 134 return null; 135 } 136
137 text = this.clearSpaces(text); 138 textBefore = this.clearSpaces(textBefore); 139 textAfter = this.clearSpaces(textAfter); 140 141 textBefore = textBefore.replace(/^\S{1,10}\s+/, " "); 142 textAfter = textAfter.replace(/\s+\S{1,10}$/, " "); 143 144 var cntLeadingSpaces = (text.match(/^(\s*)/) || [""])[0].length; 145 var cntTrailingSpaces = (text.match(/(\s*)$/) || [""])[0].length; 146 147 textBefore += text.substring(0, cntLeadingSpaces); 148 textAfter = text.substring(text.length - cntTrailingSpaces) + textAfter; 149 text = text.substring(cntLeadingSpaces, text.length - cntTrailingSpaces); 150 151 return { text_before: textBefore, text: text, text_after: textAfter }; 152 } catch (e) { 153 return null; 154 } 155 }, 156 157 showDialog: function() { 158 var self = this; 159 var options = this.options; 160 var modalEl = document.querySelector(options.modalSelector); 161 162 if (!modalEl) { 163 console.error('ErratumReporter: Modal element not found:', options.modalSelector); 164 return; 165 } 166 167 // Èíèöèàëèçèðóåì ýêçåìïëÿð Bootstrap Modal, åñëè åù¸ íå ñîçäàí 168 if (!this.bsModal) { 169 this.bsModal = new bootstrap.Modal(modalEl); 170 } 171 172 // Ôîðìèðóåì êîíòåíò äëÿ body 173 var htmlContent = 174 '<p class="mb-2">' + options.messages.description + '</p>' + 175 '<div class="card bg-body-tertiary"><div class="card-body">' + 176 self.selection.text_before + 177 '<span class="text-danger">' + self.selection.text + '</span>' + 178 self.selection.text_after + 179 '</div></div>' + 180 '<div class="mt-7">' + 181 '<label class="form-label" for="erratum-comment">' + options.messages.commentInfo + '</label>' + 182 '<textarea type="text" class="form-control" id="erratum-comment" placeholder="..." rows="2"></textarea>' + 183 '</div>'; 184 185 $(modalEl).find('.modal-title').text(options.messages.title); 186 $(modalEl).find('.modal-body').html(htmlContent); 187 188 // Äèíàìè÷åñêè ôîðìèðóåì footer ïîä êíîïêè ïëàãèíà 189 var $footer = $(modalEl).find('.modal-footer'); 190 $footer.html( 191 '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">' + options.messages.cancelButton + '</button>' + 192 '<button type="button" class="btn btn-primary" id="erratum-send">' + options.messages.sendButton + '</button>' 193 ); 194 195 // Ïðèâÿçûâàåì îòïðàâêó 196 $('#erratum-send', $footer).off('click').on('click', function() { 197 var report = $.extend({}, self.selection, { comment: $('#erratum-comment').val() }); 198 199 if (typeof options.transformReport === 'function') { 200 report = options.transformReport.call(self, report); 201 } 202 203 $.ajax({ 204 url: options.sendUrl, 205 type: 'POST', 206 data: report, 207 success: function(response) { 208 self.bsModal.hide(); 209 if (typeof options.onSuccess === 'function') { 210 options.onSuccess.call(self, response); 211 } 212 }, 213 error: function(jqXHR, textStatus, errorThrown) { 214 self.bsModal.hide(); 215 if (typeof options.onError === 'function') { 216 options.onError.call(self, jqXHR, textStatus, errorThrown); 217 } 218 } 219 }); 220 }); 221 222 this.bsModal.show(); 223 }, 224 225 showWarning: function(msg) { this.showMessage(this.options.messages.warning, msg); }, 226 showInfo: function(msg) { this.showMessage(this.options.messages.info, msg); }, 227 showError: function(msg) { this.showMessage(this.options.messages.error, msg); }, 228 229 showMessage: function(title, message) { 230 // Ñîçäà¸ì âðåìåííîå ìîäàëüíîå îêíî äëÿ ñîîáùåíèé, ÷òîáû íå ëîìàòü ñîñòîÿíèå îñíîâíîãî 231 var id = 'bs-msg-' + Date.now(); 232 var alertHtml = ` 233 <div class="modal fade" id="${id}" tabindex="-1" aria-hidden="true"> 234 <div class="modal-dialog"> 235 <div class="modal-content"> 236 <div class="modal-header py-2"> 237 <h5 class="modal-title">${title}</h5> 238 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 239 </div> 240 <div class="modal-body"> 241 <p class="mb-0">${message}</p> 242 </div> 243 </div> 244 </div> 245 </div>`; 246 247 $('body').append(alertHtml); 248 var alertModal = new bootstrap.Modal(document.getElementById(id)); 249 alertModal.show(); 250 251 // Óäàëÿåì èç DOM ïîñëå çàêðûòèÿ 252 $('#' + id).on('hidden.bs.modal', function() { $(this).remove(); }); 253 }, 254
255 clearSpaces: function(text) { 256 return (" " + text).replace(/[\s]+/g, " "); 257 } 258 }; 259 260 $[pluginName] = function(options) { 261 if (!$.data(document, "plugin_" + pluginName)) { 262 $.data(document, "plugin_" + pluginName, new Plugin(options)); 263 } 264 }; 265})(jQuery, window, document);
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.