mirror of
https://github.com/donlon/cloudflare-error-page.git
synced 2025-12-23 08:49:25 +00:00
Merge pull request #8 from donlon/save-as-dialog
Add Save As dialog to provide language examples
This commit is contained in:
@@ -143,6 +143,80 @@
|
|||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
--bs-popover-body-padding-x: 0.7rem !important;
|
||||||
|
--bs-popover-body-padding-y: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"selector"
|
||||||
|
"code"
|
||||||
|
"buttons";
|
||||||
|
height: fit-content;
|
||||||
|
grid-template-columns: auto;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__selector {
|
||||||
|
grid-area: selector;
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__code {
|
||||||
|
grid-area: code;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 700px !important;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__buttons {
|
||||||
|
grid-area: buttons;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__buttons>* {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.save-as-dialog__container {
|
||||||
|
grid-template-areas:
|
||||||
|
"selector code"
|
||||||
|
"buttons code";
|
||||||
|
grid-template-columns: auto 2fr;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__selector {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__code {
|
||||||
|
min-height: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__buttons {
|
||||||
|
grid-area: buttons;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-as-dialog__buttons>* {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.save-as-dialog__selector {
|
||||||
|
grid-area: selector;
|
||||||
|
min-width: 13em;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -396,15 +470,19 @@
|
|||||||
|
|
||||||
<div class="d-flex gap-2 mt-2 mb-2">
|
<div class="d-flex gap-2 mt-2 mb-2">
|
||||||
<!-- <button id="btnRender" class="btn btn-sm btn-primary">Render</button> -->
|
<!-- <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 type="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>
|
<!-- Button trigger modal -->
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#saveAsDialog">
|
||||||
|
Save as...
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="btnShare" class="btn btn-sm btn-primary">Create shareable link</button>
|
<button type="button" id="btnShare" class="btn btn-sm btn-secondary">Create shareable link</button>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input id="shareLink" class="form-control" readonly />
|
<input id="shareLink" class="form-control" readonly />
|
||||||
<button id="btnCopyLink" class="btn btn-outline-secondary" type="button">Copy</button>
|
<button type="button" id="btnCopyLink" class="btn btn-outline-secondary" data-bs-toggle="popover"
|
||||||
|
data-bs-placement="top" data-bs-trigger="manual" data-bs-content="Copied">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -430,9 +508,48 @@
|
|||||||
<!-- TODO: An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing. -->
|
<!-- 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>
|
<iframe id="previewFrame" class="preview-frame" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="saveAsDialog" tabindex="-1" aria-labelledby="saveAsDialogLabel">
|
||||||
|
<div class="modal-dialog modal-xl modal-fullscreen-lg-down modal-dialog-scrollable">
|
||||||
|
<div class="modal-content h-100">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="saveAsDialogLabel">Save As ...</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mx-2 save-as-dialog__container">
|
||||||
|
<div id="saveAsDialogTypes" class="list-group save-as-dialog__selector">
|
||||||
|
<button type="button" data-type="json" class="list-group-item list-group-item-action active">
|
||||||
|
JSON
|
||||||
|
</button>
|
||||||
|
<button type="button" data-type="python" class="list-group-item list-group-item-action">
|
||||||
|
Python Example
|
||||||
|
</button>
|
||||||
|
<button type="button" data-type="js" class="list-group-item list-group-item-action">
|
||||||
|
NodeJS Example
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-1 save-as-dialog__buttons">
|
||||||
|
<button type="button" class="btn btn-success" id="saveAsDialogCopyBtn" data-bs-toggle="popover"
|
||||||
|
data-bs-placement="right" data-bs-trigger="manual" data-bs-content="Copied">
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="saveAsDialogSaveBtn">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<textarea id="saveAsDialogCode" class="form-control save-as-dialog__code" spellcheck="false"
|
||||||
|
readonly></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -10,15 +10,18 @@
|
|||||||
"preview": "npm run build && vite preview"
|
"preview": "npm run build && vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bootstrap": "^5.2.10",
|
||||||
|
"@types/ejs": "^3.1.5",
|
||||||
|
"@types/html-minifier-terser": "^7.0.2",
|
||||||
"@types/node": "^24.10.2",
|
"@types/node": "^24.10.2",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"prettier": "3.7.4",
|
"prettier": "3.7.4",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.2.6"
|
"vite": "^7.2.6",
|
||||||
|
"vite-plugin-static-copy": "^3.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10"
|
||||||
"vite-plugin-static-copy": "^3.1.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
editor/web/src/assets.d.ts
vendored
Normal file
4
editor/web/src/assets.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module '*?raw' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
26
editor/web/src/codegen/index.ts
Normal file
26
editor/web/src/codegen/index.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import ejs from 'ejs';
|
||||||
|
|
||||||
|
import jsTemplate from './js.ejs?raw';
|
||||||
|
import jsonTemplate from './json.ejs?raw';
|
||||||
|
import pythonTemplate from './python.ejs?raw';
|
||||||
|
|
||||||
|
interface CodeGen {
|
||||||
|
name: string;
|
||||||
|
generate(params: any): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EjsCodeGen implements CodeGen {
|
||||||
|
name: string;
|
||||||
|
private template: ejs.TemplateFunction;
|
||||||
|
constructor(name: string, templateContent: any) {
|
||||||
|
this.name = name;
|
||||||
|
this.template = ejs.compile(templateContent);
|
||||||
|
}
|
||||||
|
generate(params: any): string {
|
||||||
|
return this.template({ params });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const jsCodeGen = new EjsCodeGen('NodeJS Example', jsTemplate);
|
||||||
|
export const jsonCodeGen = new EjsCodeGen('JSON', jsonTemplate);
|
||||||
|
export const pythonCodeGen = new EjsCodeGen('Python Example', pythonTemplate);
|
||||||
16
editor/web/src/codegen/js.ejs
Normal file
16
editor/web/src/codegen/js.ejs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import { render as render_cf_error_page } from 'cloudflare-error-page';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 3000;
|
||||||
|
|
||||||
|
// Define a route for GET requests to the root URL
|
||||||
|
<%# TODO: format to JS-style object (key w/o parens) _%>
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.status(500).send(render_cf_error_page(<%-JSON.stringify(params, null, 2).replaceAll('\n', '\n ')%>));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the server and listen on the specified port
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Example app listening at http://localhost:${port}`);
|
||||||
|
});
|
||||||
1
editor/web/src/codegen/json.ejs
Normal file
1
editor/web/src/codegen/json.ejs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<%-JSON.stringify(params, null, 4)%>
|
||||||
29
editor/web/src/codegen/python.ejs
Normal file
29
editor/web/src/codegen/python.ejs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<%
|
||||||
|
// Covert the parameters to Python format object
|
||||||
|
const randomKey = Math.random() + ''
|
||||||
|
const paramsArg = JSON.stringify(params, (key, value) => {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return randomKey + value.toString()
|
||||||
|
} else if (value === null) {
|
||||||
|
return randomKey + 'null'
|
||||||
|
} else {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}, 4)
|
||||||
|
.replace(`"${randomKey}true"`, 'True')
|
||||||
|
.replace(`"${randomKey}false"`, 'False')
|
||||||
|
.replace(`"${randomKey}null"`, 'None')
|
||||||
|
_%>
|
||||||
|
from flask import Flask
|
||||||
|
from cloudflare_error_page import render as render_cf_error_page
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Define a route for GET requests to the root URL
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
# Render the error page
|
||||||
|
return render_cf_error_page(<%=paramsArg.replaceAll('\n', '\n ')%>), 500
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True, port=5000)
|
||||||
@@ -7,8 +7,12 @@
|
|||||||
*/
|
*/
|
||||||
import ejs from 'ejs';
|
import ejs from 'ejs';
|
||||||
import templateContent from './template.ejs?raw';
|
import templateContent from './template.ejs?raw';
|
||||||
|
|
||||||
|
import 'bootstrap/js/src/modal.js';
|
||||||
|
import Popover from 'bootstrap/js/src/popover.js';
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
|
import { jsCodeGen, jsonCodeGen, pythonCodeGen } from './codegen';
|
||||||
|
|
||||||
let template = ejs.compile(templateContent);
|
let template = ejs.compile(templateContent);
|
||||||
|
|
||||||
@@ -332,22 +336,6 @@ function createShareableLink() {
|
|||||||
$('shareLink').value = result.url;
|
$('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() {
|
function resizePreviewFrame() {
|
||||||
const iframe = $('previewFrame');
|
const iframe = $('previewFrame');
|
||||||
const height = iframe.contentWindow.document.body.scrollHeight + 2;
|
const height = iframe.contentWindow.document.body.scrollHeight + 2;
|
||||||
@@ -387,24 +375,20 @@ $('presetSelect').addEventListener('change', (e) => {
|
|||||||
// Render / Open button handlers
|
// Render / Open button handlers
|
||||||
// $('btnRender').addEventListener('click', e => { e.preventDefault(); render(); });
|
// $('btnRender').addEventListener('click', e => { e.preventDefault(); render(); });
|
||||||
$('btnOpen').addEventListener('click', (e) => {
|
$('btnOpen').addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
|
||||||
openInNewTab();
|
openInNewTab();
|
||||||
});
|
});
|
||||||
$('btnShare').addEventListener('click', (e) => {
|
$('btnShare').addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
|
||||||
createShareableLink();
|
createShareableLink();
|
||||||
});
|
});
|
||||||
$('btnExport').addEventListener('click', (e) => {
|
const shareLinkPopover = new Popover($('btnCopyLink'));
|
||||||
e.preventDefault();
|
|
||||||
exportJSON();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('btnCopyLink').addEventListener('click', () => {
|
$('btnCopyLink').addEventListener('click', () => {
|
||||||
const field = $('shareLink');
|
const field = $('shareLink');
|
||||||
field.select();
|
field.select();
|
||||||
field.setSelectionRange(0, field.value.length);
|
|
||||||
navigator.clipboard.writeText(field.value).then(() => {
|
navigator.clipboard.writeText(field.value).then(() => {
|
||||||
// No notification required unless you want one
|
shareLinkPopover.show();
|
||||||
|
setTimeout(() => {
|
||||||
|
shareLinkPopover.hide();
|
||||||
|
}, 2000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -413,8 +397,7 @@ const inputs = document.querySelectorAll('#editorForm input, #editorForm textare
|
|||||||
inputs.forEach((inp) => {
|
inputs.forEach((inp) => {
|
||||||
inp.addEventListener('input', () => render());
|
inp.addEventListener('input', () => render());
|
||||||
// for radio change events (error_source)
|
// for radio change events (error_source)
|
||||||
if (inp.type === 'radio')
|
if (inp.type === 'radio') inp.addEventListener('change', () => render());
|
||||||
inp.addEventListener('change', () => render());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Automatically update frame height
|
// Automatically update frame height
|
||||||
@@ -423,3 +406,91 @@ const iframe = $('previewFrame');
|
|||||||
observer.observe(iframe.contentWindow.document.body);
|
observer.observe(iframe.contentWindow.document.body);
|
||||||
// resizePreviewFrame()
|
// resizePreviewFrame()
|
||||||
setInterval(resizePreviewFrame, 1000); // TODO...
|
setInterval(resizePreviewFrame, 1000); // TODO...
|
||||||
|
|
||||||
|
function saveFile(content, saveName) {
|
||||||
|
const file = new File([content], saveName, {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
let saveAsType;
|
||||||
|
let saveAsContent;
|
||||||
|
|
||||||
|
function updateSaveAsDialog(e) {
|
||||||
|
if (e) {
|
||||||
|
const target = e.target;
|
||||||
|
saveAsType = target.dataset.type;
|
||||||
|
} else {
|
||||||
|
saveAsType = 'json';
|
||||||
|
}
|
||||||
|
let codegen;
|
||||||
|
switch (saveAsType) {
|
||||||
|
case 'js':
|
||||||
|
codegen = jsCodeGen;
|
||||||
|
break;
|
||||||
|
case 'json':
|
||||||
|
codegen = jsonCodeGen;
|
||||||
|
break;
|
||||||
|
case 'python':
|
||||||
|
codegen = pythonCodeGen;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const params = { ...lastCfg };
|
||||||
|
delete params.time;
|
||||||
|
$('saveAsDialogCode').innerHTML = saveAsContent = codegen.generate(params);
|
||||||
|
$('saveAsDialogCode').scrollTop = 0;
|
||||||
|
|
||||||
|
document.querySelectorAll('#saveAsDialogTypes button').forEach((element) => {
|
||||||
|
const isCurrent = element.dataset.type == saveAsType;
|
||||||
|
if (isCurrent) {
|
||||||
|
element.classList.add('active');
|
||||||
|
} else {
|
||||||
|
element.classList.remove('active');
|
||||||
|
}
|
||||||
|
element.ariaCurrent = isCurrent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$('saveAsDialog').addEventListener('show.bs.modal', (e) => {
|
||||||
|
updateSaveAsDialog();
|
||||||
|
});
|
||||||
|
document.querySelectorAll('#saveAsDialogTypes button').forEach((element) => {
|
||||||
|
element.addEventListener('click', updateSaveAsDialog);
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveAsDialogCopyPopover = new Popover($('saveAsDialogCopyBtn'));
|
||||||
|
$('saveAsDialogCopyBtn').addEventListener('click', (e) => {
|
||||||
|
const field = $('saveAsDialogCode');
|
||||||
|
field.select();
|
||||||
|
// field.setSelectionRange(0, field.value.length);
|
||||||
|
navigator.clipboard.writeText(field.value).then(() => {
|
||||||
|
saveAsDialogCopyPopover.show();
|
||||||
|
setTimeout(() => {
|
||||||
|
saveAsDialogCopyPopover.hide();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('saveAsDialogSaveBtn').addEventListener('click', (e) => {
|
||||||
|
let saveName = '';
|
||||||
|
switch (saveAsType) {
|
||||||
|
case 'js':
|
||||||
|
saveName = 'cf-error-page-example.js';
|
||||||
|
break;
|
||||||
|
case 'json':
|
||||||
|
saveName = 'cf-error-page-params.json';
|
||||||
|
break;
|
||||||
|
case 'python':
|
||||||
|
saveName = 'cf_error_page_example.py';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
saveFile(saveAsContent, saveName);
|
||||||
|
});
|
||||||
|
|||||||
@@ -166,6 +166,11 @@
|
|||||||
"@jridgewell/resolve-uri" "^3.1.0"
|
"@jridgewell/resolve-uri" "^3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||||
|
|
||||||
|
"@popperjs/core@^2.9.2":
|
||||||
|
version "2.11.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||||
|
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi@4.53.3":
|
"@rollup/rollup-android-arm-eabi@4.53.3":
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz#7e478b66180c5330429dd161bf84dad66b59c8eb"
|
||||||
@@ -276,11 +281,28 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz#38ae84f4c04226c1d56a3b17296ef1e0460ecdfe"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz#38ae84f4c04226c1d56a3b17296ef1e0460ecdfe"
|
||||||
integrity sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==
|
integrity sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==
|
||||||
|
|
||||||
|
"@types/bootstrap@^5.2.10":
|
||||||
|
version "5.2.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.10.tgz#58506463bccc6602bc051487ad8d3a6458f94c6c"
|
||||||
|
integrity sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==
|
||||||
|
dependencies:
|
||||||
|
"@popperjs/core" "^2.9.2"
|
||||||
|
|
||||||
|
"@types/ejs@^3.1.5":
|
||||||
|
version "3.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.5.tgz#49d738257cc73bafe45c13cb8ff240683b4d5117"
|
||||||
|
integrity sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==
|
||||||
|
|
||||||
"@types/estree@1.0.8":
|
"@types/estree@1.0.8":
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
|
||||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||||
|
|
||||||
|
"@types/html-minifier-terser@^7.0.2":
|
||||||
|
version "7.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-7.0.2.tgz#2290fa13e6e49b6cc0ab0afa2d6cf6a66feedb48"
|
||||||
|
integrity sha512-mm2HqV22l8lFQh4r2oSsOEVea+m0qqxEmwpc9kC1p/XzmjLWrReR9D/GRs8Pex2NX/imyEH9c5IU/7tMBQCHOA==
|
||||||
|
|
||||||
"@types/node@^24.10.2":
|
"@types/node@^24.10.2":
|
||||||
version "24.10.2"
|
version "24.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.2.tgz#82a57476a19647d8f2c7750d0924788245e39b26"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.2.tgz#82a57476a19647d8f2c7750d0924788245e39b26"
|
||||||
|
|||||||
Reference in New Issue
Block a user