PageSourceSearch

https://olujevicko.cz/admin/src/js/admin_shared.js

js olujevicko.cz collected 2026-10-09 02:56:22 UTC 29,216 bytes, 694 lines download raw bytes

1
2// admin_shared.js
3// Sdílené funkce pro administraci (ostrou i inline) - přenositelné s adminem
4
5// Force cache bust for CKEditor configuration and plugins
6if (typeof CKEDITOR !== 'undefined') {
7    CKEDITOR.timestamp = 'v20260325_4';
8}
9
10/**
11 * Inicializace CKEditoru s počítadlem znaků a sanitizací
12 * @param {string} editorId - ID textarea elementu
13 * @param {string} counterId - ID inputu pro zobrazení počtu znaků
14 * @param {number} maxChars - Maximální povolený počet znaků (0 = bez limitu)
15 * @param {string} type - Typ editoru ('micro', 'mini', 'full') - default 'mini'
16 * @returns {object} Instance editoru
17 */
18function initCkEditorWithCount(editorId, counterId, maxChars, type) {
19    if (typeof CKEDITOR === 'undefined') return null;
20
21    // Pokud instance již existuje, zničit ji (prevence problémů při opětovném otevření modálního okna)
22    if (CKEDITOR.instances[editorId]) {
23        try {
24            CKEDITOR.instances[editorId].destroy(true);
25        } catch (e) {
26            console.warn('Nepodařilo se zničit instanci CKEditoru:', e);
27        }
28    }
29
30    type = type || 'mini';
31    var toolbarName = 'full'; // default fallback
32
33    // Konfigurace toolbarů dle typu (odkazuje na definice v config.js)
34    if (type === 'micro') {
35        toolbarName = 'micro';
36    } else if (type === 'mini') {
37        toolbarName = 'mini';
38    } else if (type === 'full') {
39        toolbarName = 'full';
40    }
41
42    // Nastavení konfigurace instance
43    var config = {
44        customConfig: '/admin/ckeditor/config.js?v=20260325_24',
45        language: 'cs',
46        // Toolbar reference defined in config.js
47        toolbar: toolbarName,
48        removePlugins: 'elementspath,sharedspace',
49        elementspath: false, // Explicitly disable elementspath
50        extraPlugins: 'thvlozeni',
51        resize_enabled: false, // Generally disable resize handle bar
52        // Konfigurace KCFinderu (pro upload obrázků) - platí obecně
53        filebrowserBrowseUrl: 'ckeditor/plugins/kcfinder/browse.php?type=soubory&lang=cs',
54        filebrowserImageBrowseUrl: 'ckeditor/plugins/kcfinder/browse.php?type=obrazky&lang=cs',
55        filebrowserFlashBrowseUrl: 'ckeditor/plugins/kcfinder/browse.php?type=files&lang=cs',
56        filebrowserUploadUrl: 'ckeditor/plugins/kcfinder/upload.php?type=soubory&lang=cs',
57        filebrowserImageUploadUrl: 'ckeditor/plugins/kcfinder/upload.php?type=obrazky&lang=cs',
58        filebrowserFlashUploadUrl: 'ckeditor/plugins/kcfinder/upload.php?type=flash&lang=cs'
59    };
60
61    // Custom height overrides and resize settings
62    if (type === 'micro') {
63        config.height = 80;
64        config.resize_enabled = false; // Disable resize handle for micro editor
65        config.removePlugins += ',elementspath,resize'; // Ensure resize plugin is removed to hide handle
66    } else if (type === 'mini') {
67        config.height = 150;
68        config.resize_enabled = false;
69        config.removePlugins += ',resize';
70    } else {
71        config.height = 300;
72    }
73
74    var editor = CKEDITOR.replace(editorId, config);
75
76    // --- LOGIKA POČÍTADLA A SANITIZACE ---
77
78    // Funkce pro náhradu problematických znaků (pevné mezery atd.)
79    function sanitizeText(content) {
80        if (!content) return "";
81        let replaceChars = {
82            "\u2009": " ", "\u2002": " ", "\u2003": " ", "\u2004": " ",
83            "\u2005": " ", "\u2006": " ", "\u2007": " ", "\u2008": " ",
84            "\u202F": " ", "\u205F": " ", "\u3000": " "
85        };
86        for (let char in replaceChars) {
87            content = content.replace(new RegExp(char, "g"), replaceChars[char]);
88        }
89        return content;
90    }
91
92    // Aktualizace počítadla
93    function updateCounter() {
94        if (!editor || !editor.getData) return; // Ochrana proti volání před inicializací
95
96        // Získání čistého textu (včetně HTML tagů, jak uživatel požadoval)
97        let content = editor.getData().trim();
98        content = sanitizeText(content);
99
100        let contentLength = content.length;
101
102        // Zkusíme najít element počítadla podle ID, nebo pokud je to 'small' tag v okolí textarea
103        let charCountInput = document.getElementById(counterId);
104        if (!charCountInput) {
105            // Fallback: Pokud ID nenalezeno, zkusíme najít element s ID 'count_message' (což je hardcoded v ajax-modal-novinky.php)
106            charCountInput = document.getElementById('count_message');
107        }
108
109        if (charCountInput) {
110            // Pokud je to input/textarea/select, použijeme value, jinak innerText (pro small/span/div)
111            if (charCountInput.tagName === 'INPUT' || charCountInput.tagName === 'TEXTAREA') {
112                charCountInput.value = contentLength;
113            } else {
114                charCountInput.innerText = "Počet znaků: " + contentLength + (maxChars > 0 ? " / " + maxChars : "");
115            }
116
117            // Validace Max Length
118            if (maxChars > 0) {
119                if (contentLength > maxChars) {
120                    charCountInput.classList.add('text-danger', 'font-weight-bold');
121                } else {
122                    charCountInput.classList.remove('text-danger', 'font-weight-bold');
123                }
124            }
125        }
126        return content;
127    }
128
129    // Bind events
130    editor.on('instanceReady', function (evt) {
131        updateCounter();
132    });
133
134    editor.on('paste', function (evt) {
135        evt.data.dataValue = sanitizeText(evt.data.dataValue);
136        setTimeout(updateCounter, 10);
137    });
138
139    editor.on('change', function () {
140        updateCounter();
141    });
142
143    // Bind Form Submit Validation (pokud existuje rodičovský formulář)
144    let textArea = document.getElementById(editorId);
145    if (textArea && textArea.form) {
146        textArea.form.addEventListener('submit', function (event) {
147            editor.updateElement(); // Sync data to textarea
148            let content = updateCounter(); // Check count
149
150            if (maxChars > 0 && content.length > maxChars) {
151                event.preventDefault();
152                event.stopPropagation();
153                alert('Text je příliš dlouhý! Maximální povolená délka je ' + maxChars + ' znaků (včetně HTML).');
154            }
155        });
156    }
157
158    return editor;
159}
160
161// --- ADMIN CLIPBOARD FUNKCE ---
162function copyToClipboard(element) {
163    var $temp = $("<input>");
164    $("body").append($temp);
165    $temp.val($(element).text()).select();
166    document.execCommand("copy");
167    $temp.remove();
168}
169
170window.copyUrlToClipboard = function (inputId) {
171    var textToCopy = "/" + $("#" + inputId).val();
172    var copySuccess = function () {
173        var $btn = $("#" + inputId).closest('.input-group').find('button');
174        var oldHtml = $btn.html();
175        $btn.html('<i class="fa fa-check text-success"></i>');
176        setTimeout(function () { $btn.html(oldHtml); }, 1500);
177    };
178    if (navigator.clipboard && window.isSecureContext) {
179        navigator.clipboard.writeText(textToCopy).then(copySuccess).catch(function () { fallbackCopyTextToClipboard(textToCopy, copySuccess); });
180    } else {
181        fallbackCopyTextToClipboard(textToCopy, copySuccess);
182    }
183};
184
185function fallbackCopyTextToClipboard(text, successCallback) {
186    var textArea = document.createElement("textarea");
187    textArea.value = text;
188    textArea.style.top = "0";
189    textArea.style.left = "0";
190    textArea.style.position = "fixed";
191    document.body.appendChild(textArea);
192    textArea.focus();
193    textArea.select();
194    try {
195        var successful = document.execCommand('copy');
196        if (successful) successCallback();
197    } catch (err) {
198        console.error('Fallback: Kopírování selhalo', err);
199    }
200    document.body.removeChild(textArea);
201}
202
203// --- GLOBAL HTML PREVIEW MODAL ---
204window.showHtmlPreview = function (element, titleStr) {
205    var title = titleStr || "Náhled obsahu";
206    var htmlContent = $(element).attr('data-content');
207    var $modal = $('#htmlPreviewModal');
208    if (!$modal.length) {
209        var modalHtml = '<div class="modal fade" id="htmlPreviewModal" tabindex="-1" role="dialog" aria-hidden="true">' +
210            '<div class="modal-dialog modal-lg modal-dialog-centered" role="document">' +
211            '<div class="modal-content">' +
212            '<div class="modal-header">' +
213            '<h5 class="modal-title font-weight-bold"></h5>' +
214            '<button type="button" class="close" data-dismiss="modal" aria-label="Zavřít"><span aria-hidden="true">&times;</span></button>' +
215            '</div>' +
216            '<div class="modal-body" style="overflow-x:auto; background: #fdfdfd;"></div>' +
217            '<div class="modal-footer bg-light">' +
218            '<button type="button" class="btn btn-secondary px-4 shadow-sm" data-dismiss="modal">ZAVŘÍT</button>' +
219            '</div>' +
220            '</div>' +
221            '</div>' +
222            '</div>';
223        $('body').append(modalHtml);
224        $modal = $('#htmlPreviewModal');
225    }
226    $modal.find('.modal-title').text(title);
227    $modal.find('.modal-body').html(htmlContent);
228    $modal.modal('show');
229};
230
231$(document).ready(function () {
232    // --- GRID EDITOR ---
233    var $grid = $('#myGrid');
234    if ($grid.length) {
235        $grid.find('.ge-mainControls, .ge-addRowGroup, .ge-tools-drawer, .ge-content-type-bar, .ge-layout-mode, .ge-ui').remove();
236    }
237
238    if ($.isFunction($.fn.gridEditor) && $('#myGrid').length) {
239        $('#myGrid').gridEditor({
240            new_row_layouts: [[12], [6, 6], [9, 3], [3, 9], [3, 6, 3], [3, 3, 3, 3]],
241            content_types: ['ckeditor'],
242            row_classes: [
243                { label: 'šedé pozadí', cssClass: 'bg-sede-row' },
244                { label: 'AOS: Fade Up', cssClass: 'aos-init aos-animate', data: { 'aos': 'fade-up' } },
245                { label: 'AOS: Fade Down', cssClass: 'aos-init aos-animate', data: { 'aos': 'fade-down' } },
246                { label: 'AOS: Fade Right', cssClass: 'aos-init aos-animate', data: { 'aos': 'fade-right' } },
247                { label: 'AOS: Fade Left', cssClass: 'aos-init aos-animate', data: { 'aos': 'fade-left' } },
248                { label: 'AOS: Zoom In', cssClass: 'aos-init aos-animate', data: { 'aos': 'zoom-in' } }
249            ],
250            col_classes: [
251                { label: 'šedé pozadí', cssClass: 'bg-sede-col' },
252                { label: 'druhé pozadí', cssClass: 'bg-druhe-col' },
253                { label: 'AOS: Fade Up', cssClass: 'aos-init aos-animate', data: { 'aos': 'fade-up' } },
254                { label: 'AOS: Zoom In', cssClass: 'aos-init aos-animate', data: { 'aos': 'zoom-in' } }
255            ],
256            ckeditor: {
257                config: {
258                    customConfig: '/admin/ckeditor/config.js?v=20260325_24',
259                    filebrowserBrowseUrl: '/admin/ckeditor/plugins/kcfinder/browse.php?type=files',
260                    filebrowserImageBrowseUrl: '/admin/ckeditor/plugins/kcfinder/browse.php?type=images',
261                    filebrowserFlashBrowseUrl: '/admin/ckeditor/plugins/kcfinder/browse.php?type=flash',
262                    filebrowserUploadUrl: '/admin/ckeditor/plugins/kcfinder/upload.php?type=files',
263                    filebrowserImageUploadUrl: '/admin/ckeditor/plugins/kcfinder/upload.php?type=images',
264                    filebrowserFlashUploadUrl: '/admin/ckeditor/plugins/kcfinder/upload.php?type=flash'
265                }
266            }
267        });
268    }
269
270    $(document).on('click', '.gm-fullscreen', function () {
271        $('body').toggleClass('ge-fullscreen-mode');
272    });
273
274    $(document).keyup(function (e) {
275        if (e.key === "Escape" && $('body').hasClass('ge-fullscreen-mode')) {
276            $('body').removeClass('ge-fullscreen-mode');
277        }
278    });
279
280    if (typeof CKEDITOR !== 'undefined') {
281        CKEDITOR.config.autoParagraph = false;
282        CKEDITOR.on("instanceReady", function (event) {
283            event.editor.on("beforeCommandExec", function (event) {
284                if (event.data.name == "paste") {
285                    event.editor._.forcePasteDialog = true;
286                }
287                if (event.data.name == "pastetext" && event.data.commandData.from == "keystrokeHandler") {
288                    event.cancel();
289                }
290            })
291        });
292    }
293
294    // --- MODAL WINDOWS FOR ADMIN EDITING ---
295    $("body").on("shown.bs.modal", function (event) {
296        if ($.fn.draggable) {
297            $(this).find(".modal-dialog").draggable({
298                handle: ".modal-header",
299                cursor: "move"
300            });
301            $(this).find(".modal-header").css("cursor", "move");
302        }
303    });
304
305    $("body").on("hidden.bs.modal", function (event) {
306        $(this).find(".modal-dialog").css({ top: 0, left: 0 });
307    });
308
309    /* MODAL CONTENT LOADING - Global Handler */
310    $(document).on('show.bs.modal', '.modal', function (e) {
311        if ($(this).attr('id') === 'clanek_preview_Modal') return;
312        var modal = $(this);
313        modal.data('needs-reload', false);
314
315        var button = $(e.relatedTarget);
316        if (button.length > 0 && button.attr('href') && button.attr('href') !== '#' && button.attr('href').indexOf('#') !== 0 && button.attr('href').indexOf('javascript') === -1) {
317            var url = button.attr('href');
318            
319            if (url.indexOf('src_in_window.php') !== -1) {
320                modal.find('.modal-content').html('<div class="p-5 text-center"><i class="fa fa-spinner fa-spin fa-3x text-muted"></i><br><br>Načítám...</div>');
321                modal.find('.modal-content').load(url, function (response, status, xhr) {
322                    if (status == "error") {
323                        modal.find(".modal-content").html('<div class="alert alert-danger m-3">Chyba při načítání: ' + xhr.status + " " + xhr.statusText + '</div>');
324                    }
325                });
326            } else if (url.indexOf('ajax-modal-') !== -1 || url.indexOf('modal') !== -1) {
327                var targetElement = modal.find('.modal-body').length > 0 ? modal.find('.modal-body') : modal.find('.modal-content');
328                if(modal.attr('id') !== 'anketa_vysledky_Modal') {
329                    targetElement.html('<div class="p-5 text-center"><i class="fa fa-spinner fa-spin fa-3x text-muted"></i><br><br>Načítám formulář...</div>');
330                }
331                targetElement.load(url, function (response, status, xhr) {
332                    if (status == "error") {
333                        targetElement.html('<div class="alert alert-danger m-3">Chyba při načítání: ' + xhr.status + " " + xhr.statusText + '</div>');
334                    }
335                });
336            } else {
337                modal.find('.modal-content').load(url);
338            }
339        }
340    });
341
342    $(document).on('hidden.bs.modal', '.modal', function (e) {
343        var modal = $(this);
344        if (modal.data('needs-reload') === true || modal.hasClass('needs-reload')) {
345            location.reload();
346        } else {
347            // Reset soubory modals to default loading spinner to prevent nested modal-body wrapping on second load
348            var modalId = modal.attr('id');
349            if (modalId === 'soubory_edit_Modal' || modalId === 'soubory_add_Modal') {
350                modal.find('.modal-content').html('<div class="text-center p-5"><i class="fa fa-spinner fa-spin fa-3x text-muted"></i><br><br>Načítám formulář...</div>');
351            } else if (modalId === 'soubor_delete_Modal') {
352                modal.find('.modal-content').html('<div class="text-center p-5"><i class="fa fa-spinner fa-spin fa-3x text-muted"></i><br><br>Načítám...</div>');
353            }
354        }
355    });
356
357    // Global toggle password logic
358    $(document).on('click', '.toggle-password', function () {
359        var $this = $(this);
360        var $icon = $this.is('i') ? $this : $this.find('i');
361        var $input = $this.closest('.input-group, .input-with-icon, .form-group').find('input');
362        
363        if ($input.length > 0) {
364            if ($input.attr("type") === "password") {
365                $input.attr("type", "text");
366                $icon.removeClass("fa-eye").addClass("fa-eye-slash");
367            } else {
368                $input.attr("type", "password");
369                $icon.removeClass("fa-eye-slash").addClass("fa-eye");
370            }
371        }
372    });
373
374    // Delegated SUBMIT handlers for forms loaded via AJAX in modals
375    var delegatedForms = "#form_novinky, #form_clanky, #form_edit_novinky, #form_edit_clanky, #form_delete_novinky, #form_menu, #form_delete_clanek, #form_edit_menu, #form_edit_uzivatel, #form_edit_rostlina, #form_delete_rostlina, #form_edit_ankety, #form_edit_soubory, #form_delete_newsletter, #form_delete_order, #form_delete_soubory, #form_edit_akce, #form_delete_akce";
376    
377    $(document).on("submit", delegatedForms, function (e) {
378        e.preventDefault();
379
380        var $form = $(this);
381        
382        // Clean previous validations
383        $form.find('.is-invalid').removeClass('is-invalid');
384        $form.find('.invalid-feedback').remove();
385
386        // 1. Sync CKEditor if present
387        if (typeof CKEDITOR !== 'undefined') {
388            for (var instance in CKEDITOR.instances) {
389                if (CKEDITOR.instances[instance]) {
390                    CKEDITOR.instances[instance].updateElement();
391                }
392            }
393        }
394
395        var formIsValid = true;
396
397        // 2. Custom validation for required fields
398        $form.find('input[required], select[required], textarea[required]').each(function() {
399            var $field = $(this);
400            if ($field.val().trim() === '') {
401                formIsValid = false;
402                $field.addClass('is-invalid');
403                
404                var feedbackText = 'Tento údaj je povinný.';
405                if ($field.attr('id') === 'nadpis') {
406                    feedbackText = 'Vyplňte nadpis.';
407                } else if ($field.attr('id') === 'obsah') {
408                    feedbackText = 'Vyplňte text.';
409                } else if ($field.attr('id') === 'nazev_clanku') {
410                    feedbackText = 'Vyplňte nadpis článku.';
411                }
412
413                if ($field.parent().hasClass('input-group')) {
414                    $field.parent().after('<div class="invalid-feedback d-block">' + feedbackText + '</div>');
415                } else {
416                    $field.after('<div class="invalid-feedback">' + feedbackText + '</div>');
417                }
418            }
419        });
420
421        if (!formIsValid) {
422            e.stopPropagation();
423            return false;
424        }
425
426        var $modal = $form.closest('.modal');
427        var $btn = $form.find('button[type="submit"]');
428        var originalBtnHtml = $btn.length ? $btn.html() : '';
429
430        if($btn.length) $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin mr-1"></i> UKLÁDÁM...');
431
432        // Auto-fill/validate URL if present in form before serialization
433        var $urlField = $form.find('#url');
434        if ($urlField.length) {
435            testField($urlField[0]);
436        }
437
438        var postData = $form.serializeArray();
439        var formURL = $form.attr("action");
440
441        $.ajax({
442            url: formURL,
443            type: "POST",
444            data: postData,
445            success: function (data, textStatus, jqXHR) {
446                if ($modal.length) {
447                    $modal.data('needs-reload', true);
448                    $modal.addClass('needs-reload');
449                    if ($modal.find('.modal-body').length > 0 && formURL && formURL.indexOf('ajax-modal-') !== -1) {
450                        $modal.find('.modal-body').html(data);
451                    } else if ($modal.find('.modal-content').length > 0) {
452                        $modal.find('.modal-content').html(data);
453                    } else {
454                        $modal.html(data);
455                    }
456                }
457            },
458            error: function (jqXHR, textStatus, errorThrown) {
459                if($btn.length) $btn.prop('disabled', false).html(originalBtnHtml);
460                alert("Chyba při ukládání: " + textStatus);
461            }
462        });
463    });
464
465    $("#clanek_preview_Modal").on("show.bs.modal", function (e) {
466        var link = $(e.relatedTarget);
467        var title = link.data('title') || 'Náhled článku';
468        var id = link.data('id') || '';
469        var url = link.attr("href");
470        
471        $(this).find(".modal-title").html('Náhled článku: <span class="font-weight-normal">' + title + '</span> <small class="text-muted ml-2">#' + id + '</small>');
472        
473        var $body = $(this).find(".modal-body");
474        $body.html('<div class="d-flex justify-content-center align-items-center h-100 flex-column text-muted"><i class="fa fa-spinner fa-spin fa-3x mb-3"></i>Načítám živý náhled...</div>');
475        
476        var iframeHtml = '<iframe src="' + url + '" frameborder="0" width="100%" height="100%" style="display:none; border-radius: 4px;"></iframe>';
477        var $iframe = $(iframeHtml);
478        
479        $body.append($iframe);
480        
481        $iframe.on('load', function() {
482            $body.find('.fa-spinner').parent().remove();
483            $(this).fadeIn();
484        });
485    });
486    $("#clanek_preview_Modal").on("hidden.bs.modal", function (e) {
487        $(this).find(".modal-body").empty();
488    });
489    
490    // KCFinder handler
491    $("#kcf").on("show.bs.modal", function (e) {
492        var button = e.relatedTarget;
493        var data_url = $(button).attr("data-url");
494        if(data_url){
495            data_url += (data_url.indexOf('?') !== -1 ? '&' : '?') + "t=" + new Date().getTime();
496            $(this).find(".modal-body").empty().html('<div id="kcfinder_div" class="modal_kcfinder" style="height:100%"><iframe name="kcfinder_iframe" src="' + data_url + '" frameborder="0" width="100%" height="100%" style="width:100%;height:100%;" marginwidth="0" marginheight="0" scrolling="no"></iframe></div>');
497        }
498
499        window.KCFinder = {
500            callBack: function (url) {
501                window.KCFinder = null;
502                $(button).parent().next('input').val(url);
503                $("#kcf").modal('hide');
504            }
505        };
506    });
507
508    $("#kcf").on("hidden.bs.modal", function () {
509        $(this).find(".modal-body").empty();
510    });
511
512
513    // --- OBNOVENÁ LOGIKA Z ADMINU A INLINE ---
514    
515    // Ukládání a odeslání do iframe (archiv & uložení clanku)
516    $("#save").click(function () {
517        var html = $('#myGrid').gridEditor('getHtml');
518        var id = $('#id_clanky').val();
519
520        $("#vysledek_sql").html('<i class="fa fa-spinner fa-spin"></i> Ukládám...');
521
522        $.ajax({
523            url: "inline_uloz_obsah_clanku.php",
524            type: "POST",
525            data: { id: id, content: html },
526            success: function (data) {
527                $("#vysledek_sql").html(data);
528                setTimeout(function () { $("#vysledek_sql").html(''); }, 3000);
529            },
530            error: function () {
531                $("#vysledek_sql").html('<span class="text-danger">Chyba ukládání!</span>');
532            }
533        });
534    });
535
536    // NAČTENÍ ARCHIVU (historie verzí)
537    function loadHistory() {
538        var id = $('#id_clanky').val();
539        if (id && id > 0) {
540            $("#vysledek_archiv").html('<i class="fa fa-spinner fa-spin"></i>');
541            $.ajax({
542                url: "inline_historie_clanku.php",
543                type: "POST",
544                data: { id: id },
545                success: function (data) {
546                    $("#vysledek_archiv").html(data);
547                },
548                error: function (jqXHR, textStatus, errorThrown) {
549                    $("#vysledek_archiv").html('<span class="text-danger small">Chyba: ' + textStatus + '</span>');
550                }
551            });
552        }
553    }
554
555    if($('#vysledek_archiv').length) {
556        loadHistory();
557    }
558
559    $(document).on('change', '#history_select', function () {
560        // Implementace výběru archivu
561    });
562
563    // --- GLOBAL URL GENERATION HANDLER ---
564    $(document).on('blur', '#nazev_clanku, #nazev', function () {
565        var $url = $(this).closest('form').find('#url');
566        if ($url.length && !$url.val().trim()) $url.val(make_url(this.value));
567    });
568
569    // --- CODEMIRROR TAG SELECTION ---
570    $(document).on('mousedown', '.CodeMirror', function (e) {
571        var cm = this.CodeMirror;
572        if (!cm) {
573            cm = $(this).find('*').filter(function() { return !!this.CodeMirror; }).get(0);
574            if (cm) cm = cm.CodeMirror;
575        }
576
577        if (!cm) return;
578
579        // EMERGENCY: Pokud addon stále chybí, pokusíme se ho "vnutit" z globálního prostoru
580        if (!cm.findMatchingTag && typeof CodeMirror !== 'undefined' && CodeMirror.prototype.findMatchingTag) {
581            cm.findMatchingTag = CodeMirror.prototype.findMatchingTag;
582        }
583        
584        var pos = cm.coordsChar({ left: e.clientX, top: e.clientY }, "window");
585        var token = cm.getTokenAt(pos);
586        
587        // Funkce se spustí pouze pokud klikneme na tag (název nebo závorky), ne na atributy
588        if (token && token.type && /\btag\b/.test(token.type)) {
589            if (typeof cm.findMatchingTag === 'function') {
590                var match = cm.findMatchingTag(pos);
591                if (match) {
592                    var from, to;
593                    if (match.at === 'open' && match.close) {
594                        from = match.open.from;
595                        to = match.close.to;
596                    } else if (match.at === 'close' && match.open) {
597                        from = match.open.from;
598                        to = match.close.to;
599                    }
600                    
601                    if (from && to) {
602                        setTimeout(function() {
603                            cm.setSelection(from, to, { scroll: false });
604                            cm.focus();
605                        }, 50);
606                    }
607                }
608            }
609        }
610    });
611
612});
613
614// --- GLOBÁLNÍ FUNKCE (mimo document.ready) ---
615
616function testField(field) {
617    if (!field) return;
618
619    // Automatically fill URL if empty
620    if (field.value.trim().length == 0) {
621        // Look for title ONLY within the same form/context to avoid picking up old data from hidden modals
622        var $form = $(field).closest('form');
623        var $titleField = $form.find('#nazev_clanku, #nazev');
624        if ($titleField.length) {
625            field.value = make_url($titleField.val());
626        }
627    }
628
629    var regExpr = new RegExp("^[a-z0-9\\-]*$");
630    if (!regExpr.test(field.value)) {
631        alert("Nelze použít URL '" + field.value + "' (povoleny jsou pouze malá písmena, čísla a pomlčky)");
632        field.classList.add('is-invalid');
633    } else {
634        field.classList.remove('is-invalid');
635    }
636}
637
638var nodiac = { 'á': 'a', 'č': 'c', 'ď': 'd', 'é': 'e', 'ě': 'e', 'í': 'i', 'ň': 'n', 'ó': 'o', 'ř': 'r', 'š': 's', 'ť': 't', 'ú': 'u', 'ů': 'u', 'ý': 'y', 'ž': 'z' };
639function make_url(s) {
640    if (!s) return "";
641    s = s.toLowerCase();
642    var s2 = '';
643    for (var i = 0; i < s.length; i++) {
644        s2 += (typeof nodiac[s.charAt(i)] != 'undefined' ? nodiac[s.charAt(i)] : s.charAt(i));
645    }
646    // Changed: Remove underscores to match testField regex (^[a-z0-9\-]*$)
647    return s2.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
648}
649
650function make_friendly_url(u) {
651    // This is now redundant due to the global handler, but kept for legacy inline onblur calls
652    var $urlField = $('#url');
653    if ($urlField.length && $urlField.val().trim().length == 0) {
654        $urlField.val(make_url(u));
655    }
656}
657
658function isDateorNull(theElement) {
659    if (!theElement) return true;
660    var inpDate = theElement.value;
661    if (inpDate.length == 0) return true;
662    return true;
663}
664
665function valid(form) {
666    var field = form.datum_clanku;
667    if (isDateorNull(field)) {
668        return true;
669    } else {
670        return false;
671    }
672}
673
674/* Czech initialisation for the jQuery UI date picker plugin. */
675(function (factory) {
676    if (typeof define === "function" && define.amd) {
677        define(["../widgets/datepicker"], factory);
678    } else {
679        if(typeof jQuery.datepicker !== "undefined") {
680            factory(jQuery.datepicker);
681        }
682    }
683}(function (datepicker) {
684    datepicker.regional.cs = {
685        closeText: "Zavřít", prevText: "&#x3C;Dříve", nextText: "Později&#x3E;", currentText: "Nyní",
686        monthNames: ["leden", "únor", "březen", "duben", "květen", "červen", "červenec", "srpen", "září", "říjen", "listopad", "prosinec"],
687        monthNamesShort: ["led", "úno", "bře", "dub", "kvě", "čer", "čvc", "srp", "zář", "říj", "lis", "pro"],
688        dayNames: ["neděle", "pondělí", "úterý", "středa", "čtvrtek", "pátek", "sobota"],
689        dayNamesShort: ["ne", "po", "út", "st", "čt", "pá", "so"], dayNamesMin: ["ne", "po", "út", "st", "čt", "pá", "so"],
690        weekHeader: "Týd", dateFormat: "dd.mm.yy", firstDay: 1, isRTL: false, showMonthAfterYear: false, yearSuffix: ""
691    };
692    datepicker.setDefaults(datepicker.regional.cs);
693    return datepicker.regional.cs;
694}));

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.