first commit

This commit is contained in:
2026-07-14 18:13:44 +02:00
commit 92e0df7e37
71 changed files with 14242 additions and 0 deletions
@@ -0,0 +1,81 @@
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event
// and thus this event-listener will not be executed.
document.addEventListener('submit', function (event) {
generateCsrfToken(event.target);
}, true);
// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
document.addEventListener('turbo:submit-start', function (event) {
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
Object.keys(h).map(function (k) {
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
});
});
// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
document.addEventListener('turbo:submit-end', function (event) {
removeCsrfToken(event.detail.formSubmission.formElement);
});
export function generateCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return;
}
let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
let csrfToken = csrfField.value;
if (!csrfCookie && nameCheck.test(csrfToken)) {
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
}
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
if (csrfCookie && tokenCheck.test(csrfToken)) {
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}
export function generateCsrfHeaders (formElement) {
const headers = {};
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return headers;
}
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
headers[csrfCookie] = csrfField.value;
}
return headers;
}
export function removeCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return;
}
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}
/* stimulusFetch: 'lazy' */
export default 'csrf-protection-controller';
@@ -0,0 +1,16 @@
import { Controller } from '@hotwired/stimulus';
/*
* This is an example Stimulus controller!
*
* Any element with a data-controller="hello" attribute will cause
* this controller to be executed. The name "hello" comes from the filename:
* hello_controller.js -> "hello"
*
* Delete this file or adapt it for your use!
*/
export default class extends Controller {
connect() {
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
}
}
@@ -0,0 +1,53 @@
import { Controller } from '@hotwired/stimulus';
/*
* Otwiera podgląd "live view" projektu w modalu stylizowanym na okno przeglądarki.
* Adres i tytuł są przekazywane przez przyciski z data-webview-url-param
* oraz data-webview-title-param. iframe ładuje się dopiero przy otwarciu.
*/
export default class extends Controller {
static targets = ['dialog', 'frame', 'address', 'title', 'external', 'fallback'];
connect() {
this.onKeydown = this.onKeydown.bind(this);
}
open(event) {
const { url, title } = event.params;
this.addressTarget.textContent = url.replace(/^https?:\/\//, '');
this.titleTarget.textContent = title;
this.externalTargets.forEach((el) => { el.href = url; });
this.fallbackTarget.hidden = true;
// Jeśli strona blokuje osadzanie (X-Frame-Options), pokaż podpowiedź.
this.frameTarget.onerror = () => { this.fallbackTarget.hidden = false; };
this.frameTarget.src = url;
this.dialogTarget.showModal();
this.dialogTarget.addEventListener('close', () => this.reset(), { once: true });
document.addEventListener('keydown', this.onKeydown);
}
close() {
this.dialogTarget.close();
}
backdrop(event) {
// Klik poza ramką (w tło <dialog>) zamyka podgląd.
if (event.target === this.dialogTarget) {
this.close();
}
}
onKeydown(event) {
if (event.key === 'Escape') {
this.close();
}
}
reset() {
this.frameTarget.src = 'about:blank';
document.removeEventListener('keydown', this.onKeydown);
}
}