fe_comment: widget v2, overview link setting and inline mail images
- Rebuild the widget server-backed: it renders only when GET state allows (logged-in FE user of the configured group), so the script tag is safe on public pages; form with mini rich text (bold/italic/ list), file attachment chips and Ctrl+V image paste - a paste lazily creates the draft feedback, uploads and inserts the image inline - Note pad badge mirrors the server item count; the pad button links to the overview page via new extension setting overviewPid, exposed as overviewUrl in GET state (slug lookup) - Embed /fileadmin images of notes into the feedback mail as CID parts: relative srcs are dead in mail clients and dev sits behind basic auth; attachment links stay absolute URLs - public/widget-test.html: dev test page, doubles as the reference implementation of the React login flow (fetch felogin JSON form incl. __RequestToken, urlencoded POST to /_json + form.action) - Docs section 5x (fe_comment v2 incl. CSRF login lesson, templateRootPaths.10 collision, cache:flush before extension:setup)
This commit is contained in:
1 parent
872c6e715c
commit
d496877a53
6 files changed
+510
-217
No files matched your search
@@ -0,0 +1,90 @@
|
||||
<!doctype html>
|
||||
<!-- fe_comment v2 Testseite: FE-Login (Referenz-Implementierung der React-
|
||||
Strecke) + Widget-Einbindung. Nur fuer Dev-Tests, kann jederzeit weg. -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>fe_comment widget test</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 16px; color: #1c2430; }
|
||||
h1 { font-size: 20px; }
|
||||
label { display: block; margin: 10px 0 2px; font-size: 13px; color: #5a6572; }
|
||||
input { padding: 6px 8px; border: 1px solid #c9d1da; border-radius: 6px; font: inherit; width: 240px; }
|
||||
button { margin-top: 12px; padding: 7px 14px; border: 1px solid #1f6feb; border-radius: 6px; background: #1f6feb; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
|
||||
#status { margin-top: 14px; font-weight: 600; }
|
||||
.ok { color: #2f9e44; } .err { color: #d9463e; }
|
||||
p.hint { color: #5a6572; font-size: 13px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>fe_comment v2 — Widget-Testseite</h1>
|
||||
<p class="hint">Diese Seite tut, was spaeter die React-App tut: felogin-Formular
|
||||
per JSON holen (inkl. CSRF-Token), Login-POST, dann erscheint rechts das
|
||||
Feedback-Widget (nur fuer berechtigte FE-User).</p>
|
||||
|
||||
<div id="login">
|
||||
<label>User</label><input id="user" autocomplete="username">
|
||||
<label>Passwort</label><input id="pass" type="password" autocomplete="current-password">
|
||||
<button id="loginBtn">Login</button>
|
||||
<button id="logoutBtn" style="background:#f6f8fa;color:#1c2430;border-color:#c9d1da">Logout</button>
|
||||
</div>
|
||||
<div id="status"></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var statusBox = document.getElementById('status');
|
||||
function say(text, ok) {
|
||||
statusBox.textContent = text;
|
||||
statusBox.className = ok ? 'ok' : 'err';
|
||||
}
|
||||
|
||||
function checkState() {
|
||||
return fetch('/api/fe-comment/state', { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (s) {
|
||||
if (s.allowed) { say('Eingeloggt als ' + s.user.name + ' - Widget aktiv (rechts).', true); }
|
||||
else { say('Nicht eingeloggt / nicht berechtigt (allowed:false).', false); }
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
// Die React-Login-Strecke in Kurzform:
|
||||
// 1. Seite als JSON holen -> felogin-Element -> form (setzt Nonce-Cookie)
|
||||
// 2. Alle Formfelder + user/pass urlencoded an '/_json' + form.action posten
|
||||
function login(user, pass) {
|
||||
return fetch('/_json/fe-user-profile', { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (page) {
|
||||
var el = (page.content.colPos0 || []).filter(function (e) { return e.type === 'felogin_login'; })[0];
|
||||
if (!el || !el.content || !el.content.data.form) { throw new Error('felogin-Formular nicht im JSON'); }
|
||||
var form = el.content.data.form;
|
||||
var body = new URLSearchParams();
|
||||
form.elements.forEach(function (f) { if (f.name) { body.set(f.name, f.value || ''); } });
|
||||
body.set('user', user);
|
||||
body.set('pass', pass);
|
||||
return fetch('/_json' + form.action, { method: 'POST', body: body, credentials: 'same-origin' });
|
||||
})
|
||||
.then(checkState);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
var body = new URLSearchParams();
|
||||
body.set('logintype', 'logout');
|
||||
return fetch('/_json/fe-user-profile', { method: 'POST', body: body, credentials: 'same-origin' })
|
||||
.then(function () { location.reload(); });
|
||||
}
|
||||
|
||||
document.getElementById('loginBtn').addEventListener('click', function () {
|
||||
login(document.getElementById('user').value, document.getElementById('pass').value)
|
||||
.then(function (s) { if (s.allowed) { location.reload(); } })
|
||||
.catch(function (e) { say('Fehler: ' + e.message, false); });
|
||||
});
|
||||
document.getElementById('logoutBtn').addEventListener('click', logout);
|
||||
|
||||
checkState();
|
||||
})();
|
||||
</script>
|
||||
<script src="/api/fe-comment/widget.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user