PageSourceSearch

https://www.garant.ru/static/www/js/erratum_reporter.js?933

js garant.ru collected 2026-09-24 06:23:56 UTC 11,291 bytes, 265 lines download raw bytes

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.