9
0
mirror of https://github.com/donlon/cloudflare-error-page.git synced 2025-12-19 14:59:28 +00:00

editor: migrate frontend project to npm

This commit is contained in:
Anthony Donlon
2025-12-10 00:03:09 +08:00
parent 38d19fa12d
commit 3085ecdc44
11 changed files with 1574 additions and 868 deletions

View File

@@ -0,0 +1,9 @@
{
"endOfLine": "lf",
"printWidth": 120,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}

429
editor/frontend/index.html Normal file
View File

@@ -0,0 +1,429 @@
<!-- SPDX-License-Identifier: MIT -->
<!--
!!!
!!! Note: This file is vibely generated, and could be very hard to maintain.
!!!
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Cloudflare Error Page Editor</title>
<meta name="description" content="Online editor to create customized Cloudflare-styled error pages.">
<meta name="keywords" content="cloudflare,error,page,editor">
<link rel="canonical" href="https://virt.moe/cloudflare-error-page/editor/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="moe::virt" />
<meta property="og:title" content="Cloudflare error page editor" />
<meta property="og:url" content="https://virt.moe/cloudflare-error-page/editor/" />
<meta property="og:description" content="Online editor to create customized Cloudflare-styled error pages" />
<meta property="twitter:card" content="summary" />
<meta property="twitter:site" content="moe::virt" />
<meta property="twitter:title" content="Cloudflare error page editor" />
<meta property="twitter:description" content="Online editor to create customized Cloudflare-styled error pages" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="https://virt.moe/assets/cloudflare-error-page/bootstrap.min.css" rel="stylesheet">
<script type="module" src="src/index.js"></script>
<style>
/* Layout: editor + preview */
.app {
display: flex;
flex-direction: column;
gap: 1rem;
height: 100vh;
/* padding: 0.8rem; */
}
/* On md and up, arrange horizontally: editor left, preview right */
@media (min-width: 768px) {
.app {
flex-direction: row;
align-items: stretch;
}
.editor {
flex: 0 0;
min-width: 380px;
overflow-y: auto;
}
.preview {
flex: 1 1 48%;
/* max-width: 48%; */
display: flex;
flex-direction: column;
}
iframe.preview-frame {
height: 100% !important;
}
}
/* On small screens use stacked layout: editor then iframe */
.editor {
background: #fff;
border: 1px solid #e3e6ea;
border-radius: .5rem;
padding: 0.8rem;
}
.preview {
background: #fff;
border: 1px solid #e3e6ea;
border-radius: .5rem;
padding: 0.8rem 0 0 0;
}
/* Compact form: label and control same row */
.form-row {
display: flex;
gap: .3rem;
align-items: center;
margin-bottom: .6rem;
}
.form-row>label {
min-width: 6rem;
max-width: 12rem;
margin-bottom: 0;
font-weight: 600;
}
.form-row>.control {
flex: 1;
}
/* Status block styling */
.status-block {
border: 1px solid #cfeadd;
border-radius: .375rem;
padding: .75rem;
margin-bottom: .5rem;
}
.status-ok {
background: #e9f7ef;
border: 1px solid #cfeadd;
}
.status-error {
background: #fff5f5;
border: 1px solid #f3c2c2;
}
/* Iframe styling */
iframe.preview-frame {
--expanded-height: 100%;
width: 100%;
height: var(--expanded-height);
border: 1px solid #ddd;
flex: 1 1 auto;
min-height: 360px;
border-radius: .375rem;
box-shadow: 0 1px 4px rgba(0, 0, 0, .06);
}
/* Controls toolbar */
.toolbar {
display: flex;
gap: .5rem;
align-items: center;
justify-content: space-between;
margin-bottom: .75rem;
}
/* Compact textarea resizing */
textarea.compact {
min-height: 80px;
resize: vertical;
}
</style>
</head>
<body class="bg-light">
<div class="container-fluid h-100">
<div class="app">
<!-- Editor column -->
<div class="editor">
<h5 class="form-row">Cloudflare Error Page Editor</h5>
<hr>
<div class="form-row mb-3">
<label for="presetSelect">Preset</label>
<select id="presetSelect" class="form-select form-select-sm">
<option value="default">Internal server error (Default)</option>
<option value="empty">Empty</option>
<option value="catastrophic">Catastrophic failure</option>
<option value="working">Server working</option>
<option value="consensual">Myth of consensual</option>
</select>
</div>
<hr>
<form id="editorForm" class="needs-validation" novalidate>
<!-- Basic properties -->
<div class="mb-3">
<!-- <h6 class="mb-2">Page</h6> -->
<div class="form-row">
<label for="title">Title</label>
<div class="control">
<input id="title" class="form-control form-control-sm" placeholder="Internal server error" />
</div>
</div>
<div class="form-row">
<label for="error_code">Error Code</label>
<div class="control">
<input id="error_code" class="form-control form-control-sm" placeholder="500" />
</div>
</div>
</div>
<hr>
<!-- Status blocks -->
<div class="mb-3">
<h6 class="mb-2">Status</h6>
<!-- Browser -->
<div id="block_browser" class="status-block status-ok">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Browser</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_browser" value="browser" />
<label for="err_browser" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="browser_status">Status</label>
<div class="control">
<select id="browser_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="browser_location">Location</label>
<div class="control">
<input id="browser_location" class="form-control form-control-sm" placeholder="You" />
</div>
</div>
<div class="form-row">
<label for="browser_name">Name</label>
<div class="control">
<input id="browser_name" class="form-control form-control-sm" placeholder="Browser" />
</div>
</div>
<div class="form-row">
<label for="browser_status_text">Status Text</label>
<div class="control">
<input id="browser_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
<!-- Cloudflare -->
<div id="block_cloudflare" class="status-block status-error">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Cloudflare</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_cloudflare"
value="cloudflare" />
<label for="err_cloudflare" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="cloudflare_status">Status</label>
<div class="control">
<select id="cloudflare_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="cloudflare_location">Location</label>
<div class="control">
<input id="cloudflare_location" class="form-control form-control-sm" placeholder="San Francisco" />
</div>
</div>
<div class="form-row">
<label for="cloudflare_name">Name</label>
<div class="control">
<input id="cloudflare_name" class="form-control form-control-sm" placeholder="Cloudflare" />
</div>
</div>
<div class="form-row">
<label for="cloudflare_status_text">Status Text</label>
<div class="control">
<input id="cloudflare_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
<!-- Host -->
<div id="block_host" class="status-block status-ok">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Host</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_host" value="host" />
<label for="err_host" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="host_status">Status</label>
<div class="control">
<select id="host_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="host_location">Location</label>
<div class="control">
<input id="host_location" class="form-control form-control-sm" placeholder="Website" />
</div>
</div>
<div class="form-row">
<label for="host_name">Name</label>
<div class="control">
<input id="host_name" class="form-control form-control-sm" placeholder="Host" />
</div>
</div>
<div class="form-row">
<label for="host_status_text">Status Text</label>
<div class="control">
<input id="host_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
</div>
<div class="status-block mt-3 mb-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Visit ...</strong>
<div>
<input id="more_hidden" class="form-check-input" type="checkbox" />
<label for="more_hidden" class="ms-1 small">Hidden</label>
</div>
</div>
<div class="form-row">
<label for="more_text">Text</label>
<div class="control">
<input id="more_text" class="form-control form-control-sm" placeholder="cloudflare.com" />
</div>
</div>
<div class="form-row">
<label for="more_link">Link</label>
<div class="control">
<input id="more_link" class="form-control form-control-sm" placeholder="https://www.cloudflare.com/" />
</div>
</div>
<div class="form-row">
<label for="more_for">For</label>
<div class="control">
<input id="more_for" class="form-control form-control-sm" placeholder="more information" />
</div>
</div>
</div>
<label for="what_happened" class="fw-semibold">What happened?</label>
<div class="control">
<textarea id="what_happened" class="form-control compact"
placeholder="There is an internal server error on Cloudflare's network."></textarea>
</div>
<!-- </div> -->
<!-- <div class=""> -->
<label for="what_can_i_do" class="fw-semibold mt-2">What can I do?</label>
<div class="control">
<textarea id="what_can_i_do" class="form-control compact"
placeholder="Please try again in a few minutes."></textarea>
</div>
<hr>
<h6 class="form-row">Performance & security by ...</h6>
<div class="form-row">
<label for="perf_text">Text</label>
<div class="control">
<input id="perf_text" class="form-control form-control-sm" placeholder="Cloudflare" />
</div>
</div>
<div class="form-row">
<label for="perf_link">Link</label>
<div class="control">
<input id="perf_link" class="form-control form-control-sm" placeholder="https://www.cloudflare.com/" />
</div>
</div>
<!-- </div> -->
<div class="d-flex gap-2 mt-2 mb-2">
<!-- <button id="btnRender" class="btn btn-sm btn-primary">Render</button> -->
<button id="btnOpen" class="btn btn-sm btn-primary">Preview in new tab</button>
<button id="btnExport" class="btn btn-sm btn-primary">Export JSON</button>
</div>
<button id="btnShare" class="btn btn-sm btn-primary">Create shareable link</button>
<div class="mt-2">
<div class="input-group input-group-sm">
<input id="shareLink" class="form-control" readonly />
<button id="btnCopyLink" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div>
<div class="mt-2 text-center">&gt;&gt; Star this project on
<a href="https://github.com/donlon/cloudflare-error-page" target="_blank">GitHub</a>
</div>
<div class="mt-2" style="font-size: 0.9em;">You can also embed this error page into your own website. See
<a href="https://github.com/donlon/cloudflare-error-page#quickstart-for-programmers"
target="_blank">Quickstart</a> in the
homepage for steps.
</div>
</form>
</div>
<!-- Preview column -->
<div class="preview">
<div class="d-flex justify-content-between align-items-center mb-1" style="padding: 0 0.8em;">
<h6><strong>Preview</strong></h6>
</div>
<!-- TODO: An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing. -->
<iframe id="previewFrame" class="preview-frame" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,22 @@
{
"name": "cloudflare-error-page-editor",
"version": "0.0.1",
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "vite build",
"format": "prettier src --write",
"lint": "eslint .",
"preview": "npm run build && vite preview"
},
"devDependencies": {
"html-minifier-terser": "^7.2.0",
"prettier": "3.7.4",
"typescript": "^5.9.3",
"vite": "^7.2.6"
},
"dependencies": {
"bootstrap": "^5.3.8",
"ejs": "^3.1.10"
}
}

