Mercurial
diff .cms/js/pages.js @ 0:78edf6b517a0 draft
24.10
author | Coffee CMS <info@coffee-cms.ru> |
---|---|
date | Fri, 11 Oct 2024 22:40:23 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.cms/js/pages.js Fri Oct 11 22:40:23 2024 +0000 @@ -0,0 +1,1265 @@ +document.addEventListener( "DOMContentLoaded", function( event ) { + + function _( str ) { + return __( str, "pages.mod.php" ); + } + + // Полностью этот скрипт отключить нельзя если включены Инструменты, + // поэтому нужна проверка + if ( document.querySelector( "#pages" ) ) { + api( { fn: "get_pages_list" }, set_pages_list ); + } + + function set_pages_list( r ) { + + if ( ! document.querySelector( "#pages" ) ) return; + + if ( r.no_database ) { + document.querySelector( "#pages .pages-grid" ).innerHTML = r.no_database; + return; + } + + if ( r.overloaded ) { + let m = _( "server_overloaded_xxx" ); + m = m.replace( "xxx", r.pages.length ); + notify( m, "info-error", 5000 ); + } + + // Запоминаем результаты какого поиска мы получили + // Это нужно чтобы не загружать снова все страницы при создании новой + // не производить сброс поиска. + let pages_search = document.querySelector( "#pages .page-search" ); + pages_search.setAttribute( "data-result-of", r.search ); + + let grid = document.querySelector( "#pages .pages-grid" ); + let count = document.querySelector( "#pages .main-footer .count" ); + let loaded = document.querySelector( "#pages .main-footer .loaded" ); + if ( cms.clear_pages_list ) { + cms.clear_pages_list = false; + grid.innerHTML = ""; + loaded.value = "0"; + } + + // Всего страниц в БД + count.innerText = r.count; + + // При удалении страниц подгружаются последние и не нужно менять смещение у пейджера + if ( cms.dont_change_offset ) { + cms.dont_change_offset = false; + } else { + loaded.setAttribute( "data-offset", r.offset ); + } + + // insert pages + let start = Date.now(); + for ( let i = 0; i < r.pages.length; i++ ) { + grid.insertAdjacentHTML( "beforeend", r.pages[i].html ); + loaded.value = +loaded.value + 1; + let page = grid.querySelector( `[data-id="${r.pages[i].id}"]` ); + set_controls( page ); + if ( Date.now() - start > 1000 ) { + let m = _( "browser_overloaded_xxx" ); + m = m.replace( "xxx", i + 1 ); + m = m.replace( "nnn", r.pages.length ); + notify( m, "info-error", 5000 ); + break; + } + } + + create_pager(); + + // При создании страницы было обнаружено что поиск не сброшен, + // поэтому была вызвана функция загрузки страниц с пустым поиском + // и пришло время создать новую страницу + if ( cms.create_page_fn ) { + cms.create_page_fn(); + cms.create_page_fn = null; + } + + } + + function create_pager() { + let loaded = document.querySelector( "#pages .main-footer .loaded" ); + let offset = parseInt( loaded.getAttribute( "data-offset" ) ); + let count = parseInt( document.querySelector( "#pages .main-footer .count" ).innerText ); + if ( count === 0 ) { count++; } + let cookie_pp = get_cookie( "pages_pager" ); + let pages = Math.ceil( count / cookie_pp ); + let pager = document.querySelector( "#pages .main-footer .pager" ); + pager.innerHTML = ""; + if ( pages > 1 ) { + for ( let i = 1; i <= pages; i++ ) { + let p = document.createElement( "div" ); + p.innerText = i; + p.setAttribute( "data-offset", ( i - 1 ) * cookie_pp ); + pager.appendChild( p ); + } + pager.querySelector( `[data-offset="${offset}"]` ).classList.add( "active" ); + pager.childNodes.forEach( function( el ) { + el.addEventListener( "click", function( e ) { + let offset = this.getAttribute( "data-offset" ); + let search = document.querySelector( "#pages .page-search" ).value; + let data = { + fn: "get_pages_list", + offset: offset, + search: search + } + document.querySelector( "#pages .main-main" ).scrollTop = 0; + cms.clear_pages_list = true; + api( data, set_pages_list ); + } ); + } ); + // scroll + pager.onmousedown = function( e ) { + let pageX = 0; + let pageY0 = 0; + + document.onmousemove = function( e ) { + if ( pageX !== 0 ) { + pager.scrollLeft = pager.scrollLeft + ( pageX - e.pageX ); + } + pageX = e.pageX; + // fix for google chrome + if ( pageY0 === 0 ) { + pageY0 = e.pageY; + } + if ( Math.abs( pageY0 - e.pageY ) > 64 ) { + const event = new Event( "mouseup" ); + pager.dispatchEvent( event ); + } + } + + // end drag + pager.onmouseup = function() { + document.onmousemove = null; + pager.onmouseup = null; + } + + // disable browser drag + pager.ondragstart = function() { + return false; + } + } + } + } + + // pager counter + let pager_counter = document.querySelector( "#pages .main-footer input" ); + if ( pager_counter ) pager_counter.addEventListener( "keydown", function( e ) { + if ( e.keyCode === 13 ) { // keyCode work on mobile + let p = document.querySelector( "#pages .main-footer input" ).value; + set_cookie_expires( "pages_pager", p ); + cms.clear_pages_list = true; + api( { fn: "get_pages_list", search: document.querySelector( "#pages .page-search" ).value }, set_pages_list ); + } + } ); + + function set_controls( selector ) { + + // Open properties + selector.querySelectorAll( ".page-prop-btn" ).forEach( function( button ) { + button.addEventListener( "click", function( e ) { + let id = this.closest( "[data-id]" ).getAttribute( "data-id" ); + document.querySelector( `#pages .pages-grid [data-id="${id}"]` ).classList.toggle( "open" ); + } ); + } ); + + // Save properties + selector.querySelectorAll( ".page-prop-save-btn" ).forEach( function( button ) { + button.addEventListener( "click", function( e ) { + let id = this.closest( "[data-id]" ).getAttribute( "data-id" ); + let item = document.querySelector( `#pages .pages-grid [data-id="${id}"] ` ); + let data = { + fn: "save_prop", + id: id, + title: item.querySelector( '[name="title"]' ).value, + seo_title: item.querySelector( '[name="seo_title"]' ).value, + url: item.querySelector( '[name="url"]' ).value, + date: item.querySelector( '[name="date"]' ).value, + time: item.querySelector( '[name="time"]' ).value, + template: item.querySelector( '.template-select-grid .field-select' ).getAttribute( "data-template" ), + old_template: item.querySelector( '.template-select-grid .field-select' ).getAttribute( "data-old-template" ), + description: item.querySelector( '[name="description"]' ).value, + tags: item.querySelector( '[name="tags"]' ).value, + } + api( data, function( r ) { + if ( r.ok == "false" ) { // FIXME: never fire + notify( r.info_text, r.info_class, 5000 ); + } + if ( r.ok == "true" ) { + + // Update Title, URL and Date + item.querySelector( ".page-name" ).innerHTML = r.title; + item.querySelector( ".page-name" ).setAttribute( "href", r.base_path + r.url ); + update_home(); + let url_el = item.querySelector( "[name=url]" ); + if ( url_el.value != r.url ) { + notify( _( "url_changed" ), "info-error", 5000 ); + } + url_el.value = r.url; + let date = item.querySelector( ".page-date" ); + date.innerHTML = r.created; + if ( r.planned ) { + date.classList.add( "future" ); + } else { + date.classList.remove( "future" ); + } + + // update old template + item.querySelector( '.template-select-grid .field-select' ).setAttribute( "data-old-template", data.template ); + + // edit marker + document.querySelectorAll( "#pages .pages-grid > div" ).forEach( function( el ) { + el.classList.remove( "last-edited" ); + } ); + setTimeout( function() { + item.classList.add( "last-edited" ); + }, 200 ); + + // highlight save button + button.classList.add( "saved" ); + setTimeout( function() { + button.classList.remove( "saved" ); + }, 200 ); + + notify( r.info_text, r.info_class, 5000 ); + + // update event for menu + if ( r.update_menu == "true" ) { + let event = new Event( "update_menu" ); + document.body.dispatchEvent( event ); + } + } + + } ); + } ); + } ); + + // Pin Page + selector.querySelectorAll( ".pin" ).forEach( function( pin ) { + pin.addEventListener( "click", function( e ) { + let box = this.closest( "[data-id]" ); + let id = box.getAttribute( "data-id" ); + let pin = box.getAttribute( "data-pin" ); + if ( pin === "1" ) { + pin = "0"; + } else { + pin = "1"; + } + let data = { + fn: "page_pin", + id: id, + pin: pin + } + api( data, function( r ) { + if ( r.ok == "true" ) { + box.setAttribute( "data-pin", pin ); + } + } ); + } ); + } ); + + // Publish Page + selector.querySelectorAll( ".published" ).forEach( function( pub ) { + pub.addEventListener( "click", function( e ) { + let box = this.closest( "[data-id]" ); + let id = box.getAttribute( "data-id" ); + let published = box.getAttribute( "data-published" ); + if ( published === "1" ) { + published = "0"; + } else { + published = "1"; + } + let data = { + fn: "page_publish", + id: id, + published: published + } + api( data, function( r ) { + if ( r.ok == "true" ) { + box.setAttribute( "data-published", published ); + if ( published == "1" ) { + pub.setAttribute( "title", _( "published" ) ); + } else { + pub.setAttribute( "title", _( "unpublished" ) ); + } + } + } ); + } ); + } ); + + // Edit page + selector.querySelectorAll( ".page-edit-btn" ).forEach( function( button ) { + button.addEventListener( "click", function( e ) { + button.classList.add( "loading" ); + let id = this.closest( "[data-id]" ).getAttribute( "data-id" ); + // get page from server + api( { fn: "get_page", id: id }, function( r ) { + button.classList.remove( "loading" ); + if ( r.result == "ok" ) { + let title = document.querySelector( "#pages .page-editor-title" ); + title.innerHTML = r.page.title; + title.setAttribute( "href", r.base_path + r.page.url ); + let props = document.querySelector( "#pages .page-properties" ); + props.querySelector( "input[name='title']" ).value = r.page.title; + props.querySelector( "input[name='url']" ).value = r.page.url; + props.querySelector( "input[name='seo_title']" ).value = r.page.seo_title; + props.querySelector( "textarea[name='description']" ).value = r.page.description; + props.querySelector( "textarea[name='tags']" ).value = r.page.tags; + props.querySelector( "input[name='date']" ).value = r.date; + props.querySelector( "input[name='time']" ).value = r.time; + let select = props.querySelector( ".template-select-grid" ); + let options = select.querySelector( ".field-options" ); + options.innerHTML = r.options; + let tpl = select.querySelector( ".field-select" ); + tpl.setAttribute( "data-template", r.option ); + tpl.setAttribute( "data-old-template", r.option ); + tpl.querySelector( ".value" ).innerText = r.option_tr; + document.querySelector( "#pages .page-editor > textarea" ).value = r.page.text; + if ( r.page.modified != null ) { // prevent delete attribute + document.querySelector( "#pages .page-editor > textarea" ).setAttribute( "data-modified", r.page.modified ); + } + document.querySelector( "#pages .save-page-button" ).setAttribute( "data-id", r.page.id ); + + options.querySelectorAll( ".option" ).forEach( function( option ) { + option.addEventListener( "click", click_select_option ); + } ); + + + // Images + document.querySelector( "#pages .link-file-tag" ).innerHTML = ""; + document.querySelector( "#pages .del-uploaded-files" ).classList.add( "disabled" ); + document.querySelector( "#pages .mediateka-files-grid" ).innerHTML = r.flist; + document.querySelectorAll( "#pages .mediateka-files-grid input[type=checkbox]" ).forEach( function( checkbox ) { + checkbox.addEventListener( "dblclick", img_check_dblclick ); + checkbox.addEventListener( "change", img_rechecked ); + } ); + document.querySelectorAll( "#pages .file-block" ).forEach( function( block ) { + block.addEventListener( "click", img_click ); + } ); + document.querySelectorAll( "#pages .mediateka-files-grid img" ).forEach( function( img ) { + img.addEventListener( "dblclick", img_lbox ); + } ); + + // Show Editor + document.querySelector( "#pages .page-editor-bg" ).classList.remove( "hidden" ); + document.body.classList.add( "editor" ); // for notifications + + // Connect Editor + codemirror_connect( "#pages .page-editor > textarea", "cm" ); + + // restore scroll and cursor position + let cursor = localStorage.getItem( "cursor_page_" + id ); + if ( cursor ) { + cursor = JSON.parse( cursor ); + window.cm.scrollTo( cursor.left, cursor.top ); + window.cm.setCursor( { line:cursor.line, ch:cursor.ch } ); + window.cm.refresh(); + //window.cm.scrollIntoView( { line:cursor.line, ch:cursor.ch } ); // fix glitch + } + + // track changes + document.querySelector( "#pages .close-page-button" ).setAttribute( "data-changed", "false" ); + document.querySelector( "#pages .page-editor-grid" ).setAttribute( "data-changed", "false" ); + cm.on( "change", function( cm, change ) { + document.querySelector( "#pages .close-page-button" ).setAttribute( "data-changed", "true" ); + document.querySelector( "#pages .page-editor-grid" ).setAttribute( "data-changed", "true" ); + } ); + // save scroll and cursor position + [ "cursorActivity", "scroll" ].forEach( function( event ) { + cm.on( event, function() { + let cursor = window.cm.getCursor(); + let scroll = window.cm.getScrollInfo(); + localStorage.setItem( "cursor_page_" + id, JSON.stringify( { line:cursor.line, ch:cursor.ch, left: scroll.left, top: scroll.top } ) ); + } ); + } ); + + // set focus to editor + cm.focus(); + + // Save Page Ctrl+S + document.documentElement.addEventListener( "keydown", CtrlS ); + + // open tags panel + if ( document.documentElement.offsetWidth >= 1024 ) { + document.querySelector( "#pages .page-editor-grid" ).classList.add( "tags-opened" ); + } + } + } ); + } ); + } ); + + // Select + selector.querySelectorAll( ".field-select" ).forEach( function( select ) { + select.addEventListener( "click", function( e ) { + e.stopPropagation(); + this.parentElement.classList.toggle( "open" ); + + // это можно убрать если переставить стили на родителя + select.nextElementSibling.classList.toggle( "open" ); + } ); + } ); + // Option шаблона + // вынесено в функцию потому что динамическое изменение + selector.querySelectorAll( ".field-options .option" ).forEach( function( option ) { + option.addEventListener( "click", click_select_option ); + } ); + + // Транслитерация URL + selector.querySelectorAll( ".url-translit" ).forEach( function( btn ) { + btn.addEventListener( "click", function( e ) { + let url = selector.querySelector( "input[name='title']" ).value; + let tr_url = url_translit( url ); + this.previousElementSibling.value = tr_url; + } ); + } ); + + // Выбор всех страниц двойным кликом + let check = selector.querySelector( `:scope > input[type=checkbox]` ); + if ( check ) check.addEventListener( "dblclick", function( e ) { + let stat = ! this.checked; + document.querySelectorAll( "#pages .pages-grid > div > input[type=checkbox]" ).forEach( function( chbox ) { + chbox.checked = stat; + } ); + } ); + + } + + // Клик по опции выбора шаблона + function click_select_option( e ) { + let select = this.closest( ".template-select-grid" ); + let value = this.getAttribute( "value" ); + select_switch_to( select, value ); + } + + // + function select_switch_to( select, value ) { + let field_select = select.querySelector( ".field-select" ); + let old_value = field_select.getAttribute( "data-template" ); + if ( old_value != value ) { + let old_name = field_select.innerText; + let field_options = select.querySelector( ".field-options" ); + let option = field_options.querySelector( `[value="${value}"]` ); + field_select.querySelector( ".value" ).innerText = option.innerText; + field_select.setAttribute( "data-template", value ); + option.remove(); + let option_html = `<div class=option value="${old_value}">${old_name}</div>`; + field_options.insertAdjacentHTML( "afterbegin", option_html ); + field_options.firstChild.addEventListener( "click", click_select_option ); + } + } + + // Select for editor + document.querySelectorAll( ".page-properties .field-select" ).forEach( function( select ) { + select.addEventListener( "click", function( e ) { + e.stopPropagation(); + this.parentElement.classList.toggle( "open" ); + // это можно убрать если переставить стили на родителя + select.nextElementSibling.classList.toggle( "open" ); + } ); + } ); + + // Select + // Закрытие выпадающих списков при кликах вне их, а так же по ним + document.body.addEventListener( "click", function( e ) { + document.querySelectorAll( "#pages .template-select-grid" ).forEach( function( list ) { + list.classList.remove( "open" ); + } ); + + // это можно убрать если переставить стили на родителя + document.querySelectorAll( "#pages .field-options" ).forEach( function( list ) { + list.classList.remove( "open" ); + } ); + } ); + + // Search page + let pages_search = document.querySelector( "#pages .page-search" ); + if ( pages_search ) pages_search.addEventListener( "keydown", function( e ) { + if ( e.keyCode === 13 ) { + // сохраняем событие целиком чтобы ниже считать Shift, Ctrl, Alt + cms.search_event = e; + search_pages(); + } + } ); + + let search_btn = document.querySelector( "#pages .page-search-button" ); + if ( search_btn ) search_btn.addEventListener( "click", function( e ) { + // клик по кнопке не дает Ctrl, Shift, Alt, запишем это + cms.search_event = { "ctrlKey": false, "shiftKey": false, "altKey": false }; + search_pages(); + document.querySelector( "#pages .page-search" ).focus(); + } ); + + function search_pages() { + let search_string = document.querySelector( "#pages .page-search" ).value; + let data = { + fn: "get_pages_list", + search: search_string, + Ctrl: cms.search_event.ctrlKey, + Shift: cms.search_event.shiftKey, + Alt: cms.search_event.altKey, + }; + cms.clear_pages_list = true; + api( data, set_pages_list ); + } + + // Reset Search + let reset_btn = document.querySelector( "#pages .reset" ); + if ( reset_btn ) reset_btn.addEventListener( "click", function() { + document.querySelector( "#pages .page-search" ).value = ""; + document.querySelector( "#pages .page-search-button" ).click(); + } ); + + // Delete pages + document.querySelectorAll( "#pages .del-pages-btn" ).forEach( function( button ) { + button.addEventListener( "click", function( e ) { + let ids = []; + document.querySelectorAll( "#pages .pages-grid input[type=checkbox]:checked" ).forEach( function( ch ) { + let id = ch.closest( "[data-id]" ).getAttribute( "data-id" ); + ids.push( id ); + } ); + if ( ids.length === 0 ) { + notify( _( "no_selected_pages" ), "info-error", 5000 ); + return; + } + if ( ! confirm( _( "confirm_delete_pages" ) ) ) { + return; + } + let data = { + fn: "del_pages", + ids: ids + }; + api( data, function( r ) { + if ( r.info_text ) { + notify( r.info_text, r.info_class, 5000 ); + if ( r.info_class == "info-success" ) { + data.ids.forEach( function( id ) { + document.querySelector( `#pages .pages-grid [data-id="${id}"]` ).remove(); + localStorage.removeItem( "cursor_page_" + id ); + } ); + let count = document.querySelector( "#pages .main-footer .count" ); + let loaded = document.querySelector( "#pages .main-footer .loaded" ); + loaded.value = parseInt( loaded.value ) - data.ids.length; + count.innerText = parseInt( count.innerText ) - data.ids.length; + // load pages + let offset = +loaded.getAttribute( "data-offset" ) + document.querySelectorAll( "#pages .pages-grid > *" ).length; + let search = document.querySelector( "#pages .page-search" ).value; + let data2 = { + fn: "get_pages_list", + count: data.ids.length, + offset: offset, + search: search + }; + // Не менять атрибут смещения у пейджера + cms.dont_change_offset = true; + api( data2, set_pages_list ); + } + } + // update event for menu + if ( r.update_menu == "true" ) { + let event = new Event( "update_menu" ); + document.body.dispatchEvent( event ); + } + } ); + } ); + } ); + + // copy file link button + document.querySelector( "#pages .link-file-copy-btn" ).addEventListener( "click", function( e ) { + let img = this.previousElementSibling.innerText; + let tmp = document.createElement( "textarea" ); + document.body.appendChild( tmp ); + tmp.value = img; + tmp.select(); + let r = document.execCommand( "copy" ); + tmp.remove(); + if ( r ) { + if ( img ) { + notify( _( "copyed" ), "info-success", 5000 ); + } else { + notify( _( "select_file" ), "info-error", 5000 ); + } + } else { + notify( _( "copy_error" ), "info-error", 5000 ); + } + cm.focus(); + } ); + + function CtrlS( e ) { + // ы and і - fix for librewolf + if ( ( e.code == "KeyS" || e.key == "ы" || e.key == "і" ) && e.ctrlKey == true ) { + e.preventDefault(); // don't save page + if ( window.location.hash == "#pages" ) { + document.querySelector( "#pages .save-page-button" ).click(); + } + } + /*/ Ловим кнопки codemirror + if ( e.code == "F1" ) { + let dialog = document.querySelector( ".CodeMirror-dialog-top" ); + let html = dialog.outerHTML; + cms.dialog = html; + //notify( html, "info-success", 5000000 ); + } + if ( e.code == "F2" ) { + document.querySelector( ".CodeMirror" ).insertAdjacentHTML( "beforeend", cms.dialog ); + } + */ + } + + // Save Page + document.querySelectorAll( "#pages .save-page-button" ).forEach( function( button ) { + button.addEventListener( "click", function( e ) { + window.cm.save(); // drop changes to textarea + let main_div = document.querySelector( "#pages" ); + let prop_div = main_div.querySelector( ".page-properties" ); + let data = { + fn: "save_page", + id: main_div.querySelector( ".save-page-button" ).getAttribute( "data-id" ), + modified: main_div.querySelector( ".page-editor > textarea" ).getAttribute( "data-modified" ), + text: main_div.querySelector( ".page-editor > textarea" ).value, + title: prop_div.querySelector( "input[name='title']" ).value, + url: prop_div.querySelector( "input[name='url']" ).value, + seo_title: prop_div.querySelector( "input[name='seo_title']" ).value, + description: prop_div.querySelector( "textarea[name='description']" ).value, + tags: prop_div.querySelector( "textarea[name='tags']" ).value, + date: prop_div.querySelector( "input[name='date']" ).value, + time: prop_div.querySelector( "input[name='time']" ).value, + template: prop_div.querySelector( ".template-select-grid .field-select" ).getAttribute( "data-template" ), + old_template: prop_div.querySelector( ".template-select-grid .field-select" ).getAttribute( "data-old-template" ), + } + api( data, function( r ) { + if ( r.ok == "true" ) { + // set text + if ( r.new_text ) { + window.cm.setValue( r.new_text ); + } + + // Update Title and URL + main_div.querySelector( ".page-editor-title" ).innerHTML = r.title; + main_div.querySelector( ".page-editor-title" ).setAttribute( "href", r.base_path + r.url ); + prop_div.querySelector( "input[name='url']" ).value = r.url; + + // update old template + prop_div.querySelector( ".template-select-grid .field-select" ).setAttribute( "data-old-template", data.template ); + + // Update item in page list + let item = main_div.querySelector( `.pages-grid [data-id='${data.id}']` ); + item.querySelector( `.page-name` ).innerHTML = r.title; + item.querySelector( `.page-name` ).setAttribute( "href", r.base_path + r.url ); + update_home(); + item.querySelector( `input[name='title']` ).value = r.title; + item.querySelector( `input[name='url']` ).value = r.url; + item.querySelector( `input[name='seo_title']` ).value = data.seo_title; + item.querySelector( `textarea[name='description']` ).value = data.description; + item.querySelector( `textarea[name='tags']` ).value = data.tags; + + // Выставить шаблон в плашке в списке страниц + let select = item.querySelector( `.template-select-grid` ); + select_switch_to( select, data.template ); + item.querySelector( `.template-select-grid .field-select` ).setAttribute( "data-old-template", data.template ); + + item.querySelector( `input[name='date']` ).value = data.date; + item.querySelector( `input[name='time']` ).value = data.time; + if ( r.planned ) { + item.querySelector( `.page-date` ).classList.add( "future" ); + } else { + item.querySelector( `.page-date` ).classList.remove( "future" ); + } + + main_div.querySelector( ".page-editor > textarea" ).setAttribute( "data-modified", r.modified ); + main_div.querySelector( ".close-page-button" ).setAttribute( "data-changed", "false" ); + main_div.querySelector( ".page-editor-grid" ).setAttribute( "data-changed", "false" ); + // edit marker + main_div.querySelectorAll( ".pages-grid > div" ).forEach( function( item ) { + item.classList.remove( "last-edited" ); + } ); + item.classList.add( "last-edited" ); + // close editor after save + if ( main_div.querySelector( ".save-page-button" ).getAttribute( "data-close" ) === "true" ) { + main_div.querySelector( ".save-page-button" ).setAttribute( "data-close", "false" ); + main_div.querySelector( ".close-page-button" ).click(); + } + // highlight save button + main_div.querySelector( ".save-page-button" ).classList.add( "saved" ); + setTimeout( function() { + main_div.querySelector( ".save-page-button" ).classList.remove( "saved" ); + }, 1000 ); + + notify( r.info_text, r.info_class, r.info_time ); + + // update event for menu + if ( r.update_menu == "true" ) { + let event = new Event( "update_menu" ); + document.body.dispatchEvent( event ); + } + } + if ( r.ok == "false" ) { + // highlight save button + main_div.querySelector( ".save-page-button" ).classList.add( "error" ); + setTimeout( function() { + main_div.querySelector( ".save-page-button" ).classList.remove( "error" ); + }, 1000 ); + + notify( r.info_text, r.info_class, r.info_time ); + } + } ); + } ); + } ); + + // transliterate file name + function __tr_file( str ) { + let ext = str.match( /\.[^\.]+$/, "" ); + str = str.replace( /\.[^\.]+$/, "" ); + let sp = cms.tr[" "]; + cms.tr[" "] = "_"; + for ( let i in cms.tr ) { + let re = new RegExp( i, "g" ); + str = str.replace( re, cms.tr[i] ); + } + if ( sp === undefined ) { + delete cms.tr[" "]; + } else { + cms.tr[" "] = sp; + } + str = str.replace( /[^-A-Za-z0-9_]+/g, "" ); + if ( ext[0] ) { + str = str + ext[0]; + } + str = str.toLowerCase(); + return str; + } + + // Upload files + let upload_btn = document.querySelector( "#pages .upload-files input[type=file]" ); + if ( upload_btn ) upload_btn.addEventListener( "change", async function( event ) { + const formData = new FormData(); + let id = document.querySelector( "#pages .save-page-button" ).getAttribute( "data-id" ); + formData.append( "id", id ); + formData.append( "fn", "upload_files" ); + let n = 0; + for ( let i = 0; i < this.files.length; i++ ) { + formData.append( "myfile[]", this.files[i] ); + let f = `${cms.base_path}uploads/${id}/` + __tr_file( this.files[i].name ); + let f_exists = document.querySelector( `#pages .file-block [data-src="${f}"]` ); + if ( f_exists ) { n++; } + } + let google_chrome_fix = this; + if ( n ) { + let c = confirm( _( "same_files" ) + ` - ${n} ` + _( "pc" ) + "\n" + _( "confirm_replace" ) ); + if ( ! c ) { + google_chrome_fix.value = ""; + return c; + } + } + let bar = document.querySelector( "#pages .upload-progress" ); + + + let ajax = new XMLHttpRequest(); + + ajax.upload.addEventListener( "progress", function( event ) { + let percent = Math.round( (event.loaded / event.total) * 100 ); + bar.style.width = percent + "%"; + }, false ); + + ajax.addEventListener( "error", function( event ) { + notify( _( "error_upload_file" ), "info-error", 3600000 ); + bar.style = ""; + }, false ); + + ajax.addEventListener( "abort", function( event ) { + notify( _( "error_upload_file" ), "info-error", 3600000 ); + bar.style = ""; + }, false ); + + ajax.addEventListener( "load", function( event ) { + bar.style = ""; + google_chrome_fix.value = ""; + if ( event.target.status == 413 ) { + notify( _( "too_large" ), "info-error", 5000 ); + } else { + let r = JSON.parse( event.target.responseText ); + if ( r.info_text ) { + notify( r.info_text, r.info_class, r.info_time ); + if ( r.info_class == "info-success" ) { + + // удалить файлы которые были обновлены + let tmp = document.createElement( "div" ); + tmp.innerHTML = r.flist; + let imgs = tmp.querySelectorAll( "img" ); + imgs.forEach( function( img ) { + let file = img.getAttribute( "data-src" ); + let exists_file = document.querySelector( `#pages .file-block [data-src="${file}"]` ); + if ( exists_file ) { + exists_file.parentElement.remove(); + } + } ); + + let container = document.querySelector( "#pages .mediateka-files-grid" ); + container.innerHTML = r.flist + container.innerHTML; + + // images checkboxes + container.querySelectorAll( "input[type=checkbox]" ).forEach( function( checkbox ) { + checkbox.addEventListener( "dblclick", img_check_dblclick ); + checkbox.addEventListener( "change", img_rechecked ); + } ); + + // open lightbox + container.querySelectorAll( "img" ).forEach( function( img ) { + img.addEventListener( "dblclick", img_lbox ); + } ); + + // generate link + container.querySelectorAll( ".file-block" ).forEach( function( file_block ) { + file_block.addEventListener( "click", img_click ); + } ); + + // select last uploaded + container.querySelector( ".file-block" ).click(); + } + } + } + }, false ); + + ajax.open( "POST", cms.api ); + ajax.send( formData ); + + } ); + + // Close Editor + document.querySelectorAll( "#pages .close-page-button" ).forEach( function( button ) { + button.addEventListener( "click", function( e ) { + + // hide mediateka + if ( ! document.querySelector( "#pages .page-editor-panel" ).classList.contains( "hidden" ) ) { + document.querySelector( "#pages .open-mediateka" ).click(); + } + + // hide editor + document.querySelector( "#pages .page-editor-bg" ).classList.add( "hidden" ); + document.body.classList.remove( "editor" ); + + document.documentElement.removeEventListener( "keydown", CtrlS ); + + // detach + if ( window.cm !== undefined ) { + if ( this.getAttribute( "data-changed" ) === "true" ) { + if ( confirm( _( "confirm_save" ) ) ) { + document.querySelector( "#pages .save-page-button" ).setAttribute( "data-close", "true" ); + document.querySelector( "#pages .save-page-button" ).click(); + return; + } + } + window.cm.toTextArea(); + window.cm = null; + } + + } ); + } ); + + // Create Page + document.querySelectorAll( "#pages .add-page-btn" ).forEach( function( btn ) { + btn.addEventListener( "click", function ( e ) { + // Отложенный вызов на случай если нужно очистить поиск + cms.create_page_fn = function() { + api( { fn: "create_page" }, function( r ) { + if ( r.info_text ) { + notify( r.info_text, r.info_class, r.info_time ); + } + if ( r.pages ) { + let grid = document.querySelector( "#pages .pages-grid" ); + grid.insertAdjacentHTML( "afterbegin", r.pages[0].html ); + // Подкрутить список страниц в начало + document.querySelector( "#pages .main-main" ).scrollTop = 0; + + let page_box = grid.querySelector( `[data-id="${r.pages[0].id}"]` ); + + set_controls( page_box ); + + let counter = document.querySelector( "#pages .main-footer .count" ); + counter.innerText = +counter.innerText + 1; + + let showed_pages_el = document.querySelector( "#pages .main-footer .counters input" ); + if ( showed_pages_el.value === get_cookie( "pages_pager" ) ) { + document.querySelector( "#pages .pages-grid > div:last-child" ).remove(); + } else { + showed_pages_el.value = +showed_pages_el.value + 1; + } + } + } ); + } + // Если сейчас не результаты поиска отображены, + // и если мы видим первый пейджер + // то сразу выполнить создание страницы + let empty_search = document.querySelector( "#pages .page-search" ).getAttribute( "data-result-of" ) === ""; + let offset_zero = +document.querySelector( "#pages .main-footer .counters input" ).getAttribute( "data-offset" ) === 0; + if ( empty_search && offset_zero ) { + cms.create_page_fn(); + cms.create_page_fn = null; + } else { + document.querySelector( "#pages .reset" ).click(); + // Создание страницы произведет функция set_pages_list() + } + } ); + } ); + + // Open Properties + document.querySelector( "#pages .open-properties" ).addEventListener( "click", function( e ) { + document.querySelector( "#pages .page-editor-grid" ).classList.toggle( "properties" ); + document.querySelector( "#pages .page-properties" ).classList.toggle( "hidden" ); + if ( window.cm ) { + let cursor = window.cm.getCursor(); + window.cm.scrollIntoView( { line:cursor.line, ch:cursor.ch } ); + } + if ( document.querySelector( "#pages .page-editor-grid" ).classList.contains( "properties" ) ) { + document.querySelector( "#pages .page-editor-grid" ).classList.remove( "mediateka" ); + document.querySelector( "#pages .page-editor-panel" ).classList.add( "hidden" ); + } + } ); + + // Open Mediateka + document.querySelector( "#pages .open-mediateka" ).addEventListener( "click", function( e ) { + document.querySelector( "#pages .page-editor-grid" ).classList.toggle( "mediateka" ); + document.querySelector( "#pages .page-editor-panel" ).classList.toggle( "hidden" ); + if ( window.cm ) { + let cursor = window.cm.getCursor(); + window.cm.scrollIntoView( { line:cursor.line, ch:cursor.ch } ); + } + if ( document.querySelector( "#pages .page-editor-grid" ).classList.contains( "mediateka" ) ) { + document.querySelector( "#pages .page-editor-grid" ).classList.remove( "properties" ); + document.querySelector( "#pages .page-properties" ).classList.add( "hidden" ); + } + cm.focus(); + } ); + + // Replace Dialog Toggle + document.querySelector( "#pages .codemirror-replace" ).addEventListener( "click", function( e ) { + let dialog = document.querySelector( "#pages .CodeMirror-dialog" ); + if ( dialog ) { + dialog.remove(); + } else { + let mediateka = ! document.querySelector( "#pages .page-editor-panel" ).classList.contains( "hidden" ); + if ( mediateka && window.innerWidth < 1024 ) { + document.querySelector( "#pages .open-mediateka" ).click(); + } + window.cm.execCommand( "replace" ); + } + } ); + + // generate link to clicked file + function img_click() { + this.parentElement.querySelectorAll( ".file-block" ).forEach( function( block ) { + block.classList.remove( "active-file" ); + } ); + this.classList.add( "active-file" ); + let i = this.querySelector( "img" ); + let t = i.getAttribute( "data-type" ); + let link = i.getAttribute( "data-src" ); + let w = i.getAttribute( "width" ); + let h = i.getAttribute( "height" ); + let e = link.replace( /.*\./, "" ); + let img = [ "webp", "tiff", "jpeg", "jpg", "png", "svg", "gif", "bmp", "ico" ]; + let mus = [ "mp3", "ogg", "m4a", "flac" ]; + let vid = [ "mp4", "mkv", "webm" ]; + let a = `<a href="${link}" target=_blank>${link}</a>`; + let tag = this.closest( ".mediateka-grid" ).querySelector( ".link-file-tag" ); + if ( img.indexOf( e ) >= 0 ) { + link = `<img alt="" src="${a}"`; + if ( w ) { + link += ` width="${w}"`; + } + if ( h ) { + link += ` height="${h}"`; + } + link += ` loading="lazy"`; + link += ">"; + tag.innerHTML = link; + } else if ( mus.indexOf( e ) >= 0 ) { + link = `<audio src="${a}" controls></audio>`; + tag.innerHTML = link; + } else if ( vid.indexOf( e ) >= 0 ) { + link = `<video src="${a}" poster="" controls preload="none"></video>`; + tag.innerHTML = link; + } else { + link = `<a href="${a}">TEXT</a>`; + tag.innerHTML = link; + } + let inner_link = tag.querySelector( "a" ); + inner_link.addEventListener( "click", file_link_click ); + cm.focus(); + } + + function file_link_click( e ) { + e.preventDefault(); + let tmp = document.createElement( "textarea" ); + document.body.appendChild( tmp ); + tmp.value = e.target.getAttribute( "href" ); + tmp.select(); + let r = document.execCommand( "copy" ); + tmp.remove(); + if ( r ) { + notify( _( "copyed" ), "info-success", 5000 ); + } else { + notify( _( "copy_error" ), "info-error", 5000 ); + } + cm.focus(); + } + + // enable or disable delete files button + function img_rechecked() { + let checked = document.querySelectorAll( "#pages .mediateka-files-grid input[type=checkbox]:checked" ); + if ( checked.length ) { + document.querySelector( "#pages .del-uploaded-files" ).classList.remove( "disabled" ); + } else { + document.querySelector( "#pages .del-uploaded-files" ).classList.add( "disabled" ); + } + } + + // Выбор всех картинок двойным кликом + function img_check_dblclick( e ) { + let stat = ! this.checked; + document.querySelectorAll( "#pages .mediateka-files-grid input[type=checkbox]" ).forEach( function( chbox ) { + chbox.checked = stat; + } ); + img_rechecked(); + } + + // view in lightbox + function img_lbox() { + let src = this.getAttribute( "data-src" ); + let e = src.replace( /.*\./, "" ); + let img = [ "webp", "tiff", "jpeg", "jpg", "png", "svg", "gif", "bmp", "ico" ]; + let mus = [ "mp3", "ogg", "m4a", "flac" ]; + let vid = [ "mp4", "mkv", "webm" ]; + let t; + if ( document.querySelector( "#lbox-window" ) == null ) { + if ( img.indexOf( e ) >= 0 ) { + t = document.createElement( "img" ); + } else if ( mus.indexOf( e ) >= 0 ) { + t = document.createElement( "audio" ); + t.setAttribute( "controls", true ); + } else if ( vid.indexOf( e ) >= 0 ) { + t = document.createElement( "video" ); + t.setAttribute( "controls", true ); + } + if ( t !== undefined ) { + t.src = src; + let d = document.createElement( "div" ); + d.id = "lbox-window"; + d.appendChild( t ); + document.body.appendChild( d ); + d.addEventListener( "click", function( e ) { + this.remove(); + } ); + } + } + } + + + // Delete files + document.querySelector( "#pages .del-uploaded-files" ).addEventListener( "click", function( e ) { + if ( ! this.classList.contains( "disabled" ) ) { + let flist = []; + document.querySelectorAll( "#pages .mediateka-files-grid input[type=checkbox]:checked" ).forEach( function( e ) { + let f = e.closest( ".file-block" ).querySelector( "img" ).getAttribute( "data-src" ); + flist.push( f ); + } ); + let data = { + fn: "del_files", + flist: flist + }; + api( data, function( r ) { + if ( r.info_text ) { + document.querySelector( "#pages .link-file-tag" ).innerHTML = ""; + notify( r.info_text, r.info_class, r.info_time ); + if ( r.info_class == "info-success" ) { + for ( let f in flist ) { + document.querySelector( `#pages .mediateka-files-grid img[data-src="${flist[f]}"]` ).parentElement.remove(); + } + document.querySelector( "#pages .del-uploaded-files" ).classList.add( "disabled" ); + } + } + } ); + } + } ); + + // prevent hide cursor when window resize + window.addEventListener( "resize", function() { + if ( window.cm ) { + let cursor = window.cm.getCursor(); + window.cm.scrollIntoView( { line:cursor.line, ch:cursor.ch } ); + } + } ); + + // show/hide tags + let tags_btn = document.querySelector( "#pages .tags-helper" ); + if ( tags_btn ) tags_btn.addEventListener( "click", function( e ) { + document.querySelector( "#pages .page-editor-grid" ).classList.toggle( "tags-opened" ); + cm.focus(); + cm.refresh(); + } ); + + // for tags + document.querySelectorAll( "#pages .tags-grid [data-type='wrap']" ) .forEach( function( btn ) { + btn.addEventListener( "click", function( e ) { + let otag = this.getAttribute( "data-otag" ); + let ctag = this.getAttribute( "data-ctag" ); + let len = this.getAttribute( "data-len" ); + let ch = this.getAttribute( "data-ch" ); + let line = this.getAttribute( "data-line" ); + + //wrap_selections( otag, ctag, len, line, ch ); + let cursor = cm.getCursor(); + let selections = cm.getSelections(); + let replacements = []; + for ( let i = 0; i < selections.length; i++ ) { + replacements[i] = otag + selections[i] + ctag; + } + cm.replaceSelections( replacements ); + // Прятять панельку на мобильнике + if ( window.innerWidth < 1024 ) { + //document.querySelector( "#pages .tags-helper" ).click(); + let grid = this.closest( ".editor-grid" ); + grid.classList.remove( "tags-opened" ); + } + if ( selections.length < 2 ) { + if ( line ) { + cursor.line += +line; + cursor.ch = +ch; + } else if ( len ) { + cursor.ch += +len; + } + cm.setCursor( cursor ); + } + cm.focus(); + cm.refresh(); + } ); + } ); + // for <a> tag + document.querySelectorAll( "#pages .tags-grid [data-type='wrap-a']" ) .forEach( function( btn ) { + btn.addEventListener( "click", function( e ) { + let cursor = cm.getCursor(); + let selections = cm.getSelections(); + let replacements = []; + + for ( let i = 0; i < selections.length; i++ ) { + if ( selections[i].match( /https?:\/\// ) ) { + replacements[i] = `<a href="${selections[i]}" target=_blank>${selections[i]}</a>`; + } else { + replacements[i] = `<a href="" target=_blank>${selections[i]}</a>`; + } + } + + cm.replaceSelections( replacements ); + + if ( selections.length == 1 && selections[0] === "" ) { + cursor.ch += 9; + cm.setCursor( cursor ); + } + + if ( window.innerWidth < 1024 ) { + //document.querySelector( "#pages .tags-helper" ).click(); + let grid = this.closest( ".editor-grid" ); + grid.classList.remove( "tags-opened" ); + } + + cm.focus(); + cm.refresh(); + } ); + } ); + // for tags <ul> and <ol> + document.querySelectorAll( "#pages .tags-grid [data-type='wrap-list']" ) .forEach( function( btn ) { + btn.addEventListener( "click", function( e ) { + let tag = this.getAttribute( "data-tag" ); + let selections = cm.getSelections(); + let replacements = []; + + if ( selections.length == 1 && selections[0] === "" ) { + replacements[0] = `<${tag}>\n <li></li>\n</${tag}>`; + } else if ( selections.length == 1 ) { + let lines = selections[0].split( "\n" ); + replacements[0] = `<${tag}>\n <li>` + lines.join( "</li>\n <li>" ) + `</li>\n</${tag}>`; + } else { + let n = selections.length - 1; + for ( let i = 0; i <= n; i++ ) { + replacements[i] = ` <li>${selections[i]}</li>`; + } + replacements[0] = `<${tag}>\n` + replacements[0]; + replacements[n] = replacements[n] + `\n</${tag}>`; + } + + cm.replaceSelections( replacements ); + + if ( window.innerWidth < 1024 ) { + //document.querySelector( "#pages .tags-helper" ).click(); + let grid = this.closest( ".editor-grid" ); + grid.classList.remove( "tags-opened" ); + } + + cm.focus(); + cm.refresh(); + } ); + } ); + + // fix glitches codemirror + let fix = document.querySelector( "aside a[href='#pages']" ); + if ( fix ) fix.addEventListener( "click", function( e ) { + setTimeout( function( e ) { + if ( window.cm ) { + cm.refresh(); + cm.focus(); + } + }, 50 ); + } ); + + // Транслитерация URL + let tr_url = document.querySelector( "#pages .page-editor-grid .url-translit" ); + if ( tr_url ) tr_url.addEventListener( "click", function( e ) { + let url = document.querySelector( "#pages .page-editor-grid .page-properties input[name='title']" ).value; + let tr_url = url_translit( url ); + this.previousElementSibling.value = tr_url; + } ); + function url_translit( url ) { + url = url.toLowerCase(); + for ( let i in cms.tr ) { + let re = new RegExp( i, "g" ); + url = url.replace( re, cms.tr[i] ); + } + url = url.replace( / +/g, "-" ); + url = url.replace( /[^-a-z0-9_]+/g, "" ); + url = url.replace( /^[-_]+|[-_]+$/g, "" ); + return url; + } + + function update_home() { + document.querySelectorAll( `#pages .pages-grid [data-id]` ).forEach( function( page ) { + if ( page.querySelector( `.page-name[href="${cms.base_path}"]` ) ) { + page.classList.add( "home" ); + } else { + page.classList.remove( "home" ); + } + } ); + } + + + // Замена текста на всех страницах + document.querySelectorAll( "#pages-utils .replace-btn" ).forEach( function( button ) { + button.addEventListener( "click", function( e ) { + let data = { + fn: "replace_in_pages", + table: document.querySelector( "#pages-utils input[name='table']" ).value, + id_col: document.querySelector( "#pages-utils input[name='id_col']" ).value, + column: document.querySelector( "#pages-utils input[name='column']" ).value, + search_regex: document.querySelector( "#pages-utils input[name='search_regex']" ).value, + replace: document.querySelector( "#pages-utils input[name='replace']" ).value, + }; + if ( data.search_regex && confirm( _( "replace_in_pages_confirm" ) ) ) { + api( data, function( r ) { + if ( r.info_text ) { + notify( r.info_text, r.info_class, r.info_time ); + } + } ); + } + } ); + } ); + +} );