v2.0.0.1
Publisium
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/includes/config.php';
- require_once __DIR__ . '/includes/database.php';
- require_once __DIR__ . '/includes/auth.php';
- require_once __DIR__ . '/includes/csrf.php';
- require_once __DIR__ . '/includes/font-library.php';
- require_once __DIR__ . '/includes/ai.php';
-
- Auth::boot();
- Auth::requireLogin();
-
- $currentUser = Auth::user();
- $currentRole = Auth::role();
- $interfaceAvailableFonts = FontLibrary::available();
- $interfaceAiAvailable = Ai::availableFor($currentRole);
-
- $activeTab = $_GET['tab'] ?? 'appearance';
- $allowedTabs = ['appearance', 'shortcuts'];
- if (!in_array($activeTab, $allowedTabs, true)) {
- $activeTab = 'appearance';
- }
-
- $dashActivePage = 'interface';
- $dashPageTitle = 'Interface';
-
- require __DIR__ . '/includes/dash-header.php';
-
- ?>
-
- <h1 class="dash-title"><?= Icons::icon("sliders", "icon icon-lg") ?>Interface</h1>
-
- <div class="settings-tabs">
- <a href="interface.php?tab=appearance" class="<?= $activeTab === 'appearance' ? 'active' : '' ?>"><?= Icons::icon("palette", "icon icon-sm") ?>Appearance</a>
- <a href="interface.php?tab=shortcuts" class="<?= $activeTab === 'shortcuts' ? 'active' : '' ?>"><?= Icons::icon("keyboard", "icon icon-sm") ?>Shortcuts</a>
- </div>
-
- <?php if ($activeTab === 'appearance'): ?>
- <div class="settings-section">
- <p style="font-size:12.5px;color:var(--muted);margin:-4px 0 20px;">Make the dashboard and editor comfortable for you, from text size and fonts to editor helpers. These settings only apply to you, in this browser. Nobody else on the team sees them.</p>
-
- <label>Sidebar text size</label>
- <select id="appearance-sidebar-scale" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 18px;">
- <option value="80">80%</option>
- <option value="90">90%</option>
- <option value="100">100% (default)</option>
- <option value="110">110%</option>
- <option value="120">120%</option>
- <option value="130">130%</option>
- <option value="140">140%</option>
- <option value="150">150%</option>
- </select>
-
- <label>Page text size</label>
- <select id="appearance-main-scale" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 18px;">
- <option value="80">80%</option>
- <option value="90">90%</option>
- <option value="100">100% (default)</option>
- <option value="110">110%</option>
- <option value="120">120%</option>
- <option value="130">130%</option>
- <option value="140">140%</option>
- <option value="150">150%</option>
- </select>
-
- <label>Editor interface font</label>
- <select id="appearance-ui-font" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 18px;">
- <option value="">Default</option>
- <?php foreach ($interfaceAvailableFonts as $interfaceFont): ?>
- <option value="<?= htmlspecialchars($interfaceFont['display_name'], ENT_QUOTES) ?>"><?= htmlspecialchars($interfaceFont['display_name']) ?></option>
- <?php endforeach; ?>
- </select>
-
- <label>Font for article text in the editor</label>
- <select id="appearance-content-font" style="width:100%;padding:10px;border-radius:8px;border:1px solid var(--border);background:var(--panel);color:var(--text);margin:6px 0 18px;">
- <option value="">Default</option>
- <?php foreach ($interfaceAvailableFonts as $interfaceFont): ?>
- <option value="<?= htmlspecialchars($interfaceFont['display_name'], ENT_QUOTES) ?>"><?= htmlspecialchars($interfaceFont['display_name']) ?></option>
- <?php endforeach; ?>
- </select>
-
- <label style="margin-top:18px;"><input type="checkbox" id="appearance-glass"> Glass effect (see-through buttons and soft blurred backgrounds). Turn it off for solid colors.</label>
- <label><input type="checkbox" id="appearance-minimal"> Simple mode (no animations, gradients or glass effect). Helps on older computers.</label>
- <label><input type="checkbox" id="appearance-block-icons"> Show block icons in the article editor</label>
- <label><input type="checkbox" id="appearance-spellcheck"> Underline spelling mistakes in the article editor</label>
- <label><input type="checkbox" id="appearance-paste-cleanup"> Always paste as plain text in the article editor (Ctrl+Shift+V does this anyway)</label>
-
- <?php if ($interfaceAiAvailable): ?>
- <label><input type="checkbox" id="appearance-ai-helper"> Show the AI assistant button in the article and page editors</label>
- <?php endif; ?>
-
- <div class="publish-actions">
- <button type="button" id="appearance-reset" class="dash-btn dash-btn-small"><?= Icons::icon('trash', 'icon icon-sm') ?>Back to defaults</button>
- </div>
- </div>
-
- <script>
- (function() {
- var SIDEBAR_KEY = 'publisium_dashboard_sidebar_scale';
- var MAIN_KEY = 'publisium_dashboard_main_scale';
- var UI_FONT_KEY = 'publisium_dashboard_ui_font';
- var CONTENT_FONT_KEY = 'publisium_dashboard_content_font';
- var BLOCK_ICONS_KEY = 'publisium_dashboard_block_icons';
- var SPELLCHECK_KEY = 'publisium_dashboard_spellcheck';
- var PASTE_CLEANUP_KEY = 'publisium_dashboard_paste_cleanup';
-
- var sidebarSelect = document.getElementById('appearance-sidebar-scale');
- var mainSelect = document.getElementById('appearance-main-scale');
- var blockIconsCheckbox = document.getElementById('appearance-block-icons');
- var spellcheckCheckbox = document.getElementById('appearance-spellcheck');
- var pasteCleanupCheckbox = document.getElementById('appearance-paste-cleanup');
-
- var GLASS_KEY = 'publisium_dashboard_glass';
- var glassCheckbox = document.getElementById('appearance-glass');
-
- var MINIMAL_KEY = 'publisium_dashboard_minimal';
- var minimalCheckbox = document.getElementById('appearance-minimal');
-
- function syncGlassCheckbox() {
- var minimal = localStorage.getItem(MINIMAL_KEY) === '1';
- glassCheckbox.disabled = minimal;
- glassCheckbox.checked = !minimal && localStorage.getItem(GLASS_KEY) !== '0';
- }
-
- minimalCheckbox.checked = localStorage.getItem(MINIMAL_KEY) === '1';
- minimalCheckbox.addEventListener('change', function() {
- if (this.checked) {
- localStorage.setItem(MINIMAL_KEY, '1');
- } else {
- localStorage.removeItem(MINIMAL_KEY);
- }
- syncGlassCheckbox();
- if (window.PublisiumAppearance) {
- window.PublisiumAppearance.apply();
- }
- });
-
- syncGlassCheckbox();
- glassCheckbox.addEventListener('change', function() {
- if (this.checked) {
- localStorage.removeItem(GLASS_KEY);
- } else {
- localStorage.setItem(GLASS_KEY, '0');
- }
- if (window.PublisiumAppearance) {
- window.PublisiumAppearance.apply();
- }
- });
-
- blockIconsCheckbox.checked = localStorage.getItem(BLOCK_ICONS_KEY) !== '0';
- blockIconsCheckbox.addEventListener('change', function() {
- localStorage.setItem(BLOCK_ICONS_KEY, this.checked ? '1' : '0');
- });
-
- spellcheckCheckbox.checked = localStorage.getItem(SPELLCHECK_KEY) !== '0';
- spellcheckCheckbox.addEventListener('change', function() {
- localStorage.setItem(SPELLCHECK_KEY, this.checked ? '1' : '0');
- });
-
- pasteCleanupCheckbox.checked = localStorage.getItem(PASTE_CLEANUP_KEY) === '1';
- pasteCleanupCheckbox.addEventListener('change', function() {
- localStorage.setItem(PASTE_CLEANUP_KEY, this.checked ? '1' : '0');
- });
- var AI_HELPER_KEY = 'publisium_dashboard_ai_helper';
- var aiHelperCheckbox = document.getElementById('appearance-ai-helper');
-
- if (aiHelperCheckbox) {
- aiHelperCheckbox.checked = localStorage.getItem(AI_HELPER_KEY) !== '0';
- aiHelperCheckbox.addEventListener('change', function() {
- localStorage.setItem(AI_HELPER_KEY, this.checked ? '1' : '0');
- });
- }
- var uiFontSelect = document.getElementById('appearance-ui-font');
- var contentFontSelect = document.getElementById('appearance-content-font');
-
- function loadScale(key) {
- var stored = parseInt(localStorage.getItem(key), 10);
- var valid = [80, 90, 100, 110, 120, 130, 140, 150];
- return valid.indexOf(stored) !== -1 ? stored : 100;
- }
-
- sidebarSelect.value = String(loadScale(SIDEBAR_KEY));
- mainSelect.value = String(loadScale(MAIN_KEY));
- uiFontSelect.value = localStorage.getItem(UI_FONT_KEY) || '';
- contentFontSelect.value = localStorage.getItem(CONTENT_FONT_KEY) || '';
-
- sidebarSelect.addEventListener('change', function() {
- localStorage.setItem(SIDEBAR_KEY, this.value);
- if (window.PublisiumAppearance) {
- window.PublisiumAppearance.apply();
- }
- });
-
- mainSelect.addEventListener('change', function() {
- localStorage.setItem(MAIN_KEY, this.value);
- if (window.PublisiumAppearance) {
- window.PublisiumAppearance.apply();
- }
- });
-
- uiFontSelect.addEventListener('change', function() {
- if (this.value) {
- localStorage.setItem(UI_FONT_KEY, this.value);
- } else {
- localStorage.removeItem(UI_FONT_KEY);
- }
- if (window.PublisiumAppearance) {
- window.PublisiumAppearance.apply();
- }
- });
-
- contentFontSelect.addEventListener('change', function() {
- if (this.value) {
- localStorage.setItem(CONTENT_FONT_KEY, this.value);
- } else {
- localStorage.removeItem(CONTENT_FONT_KEY);
- }
- if (window.PublisiumAppearance) {
- window.PublisiumAppearance.apply();
- }
- });
-
- document.getElementById('appearance-reset').addEventListener('click', function() {
- localStorage.removeItem(SIDEBAR_KEY);
- localStorage.removeItem(MAIN_KEY);
- localStorage.removeItem(UI_FONT_KEY);
- localStorage.removeItem(CONTENT_FONT_KEY);
- localStorage.removeItem(BLOCK_ICONS_KEY);
- localStorage.removeItem(SPELLCHECK_KEY);
- localStorage.removeItem(PASTE_CLEANUP_KEY);
- localStorage.removeItem(AI_HELPER_KEY);
- localStorage.removeItem(GLASS_KEY);
- localStorage.removeItem(MINIMAL_KEY);
- minimalCheckbox.checked = false;
- syncGlassCheckbox();
- if (aiHelperCheckbox) {
- aiHelperCheckbox.checked = true;
- }
- sidebarSelect.value = '100';
- mainSelect.value = '100';
- uiFontSelect.value = '';
- contentFontSelect.value = '';
- blockIconsCheckbox.checked = true;
- spellcheckCheckbox.checked = true;
- pasteCleanupCheckbox.checked = false;
- if (window.PublisiumAppearance) {
- window.PublisiumAppearance.apply();
- }
- });
- })();
- </script>
- <?php endif; ?>
-
- <?php if ($activeTab === 'shortcuts'): ?>
- <style>
- .shortcut-row {
- display: flex;
- align-items: center;
- gap: 14px;
- padding: 10px 0;
- border-bottom: 1px solid var(--border);
- }
-
- .shortcut-row:last-child {
- border-bottom: none;
- }
-
- .shortcut-row-label {
- flex: 1;
- font-size: 13.5px;
- }
-
- .shortcut-combo-btn {
- font-family: inherit;
- font-size: 12.5px;
- font-weight: 600;
- padding: 6px 14px;
- border-radius: 8px;
- border: 1px solid var(--border);
- background: var(--panel-raised);
- color: var(--text);
- cursor: pointer;
- min-width: 150px;
- text-align: center;
- }
-
- .shortcut-combo-btn:hover {
- border-color: var(--border-strong);
- }
-
- .shortcut-combo-btn.capturing {
- border-color: var(--accent);
- color: var(--accent);
- }
- </style>
-
- <div class="settings-section">
- <p style="font-size:12.5px;color:var(--muted);margin:-4px 0 20px;">Keyboard shortcuts for the article editor. They’re saved in this browser only.</p>
-
- <div id="shortcuts-list"></div>
-
- <div class="publish-actions" style="margin-top:16px;">
- <button type="button" id="shortcuts-reset-all" class="dash-btn dash-btn-small"><?= Icons::icon('trash', 'icon icon-sm') ?>Reset all shortcuts</button>
- </div>
- </div>
-
- <?= Asset::js('assets/shortcuts.js') ?>
- <script>
- (function() {
- var listRoot = document.getElementById('shortcuts-list');
- var captureRow = null;
-
- function comboLabel(combo) {
- return combo.split('+').join(' + ');
- }
-
- function render() {
- var shortcuts = window.PublisiumShortcuts.getShortcuts();
- listRoot.innerHTML = '';
-
- Object.keys(shortcuts).forEach(function(action) {
- var entry = shortcuts[action];
-
- var row = document.createElement('div');
- row.className = 'shortcut-row';
- row.dataset.action = action;
-
- var labelEl = document.createElement('span');
- labelEl.className = 'shortcut-row-label';
- labelEl.textContent = entry.label;
-
- var comboBtn = document.createElement('button');
- comboBtn.type = 'button';
- comboBtn.className = 'shortcut-combo-btn';
- comboBtn.textContent = comboLabel(entry.combo);
- comboBtn.title = 'Click, then press a new key combination';
-
- var resetBtn = document.createElement('button');
- resetBtn.type = 'button';
- resetBtn.className = 'dash-btn-small';
- resetBtn.textContent = 'Reset';
- resetBtn.disabled = entry.isDefault;
- resetBtn.addEventListener('click', function() {
- window.PublisiumShortcuts.resetShortcut(action);
- render();
- });
-
- comboBtn.addEventListener('click', function() {
- startCapture(action, comboBtn);
- });
-
- row.appendChild(labelEl);
- row.appendChild(comboBtn);
- row.appendChild(resetBtn);
- listRoot.appendChild(row);
- });
- }
-
- function startCapture(action, btn) {
- if (captureRow) {
- captureRow.classList.remove('capturing');
- }
- captureRow = btn;
- btn.classList.add('capturing');
- btn.textContent = 'Press a key…';
-
- function onKeydown(event) {
- event.preventDefault();
- event.stopPropagation();
-
- var combo = window.PublisiumShortcuts.comboFromEvent(event);
- if (!combo) {
- return;
- }
-
- window.PublisiumShortcuts.setShortcut(action, combo);
- cleanup();
- render();
- }
-
- function onBlurAway(event) {
- if (event.target !== btn) {
- cleanup();
- render();
- }
- }
-
- function cleanup() {
- document.removeEventListener('keydown', onKeydown, true);
- document.removeEventListener('mousedown', onBlurAway, true);
- btn.classList.remove('capturing');
- captureRow = null;
- }
-
- document.addEventListener('keydown', onKeydown, true);
- setTimeout(function() {
- document.addEventListener('mousedown', onBlurAway, true);
- }, 0);
- }
-
- document.getElementById('shortcuts-reset-all').addEventListener('click', function() {
- window.PublisiumShortcuts.resetAllShortcuts();
- render();
- });
-
- render();
- })();
- </script>
- <?php endif; ?>
-
- <?php require __DIR__ . '/includes/dash-footer.php'; ?>