PHP Classes

File: templates/admin/template_editor.twig

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   templates/admin/template_editor.twig   Download  
File: templates/admin/template_editor.twig
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 13,118 bytes
 

Contents

Class file image Download
{% extends "base.twig" %} {% block title %}{{ t('ui.admin.template_editor.page_title_prefix', {}, locale, ['common']) }} - {{ parent() }}{% endblock %} {% block content %} <div class="d-flex justify-content-between align-items-center mb-4"> <h2> <i class="fas fa-file-code"></i> {{ t('ui.admin.template_editor.heading', {}, locale, ['common']) }} </h2> <div class="text-muted small"> {{ t('ui.admin.template_editor.restricted_edits_prefix', {}, locale, ['common']) }} <code>templates/custom</code> </div> </div> <div class="row"> <div class="col-lg-4"> <div class="card mb-3"> <div class="card-header d-flex justify-content-between align-items-center"> <h6 class="mb-0">{{ t('ui.admin.template_editor.custom_templates', {}, locale, ['common']) }}</h6> <button class="btn btn-sm btn-outline-secondary" onclick="loadTemplates()"> <i class="fas fa-sync"></i> </button> </div> <div class="card-body"> <div class="mb-3"> <input type="text" class="form-control" id="templateSearch" placeholder="{{ t('ui.admin.template_editor.search_templates_placeholder', {}, locale, ['common']) }}"> </div> <div id="templateList" class="list-group small"> <div class="text-muted">{{ t('ui.admin.template_editor.loading_templates', {}, locale, ['common']) }}</div> </div> </div> </div> <div class="card"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.template_editor.new_template', {}, locale, ['common']) }}</h6> </div> <div class="card-body"> <div class="mb-2"> <label class="form-label">{{ t('ui.admin.template_editor.path_relative_custom', {}, locale, ['common']) }}</label> <input type="text" class="form-control font-monospace" id="newTemplatePath" placeholder="{{ t('ui.admin.template_editor.new_template_path_placeholder', {}, locale, ['common']) }}"> </div> <button class="btn btn-sm btn-outline-primary" onclick="createTemplate()"> <i class="fas fa-plus"></i> {{ t('ui.admin.template_editor.create', {}, locale, ['common']) }} </button> </div> </div> </div> <div class="col-lg-8"> <div class="card"> <div class="card-header d-flex justify-content-between align-items-center"> <div> <h6 class="mb-1">{{ t('ui.admin.template_editor.editor', {}, locale, ['common']) }}</h6> <div class="text-muted small" id="currentTemplateLabel">{{ t('ui.admin.template_editor.no_template_selected', {}, locale, ['common']) }}</div> </div> <div class="btn-group"> <button class="btn btn-outline-primary btn-sm" id="installTemplateBtn" onclick="installTemplate()" disabled> <i class="fas fa-download"></i> {{ t('ui.admin.template_editor.install', {}, locale, ['common']) }} </button> <button class="btn btn-outline-success btn-sm" id="saveTemplateBtn" onclick="saveTemplate()" disabled> <i class="fas fa-save"></i> {{ t('ui.common.save', {}, locale, ['common']) }} </button> <button class="btn btn-outline-danger btn-sm" id="deleteTemplateBtn" onclick="deleteTemplate()" disabled> <i class="fas fa-trash"></i> {{ t('ui.common.delete', {}, locale, ['common']) }} </button> </div> </div> <div class="card-body"> <textarea class="form-control font-monospace" id="templateEditor" rows="20" disabled></textarea> <div class="form-text mt-2"> {{ t('ui.admin.template_editor.changes_apply_immediately', {}, locale, ['common']) }} </div> </div> </div> <div id="templateStatus" class="alert d-none mt-3" role="alert"></div> </div> </div> {% endblock %} {% block scripts %} <script> let templates = []; let currentTemplate = null; let isDirty = false; function apiError(payload, fallback) { if (window.getApiErrorMessage) { return window.getApiErrorMessage(payload, fallback); } return payload && payload.error ? payload.error : fallback; } function uiT(key, fallback, params = {}) { if (window.t) { return window.t(key, params, fallback); } return fallback; } document.addEventListener('DOMContentLoaded', function() { loadTemplates(); document.getElementById('templateSearch').addEventListener('input', function() { renderTemplateList(this.value.trim().toLowerCase()); }); document.getElementById('templateEditor').addEventListener('input', function() { if (currentTemplate) { isDirty = true; } }); }); function setStatus(message, type = 'info') { const status = document.getElementById('templateStatus'); status.className = `alert alert-${type}`; status.textContent = message; status.classList.remove('d-none'); } function clearStatus() { const status = document.getElementById('templateStatus'); status.classList.add('d-none'); status.textContent = ''; } function loadTemplates() { clearStatus(); fetch('/admin/api/custom-templates') .then(response => response.json()) .then(data => { templates = data.templates || []; renderTemplateList(document.getElementById('templateSearch').value.trim().toLowerCase()); }) .catch(() => { setStatus(uiT('ui.admin.template_editor.load_templates_failed', 'Failed to load templates.'), 'danger'); }); } function renderTemplateList(filter = '') { const list = document.getElementById('templateList'); list.innerHTML = ''; const filtered = templates.filter(t => t.path.toLowerCase().includes(filter)); if (filtered.length === 0) { list.innerHTML = `<div class="text-muted">${uiT('ui.admin.template_editor.no_templates_found', 'No templates found.')}</div>`; return; } filtered.forEach(t => { const item = document.createElement('button'); item.type = 'button'; item.className = 'list-group-item list-group-item-action'; item.textContent = t.path; item.onclick = () => loadTemplate(t.path); list.appendChild(item); }); } function loadTemplate(path) { if (isDirty && !confirm(uiT('ui.admin.template_editor.unsaved_changes_confirm', 'You have unsaved changes. Continue and discard them?'))) { return; } clearStatus(); fetch(`/admin/api/custom-templates/file?path=${encodeURIComponent(path)}`) .then(response => { if (!response.ok) { throw new Error(uiT('ui.admin.template_editor.load_template_failed', 'Failed to load template')); } return response.json(); }) .then(data => { currentTemplate = data.path; isDirty = false; document.getElementById('currentTemplateLabel').textContent = data.path; const editor = document.getElementById('templateEditor'); editor.value = data.content || ''; const isExample = data.path.endsWith('.twig.example'); editor.disabled = isExample; document.getElementById('saveTemplateBtn').disabled = isExample; document.getElementById('deleteTemplateBtn').disabled = isExample; document.getElementById('installTemplateBtn').disabled = !isExample; }) .catch(() => { setStatus(uiT('ui.admin.template_editor.load_template_failed', 'Failed to load template'), 'danger'); }); } function createTemplate() { const path = document.getElementById('newTemplatePath').value.trim(); if (!path) { setStatus(uiT('ui.admin.template_editor.enter_template_path', 'Enter a template path (e.g., systemnews.twig).'), 'warning'); return; } currentTemplate = path; isDirty = true; document.getElementById('currentTemplateLabel').textContent = path; const editor = document.getElementById('templateEditor'); editor.value = ''; editor.disabled = false; document.getElementById('saveTemplateBtn').disabled = false; document.getElementById('deleteTemplateBtn').disabled = true; document.getElementById('installTemplateBtn').disabled = true; } function saveTemplate() { if (!currentTemplate) { return; } const content = document.getElementById('templateEditor').value; fetch('/admin/api/custom-templates/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: currentTemplate, content }) }) .then(response => response.json().then(data => ({ ok: response.ok, data }))) .then(({ ok, data }) => { if (!ok) { throw new Error(apiError(data, uiT('ui.admin.template_editor.save_template_failed', 'Failed to save template'))); } isDirty = false; loadTemplates(); const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.template_editor.template_saved_success', 'Template saved successfully.')) : uiT('ui.admin.template_editor.template_saved_success', 'Template saved successfully.'); setStatus(successMessage, 'success'); document.getElementById('deleteTemplateBtn').disabled = false; document.getElementById('installTemplateBtn').disabled = true; }) .catch(err => { setStatus(err.message || uiT('ui.admin.template_editor.save_template_failed', 'Failed to save template'), 'danger'); }); } function deleteTemplate() { if (!currentTemplate) { return; } if (!confirm(uiT('ui.admin.template_editor.delete_confirm', `Delete ${currentTemplate}? This cannot be undone.`, { template: currentTemplate }))) { return; } fetch(`/admin/api/custom-templates/file?path=${encodeURIComponent(currentTemplate)}`, { method: 'DELETE' }) .then(response => response.json().then(data => ({ ok: response.ok, data }))) .then(({ ok, data }) => { if (!ok) { throw new Error(apiError(data, uiT('ui.admin.template_editor.delete_template_failed', 'Failed to delete template'))); } currentTemplate = null; isDirty = false; document.getElementById('templateEditor').value = ''; document.getElementById('templateEditor').disabled = true; document.getElementById('currentTemplateLabel').textContent = uiT('ui.admin.template_editor.no_template_selected', 'No template selected'); document.getElementById('saveTemplateBtn').disabled = true; document.getElementById('deleteTemplateBtn').disabled = true; document.getElementById('installTemplateBtn').disabled = true; loadTemplates(); const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.template_editor.template_deleted_success', 'Template deleted.')) : uiT('ui.admin.template_editor.template_deleted_success', 'Template deleted.'); setStatus(successMessage, 'success'); }) .catch(err => { setStatus(err.message || uiT('ui.admin.template_editor.delete_template_failed', 'Failed to delete template'), 'danger'); }); } function installTemplate() { if (!currentTemplate || !currentTemplate.endsWith('.twig.example')) { return; } fetch('/admin/api/custom-templates/install', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: currentTemplate }) }) .then(response => response.json().then(data => ({ ok: response.ok, data }))) .then(({ ok, data }) => { if (!ok) { throw new Error(apiError(data, uiT('ui.admin.template_editor.install_template_failed', 'Failed to install template'))); } loadTemplates(); loadTemplate(data.path); const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.template_editor.template_installed_success', 'Template installed from example.')) : uiT('ui.admin.template_editor.template_installed_success', 'Template installed from example.'); setStatus(successMessage, 'success'); }) .catch(err => { setStatus(err.message || uiT('ui.admin.template_editor.install_template_failed', 'Failed to install template'), 'danger'); }); } </script> {% endblock %}