View File

@@ -0,0 +1,438 @@
/*
Editor logic
- initialConfig: provided default config (from your JSON)
- render(): placeholder that generates HTML and writes to iframe.srcdoc
- inputs call render() on change
- "Open in new tab" opens the rendered HTML in a new window using a blob URL
*/
import ejs from 'ejs'
import templateContent from './template.ejs?raw'
let template = ejs.compile(templateContent);
// can be changed if specified by '?from=<name>'
let initialConfig = {
title: 'Internal server error',
error_code: '500',
more_information: {
hidden: false,
text: 'cloudflare.com',
for: 'more information',
},
browser_status: {
status: 'ok',
location: 'You',
name: 'Browser',
status_text: 'Working',
},
cloudflare_status: {
status: 'error',
location: 'San Francisco',
name: 'Cloudflare',
status_text: 'Error',
},
host_status: {
status: 'ok',
location: 'Website',
name: 'Host',
status_text: 'Working',
},
error_source: 'cloudflare',
what_happened: "There is an internal server error on Cloudflare's network.",
what_can_i_do: 'Please try again in a few minutes.',
};
// Demo presets (content brief — replace or expand as needed)
const PRESETS = {
default: structuredClone(initialConfig),
empty: {
error_code: '500',
},
catastrophic: {
title: 'Catastrophic infrastructure failure',
error_code: '500',
more_information: {
for: 'no information',
},
browser_status: {
status: 'error',
status_text: 'Out of Memory',
},
cloudflare_status: {
status: 'error',
location: 'Everywhere',
status_text: 'Error',
},
host_status: {
status: 'error',
location: 'example.com',
status_text: 'On Fire',
},
error_source: 'cloudflare',
what_happened: 'There is a catastrophic failure.',
what_can_i_do: 'Please try again in a few years.',
},
working: {
title: 'Web server is working',
error_code: '200',
more_information: {
hidden: true,
},
browser_status: {
status: 'ok',
status_text: 'Seems Working',
},
cloudflare_status: {
status: 'ok',
status_text: 'Often Working',
},
host_status: {
status: 'ok',
location: 'example.com',
status_text: 'Almost Working',
},
error_source: 'host',
what_happened: 'This site is still working. And it looks great.',
what_can_i_do: 'Visit the site before it crashes someday.',
},
consensual: {
title: 'The Myth Of "Consensual" Internet',
error_code: 'lmao',
more_information: {
hidden: false,
text: 'r/ProgrammerHumor',
link: 'https://redd.it/1p2yola',
},
browser_status: {
status: 'ok',
location: 'You',
name: 'Browser',
status_text: 'I Consent',
},
cloudflare_status: {
status: 'error',
location: 'F***ing Everywhere',
name: 'Cloudflare',
status_text: "I Don't!",
},
host_status: {
status: 'ok',
location: 'Remote',
name: 'Host',
status_text: 'I Consent',
},
error_source: 'cloudflare',
what_happened: "Isn't There Someone You Forgot To Ask?",
what_can_i_do: 'Kill Yourself',
},
};
function extractUrlParam(str, key) {
const urlParams = new URLSearchParams(str);
return urlParams.get(key);
}
function getDefaultPresetName() {
const key = 'from';
let name = extractUrlParam(window.location.search, key);
if (!name) {
name = extractUrlParam(window.location.hash.substr(1), key);
}
return name;
}
const defaultPresetName = getDefaultPresetName();
if (defaultPresetName && defaultPresetName.indexOf('/') < 0) {
fetch(`../s/${defaultPresetName}`, {
headers: {
Accept: 'application/json',
},
})
.then((response) => {
if (!response.ok) {
throw new Error('failed to get preset');
}
return response.json();
})
.then((result) => {
if (result.status != 'ok') {
return;
}
console.log(result.parameters);
initialConfig = result.parameters;
loadConfig(initialConfig);
render();
});
}
/* Utilities */
function $(id) {
return document.getElementById(id);
}
/* Fill form from config */
function loadConfig(cfg) {
$('title').value = cfg.title ?? '';
$('error_code').value = cfg.error_code ?? '';
$('more_hidden').checked = !!(cfg.more_information && cfg.more_information.hidden);
$('more_text').value = cfg.more_information?.text ?? '';
$('more_link').value = cfg.more_information?.link ?? '';
$('more_for').value = cfg.more_information?.for ?? '';
$('browser_status').value = cfg.browser_status?.status ?? 'ok';
$('browser_location').value = cfg.browser_status?.location ?? '';
$('browser_name').value = cfg.browser_status?.name ?? '';
$('browser_status_text').value = cfg.browser_status?.status_text ?? '';
$('cloudflare_status').value = cfg.cloudflare_status?.status ?? 'ok';
$('cloudflare_location').value = cfg.cloudflare_status?.location ?? '';
$('cloudflare_name').value = cfg.cloudflare_status?.name ?? '';
$('cloudflare_status_text').value = cfg.cloudflare_status?.status_text ?? '';
$('host_status').value = cfg.host_status?.status ?? 'ok';
$('host_location').value = cfg.host_status?.location ?? '';
$('host_name').value = cfg.host_status?.name ?? '';
$('host_status_text').value = cfg.host_status?.status_text ?? '';
if (cfg.error_source === 'browser') $('err_browser').checked = true;
else if (cfg.error_source === 'cloudflare') $('err_cloudflare').checked = true;
else $('err_host').checked = true;
$('what_happened').value = cfg.what_happened ?? '';
$('what_can_i_do').value = cfg.what_can_i_do ?? '';
$('perf_text').value = cfg.perf_sec_by?.text ?? '';
$('perf_link').value = cfg.perf_sec_by?.link ?? '';
}
/* Read config from form inputs */
function readConfig() {
return {
title: $('title').value,
error_code: $('error_code').value,
more_information: {
hidden: !!$('more_hidden').checked,
text: $('more_text').value,
link: $('more_link').value,
for: $('more_for').value,
},
browser_status: {
status: $('browser_status').value,
location: $('browser_location').value,
name: $('browser_name').value,
status_text: $('browser_status_text').value,
},
cloudflare_status: {
status: $('cloudflare_status').value,
location: $('cloudflare_location').value,
name: $('cloudflare_name').value,
status_text: $('cloudflare_status_text').value,
},
host_status: {
status: $('host_status').value,
location: $('host_location').value,
name: $('host_name').value,
status_text: $('host_status_text').value,
},
error_source: (
document.querySelector('input[name="error_source"]:checked') || {
value: 'host',
}
).value,
what_happened: $('what_happened').value,
what_can_i_do: $('what_can_i_do').value,
perf_sec_by: {
text: $('perf_text').value,
link: $('perf_link').value,
},
};
}
function formatUtcTimestamp() {
const d = new Date();
const year = d.getUTCFullYear();
const month = String(d.getUTCMonth() + 1).padStart(2, '0');
const day = String(d.getUTCDate()).padStart(2, '0');
const hours = String(d.getUTCHours()).padStart(2, '0');
const minutes = String(d.getUTCMinutes()).padStart(2, '0');
const seconds = String(d.getUTCSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} UTC`;
}
function renderEjs(params) {
return template({
params: params,
});
}
/* Basic render: build HTML string from config and put into iframe.srcdoc */
function render() {
const cfg = readConfig();
window.lastCfg = cfg;
cfg.time = formatUtcTimestamp();
cfg.ray_id = '0123456789abcdef';
cfg.client_ip = '1.1.1.1';
if (Number.isNaN(Number(cfg.error_code))) {
cfg.html_title = cfg.title || 'Internal server error';
}
let pageHtml = renderEjs(cfg);
// Write into iframe
const iframe = $('previewFrame');
let doc = iframe.contentDocument;
doc.open();
doc.write(pageHtml);
doc.close();
updateStatusBlockStyles();
// store last rendered HTML for "open in new tab"
lastRenderedHtml = pageHtml;
}
/* Open in new tab: create blob and open */
let lastRenderedHtml = '';
function openInNewTab() {
if (!lastRenderedHtml) render();
const blob = new Blob([lastRenderedHtml], { type: 'text/html' });
const url = URL.createObjectURL(blob);
window.open(url, '_blank', 'noopener');
// note that this url won't be revoked
}
function createShareableLink() {
$('shareLink').value = '';
fetch('../s/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
parameters: window.lastCfg,
}),
})
.then((response) => {
if (!response.ok) {
alert('failed to create link');
}
return response.json();
})
.then((result) => {
if (result.status != 'ok') {
alert('failed to create link');
return;
}
$('shareLink').value = result.url;
});
}
function exportJSON() {
let content = JSON.stringify(lastCfg, null, 4);
const file = new File([content], 'cloudflare-error-page-params.json', {
type: 'text/plain',
});
const url = URL.createObjectURL(file);
const link = document.createElement('a');
link.href = url;
link.download = file.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}
function resizePreviewFrame() {
const iframe = $('previewFrame');
const height = iframe.contentWindow.document.body.scrollHeight + 2;
iframe.style.setProperty('--expanded-height', height + 'px');
}
/* Update status block colors based on selected status and error_source */
function updateStatusBlockStyles() {
const browserOk = $('browser_status').value === 'ok';
const cfOk = $('cloudflare_status').value === 'ok';
const hostOk = $('host_status').value === 'ok';
setBlockClass('block_browser', browserOk ? 'status-ok' : 'status-error');
setBlockClass('block_cloudflare', cfOk ? 'status-ok' : 'status-error');
setBlockClass('block_host', hostOk ? 'status-ok' : 'status-error');
}
function setBlockClass(id, cls) {
const el = $(id);
if (!el) return;
el.classList.remove('status-ok', 'status-error');
el.classList.add(cls);
}
/* Simple debounce */
function debounce(fn, wait) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), wait);
};
}
/* Wire up events */
// initialize form values from initialConfig
loadConfig(initialConfig);
render();
// On preset change, load preset and render
$('presetSelect').addEventListener('change', (e) => {
const p = e.target.value;
if (PRESETS[p]) loadConfig(PRESETS[p]);
render();
});
// Render / Open button handlers
// $('btnRender').addEventListener('click', e => { e.preventDefault(); render(); });
$('btnOpen').addEventListener('click', (e) => {
e.preventDefault();
openInNewTab();
});
$('btnShare').addEventListener('click', (e) => {
e.preventDefault();
createShareableLink();
});
$('btnExport').addEventListener('click', (e) => {
e.preventDefault();
exportJSON();
});
$('btnCopyLink').addEventListener('click', () => {
const field = $('shareLink');
field.select();
field.setSelectionRange(0, field.value.length);
navigator.clipboard.writeText(field.value).then(() => {
// No notification required unless you want one
});
});
// Input change -> render
const inputs = document.querySelectorAll('#editorForm input, #editorForm textarea, #editorForm select');
inputs.forEach((inp) => {
inp.addEventListener(
'input',
debounce(() => {
// Update status block color classes for quick visual feedback in the editor
render();
}, 200)
);
// for radio change events (error_source)
if (inp.type === 'radio')
inp.addEventListener('change', () => {
render();
});
});
// Automatically update frame height
const observer = new ResizeObserver((entries) => resizePreviewFrame());
const iframe = $('previewFrame');
observer.observe(iframe.contentWindow.document.body);
// resizePreviewFrame()
setInterval(resizePreviewFrame, 1000); // TODO...

View File

@@ -0,0 +1,45 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"include": ["src/**/*.ts"],
"compilerOptions": {
// File Layout
// "rootDir": "./src",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "esnext",
"target": "esnext",
"types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
"strict": true,
"jsx": "react-jsx",
// "verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleResolution": "bundler",
"moduleDetection": "force",
"skipLibCheck": true
}
}

View File

@@ -0,0 +1,36 @@
import { defineConfig } from 'vite';
import { minify as htmlMinify } from 'html-minifier-terser';
export default defineConfig({
appType: 'mpa',
base: '/editor/',
build: {
minify: true,
sourcemap: true,
},
server: {
port: 3000,
proxy: {
'/s': {
target: 'http://localhost:5000',
},
},
},
plugins: [
{
name: 'html-minifier',
transformIndexHtml: {
order: 'post',
handler(html) {
return htmlMinify(html, {
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true,
});
},
},
},
],
});

593
editor/frontend/yarn.lock Normal file
View File

@@ -0,0 +1,593 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@esbuild/aix-ppc64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz#80fcbe36130e58b7670511e888b8e88a259ed76c"
integrity sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==
"@esbuild/android-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz#8aa4965f8d0a7982dc21734bf6601323a66da752"
integrity sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==
"@esbuild/android-arm@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz#300712101f7f50f1d2627a162e6e09b109b6767a"
integrity sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==
"@esbuild/android-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz#87dfb27161202bdc958ef48bb61b09c758faee16"
integrity sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==
"@esbuild/darwin-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz#79197898ec1ff745d21c071e1c7cc3c802f0c1fd"
integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==
"@esbuild/darwin-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz#146400a8562133f45c4d2eadcf37ddd09718079e"
integrity sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==
"@esbuild/freebsd-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz#1c5f9ba7206e158fd2b24c59fa2d2c8bb47ca0fe"
integrity sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==
"@esbuild/freebsd-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz#ea631f4a36beaac4b9279fa0fcc6ca29eaeeb2b3"
integrity sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==
"@esbuild/linux-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz#e1066bce58394f1b1141deec8557a5f0a22f5977"
integrity sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==
"@esbuild/linux-arm@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz#452cd66b20932d08bdc53a8b61c0e30baf4348b9"
integrity sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==
"@esbuild/linux-ia32@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz#b24f8acc45bcf54192c7f2f3be1b53e6551eafe0"
integrity sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==
"@esbuild/linux-loong64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz#f9cfffa7fc8322571fbc4c8b3268caf15bd81ad0"
integrity sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==
"@esbuild/linux-mips64el@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz#575a14bd74644ffab891adc7d7e60d275296f2cd"
integrity sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==
"@esbuild/linux-ppc64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz#75b99c70a95fbd5f7739d7692befe60601591869"
integrity sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==
"@esbuild/linux-riscv64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz#2e3259440321a44e79ddf7535c325057da875cd6"
integrity sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==
"@esbuild/linux-s390x@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz#17676cabbfe5928da5b2a0d6df5d58cd08db2663"
integrity sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==
"@esbuild/linux-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz#0583775685ca82066d04c3507f09524d3cd7a306"
integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==
"@esbuild/netbsd-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz#f04c4049cb2e252fe96b16fed90f70746b13f4a4"
integrity sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==
"@esbuild/netbsd-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz#77da0d0a0d826d7c921eea3d40292548b258a076"
integrity sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==
"@esbuild/openbsd-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz#6296f5867aedef28a81b22ab2009c786a952dccd"
integrity sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==
"@esbuild/openbsd-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz#f8d23303360e27b16cf065b23bbff43c14142679"
integrity sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==
"@esbuild/openharmony-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz#49e0b768744a3924be0d7fd97dd6ce9b2923d88d"
integrity sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==
"@esbuild/sunos-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz#a6ed7d6778d67e528c81fb165b23f4911b9b13d6"
integrity sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==
"@esbuild/win32-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz#9ac14c378e1b653af17d08e7d3ce34caef587323"
integrity sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==
"@esbuild/win32-ia32@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz#918942dcbbb35cc14fca39afb91b5e6a3d127267"
integrity sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==
"@esbuild/win32-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz#9bdad8176be7811ad148d1f8772359041f46c6c5"
integrity sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.13"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/source-map@^0.3.3":
version "0.3.11"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba"
integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@rollup/rollup-android-arm-eabi@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz#7e478b66180c5330429dd161bf84dad66b59c8eb"
integrity sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==
"@rollup/rollup-android-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz#2b025510c53a5e3962d3edade91fba9368c9d71c"
integrity sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==
"@rollup/rollup-darwin-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz#3577c38af68ccf34c03e84f476bfd526abca10a0"
integrity sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==
"@rollup/rollup-darwin-x64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz#2bf5f2520a1f3b551723d274b9669ba5b75ed69c"
integrity sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==
"@rollup/rollup-freebsd-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz#4bb9cc80252564c158efc0710153c71633f1927c"
integrity sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==
"@rollup/rollup-freebsd-x64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz#2301289094d49415a380cf942219ae9d8b127440"
integrity sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==
"@rollup/rollup-linux-arm-gnueabihf@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz#1d03d776f2065e09fc141df7d143476e94acca88"
integrity sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==
"@rollup/rollup-linux-arm-musleabihf@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz#8623de0e040b2fd52a541c602688228f51f96701"
integrity sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==
"@rollup/rollup-linux-arm64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz#ce2d1999bc166277935dde0301cde3dd0417fb6e"
integrity sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==
"@rollup/rollup-linux-arm64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz#88c2523778444da952651a2219026416564a4899"
integrity sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==
"@rollup/rollup-linux-loong64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz#578ca2220a200ac4226c536c10c8cc6e4f276714"
integrity sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==
"@rollup/rollup-linux-ppc64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz#aa338d3effd4168a20a5023834a74ba2c3081293"
integrity sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==
"@rollup/rollup-linux-riscv64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz#16ba582f9f6cff58119aa242782209b1557a1508"
integrity sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==
"@rollup/rollup-linux-riscv64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz#e404a77ebd6378483888b8064c703adb011340ab"
integrity sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==
"@rollup/rollup-linux-s390x-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz#92ad52d306227c56bec43d96ad2164495437ffe6"
integrity sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==
"@rollup/rollup-linux-x64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz#fd0dea3bb9aa07e7083579f25e1c2285a46cb9fa"
integrity sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==
"@rollup/rollup-linux-x64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz#37a3efb09f18d555f8afc490e1f0444885de8951"
integrity sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==
"@rollup/rollup-openharmony-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz#c489bec9f4f8320d42c9b324cca220c90091c1f7"
integrity sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==
"@rollup/rollup-win32-arm64-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz#152832b5f79dc22d1606fac3db946283601b7080"
integrity sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==
"@rollup/rollup-win32-ia32-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz#54d91b2bb3bf3e9f30d32b72065a4e52b3a172a5"
integrity sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==
"@rollup/rollup-win32-x64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz#df9df03e61a003873efec8decd2034e7f135c71e"
integrity sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==
"@rollup/rollup-win32-x64-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz#38ae84f4c04226c1d56a3b17296ef1e0460ecdfe"
integrity sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==
"@types/estree@1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
acorn@^8.15.0:
version "8.15.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
async@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
bootstrap@^5.3.8:
version "5.3.8"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.8.tgz#6401a10057a22752d21f4e19055508980656aeed"
integrity sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==
brace-expansion@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7"
integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==
dependencies:
balanced-match "^1.0.0"
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
camel-case@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
dependencies:
pascal-case "^3.1.2"
tslib "^2.0.3"
clean-css@~5.3.2:
version "5.3.3"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd"
integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==
dependencies:
source-map "~0.6.0"
commander@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
dot-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
ejs@^3.1.10:
version "3.1.10"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
dependencies:
jake "^10.8.5"
entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
esbuild@^0.25.0:
version "0.25.12"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.12.tgz#97a1d041f4ab00c2fce2f838d2b9969a2d2a97a5"
integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==
optionalDependencies:
"@esbuild/aix-ppc64" "0.25.12"
"@esbuild/android-arm" "0.25.12"
"@esbuild/android-arm64" "0.25.12"
"@esbuild/android-x64" "0.25.12"
"@esbuild/darwin-arm64" "0.25.12"
"@esbuild/darwin-x64" "0.25.12"
"@esbuild/freebsd-arm64" "0.25.12"
"@esbuild/freebsd-x64" "0.25.12"
"@esbuild/linux-arm" "0.25.12"
"@esbuild/linux-arm64" "0.25.12"
"@esbuild/linux-ia32" "0.25.12"
"@esbuild/linux-loong64" "0.25.12"
"@esbuild/linux-mips64el" "0.25.12"
"@esbuild/linux-ppc64" "0.25.12"
"@esbuild/linux-riscv64" "0.25.12"
"@esbuild/linux-s390x" "0.25.12"
"@esbuild/linux-x64" "0.25.12"
"@esbuild/netbsd-arm64" "0.25.12"
"@esbuild/netbsd-x64" "0.25.12"
"@esbuild/openbsd-arm64" "0.25.12"
"@esbuild/openbsd-x64" "0.25.12"
"@esbuild/openharmony-arm64" "0.25.12"
"@esbuild/sunos-x64" "0.25.12"
"@esbuild/win32-arm64" "0.25.12"
"@esbuild/win32-ia32" "0.25.12"
"@esbuild/win32-x64" "0.25.12"
fdir@^6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
dependencies:
minimatch "^5.0.1"
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
html-minifier-terser@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz#18752e23a2f0ed4b0f550f217bb41693e975b942"
integrity sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==
dependencies:
camel-case "^4.1.2"
clean-css "~5.3.2"
commander "^10.0.0"
entities "^4.4.0"
param-case "^3.0.4"
relateurl "^0.2.7"
terser "^5.15.1"
jake@^10.8.5:
version "10.9.4"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.4.tgz#d626da108c63d5cfb00ab5c25fadc7e0084af8e6"
integrity sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==
dependencies:
async "^3.2.6"
filelist "^1.0.4"
picocolors "^1.1.1"
lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
dependencies:
tslib "^2.0.3"
minimatch@^5.0.1:
version "5.1.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
dependencies:
brace-expansion "^2.0.1"
nanoid@^3.3.11:
version "3.3.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
dependencies:
lower-case "^2.0.2"
tslib "^2.0.3"
param-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
dependencies:
dot-case "^3.0.4"
tslib "^2.0.3"
pascal-case@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
postcss@^8.5.6:
version "8.5.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
dependencies:
nanoid "^3.3.11"
picocolors "^1.1.1"
source-map-js "^1.2.1"
prettier@3.7.4:
version "3.7.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f"
integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==
relateurl@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
rollup@^4.43.0:
version "4.53.3"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.53.3.tgz#dbc8cd8743b38710019fb8297e8d7a76e3faa406"
integrity sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==
dependencies:
"@types/estree" "1.0.8"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.53.3"
"@rollup/rollup-android-arm64" "4.53.3"
"@rollup/rollup-darwin-arm64" "4.53.3"
"@rollup/rollup-darwin-x64" "4.53.3"
"@rollup/rollup-freebsd-arm64" "4.53.3"
"@rollup/rollup-freebsd-x64" "4.53.3"
"@rollup/rollup-linux-arm-gnueabihf" "4.53.3"
"@rollup/rollup-linux-arm-musleabihf" "4.53.3"
"@rollup/rollup-linux-arm64-gnu" "4.53.3"
"@rollup/rollup-linux-arm64-musl" "4.53.3"
"@rollup/rollup-linux-loong64-gnu" "4.53.3"
"@rollup/rollup-linux-ppc64-gnu" "4.53.3"
"@rollup/rollup-linux-riscv64-gnu" "4.53.3"
"@rollup/rollup-linux-riscv64-musl" "4.53.3"
"@rollup/rollup-linux-s390x-gnu" "4.53.3"
"@rollup/rollup-linux-x64-gnu" "4.53.3"
"@rollup/rollup-linux-x64-musl" "4.53.3"
"@rollup/rollup-openharmony-arm64" "4.53.3"
"@rollup/rollup-win32-arm64-msvc" "4.53.3"
"@rollup/rollup-win32-ia32-msvc" "4.53.3"
"@rollup/rollup-win32-x64-gnu" "4.53.3"
"@rollup/rollup-win32-x64-msvc" "4.53.3"
fsevents "~2.3.2"
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0, source-map@~0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
terser@^5.15.1:
version "5.44.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.1.tgz#e391e92175c299b8c284ad6ded609e37303b0a9c"
integrity sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==
dependencies:
"@jridgewell/source-map" "^0.3.3"
acorn "^8.15.0"
commander "^2.20.0"
source-map-support "~0.5.20"
tinyglobby@^0.2.15:
version "0.2.15"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
dependencies:
fdir "^6.5.0"
picomatch "^4.0.3"
tslib@^2.0.3:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
typescript@^5.9.3:
version "5.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
vite@^7.2.6:
version "7.2.7"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.2.7.tgz#0789a4c3206081699f34a9ecca2dda594a07478e"
integrity sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==
dependencies:
esbuild "^0.25.0"
fdir "^6.5.0"
picomatch "^4.0.3"
postcss "^8.5.6"
rollup "^4.43.0"
tinyglobby "^0.2.15"
optionalDependencies:
fsevents "~2.3.3"

View File

@@ -1,866 +0,0 @@
<!-- SPDX-License-Identifier: MIT -->
<!--
!!!
!!! Note: This file is vibely generated, and could be very hard to maintain.
!!!
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Cloudflare Error Page Editor</title>
<meta name="description" content="Online editor to create customized Cloudflare-styled error pages.">
<meta name="keywords" content="cloudflare,error,page,editor">
<link rel="canonical" href="https://virt.moe/cloudflare-error-page/editor/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="moe::virt" />
<meta property="og:title" content="Cloudflare error page editor" />
<meta property="og:url" content="https://virt.moe/cloudflare-error-page/editor/" />
<meta property="og:description" content="Online editor to create customized Cloudflare-styled error pages" />
<meta property="twitter:card" content="summary" />
<meta property="twitter:site" content="moe::virt" />
<meta property="twitter:title" content="Cloudflare error page editor" />
<meta property="twitter:description" content="Online editor to create customized Cloudflare-styled error pages" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<script src="https://virt.moe/assets/cloudflare-error-page/ejs.min.js"></script>
<link href="https://virt.moe/assets/cloudflare-error-page/bootstrap.min.css" rel="stylesheet">
<script>
(async function () {
/*
Editor logic
- initialConfig: provided default config (from your JSON)
- render(): placeholder that generates HTML and writes to iframe.srcdoc
- inputs call render() on change
- "Open in new tab" opens the rendered HTML in a new window using a blob URL
*/
let template;
let postponeRendering = false;
fetch('template.ejs')
.then(response => {
if (!response.ok) {
alert('failed to fetch template');
}
return response.text(); // Returns a Promise
})
.then(templateContent => {
template = ejs.compile(templateContent)
if (postponeRendering) {
render()
}
})
// can be changed if specified by '?from=<name>'
let initialConfig = {
"title": "Internal server error",
"error_code": '500',
"more_information": {
"hidden": false,
"text": "cloudflare.com",
"for": "more information",
},
"browser_status": {
"status": "ok",
"location": "You",
"name": "Browser",
"status_text": "Working"
},
"cloudflare_status": {
"status": "error",
"location": "San Francisco",
"name": "Cloudflare",
"status_text": "Error"
},
"host_status": {
"status": "ok",
"location": "Website",
"name": "Host",
"status_text": "Working"
},
"error_source": "cloudflare",
"what_happened": "There is an internal server error on Cloudflare's network.",
"what_can_i_do": "Please try again in a few minutes.",
};
// Demo presets (content brief — replace or expand as needed)
const PRESETS = {
"default": structuredClone(initialConfig),
"empty": {
"error_code": '500',
},
"catastrophic": {
"title": "Catastrophic infrastructure failure",
"error_code": '500',
"more_information": {
"for": "no information",
},
"browser_status": {
"status": "error",
"status_text": "Out of Memory"
},
"cloudflare_status": {
"status": "error",
"location": "Everywhere",
"status_text": "Error"
},
"host_status": {
"status": "error",
"location": "example.com",
"status_text": "On Fire"
},
"error_source": "cloudflare",
"what_happened": "There is a catastrophic failure.",
"what_can_i_do": "Please try again in a few years.",
},
"working": {
"title": "Web server is working",
"error_code": '200',
"more_information": {
"hidden": true
},
"browser_status": {
"status": "ok",
"status_text": "Seems Working"
},
"cloudflare_status": {
"status": "ok",
"status_text": "Often Working"
},
"host_status": {
"status": "ok",
"location": "example.com",
"status_text": "Almost Working"
},
"error_source": "host",
"what_happened": "This site is still working. And it looks great.",
"what_can_i_do": "Visit the site before it crashes someday."
},
"consensual": {
"title": "The Myth Of \"Consensual\" Internet",
"error_code": "lmao",
"more_information": {
"hidden": false,
"text": "r/ProgrammerHumor",
"link": "https://redd.it/1p2yola"
},
"browser_status": {
"status": "ok",
"location": "You",
"name": "Browser",
"status_text": "I Consent"
},
"cloudflare_status": {
"status": "error",
"location": "F***ing Everywhere",
"name": "Cloudflare",
"status_text": "I Don't!"
},
"host_status": {
"status": "ok",
"location": "Remote",
"name": "Host",
"status_text": "I Consent"
},
"error_source": "cloudflare",
"what_happened": "Isn't There Someone You Forgot To Ask?",
"what_can_i_do": "Kill Yourself",
}
};
function extractUrlParam(str, key) {
const urlParams = new URLSearchParams(str)
return urlParams.get(key)
}
function getDefaultPresetName() {
const key = 'from';
let name = extractUrlParam(window.location.search, key);
if (!name) {
name = extractUrlParam(window.location.hash.substr(1), key)
}
return name
}
const defaultPresetName = getDefaultPresetName();
if (defaultPresetName && defaultPresetName.indexOf('/') < 0) {
fetch(`../s/${defaultPresetName}`, {
headers: {
'Accept': 'application/json'
},
}).then(response => {
if (!response.ok) {
throw new Error('failed to get preset');
}
return response.json();
}).then(result => {
if (result.status != 'ok') {
return
}
console.log(result.parameters)
initialConfig = result.parameters
loadConfig(initialConfig);
render()
})
}
/* Utilities */
function $(id) { return document.getElementById(id); }
/* Fill form from config */
function loadConfig(cfg) {
$('title').value = cfg.title ?? '';
$('error_code').value = cfg.error_code ?? '';
$('more_hidden').checked = !!(cfg.more_information && cfg.more_information.hidden);
$('more_text').value = cfg.more_information?.text ?? '';
$('more_link').value = cfg.more_information?.link ?? '';
$('more_for').value = cfg.more_information?.for ?? '';
$('browser_status').value = cfg.browser_status?.status ?? 'ok';
$('browser_location').value = cfg.browser_status?.location ?? '';
$('browser_name').value = cfg.browser_status?.name ?? '';
$('browser_status_text').value = cfg.browser_status?.status_text ?? '';
$('cloudflare_status').value = cfg.cloudflare_status?.status ?? 'ok';
$('cloudflare_location').value = cfg.cloudflare_status?.location ?? '';
$('cloudflare_name').value = cfg.cloudflare_status?.name ?? '';
$('cloudflare_status_text').value = cfg.cloudflare_status?.status_text ?? '';
$('host_status').value = cfg.host_status?.status ?? 'ok';
$('host_location').value = cfg.host_status?.location ?? '';
$('host_name').value = cfg.host_status?.name ?? '';
$('host_status_text').value = cfg.host_status?.status_text ?? '';
if (cfg.error_source === 'browser') $('err_browser').checked = true;
else if (cfg.error_source === 'cloudflare') $('err_cloudflare').checked = true;
else $('err_host').checked = true;
$('what_happened').value = cfg.what_happened ?? '';
$('what_can_i_do').value = cfg.what_can_i_do ?? '';
$('perf_text').value = cfg.perf_sec_by?.text ?? '';
$('perf_link').value = cfg.perf_sec_by?.link ?? '';
}
/* Read config from form inputs */
function readConfig() {
return {
title: $('title').value,
error_code: $('error_code').value,
more_information: {
hidden: !!$('more_hidden').checked,
text: $('more_text').value,
link: $('more_link').value,
for: $('more_for').value
},
browser_status: {
status: $('browser_status').value,
location: $('browser_location').value,
name: $('browser_name').value,
status_text: $('browser_status_text').value
},
cloudflare_status: {
status: $('cloudflare_status').value,
location: $('cloudflare_location').value,
name: $('cloudflare_name').value,
status_text: $('cloudflare_status_text').value
},
host_status: {
status: $('host_status').value,
location: $('host_location').value,
name: $('host_name').value,
status_text: $('host_status_text').value
},
error_source: (document.querySelector('input[name="error_source"]:checked') || { value: 'host' }).value,
what_happened: $('what_happened').value,
what_can_i_do: $('what_can_i_do').value,
perf_sec_by: {
text: $('perf_text').value,
link: $('perf_link').value
}
};
}
function formatUtcTimestamp() {
const d = new Date();
const year = d.getUTCFullYear();
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
const day = String(d.getUTCDate()).padStart(2, "0");
const hours = String(d.getUTCHours()).padStart(2, "0");
const minutes = String(d.getUTCMinutes()).padStart(2, "0");
const seconds = String(d.getUTCSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} UTC`;
}
function renderEjs(params) {
return template({
params: params,
})
}
/* Basic render: build HTML string from config and put into iframe.srcdoc */
function render() {
const cfg = readConfig();
window.lastCfg = cfg
if (!template) {
postponeRendering = true
return
}
cfg.time = formatUtcTimestamp()
cfg.ray_id = '0123456789abcdef'
cfg.client_ip = '1.1.1.1'
if (Number.isNaN(Number(cfg.error_code))) {
cfg.html_title = cfg.title || 'Internal server error'
}
let pageHtml = renderEjs(cfg)
// Write into iframe
const iframe = $('previewFrame');
let doc = iframe.contentDocument
doc.open();
doc.write(pageHtml);
doc.close();
updateStatusBlockStyles();
// store last rendered HTML for "open in new tab"
lastRenderedHtml = pageHtml;
}
/* Open in new tab: create blob and open */
let lastRenderedHtml = '';
function openInNewTab() {
if (!lastRenderedHtml) render();
const blob = new Blob([lastRenderedHtml], { type: 'text/html' });
const url = URL.createObjectURL(blob);
window.open(url, '_blank', 'noopener');
// note that this url won't be revoked
}
function createShareableLink() {
$('shareLink').value = ''
fetch('../s/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'parameters': window.lastCfg
})
})
.then(response => {
if (!response.ok) {
alert('failed to create link');
}
return response.json();
})
.then(result => {
if (result.status != 'ok') {
alert('failed to create link');
return
}
$('shareLink').value = result.url
})
}
function exportJSON() {
let content = JSON.stringify(lastCfg, null, 4)
const file = new File([content], 'cloudflare-error-page-params.json', {
type: 'text/plain',
})
const url = URL.createObjectURL(file)
const link = document.createElement('a')
link.href = url
link.download = file.name
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
}
function resizePreviewFrame() {
const iframe = $('previewFrame');
const height = iframe.contentWindow.document.body.scrollHeight + 2
iframe.style.setProperty('--expanded-height', height + 'px')
}
/* Wire up events */
document.addEventListener('DOMContentLoaded', () => {
// initialize form values from initialConfig
loadConfig(initialConfig);
render();
// On preset change, load preset and render
$('presetSelect').addEventListener('change', e => {
const p = e.target.value;
if (PRESETS[p]) loadConfig(PRESETS[p]);
render();
});
// Render / Open button handlers
// $('btnRender').addEventListener('click', e => { e.preventDefault(); render(); });
$('btnOpen').addEventListener('click', e => { e.preventDefault(); openInNewTab(); });
$('btnShare').addEventListener('click', e => { e.preventDefault(); createShareableLink() });
$('btnExport').addEventListener('click', e => { e.preventDefault(); exportJSON() });
$('btnCopyLink').addEventListener('click', () => {
const field = $('shareLink');
field.select();
field.setSelectionRange(0, field.value.length);
navigator.clipboard.writeText(field.value).then(() => {
// No notification required unless you want one
});
});
// Input change -> render
const inputs = document.querySelectorAll('#editorForm input, #editorForm textarea, #editorForm select');
inputs.forEach(inp => {
inp.addEventListener('input', debounce(() => {
// Update status block color classes for quick visual feedback in the editor
render();
}, 200));
// for radio change events (error_source)
if (inp.type === 'radio') inp.addEventListener('change', () => { render(); });
});
// Automatically update frame height
const observer = new ResizeObserver(entries => resizePreviewFrame());
const iframe = $('previewFrame');
observer.observe(iframe.contentWindow.document.body);
// resizePreviewFrame()
setInterval(resizePreviewFrame, 1000) // TODO...
});
/* Update status block colors based on selected status and error_source */
function updateStatusBlockStyles() {
const browserOk = $('browser_status').value === 'ok';
const cfOk = $('cloudflare_status').value === 'ok';
const hostOk = $('host_status').value === 'ok';
setBlockClass('block_browser', browserOk ? 'status-ok' : 'status-error');
setBlockClass('block_cloudflare', cfOk ? 'status-ok' : 'status-error');
setBlockClass('block_host', hostOk ? 'status-ok' : 'status-error');
}
function setBlockClass(id, cls) {
const el = $(id);
if (!el) return;
el.classList.remove('status-ok', 'status-error');
el.classList.add(cls);
}
/* Simple debounce */
function debounce(fn, wait) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), wait);
};
}
})()
</script>
<style>
/* Layout: editor + preview */
.app {
display: flex;
flex-direction: column;
gap: 1rem;
height: 100vh;
/* padding: 0.8rem; */
}
/* On md and up, arrange horizontally: editor left, preview right */
@media (min-width: 768px) {
.app {
flex-direction: row;
align-items: stretch;
}
.editor {
flex: 0 0;
min-width: 380px;
overflow-y: auto;
}
.preview {
flex: 1 1 48%;
/* max-width: 48%; */
display: flex;
flex-direction: column;
}
iframe.preview-frame {
height: 100% !important;
}
}
/* On small screens use stacked layout: editor then iframe */
.editor {
background: #fff;
border: 1px solid #e3e6ea;
border-radius: .5rem;
padding: 0.8rem;
}
.preview {
background: #fff;
border: 1px solid #e3e6ea;
border-radius: .5rem;
padding: 0.8rem 0 0 0;
}
/* Compact form: label and control same row */
.form-row {
display: flex;
gap: .3rem;
align-items: center;
margin-bottom: .6rem;
}
.form-row>label {
min-width: 6rem;
max-width: 12rem;
margin-bottom: 0;
font-weight: 600;
}
.form-row>.control {
flex: 1;
}
/* Status block styling */
.status-block {
border: 1px solid #cfeadd;
border-radius: .375rem;
padding: .75rem;
margin-bottom: .5rem;
}
.status-ok {
background: #e9f7ef;
border: 1px solid #cfeadd;
}
.status-error {
background: #fff5f5;
border: 1px solid #f3c2c2;
}
/* Iframe styling */
iframe.preview-frame {
--expanded-height: 100%;
width: 100%;
height: var(--expanded-height);
border: 1px solid #ddd;
flex: 1 1 auto;
min-height: 360px;
border-radius: .375rem;
box-shadow: 0 1px 4px rgba(0, 0, 0, .06);
}
/* Controls toolbar */
.toolbar {
display: flex;
gap: .5rem;
align-items: center;
justify-content: space-between;
margin-bottom: .75rem;
}
/* Compact textarea resizing */
textarea.compact {
min-height: 80px;
resize: vertical;
}
</style>
</head>
<body class="bg-light">
<div class="container-fluid h-100">
<div class="app">
<!-- Editor column -->
<div class="editor">
<h5 class="form-row">Cloudflare Error Page Editor</h5>
<hr>
<div class="form-row mb-3">
<label for="presetSelect">Preset</label>
<select id="presetSelect" class="form-select form-select-sm">
<option value="default">Internal server error (Default)</option>
<option value="empty">Empty</option>
<option value="catastrophic">Catastrophic failure</option>
<option value="working">Server working</option>
<option value="consensual">Myth of consensual</option>
</select>
</div>
<hr>
<form id="editorForm" class="needs-validation" novalidate>
<!-- Basic properties -->
<div class="mb-3">
<!-- <h6 class="mb-2">Page</h6> -->
<div class="form-row">
<label for="title">Title</label>
<div class="control">
<input id="title" class="form-control form-control-sm" placeholder="Internal server error" />
</div>
</div>
<div class="form-row">
<label for="error_code">Error Code</label>
<div class="control">
<input id="error_code" class="form-control form-control-sm" placeholder="500" />
</div>
</div>
</div>
<hr>
<!-- Status blocks -->
<div class="mb-3">
<h6 class="mb-2">Status</h6>
<!-- Browser -->
<div id="block_browser" class="status-block status-ok">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Browser</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_browser" value="browser" />
<label for="err_browser" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="browser_status">Status</label>
<div class="control">
<select id="browser_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="browser_location">Location</label>
<div class="control">
<input id="browser_location" class="form-control form-control-sm" placeholder="You" />
</div>
</div>
<div class="form-row">
<label for="browser_name">Name</label>
<div class="control">
<input id="browser_name" class="form-control form-control-sm" placeholder="Browser" />
</div>
</div>
<div class="form-row">
<label for="browser_status_text">Status Text</label>
<div class="control">
<input id="browser_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
<!-- Cloudflare -->
<div id="block_cloudflare" class="status-block status-error">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Cloudflare</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_cloudflare"
value="cloudflare" />
<label for="err_cloudflare" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="cloudflare_status">Status</label>
<div class="control">
<select id="cloudflare_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="cloudflare_location">Location</label>
<div class="control">
<input id="cloudflare_location" class="form-control form-control-sm" placeholder="San Francisco" />
</div>
</div>
<div class="form-row">
<label for="cloudflare_name">Name</label>
<div class="control">
<input id="cloudflare_name" class="form-control form-control-sm" placeholder="Cloudflare" />
</div>
</div>
<div class="form-row">
<label for="cloudflare_status_text">Status Text</label>
<div class="control">
<input id="cloudflare_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
<!-- Host -->
<div id="block_host" class="status-block status-ok">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Host</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_host" value="host" />
<label for="err_host" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="host_status">Status</label>
<div class="control">
<select id="host_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="host_location">Location</label>
<div class="control">
<input id="host_location" class="form-control form-control-sm" placeholder="Website" />
</div>
</div>
<div class="form-row">
<label for="host_name">Name</label>
<div class="control">
<input id="host_name" class="form-control form-control-sm" placeholder="Host" />
</div>
</div>
<div class="form-row">
<label for="host_status_text">Status Text</label>
<div class="control">
<input id="host_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
</div>
<div class="status-block mt-3 mb-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Visit ...</strong>
<div>
<input id="more_hidden" class="form-check-input" type="checkbox" />
<label for="more_hidden" class="ms-1 small">Hidden</label>
</div>
</div>
<div class="form-row">
<label for="more_text">Text</label>
<div class="control">
<input id="more_text" class="form-control form-control-sm" placeholder="cloudflare.com" />
</div>
</div>
<div class="form-row">
<label for="more_link">Link</label>
<div class="control">
<input id="more_link" class="form-control form-control-sm" placeholder="https://www.cloudflare.com/" />
</div>
</div>
<div class="form-row">
<label for="more_for">For</label>
<div class="control">
<input id="more_for" class="form-control form-control-sm" placeholder="more information" />
</div>
</div>
</div>
<label for="what_happened" class="fw-semibold">What happened?</label>
<div class="control">
<textarea id="what_happened" class="form-control compact"
placeholder="There is an internal server error on Cloudflare's network."></textarea>
</div>
<!-- </div> -->
<!-- <div class=""> -->
<label for="what_can_i_do" class="fw-semibold mt-2">What can I do?</label>
<div class="control">
<textarea id="what_can_i_do" class="form-control compact"
placeholder="Please try again in a few minutes."></textarea>
</div>
<hr>
<h6 class="form-row">Performance & security by ...</h6>
<div class="form-row">
<label for="perf_text">Text</label>
<div class="control">
<input id="perf_text" class="form-control form-control-sm" placeholder="Cloudflare" />
</div>
</div>
<div class="form-row">
<label for="perf_link">Link</label>
<div class="control">
<input id="perf_link" class="form-control form-control-sm" placeholder="https://www.cloudflare.com/" />
</div>
</div>
<!-- </div> -->
<div class="d-flex gap-2 mt-2 mb-2">
<!-- <button id="btnRender" class="btn btn-sm btn-primary">Render</button> -->
<button id="btnOpen" class="btn btn-sm btn-primary">Preview in new tab</button>
<button id="btnExport" class="btn btn-sm btn-primary">Export JSON</button>
</div>
<button id="btnShare" class="btn btn-sm btn-primary">Create shareable link</button>
<div class="mt-2">
<div class="input-group input-group-sm">
<input id="shareLink" class="form-control" readonly />
<button id="btnCopyLink" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div>
<div class="mt-2 text-center">&gt;&gt; Star this project on
<a href="https://github.com/donlon/cloudflare-error-page" target="_blank">GitHub</a>
</div>
<div class="mt-2" style="font-size: 0.9em;">You can also embed this error page into your own website. See
<a href="https://github.com/donlon/cloudflare-error-page#quickstart-for-programmers"
target="_blank">Quickstart</a> in the
homepage for steps.
</div>
</form>
</div>
<!-- Preview column -->
<div class="preview">
<div class="d-flex justify-content-between align-items-center mb-1" style="padding: 0 0.8em;">
<h6><strong>Preview</strong></h6>
</div>
<!-- TODO: An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing. -->
<iframe id="previewFrame" class="preview-frame" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
</div>
</div>
</body>
</html>

View File

@@ -8,7 +8,7 @@ from flask import (
)
root_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')
res_folder = os.path.join(root_dir, 'editor/resources')
res_folder = os.path.join(root_dir, 'editor/frontend/dist')
bp = Blueprint('editor', __name__, url_prefix='/')

View File

@@ -69,5 +69,5 @@ if __name__ == '__main__':
inline_css_resource(
os.path.join(resources_folder, 'templates/error.ejs'),
os.path.join(resources_folder, 'styles/main.css'),
os.path.join(root, 'editor/resources/template.ejs'),
os.path.join(root, 'editor/frontend/src/template.ejs'),
)