fix: load Gridstack.js locally in HTML head - CDN URLs were 404

This commit is contained in:
root
2026-07-16 08:15:59 +00:00
parent 544ef4cfbd
commit 4e69e9b1b2
6 changed files with 48 additions and 31 deletions

View File

@@ -1,5 +1,7 @@
/* A4 Template Designer with Gridstack.js */ /* A4 Template Designer with Gridstack.js */
@import url('https://cdn.jsdelivr.net/npm/gridstack@11.1.3/gridstack.min.css');
/* Gridstack base styles (loaded locally) */
/* gridstack.min.css is loaded via <link> in the page */
/* Designer layout */ /* Designer layout */
.designer-layout { .designer-layout {

View File

@@ -80,25 +80,8 @@ function showDesigner(templateId = null) {
`; `;
showModal(body, 'A4 Template Designer', 'large'); showModal(body, 'A4 Template Designer', 'large');
// Load Gridstack CSS dynamically // Gridstack is loaded via <script> in index.html, just init
if (!document.getElementById('gridstack-css')) { initGridstack();
const link = document.createElement('link');
link.id = 'gridstack-css';
link.rel = 'stylesheet';
link.href = 'https://cdn.jsdelivr.net/npm/gridstack@11.1.3/gridstack.min.css';
document.head.appendChild(link);
}
// Load Gridstack JS dynamically
if (typeof GridStack === 'undefined') {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/gridstack@11.1.3/gridstack-all-jq.js';
script.onload = () => initGridstack();
document.head.appendChild(script);
} else {
initGridstack();
}
renderDesignerPalette(); renderDesignerPalette();
}); });
} }
@@ -156,7 +139,7 @@ function renderDesignerPalette() {
const el = document.getElementById('designer-palette-list'); const el = document.getElementById('designer-palette-list');
if (!el) return; if (!el) return;
el.innerHTML = Object.entries(BLOCK_TYPES).map(([type, info]) => ` el.innerHTML = Object.entries(BLOCK_TYPES).map(([type, info]) => `
<div class="palette-block" data-type="${type}" onclick="addBlockFromPalette('${type}')"> <div class="palette-block" draggable="true" data-type="${type}" onclick="addBlockFromPalette('${type}')">
<div class="icon" style="background:${info.color}">${info.icon}</div> <div class="icon" style="background:${info.color}">${info.icon}</div>
<div> <div>
<div class="label">${info.label}</div> <div class="label">${info.label}</div>
@@ -167,16 +150,27 @@ function renderDesignerPalette() {
} }
function setupPaletteDrag() { function setupPaletteDrag() {
// Gridstack native widget drop from sidebar // Palette items use click-to-add (onclick in renderDesignerPalette)
if (designerGrid) { // Native HTML5 drag-and-drop is also supported via the palette items' draggable attribute
designerGrid.removeDraggable('.palette-block'); document.querySelectorAll('.palette-block').forEach(el => {
document.querySelectorAll('.palette-block').forEach(el => { el.addEventListener('dragstart', (e) => {
designerGrid.draggable(el, { const type = el.dataset.type;
appendTo: '#a4-page', e.dataTransfer.setData('text/plain', type);
helper: 'clone', });
revert: 'invalid', });
handle: 'div',
}); // Make the canvas accept drops
const canvas = document.getElementById('a4-page');
if (canvas) {
canvas.addEventListener('dragover', (e) => {
e.preventDefault();
});
canvas.addEventListener('drop', (e) => {
e.preventDefault();
const type = e.dataTransfer.getData('text/plain');
if (type && BLOCK_TYPES[type]) {
addBlockFromPalette(type);
}
}); });
} }
} }

3
static/gridstack-all.js Normal file

File diff suppressed because one or more lines are too long

1
static/gridstack.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.grid-stack{position:relative}.grid-stack-rtl{direction:ltr}.grid-stack-rtl>.grid-stack-item{direction:rtl}.grid-stack-placeholder>.placeholder-content{background-color:rgba(0,0,0,.1);margin:0;position:absolute;width:auto;z-index:0!important}.grid-stack>.grid-stack-item{position:absolute;padding:0;top:0;width:var(--gs-column-width);height:var(--gs-cell-height)}.grid-stack>.grid-stack-item>.grid-stack-item-content{margin:0;position:absolute;width:auto;overflow-x:hidden;overflow-y:auto}.grid-stack>.grid-stack-item.size-to-content:not(.size-to-content-max)>.grid-stack-item-content{overflow-y:hidden}.grid-stack:not(.grid-stack-rtl)>.grid-stack-item{left:0}.grid-stack.grid-stack-rtl>.grid-stack-item{right:0}.grid-stack>.grid-stack-item>.grid-stack-item-content,.grid-stack>.grid-stack-placeholder>.placeholder-content{top:var(--gs-item-margin-top);right:var(--gs-item-margin-right);bottom:var(--gs-item-margin-bottom);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-handle{position:absolute;font-size:.1px;display:block;-ms-touch-action:none;touch-action:none}.grid-stack-item.ui-resizable-autohide>.ui-resizable-handle,.grid-stack-item.ui-resizable-disabled>.ui-resizable-handle{display:none}.grid-stack-item>.ui-resizable-ne,.grid-stack-item>.ui-resizable-nw,.grid-stack-item>.ui-resizable-se,.grid-stack-item>.ui-resizable-sw{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="%23666" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 20 20"><path d="m10 3 2 2H8l2-2v14l-2-2h4l-2 2"/></svg>');background-repeat:no-repeat;background-position:center}.grid-stack-item>.ui-resizable-ne{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-sw{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-nw{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-se{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-nw{cursor:nw-resize;width:20px;height:20px;top:var(--gs-item-margin-top);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-n{cursor:n-resize;height:10px;top:var(--gs-item-margin-top);left:25px;right:25px}.grid-stack-item>.ui-resizable-ne{cursor:ne-resize;width:20px;height:20px;top:var(--gs-item-margin-top);right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-e{cursor:e-resize;width:10px;top:15px;bottom:15px;right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-se{cursor:se-resize;width:20px;height:20px;bottom:var(--gs-item-margin-bottom);right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-s{cursor:s-resize;height:10px;left:25px;bottom:var(--gs-item-margin-bottom);right:25px}.grid-stack-item>.ui-resizable-sw{cursor:sw-resize;width:20px;height:20px;bottom:var(--gs-item-margin-bottom);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-w{cursor:w-resize;width:10px;top:15px;bottom:15px;left:var(--gs-item-margin-left)}.grid-stack-item.ui-draggable-dragging>.ui-resizable-handle{display:none!important}.grid-stack-item.ui-draggable-dragging{will-change:left,right,top}.grid-stack-item.ui-resizable-resizing{will-change:width,height}.ui-draggable-dragging,.ui-resizable-resizing{z-index:10000}.ui-draggable-dragging>.grid-stack-item-content,.ui-resizable-resizing>.grid-stack-item-content{box-shadow:1px 4px 6px rgba(0,0,0,.2);opacity:.8}.grid-stack-animate,.grid-stack-animate .grid-stack-item{transition:left .3s,right .3s,top .3s,height .3s,width .3s}.grid-stack-animate .grid-stack-item.grid-stack-placeholder,.grid-stack-animate .grid-stack-item.ui-draggable-dragging,.grid-stack-animate .grid-stack-item.ui-resizable-resizing{transition:left 0s,right 0s,top 0s,height 0s,width 0s}.grid-stack>.grid-stack-item[gs-y="0"]{top:0}.grid-stack:not(.grid-stack-rtl)>.grid-stack-item[gs-x="0"]{left:0}.grid-stack.grid-stack-rtl>.grid-stack-item[gs-x="0"]{right:0}

15
static/gridstack.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,8 @@
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="/static/builder.css"> <link rel="stylesheet" href="/static/builder.css">
<link rel="stylesheet" href="/static/designer.css"> <link rel="stylesheet" href="/static/designer.css">
<link rel="stylesheet" href="/static/gridstack.min.css">
<script src="/static/gridstack-all.js"></script>
</head> </head>
<body> <body>
<div class="app"> <div class="app">