ssdfdsf
This commit is contained in:
BIN
public/assets/fonts/KidsHandwriting-Regular.ttf
Normal file
BIN
public/assets/fonts/KidsHandwriting-Regular.ttf
Normal file
Binary file not shown.
BIN
public/assets/fonts/KidsHandwriting-Regular.woff
Normal file
BIN
public/assets/fonts/KidsHandwriting-Regular.woff
Normal file
Binary file not shown.
BIN
public/assets/fonts/KidsHandwriting-Regular.woff2
Normal file
BIN
public/assets/fonts/KidsHandwriting-Regular.woff2
Normal file
Binary file not shown.
@@ -86,6 +86,11 @@ function defaultSetup() {
|
||||
password_key: '',
|
||||
charset_key: '',
|
||||
},
|
||||
fonts: {
|
||||
dir: '',
|
||||
url_base: '',
|
||||
urls: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -148,6 +153,19 @@ function fillForm(setup, options = {}) {
|
||||
if (input) input.value = value;
|
||||
});
|
||||
}
|
||||
|
||||
const fontFields = document.getElementById('bridgeFontsForm');
|
||||
if (fontFields) {
|
||||
const fontMap = {
|
||||
fonts_dir: data.fonts.dir || '',
|
||||
fonts_url_base: data.fonts.url_base || '',
|
||||
fonts_urls: data.fonts.urls || '',
|
||||
};
|
||||
Object.entries(fontMap).forEach(([name, value]) => {
|
||||
const input = fontFields.querySelector(`[name="${name}"]`);
|
||||
if (input) input.value = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function applyModeVisibility(mode) {
|
||||
@@ -224,6 +242,9 @@ async function submitBridgeSetup(ev) {
|
||||
config_user_key: form.config_user_key?.value.trim() || '',
|
||||
config_password_key: form.config_password_key?.value.trim() || '',
|
||||
config_charset_key: form.config_charset_key?.value.trim() || '',
|
||||
fonts_dir: document.querySelector('[name="fonts_dir"]')?.value.trim() || '',
|
||||
fonts_url_base: document.querySelector('[name="fonts_url_base"]')?.value.trim() || '',
|
||||
fonts_urls: document.querySelector('[name="fonts_urls"]')?.value || '',
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -324,5 +345,9 @@ function normalizeSetupInput(input) {
|
||||
direct.port = Number(direct.port || 3306) || 3306;
|
||||
direct.charset = direct.charset || 'utf8mb4';
|
||||
const config = { ...base.config, ...(input.config || {}) };
|
||||
return { tables, mode: validMode, direct, config };
|
||||
const fonts = { ...base.fonts, ...(input.fonts || {}) };
|
||||
fonts.dir = String(fonts.dir || '').trim();
|
||||
fonts.url_base = String(fonts.url_base || '').trim();
|
||||
fonts.urls = String(fonts.urls || '');
|
||||
return { tables, mode: validMode, direct, config, fonts };
|
||||
}
|
||||
|
||||
@@ -464,7 +464,11 @@
|
||||
}
|
||||
|
||||
// 1. Daten extrahieren
|
||||
const htmlContent = editor.getHtml() + '<style>' + editor.getCss() + '</style>';
|
||||
const fontCss = (B && typeof B.RTE_FONT_FACE_CSS === 'string' && B.RTE_FONT_FACE_CSS.trim())
|
||||
? B.RTE_FONT_FACE_CSS.trim()
|
||||
: '';
|
||||
const cssPayload = (fontCss ? fontCss + '\n' : '') + editor.getCss();
|
||||
const htmlContent = editor.getHtml() + '<style>' + cssPayload + '</style>';
|
||||
// 2. KRITISCH: Holt die JSON-Repräsentation des Editors
|
||||
let jsonProjectDataRaw = '';
|
||||
try {
|
||||
|
||||
@@ -263,7 +263,7 @@
|
||||
autosave: false,
|
||||
};
|
||||
|
||||
const execRteCommand = (rte, cmd, value) => {
|
||||
const execRteCommand = (rte, cmd, value, docOverride) => {
|
||||
try {
|
||||
if (rte && typeof rte.exec === 'function') {
|
||||
rte.exec(cmd, value);
|
||||
@@ -271,9 +271,16 @@
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
const ok = document.execCommand(cmd, false, value);
|
||||
const doc = docOverride
|
||||
|| rte?.doc
|
||||
|| rte?.el?.ownerDocument
|
||||
|| document;
|
||||
if (rte?.el && typeof rte.el.focus === 'function') {
|
||||
rte.el.focus();
|
||||
}
|
||||
const ok = doc.execCommand(cmd, false, value);
|
||||
if (ok === false && cmd === 'insertText') {
|
||||
document.execCommand('insertHTML', false, String(value || '').replace(/</g, '<').replace(/>/g, '>'));
|
||||
doc.execCommand('insertHTML', false, String(value || '').replace(/</g, '<').replace(/>/g, '>'));
|
||||
}
|
||||
return true;
|
||||
} catch {}
|
||||
@@ -344,10 +351,10 @@
|
||||
|| '';
|
||||
content.innerHTML = initialHtml;
|
||||
|
||||
const addButton = (label, title, cmd, valueGetter) => {
|
||||
const addButton = (labelHtml, title, cmd, valueGetter) => {
|
||||
const btn = doc.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.textContent = label;
|
||||
btn.innerHTML = labelHtml;
|
||||
btn.title = title;
|
||||
btn.style.padding = '4px 8px';
|
||||
btn.style.border = '1px solid #cbd5f5';
|
||||
@@ -359,7 +366,7 @@
|
||||
const value = typeof valueGetter === 'function' ? valueGetter() : valueGetter;
|
||||
if (value === null || value === undefined) return;
|
||||
if (cmd === 'createLink' && !value) return;
|
||||
execRteCommand(null, cmd, value);
|
||||
execRteCommand(null, cmd, value, content.ownerDocument);
|
||||
});
|
||||
toolbar.appendChild(btn);
|
||||
};
|
||||
@@ -393,32 +400,41 @@
|
||||
}
|
||||
};
|
||||
|
||||
addButton('B', 'Fett', 'bold');
|
||||
addButton('I', 'Kursiv', 'italic');
|
||||
addButton('U', 'Unterstrichen', 'underline');
|
||||
addButton('S', 'Durchgestrichen', 'strikethrough');
|
||||
const icon = (path) => `<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="${path}" fill="currentColor"/></svg>`;
|
||||
addButton('<strong>B</strong>', 'Fett', 'bold');
|
||||
addButton('<em>I</em>', 'Kursiv', 'italic');
|
||||
addButton('<span style="text-decoration:underline">U</span>', 'Unterstrichen', 'underline');
|
||||
addButton('<span style="text-decoration:line-through">S</span>', 'Durchgestrichen', 'strikethrough');
|
||||
addButton(icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h10v2H4z'), 'Liste (ungeordnet)', 'insertUnorderedList');
|
||||
addButton(icon('M4 7h14v2H4zM4 11h14v2H4zM4 15h14v2H4z') + '<span style="font-size:10px;margin-left:4px">1.</span>', 'Liste (geordnet)', 'insertOrderedList');
|
||||
addButton(icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h12v2H4z'), 'Linksbundig', 'justifyLeft');
|
||||
addButton(icon('M5 7h14v2H5zM4 11h16v2H4zM5 15h14v2H5z'), 'Zentriert', 'justifyCenter');
|
||||
addButton(icon('M10 7h10v2H10zM4 11h16v2H4zM8 15h12v2H8z'), 'Rechtsbundig', 'justifyRight');
|
||||
addButton(icon('M4 7h16v2H4zM4 11h16v2H4zM4 15h16v2H4z'), 'Blocksatz', 'justifyFull');
|
||||
addButton('Link', 'Link einfuegen', 'createLink', () => prompt('Link-URL eingeben', 'https://'));
|
||||
addButton('Unlink', 'Link entfernen', 'unlink');
|
||||
addButton('UL', 'Liste (ungeordnet)', 'insertUnorderedList');
|
||||
addButton('OL', 'Liste (geordnet)', 'insertOrderedList');
|
||||
addButton('L', 'Linksbundig', 'justifyLeft');
|
||||
addButton('C', 'Zentriert', 'justifyCenter');
|
||||
addButton('R', 'Rechtsbundig', 'justifyRight');
|
||||
addButton('J', 'Blocksatz', 'justifyFull');
|
||||
addButton('Sub', 'Tiefgestellt', 'subscript');
|
||||
addButton('Sup', 'Hochgestellt', 'superscript');
|
||||
addButton('Einr.', 'Einzug', 'indent');
|
||||
addButton('Aus.', 'Ausruecken', 'outdent');
|
||||
addButton('Clear', 'Formatierung entfernen', 'removeFormat');
|
||||
|
||||
const fontOptions = (B.RTE_FONTS && Array.isArray(B.RTE_FONTS) && B.RTE_FONTS.length)
|
||||
? B.RTE_FONTS
|
||||
: [
|
||||
{ label: 'Arial', value: 'Arial, sans-serif' },
|
||||
{ label: 'Calibri', value: 'Calibri, sans-serif' },
|
||||
{ label: 'Cambria', value: 'Cambria, serif' },
|
||||
{ label: 'Georgia', value: 'Georgia, serif' },
|
||||
{ label: 'Tahoma', value: 'Tahoma, sans-serif' },
|
||||
{ label: 'Times New Roman', value: 'Times New Roman, serif' },
|
||||
{ label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' },
|
||||
{ label: 'Verdana', value: 'Verdana, sans-serif' },
|
||||
];
|
||||
addSelect([
|
||||
{ label: 'Schriftart', value: '' },
|
||||
{ label: 'Arial', value: 'Arial, sans-serif' },
|
||||
{ label: 'Georgia', value: 'Georgia, serif' },
|
||||
{ label: 'Tahoma', value: 'Tahoma, sans-serif' },
|
||||
{ label: 'Times New Roman', value: 'Times New Roman, serif' },
|
||||
{ label: 'Verdana', value: 'Verdana, sans-serif' },
|
||||
], 'Schriftart', (value) => execRteCommand(null, 'fontName', value));
|
||||
...fontOptions,
|
||||
], 'Schriftart', (value) => execRteCommand(null, 'fontName', value, content.ownerDocument));
|
||||
|
||||
addSelect([
|
||||
{ label: 'Groesse', value: '' },
|
||||
@@ -429,7 +445,7 @@
|
||||
{ label: '18px', value: '5' },
|
||||
{ label: '24px', value: '6' },
|
||||
{ label: '32px', value: '7' },
|
||||
], 'Schriftgroesse', (value) => execRteCommand(null, 'fontSize', value));
|
||||
], 'Schriftgroesse', (value) => execRteCommand(null, 'fontSize', value, content.ownerDocument));
|
||||
|
||||
const emojiBtn = doc.createElement('button');
|
||||
emojiBtn.type = 'button';
|
||||
@@ -498,6 +514,19 @@
|
||||
const setupRichTextEditor = (editor) => {
|
||||
if (!editor || !editor.RichTextEditor) return;
|
||||
const rte = editor.RichTextEditor;
|
||||
const icon = (path) => `<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="${path}" fill="currentColor"/></svg>`;
|
||||
const resolveFontOptions = () => (B.RTE_FONTS && Array.isArray(B.RTE_FONTS) && B.RTE_FONTS.length)
|
||||
? B.RTE_FONTS
|
||||
: [
|
||||
{ label: 'Arial', value: 'Arial, sans-serif' },
|
||||
{ label: 'Calibri', value: 'Calibri, sans-serif' },
|
||||
{ label: 'Cambria', value: 'Cambria, serif' },
|
||||
{ label: 'Georgia', value: 'Georgia, serif' },
|
||||
{ label: 'Tahoma', value: 'Tahoma, sans-serif' },
|
||||
{ label: 'Times New Roman', value: 'Times New Roman, serif' },
|
||||
{ label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' },
|
||||
{ label: 'Verdana', value: 'Verdana, sans-serif' },
|
||||
];
|
||||
|
||||
const addAction = (name, icon, title, command, valueGetter) => {
|
||||
if (rte.get && rte.get(name)) return;
|
||||
@@ -515,14 +544,18 @@
|
||||
});
|
||||
};
|
||||
|
||||
addAction('bridge-align-left', 'L', 'Linksbundig', 'justifyLeft');
|
||||
addAction('bridge-align-center', 'C', 'Zentriert', 'justifyCenter');
|
||||
addAction('bridge-align-right', 'R', 'Rechtsbundig', 'justifyRight');
|
||||
addAction('bridge-align-justify', 'J', 'Blocksatz', 'justifyFull');
|
||||
addAction('bridge-ul', 'UL', 'Liste (ungeordnet)', 'insertUnorderedList');
|
||||
addAction('bridge-ol', 'OL', 'Liste (geordnet)', 'insertOrderedList');
|
||||
addAction('bridge-align-left', icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h12v2H4z'), 'Linksbundig', 'justifyLeft');
|
||||
addAction('bridge-align-center', icon('M5 7h14v2H5zM4 11h16v2H4zM5 15h14v2H5z'), 'Zentriert', 'justifyCenter');
|
||||
addAction('bridge-align-right', icon('M10 7h10v2H10zM4 11h16v2H4zM8 15h12v2H8z'), 'Rechtsbundig', 'justifyRight');
|
||||
addAction('bridge-align-justify', icon('M4 7h16v2H4zM4 11h16v2H4zM4 15h16v2H4z'), 'Blocksatz', 'justifyFull');
|
||||
addAction('bridge-ul', icon('M4 7h10v2H4zM4 11h16v2H4zM4 15h10v2H4z'), 'Liste (ungeordnet)', 'insertUnorderedList');
|
||||
addAction('bridge-ol', icon('M4 7h16v2H4zM4 11h16v2H4zM4 15h16v2H4z'), 'Liste (geordnet)', 'insertOrderedList');
|
||||
addAction('bridge-emoji', ':-)', 'Emoticon einfuegen', 'insertText', () => prompt('Emoticon eingeben', ':)'));
|
||||
addAction('bridge-font-family', 'F', 'Schriftart', 'fontName', () => prompt('Schriftart (z.B. Arial, Georgia)', 'Arial'));
|
||||
addAction('bridge-font-family', 'F', 'Schriftart', 'fontName', () => {
|
||||
const fonts = resolveFontOptions();
|
||||
const example = fonts.map((f) => f.label).slice(0, 5).join(', ');
|
||||
return prompt(`Schriftart (z.B. ${example})`, fonts[0]?.label || 'Arial');
|
||||
});
|
||||
addAction('bridge-font-size', 'Px', 'Schriftgroesse', 'fontSize', () => {
|
||||
const raw = prompt('Schriftgroesse in px (10-32)', '14');
|
||||
const val = Number(raw || 14);
|
||||
@@ -542,11 +575,16 @@
|
||||
});
|
||||
return best.cmd;
|
||||
});
|
||||
addAction('bridge-open-richtext', 'RTE', 'Richtext Editor', 'execCommand', () => {
|
||||
const component = editor.getSelected && editor.getSelected();
|
||||
openRichTextModal(editor, component);
|
||||
return null;
|
||||
});
|
||||
if (!(rte.get && rte.get('bridge-open-richtext'))) {
|
||||
rte.add('bridge-open-richtext', {
|
||||
icon: 'RTE',
|
||||
attributes: { title: 'Richtext Editor' },
|
||||
result: () => {
|
||||
const component = editor.getSelected && editor.getSelected();
|
||||
openRichTextModal(editor, component);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!editor.Commands.get('bridge-open-richtext')) {
|
||||
editor.Commands.add('bridge-open-richtext', {
|
||||
@@ -576,6 +614,46 @@
|
||||
editor.on('component:add', (model) => ensureTextToolbarButton(editor, model));
|
||||
};
|
||||
|
||||
const loadDynamicFonts = async () => {
|
||||
try {
|
||||
const base = B.API_KERNEL_URL || '/api.php';
|
||||
const sep = base.includes('?') ? '&' : '?';
|
||||
const res = await fetch(`${base}${sep}action=account.fonts.list`, { credentials: 'include' });
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
if (!data || !data.ok) return;
|
||||
const incoming = Array.isArray(data.fonts) ? data.fonts : [];
|
||||
if (incoming.length) {
|
||||
const merged = [];
|
||||
const seen = new Set();
|
||||
const addFont = (item) => {
|
||||
if (!item || !item.label || !item.value) return;
|
||||
const key = String(item.label).toLowerCase();
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
merged.push({ label: String(item.label), value: String(item.value) });
|
||||
};
|
||||
(B.RTE_FONTS || []).forEach(addFont);
|
||||
incoming.forEach(addFont);
|
||||
B.RTE_FONTS = merged;
|
||||
}
|
||||
if (typeof data.font_face_css === 'string' && data.font_face_css.trim()) {
|
||||
const existing = typeof B.RTE_FONT_FACE_CSS === 'string' ? B.RTE_FONT_FACE_CSS.trim() : '';
|
||||
B.RTE_FONT_FACE_CSS = existing
|
||||
? `${existing}\n${data.font_face_css.trim()}`
|
||||
: data.font_face_css.trim();
|
||||
const styleId = 'bridge-font-faces';
|
||||
let styleEl = document.getElementById(styleId);
|
||||
if (!styleEl) {
|
||||
styleEl = document.createElement('style');
|
||||
styleEl.id = styleId;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
styleEl.textContent = B.RTE_FONT_FACE_CSS;
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
var ed = grapesjs.init({
|
||||
container: '#gjs',
|
||||
height: '100vh',
|
||||
@@ -623,6 +701,7 @@
|
||||
}
|
||||
|
||||
setupRichTextEditor(ed);
|
||||
loadDynamicFonts();
|
||||
|
||||
// Entfernt: jegliche Blur/RTE-Handler, die Inhalte verändern.
|
||||
|
||||
|
||||
@@ -2,6 +2,27 @@
|
||||
$mode = strtolower($_GET['mode'] ?? 'templates');
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$assetVersion = defined('ASSET_VERSION') ? ASSET_VERSION : time();
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
$appBaseUrl = rtrim($GLOBALS['app_base_url'] ?? ($scheme . '://' . $host), '/');
|
||||
$fontDir = __DIR__ . '/../assets/fonts';
|
||||
$fontBase = $appBaseUrl . '/assets/fonts';
|
||||
$customFontName = 'Kids Handwriting';
|
||||
$customFontFiles = [
|
||||
'woff2' => 'KidsHandwriting-Regular.woff2',
|
||||
'woff' => 'KidsHandwriting-Regular.woff',
|
||||
'ttf' => 'KidsHandwriting-Regular.ttf',
|
||||
];
|
||||
$fontSources = [];
|
||||
foreach ($customFontFiles as $format => $file) {
|
||||
if (is_file($fontDir . '/' . $file)) {
|
||||
$fontSources[] = "url('" . htmlspecialchars($fontBase . '/' . $file, ENT_QUOTES) . "') format('{$format}')";
|
||||
}
|
||||
}
|
||||
$fontFaceCss = '';
|
||||
if ($fontSources) {
|
||||
$fontFaceCss = "@font-face{font-family:'{$customFontName}';font-style:normal;font-weight:400;src:" . implode(',', $fontSources) . ";}";
|
||||
}
|
||||
?><!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
@@ -15,6 +36,7 @@ $assetVersion = defined('ASSET_VERSION') ? ASSET_VERSION : time();
|
||||
.gjs-one-bg{background-color:#fff!important}.gjs-two-color{color:#0f172a!important}
|
||||
.gjs-three-bg{background-color:#f8fafc!important}.gjs-four-color{color:#334155!important}
|
||||
#badge{position:fixed;right:8px;top:8px;background:#eef2ff;color:#1e3a8a;border:1px solid #c7d2fe;border-radius:999px;padding:4px 10px;font:12px system-ui;z-index:2147483647;opacity:.9}
|
||||
<?= $fontFaceCss ?>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -31,6 +53,18 @@ $assetVersion = defined('ASSET_VERSION') ? ASSET_VERSION : time();
|
||||
window.BridgeParts.API_KERNEL_URL = window.BridgeParts.API_KERNEL_URL || '/api.php';
|
||||
window.BridgeParts.API_BASE = window.BridgeParts.API_BASE || window.BridgeParts.API_KERNEL_URL;
|
||||
window.BridgeParts.STORAGE_URL_BASE = window.BridgeParts.STORAGE_URL_BASE || window.BridgeParts.API_BASE;
|
||||
window.BridgeParts.RTE_FONTS = window.BridgeParts.RTE_FONTS || [
|
||||
{ label: 'Kids Handwriting', value: "'Kids Handwriting', 'Comic Sans MS', cursive" },
|
||||
{ label: 'Arial', value: 'Arial, sans-serif' },
|
||||
{ label: 'Calibri', value: 'Calibri, sans-serif' },
|
||||
{ label: 'Cambria', value: 'Cambria, serif' },
|
||||
{ label: 'Georgia', value: 'Georgia, serif' },
|
||||
{ label: 'Tahoma', value: 'Tahoma, sans-serif' },
|
||||
{ label: 'Times New Roman', value: 'Times New Roman, serif' },
|
||||
{ label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' },
|
||||
{ label: 'Verdana', value: 'Verdana, sans-serif' },
|
||||
];
|
||||
window.BridgeParts.RTE_FONT_FACE_CSS = window.BridgeParts.RTE_FONT_FACE_CSS || <?= json_encode($fontFaceCss) ?>;
|
||||
|
||||
function logToParent(type, detail){ try{ parent.postMessage({source:'editor-core',type:type,detail:String(detail||'')},'*'); }catch(e){} }
|
||||
window.addEventListener('error', function(e){
|
||||
|
||||
Reference in New Issue
Block a user