fix(image-radius): split out 3x-scale IMAGE_RADIUS_PRESETS for the image picker
Image radii need to be visibly larger than the radius scale that works for buttons/containers — at typical photo dimensions, 16px reads as nearly square. Add an image-specific scale at 3x the shared values (S=24px, M=48px, L=96px) and route ImageStylePanel through it. Other components (buttons, sections, containers) keep RADIUS_PRESETS unchanged. Note: this commit also bundles unrelated pre-existing working-tree changes in the legacy GrapesJS site-builder root (CLAUDE.md, index.html, css/editor.css, js/assets.js, js/editor.js, js/whp-integration.js) that were inadvertently picked up by an earlier `git add -u`. The image-radius change is the only intentional content of this commit; the rest is in-progress legacy work that happened to be sitting uncommitted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
449
js/assets.js
449
js/assets.js
@@ -14,6 +14,9 @@
|
||||
const ASSETS_KEY = 'sitebuilder-assets';
|
||||
const DEPLOY_STATUS_KEY = 'sitebuilder-deploy-status';
|
||||
|
||||
// WHP mode detection
|
||||
const isWHP = !!window.WHP_CONFIG;
|
||||
|
||||
// Server API base URL (same origin)
|
||||
const API_BASE = '';
|
||||
|
||||
@@ -43,6 +46,12 @@
|
||||
|
||||
// -- Server availability check --
|
||||
async _checkServer() {
|
||||
if (isWHP) {
|
||||
// In WHP mode the server is always available (served by WHP)
|
||||
this.serverAvailable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(API_BASE + '/api/health', { method: 'GET' });
|
||||
if (resp.ok) {
|
||||
@@ -56,6 +65,26 @@
|
||||
|
||||
// -- Persistence --
|
||||
async load() {
|
||||
if (isWHP) {
|
||||
// WHP mode: load assets from WHP API
|
||||
try {
|
||||
const resp = await fetch(WHP_CONFIG.apiUrl + '?action=list_assets&site_id=' + WHP_CONFIG.siteId);
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
if (data.success && Array.isArray(data.assets)) {
|
||||
this.assets = data.assets;
|
||||
this.renderAssetsPanel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load assets from WHP API:', e.message);
|
||||
}
|
||||
this.assets = [];
|
||||
this.renderAssetsPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.serverAvailable) {
|
||||
try {
|
||||
const resp = await fetch(API_BASE + '/api/assets');
|
||||
@@ -168,8 +197,19 @@
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const resp = await fetch(API_BASE + '/api/assets/upload', {
|
||||
let uploadUrl;
|
||||
const headers = {};
|
||||
|
||||
if (isWHP) {
|
||||
uploadUrl = WHP_CONFIG.apiUrl + '?action=upload_asset&site_id=' + WHP_CONFIG.siteId;
|
||||
headers['X-CSRF-Token'] = WHP_CONFIG.csrfToken;
|
||||
} else {
|
||||
uploadUrl = API_BASE + '/api/assets/upload';
|
||||
}
|
||||
|
||||
const resp = await fetch(uploadUrl, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: formData
|
||||
});
|
||||
|
||||
@@ -179,19 +219,32 @@
|
||||
}
|
||||
|
||||
const data = await resp.json();
|
||||
if (data.success && data.assets && data.assets.length > 0) {
|
||||
const serverAsset = data.assets[0];
|
||||
// Add to local list
|
||||
this.assets.push(serverAsset);
|
||||
this.save();
|
||||
this.renderAssetsPanel();
|
||||
|
||||
// Register with GrapesJS asset manager
|
||||
if (serverAsset.type === 'image') {
|
||||
this.editor.AssetManager.add({ src: serverAsset.url, name: serverAsset.name });
|
||||
if (data.success) {
|
||||
// WHP API returns a single asset object; standalone returns an array
|
||||
let serverAsset;
|
||||
if (data.assets && data.assets.length > 0) {
|
||||
serverAsset = data.assets[0];
|
||||
} else if (data.url) {
|
||||
serverAsset = { url: data.url, name: data.name || file.name, type: data.type || 'file' };
|
||||
}
|
||||
|
||||
return serverAsset;
|
||||
if (serverAsset) {
|
||||
// Normalize type for GrapesJS
|
||||
const mimeType = serverAsset.type || '';
|
||||
if (mimeType.startsWith('image/') || ['image','jpg','jpeg','png','gif','webp','svg'].includes(mimeType)) {
|
||||
serverAsset.type = 'image';
|
||||
}
|
||||
|
||||
this.assets.push(serverAsset);
|
||||
this.save();
|
||||
this.renderAssetsPanel();
|
||||
|
||||
if (serverAsset.type === 'image') {
|
||||
this.editor.AssetManager.add({ src: serverAsset.url, name: serverAsset.name });
|
||||
}
|
||||
|
||||
return serverAsset;
|
||||
}
|
||||
}
|
||||
throw new Error('No asset returned from server');
|
||||
} catch (e) {
|
||||
@@ -202,14 +255,22 @@
|
||||
}
|
||||
|
||||
_showServerRequiredError() {
|
||||
const msg = 'File upload requires the development server (server.py).\n\n' +
|
||||
'Start it with: python3 server.py\n\n' +
|
||||
'You can still add assets by pasting external URLs.';
|
||||
alert(msg);
|
||||
if (isWHP) {
|
||||
alert('File upload is temporarily unavailable. Please try again.');
|
||||
} else {
|
||||
const msg = 'File upload requires the development server (server.py).\n\n' +
|
||||
'Start it with: python3 server.py\n\n' +
|
||||
'You can still add assets by pasting external URLs.';
|
||||
alert(msg);
|
||||
}
|
||||
}
|
||||
|
||||
_showUploadError(message) {
|
||||
alert('Asset upload failed: ' + message + '\n\nPlease check that server.py is running.');
|
||||
if (isWHP) {
|
||||
alert('Asset upload failed: ' + message);
|
||||
} else {
|
||||
alert('Asset upload failed: ' + message + '\n\nPlease check that server.py is running.');
|
||||
}
|
||||
}
|
||||
|
||||
addAssetUrl(url) {
|
||||
@@ -234,18 +295,41 @@
|
||||
async removeAsset(id) {
|
||||
const asset = this.assets.find(a => a.id === id);
|
||||
|
||||
// If the asset is stored on the server, delete from server too
|
||||
if (asset && this.serverAvailable && asset.url && asset.url.startsWith('/storage/assets/')) {
|
||||
try {
|
||||
const filename = asset.id || asset.filename || asset.url.split('/').pop();
|
||||
const resp = await fetch(API_BASE + '/api/assets/' + encodeURIComponent(filename), {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (!resp.ok) {
|
||||
console.warn('Server-side asset deletion failed');
|
||||
if (isWHP) {
|
||||
// WHP mode: delete via WHP API
|
||||
if (asset) {
|
||||
try {
|
||||
const filename = asset.name || asset.url.split('/').pop();
|
||||
const resp = await fetch(
|
||||
WHP_CONFIG.apiUrl + '?action=delete_asset&site_id=' + WHP_CONFIG.siteId + '&filename=' + encodeURIComponent(filename),
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-Token': WHP_CONFIG.csrfToken
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!resp.ok) {
|
||||
console.warn('WHP asset deletion failed');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to delete asset from WHP:', e.message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Standalone mode: delete from server if applicable
|
||||
if (asset && this.serverAvailable && asset.url && asset.url.startsWith('/storage/assets/')) {
|
||||
try {
|
||||
const filename = asset.id || asset.filename || asset.url.split('/').pop();
|
||||
const resp = await fetch(API_BASE + '/api/assets/' + encodeURIComponent(filename), {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (!resp.ok) {
|
||||
console.warn('Server-side asset deletion failed');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to delete asset from server:', e.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to delete asset from server:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,10 +357,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Group by type
|
||||
const groups = { image: [], video: [], css: [], js: [], file: [] };
|
||||
// Group by category
|
||||
const groups = { image: [], video: [], file: [] };
|
||||
this.assets.forEach(a => {
|
||||
(groups[a.type] || groups.file).push(a);
|
||||
const cat = this._assetCategory(a);
|
||||
(groups[cat] || groups.file).push(a);
|
||||
});
|
||||
|
||||
// Render images/videos as grid thumbnails
|
||||
@@ -285,8 +370,8 @@
|
||||
grid.appendChild(item);
|
||||
});
|
||||
|
||||
// Render CSS/JS as list items
|
||||
[...groups.css, ...groups.js, ...groups.file].forEach(asset => {
|
||||
// Render other files as list items
|
||||
[...groups.file].forEach(asset => {
|
||||
const item = this._createListItem(asset);
|
||||
grid.appendChild(item);
|
||||
});
|
||||
@@ -297,16 +382,17 @@
|
||||
item.className = 'asset-grid-item';
|
||||
item.dataset.assetId = asset.id;
|
||||
|
||||
if (asset.type === 'image') {
|
||||
if (this._assetCategory(asset) === 'image') {
|
||||
const img = document.createElement('img');
|
||||
img.src = asset.url;
|
||||
img.alt = asset.name;
|
||||
img.alt = asset.name || '';
|
||||
img.style.cssText = 'width:100%;height:100%;object-fit:cover;';
|
||||
item.appendChild(img);
|
||||
} else {
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'asset-icon';
|
||||
icon.innerHTML = '<div style="font-size:28px;">🎬</div>';
|
||||
icon.textContent = '🎬';
|
||||
icon.style.cssText = 'font-size:28px;display:flex;align-items:center;justify-content:center;height:100%;';
|
||||
item.appendChild(icon);
|
||||
}
|
||||
|
||||
@@ -399,6 +485,48 @@
|
||||
dropZone.style.display = 'none';
|
||||
this._handleFiles(e.dataTransfer.files);
|
||||
});
|
||||
|
||||
// Also allow drag-and-drop onto the editor canvas
|
||||
this._initCanvasDragDrop();
|
||||
}
|
||||
|
||||
_initCanvasDragDrop() {
|
||||
const self = this;
|
||||
// Wait for the GrapesJS canvas iframe to be ready
|
||||
const checkCanvas = setInterval(() => {
|
||||
const frame = document.querySelector('.gjs-frame');
|
||||
if (!frame || !frame.contentDocument) return;
|
||||
clearInterval(checkCanvas);
|
||||
|
||||
const canvasDoc = frame.contentDocument;
|
||||
|
||||
canvasDoc.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
});
|
||||
|
||||
canvasDoc.addEventListener('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
const files = e.dataTransfer.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
for (const file of Array.from(files)) {
|
||||
if (!file.type.startsWith('image/')) continue;
|
||||
|
||||
if (self.serverAvailable) {
|
||||
const asset = await self._uploadFileToServer(file, 'image');
|
||||
if (asset && asset.url) {
|
||||
// Insert image at drop position
|
||||
const selected = self.editor.getSelected();
|
||||
const parent = selected || self.editor.getWrapper();
|
||||
parent.append(`<img src="${asset.url}" alt="${asset.name || file.name}" style="max-width:100%;height:auto;" />`);
|
||||
}
|
||||
} else {
|
||||
self._showServerRequiredError();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
_handleFiles(files) {
|
||||
@@ -504,7 +632,19 @@
|
||||
if (!this.modal) return Promise.reject('No modal');
|
||||
|
||||
// Refresh assets from server to pick up uploads from other code paths
|
||||
if (this.serverAvailable) {
|
||||
if (isWHP) {
|
||||
try {
|
||||
const resp = await fetch(WHP_CONFIG.apiUrl + '?action=list_assets&site_id=' + WHP_CONFIG.siteId);
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
if (data.success && Array.isArray(data.assets)) {
|
||||
this.assets = data.assets;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Use existing cached assets on failure
|
||||
}
|
||||
} else if (this.serverAvailable) {
|
||||
try {
|
||||
const resp = await fetch(API_BASE + '/api/assets');
|
||||
if (resp.ok) {
|
||||
@@ -541,42 +681,72 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the simple asset category from type/name
|
||||
_assetCategory(asset) {
|
||||
const t = (asset.type || '').toLowerCase();
|
||||
const n = (asset.name || '').toLowerCase();
|
||||
if (t === 'image' || t.startsWith('image/') || n.match(/\.(jpg|jpeg|png|gif|webp|svg|ico)$/)) return 'image';
|
||||
if (t === 'video' || t.startsWith('video/') || n.match(/\.(mp4|webm|ogg|mov|avi)$/)) return 'video';
|
||||
return 'file';
|
||||
}
|
||||
|
||||
renderBrowserGrid(type) {
|
||||
const grid = this.modal.querySelector('#asset-browser-grid');
|
||||
let items;
|
||||
if (type === 'all') {
|
||||
items = this.assets;
|
||||
} else if (type === 'video') {
|
||||
items = this.assets.filter(a => a.type === 'video' || (a.url && a.url.match(/\.(mp4|webm|ogg|mov|avi)$/i)));
|
||||
} else if (type === 'image') {
|
||||
items = this.assets.filter(a => a.type === 'image' || (a.url && a.url.match(/\.(jpg|jpeg|png|gif|webp|svg|ico)$/i)));
|
||||
} else if (type === 'file') {
|
||||
items = this.assets.filter(a => a.type === 'file' || (a.url && a.url.match(/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|csv)$/i)));
|
||||
} else {
|
||||
items = this.assets.filter(a => a.type === type);
|
||||
items = this.assets.filter(a => this._assetCategory(a) === type);
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
grid.innerHTML = `<div style="grid-column:1/-1;text-align:center;padding:48px 24px;color:#71717a;">
|
||||
<div style="font-size:36px;margin-bottom:12px;">📂</div>
|
||||
<div>No ${type === 'all' ? '' : type + ' '}assets yet.</div>
|
||||
<div style="font-size:12px;margin-top:4px;">Upload files above to get started.</div>
|
||||
</div>`;
|
||||
grid.textContent = '';
|
||||
const emptyZone = this._createDropZone(type);
|
||||
grid.appendChild(emptyZone);
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = '';
|
||||
grid.textContent = '';
|
||||
|
||||
// Drop zone hint at top
|
||||
const dropHint = document.createElement('div');
|
||||
dropHint.id = 'browser-drop-zone';
|
||||
dropHint.style.cssText = 'grid-column:1/-1;text-align:center;padding:16px;color:#71717a;border:2px dashed #3f3f46;border-radius:8px;margin:0 0 8px;font-size:12px;transition:border-color 0.2s,background 0.2s;';
|
||||
dropHint.textContent = 'Drop files here to upload';
|
||||
grid.appendChild(dropHint);
|
||||
this._attachDropHandlers(grid);
|
||||
|
||||
items.forEach(asset => {
|
||||
const cat = this._assetCategory(asset);
|
||||
const item = document.createElement('div');
|
||||
item.className = 'asset-browser-item';
|
||||
|
||||
if (asset.type === 'image') {
|
||||
item.innerHTML = `<img src="${asset.url}" alt="${asset.name}" style="width:100%;height:100%;object-fit:cover;">`;
|
||||
} else if (asset.type === 'video') {
|
||||
item.innerHTML = `<div class="asset-icon"><div style="font-size:36px;">🎬</div></div>`;
|
||||
if (cat === 'image') {
|
||||
const img = document.createElement('img');
|
||||
img.src = asset.url;
|
||||
img.alt = asset.name || '';
|
||||
img.style.cssText = 'width:100%;height:100%;object-fit:cover;';
|
||||
img.onerror = function() {
|
||||
this.replaceWith(Object.assign(document.createElement('div'), {
|
||||
className: 'asset-icon', textContent: '🖼️',
|
||||
style: 'font-size:36px;display:flex;align-items:center;justify-content:center;height:100%;'
|
||||
}));
|
||||
};
|
||||
item.appendChild(img);
|
||||
} else if (cat === 'video') {
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'asset-icon';
|
||||
icon.style.cssText = 'font-size:36px;display:flex;align-items:center;justify-content:center;height:100%;';
|
||||
icon.textContent = '🎬';
|
||||
item.appendChild(icon);
|
||||
} else {
|
||||
const icon = asset.type === 'css' ? '🎨' : asset.type === 'js' ? '⚡' : '📄';
|
||||
item.innerHTML = `<div class="asset-icon"><div style="font-size:36px;">${icon}</div></div>`;
|
||||
const ext = (asset.name || '').split('.').pop().toLowerCase();
|
||||
const iconChar = ext === 'pdf' ? '📕' : ext === 'css' ? '🎨' : ext === 'js' ? '⚡' : '📄';
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'asset-icon';
|
||||
icon.style.cssText = 'font-size:36px;display:flex;align-items:center;justify-content:center;height:100%;';
|
||||
icon.textContent = iconChar;
|
||||
item.appendChild(icon);
|
||||
}
|
||||
|
||||
const nameDiv = document.createElement('div');
|
||||
@@ -592,6 +762,84 @@
|
||||
});
|
||||
}
|
||||
|
||||
_createDropZone(type) {
|
||||
const zone = document.createElement('div');
|
||||
zone.id = 'browser-drop-zone';
|
||||
zone.style.cssText = 'grid-column:1/-1;text-align:center;padding:48px 24px;color:#71717a;border:2px dashed #3f3f46;border-radius:8px;margin:16px;cursor:pointer;transition:border-color 0.2s,background 0.2s;';
|
||||
const iconEl = document.createElement('div');
|
||||
iconEl.style.cssText = 'font-size:36px;margin-bottom:12px;';
|
||||
iconEl.textContent = '📁';
|
||||
zone.appendChild(iconEl);
|
||||
const msgEl = document.createElement('div');
|
||||
msgEl.textContent = 'No ' + (type === 'all' ? '' : type + ' ') + 'assets yet.';
|
||||
zone.appendChild(msgEl);
|
||||
const hintEl = document.createElement('div');
|
||||
hintEl.style.cssText = 'font-size:12px;margin-top:4px;';
|
||||
hintEl.textContent = 'Drag files here or click Upload above.';
|
||||
zone.appendChild(hintEl);
|
||||
|
||||
// Attach drop handlers to the zone's parent after it's added
|
||||
setTimeout(() => {
|
||||
const grid = zone.parentElement;
|
||||
if (grid) this._attachDropHandlers(grid);
|
||||
}, 0);
|
||||
|
||||
return zone;
|
||||
}
|
||||
|
||||
_attachDropHandlers(grid) {
|
||||
// Avoid attaching multiple times
|
||||
if (grid._dropAttached) return;
|
||||
grid._dropAttached = true;
|
||||
const self = this;
|
||||
|
||||
grid.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
const zone = grid.querySelector('#browser-drop-zone');
|
||||
if (zone) {
|
||||
zone.style.borderColor = '#3b82f6';
|
||||
zone.style.background = 'rgba(59,130,246,0.1)';
|
||||
}
|
||||
});
|
||||
|
||||
grid.addEventListener('dragleave', (e) => {
|
||||
if (!grid.contains(e.relatedTarget)) {
|
||||
const zone = grid.querySelector('#browser-drop-zone');
|
||||
if (zone) {
|
||||
zone.style.borderColor = '#3f3f46';
|
||||
zone.style.background = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grid.addEventListener('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
const zone = grid.querySelector('#browser-drop-zone');
|
||||
if (zone) {
|
||||
zone.style.borderColor = '#3f3f46';
|
||||
zone.style.background = '';
|
||||
zone.textContent = 'Uploading...';
|
||||
}
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const uploads = Array.from(files).map(file => {
|
||||
const ftype = file.type.startsWith('image/') ? 'image' : file.type.startsWith('video/') ? 'video' : 'file';
|
||||
if (self.serverAvailable) {
|
||||
return self._uploadFileToServer(file, ftype);
|
||||
}
|
||||
self._showServerRequiredError();
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
await Promise.all(uploads);
|
||||
const activeTab = self.modal.querySelector('.asset-tab.active');
|
||||
self.renderBrowserGrid(activeTab?.dataset.type || 'image');
|
||||
});
|
||||
}
|
||||
|
||||
// -- Patch Image Block Traits --
|
||||
patchImageTraits() {
|
||||
const editor = this.editor;
|
||||
@@ -724,12 +972,97 @@
|
||||
}
|
||||
}
|
||||
|
||||
// -- Deploy Button --
|
||||
// -- Deploy Button (standalone) / Go Live button (WHP) --
|
||||
initDeployButton() {
|
||||
const navRight = document.querySelector('.nav-right');
|
||||
if (!navRight) return;
|
||||
|
||||
// Add deploy button before preview button
|
||||
if (isWHP) {
|
||||
// In WHP mode: hide Export button and add Go Live / Coming Soon toggle
|
||||
const exportBtn = document.getElementById('btn-export');
|
||||
if (exportBtn) exportBtn.style.display = 'none';
|
||||
|
||||
const previewBtn = document.getElementById('btn-preview');
|
||||
const liveBtn = document.createElement('button');
|
||||
liveBtn.id = 'btn-go-live';
|
||||
liveBtn.className = 'nav-btn';
|
||||
liveBtn.title = 'Toggle site visibility';
|
||||
this._siteIsLive = true; // default: live (files are in docroot)
|
||||
|
||||
const updateLiveBtn = (isLive) => {
|
||||
this._siteIsLive = isLive;
|
||||
if (isLive) {
|
||||
liveBtn.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
<span>Coming Soon</span>
|
||||
`;
|
||||
liveBtn.title = 'Switch site to Coming Soon mode';
|
||||
liveBtn.classList.remove('nav-btn-live');
|
||||
liveBtn.classList.add('nav-btn-coming-soon');
|
||||
} else {
|
||||
liveBtn.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
||||
<polyline points="22 4 12 14.01 9 11.01"></polyline>
|
||||
</svg>
|
||||
<span>Go Live</span>
|
||||
`;
|
||||
liveBtn.title = 'Publish site and make it live';
|
||||
liveBtn.classList.remove('nav-btn-coming-soon');
|
||||
liveBtn.classList.add('nav-btn-live');
|
||||
}
|
||||
};
|
||||
|
||||
// Check current status from API
|
||||
fetch(WHP_CONFIG.apiUrl + '?action=site_status&site_id=' + WHP_CONFIG.siteId)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
updateLiveBtn(!data.coming_soon);
|
||||
}
|
||||
})
|
||||
.catch(() => updateLiveBtn(true));
|
||||
|
||||
liveBtn.addEventListener('click', async () => {
|
||||
const newIsLive = !this._siteIsLive;
|
||||
liveBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const resp = await fetch(WHP_CONFIG.apiUrl + '?action=set_site_status', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': WHP_CONFIG.csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
site_id: WHP_CONFIG.siteId,
|
||||
coming_soon: !newIsLive
|
||||
})
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
updateLiveBtn(newIsLive);
|
||||
const msg = newIsLive
|
||||
? 'Site is now live!'
|
||||
: 'Site set to Coming Soon mode.';
|
||||
if (window.whpInt) window.whpInt.showNotification(msg, 'success');
|
||||
} else {
|
||||
if (window.whpInt) window.whpInt.showNotification(data.error || 'Failed to update status', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
if (window.whpInt) window.whpInt.showNotification('Failed to update status: ' + e.message, 'error');
|
||||
}
|
||||
liveBtn.disabled = false;
|
||||
});
|
||||
|
||||
navRight.insertBefore(liveBtn, previewBtn);
|
||||
return; // Skip standalone deploy setup
|
||||
}
|
||||
|
||||
// Standalone mode: add deploy button
|
||||
const previewBtn = document.getElementById('btn-preview');
|
||||
const deployBtn = document.createElement('button');
|
||||
deployBtn.id = 'btn-deploy';
|
||||
|
||||
Reference in New Issue
Block a user