Docs

#### Translations

Return to domains Saving ... Save
Vvveb 0.2 is now available! Update now
You successfully read this important alert message.
You successfully read this important warning message.
You successfully read this important alert message.

Text | Translation
--- | ---
Text | Translation

Text </text> </div> <div class=""> <label for="translation-textarea" class="small py-1">Translation</label> <textarea id="translation-textarea" class="form-control" rows="4"></text> </div> </div> </div> </div> </div> <input type="hidden" name="csrf" id="csrf" data-v-csrf> <div id="main-footer"> Powered by <a href="https://www.vvveb.com" target="_blank">Vvveb</a> </div> </div> </div> <style> table[data-v-translations] thead th { resize:horizontal; overflow:auto } table[data-v-translations] tbody { cursor:pointer; } table[data-v-translations] tbody tr.active td { background-color: var(--bs-primary) !important; color: var(--bs-light); } table[data-v-translations] tbody tr.changed td { background-color: var(--bs-success-bg-subtle); } table[data-v-translations] tbody tr td:empty { background-color: rgba(var(--bs-warning-rgb), 0.1); } </style> <div class="modal fade" id="searchModal" tabindex="-1" aria-labelledby="searchModalLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-scrollable"> <div class="modal-content"> <div class="modal-body p-0"> <form action="{$this.adminPath}/index.php?module=tools/search" method="get"> <input type="hidden" name="module" value="tools/search"> <div class="d-flex"> <div class="input-group"> <input type="search" name="search" class="form-control border-0 p-3 px-4" id="headerSearch" placeholder="Start typing to search" data-v-vvveb-action="search.autocomplete" data-selector=".search-results" data-v-vvveb-on="keyup"> <button class="btn btn-outline-primary border-0" type="submit" title="Search"> <div class="la-flip-horizontal"> <i class="la la-search la-lg" aria-hidden="true"></i> </div> </button> </div> </div> <div class="loading d-none d-flex justify-content-center m-5"> <div class="spinner-border" role="status"> <span class="visually-hidden">Loading...</span> </div> </div> </form> <div class="search-results"> </div> </div> </div> <button type="button" class="btn-close btn-close-white small" data-bs-dismiss="modal" aria-label="Close"></button> </div> </div> <div id="page-loading-status" class="progress progress-bar"></div> <!-- <script id="jquery-js" src="js/jquery.min.js"></script> --> <script id="popper-js" src="js/popper.min.js"></script> <script id="bootstrap-js" src="js/bootstrap.min.js"></script> <script id="theme-js" src="js/theme.js"></script> <!-- vvveb modules code --> <script id="admin-js" type="module" src="/js/admin.js"></script> <script> var rbracket = /\[\]$/; function buildParams( prefix, obj, add ) { var name; if ( Array.isArray( obj ) ) { // Serialize array item. for(var key in obj) { let v = obj[key]; if ( rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // Item is non-scalar (array or object), encode its numeric index. buildParams( prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", v, add ); } } } else if ( typeof obj === "object" ) { // Serialize object item. for ( name in obj ) { buildParams( prefix + "[" + name + "]", obj[ name ], add ); } } else { // Serialize scalar item. add( prefix, obj ); } } // Serialize an array of form elements or a set of // key/values into a query string function nestedFormData( a ) { var prefix, s = [], add = function( key, valueOrFunction ) { // If value is a function, invoke it and use its return value var value = typeof valueOrFunction === "function" ? valueOrFunction() : valueOrFunction; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value == null ? "" : value ); }; if ( a == null ) { return ""; } // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( Object.is( a ) ) ) { // Serialize the form elements for(var key in object) { let v = object[key]; //jQuery.each( a, function() { add( key, v ); }; } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( prefix in a ) { buildParams( prefix, a[ prefix ], add ); } } // Return the resulting serialization return s.join( "&" ); }; let selectedTranslation; let selectedRow; document.querySelector("table[data-v-translations] tbody").addEventListener("click", function (e) { let element = e.target.closest("tr"); let columns; if (element) { columns = element.querySelectorAll("td"); document.getElementById("text-textarea").value = columns[0].innerHTML; document.getElementById("translation-textarea").value = columns[1].innerHTML; selectedTranslation = columns[1]; selectedRow = element; document.querySelectorAll("table[data-v-translations] tbody tr").forEach(e => e.classList.remove("active")); this.classList.add("active"); } }); let search = function (e) { let searchText = this.value; delay(() => { if (searchText.length) { document.querySelectorAll("table[data-v-translations] tbody tr").forEach(e => { e.style.display = "none"; if (e.innerHTML.indexOf(searchText) > -1) e.style.display = ""; }); } else { document.querySelectorAll("table[data-v-translations] tbody tr").forEach(e => e.style.display = ""); } }, 300); }; let filter = document.getElementById("filter"); filter.addEventListener("keyup", search); filter.addEventListener("search", search); let save = function (e) { let btn = this; let translations = {}; let saveUrl = this.href; let changedRows = document.querySelectorAll("table[data-v-translations] tbody tr.changed"); changedRows.forEach(function (e) { let columns = e.querySelectorAll("td"); translations[columns[0].innerHTML] = columns[1].innerHTML; }); btn.querySelector(".loading")?.classList.toggle("d-none"); btn.querySelector(".button-text")?.classList.toggle("d-none"); fetch(saveUrl, {method:"POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: nestedFormData({translations,csrf:document.getElementById("csrf")?.value})}) .then(response => { if (!response.ok) { return Promise.reject(response); } return response.json() }) .then(data => { btn.querySelector(".loading")?.classList.toggle("d-none"); btn.querySelector(".button-text")?.classList.toggle("d-none"); displayToast("bg-success", "Save", data.message ?? data); changedRows.forEach(e => e.classList.remove("changed")); }) .catch(error => { console.log(error.statusText); displayToast("bg-danger", "Error", "Error saving!\n" + error.responseText); //displayToast("bg-danger", "Revision", "Error!"); }); e.preventDefault(); return false; }; document.getElementById("save-btn").addEventListener("click", save); document.getElementById("add-text").addEventListener("click", function () { let text = prompt("Enter new text"); if (text) { document.querySelector("[data-v-translations] tbody").append(generateElements(`<tr class="changed"><td>${text}</td><td></td></tr>`)[0]); window.scrollTo(0, document.body.scrollHeight); } }); document.getElementById("translation-textarea").addEventListener("keyup", function (e) { if (selectedTranslation) { delay(() => { selectedTranslation.innerHTML = this.value; selectedRow.classList.add("changed"); }, 200); } }); </script> <!-- Modal --> <div class="modal fade" id="heartBeatLogin" data-bs-backdrop="static" tabindex="-1" aria-labelledby="heartBeatLogin" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"> <span class="modal-title" id="heartBeatLogin"> <h5>Session has expired</h5> <span>Login to resume your work</span> </span> <button type="button" class="btn-close me-2" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body p-5"> </div> </div> </div> </div> <!-- save toast --> <div class="toast-container position-fixed end-0 bottom-0 me-4 my-4"> <div class="toast " role="alert" aria-live="assertive" aria-atomic="true" id="bottom-toast"> <div class="toast-header bg-success bg-opacity-25 border-0"> <strong class="me-auto">Page save</strong> <!-- <small class="badge bg-success">status</small> --> <button type="button" class="btn-close me-1" data-bs-dismiss="toast" aria-label="Close"></button> </div> <div class="toast-body"> <div class="flex-grow-1"> <div class="message"> <div>Elements saved!</div> <div>Template backup was saved!</div> <div>Template was saved!</div> </div> <div> <!-- <a class="btn btn-success btn-icon btn-sm w-100 mt-2" href="">View page</a></div> --> </div> </div> </div> </div> <div class="toast-container position-fixed end-0 top-0 me-4 my-4"> <div class="toast " role="alert" aria-live="assertive" aria-atomic="true" id="top-toast"> <div class="toast-header bg-success bg-opacity-25 border-0"> <strong class="me-auto">Page save</strong> <!-- <small class="badge bg-success">status</small> --> <button type="button" class="btn-close me-1" data-bs-dismiss="toast" aria-label="Close"></button> </div> <div class="toast-body"> <div class="flex-grow-1"> <div class="message"> <div>Elements saved!</div> <div>Template backup was saved!</div> <div>Template was saved!</div> </div> <div> <!-- <a class="btn btn-success btn-icon btn-sm w-100 mt-2" href="">View page</a></div> --> </div> </div> </div> </div> </body> </html>