Skip to content

Commit 7c032b7

Browse files
Add assets/cs-code-migrate.js
1 parent 8193d9e commit 7c032b7

1 file changed

Lines changed: 336 additions & 0 deletions

File tree

assets/cs-code-migrate.js

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
/**
2+
* CloudScale Code Block Migrator - Admin JS
3+
*/
4+
(function () {
5+
'use strict';
6+
7+
var scanBtn = document.getElementById('cs-scan-btn');
8+
var migrateAllBtn = document.getElementById('cs-migrate-all-btn');
9+
var statusEl = document.getElementById('cs-scan-status');
10+
var resultsArea = document.getElementById('cs-results-area');
11+
var modal = document.getElementById('cs-preview-modal');
12+
var modalTitle = document.getElementById('cs-modal-title');
13+
var modalBody = document.getElementById('cs-modal-body');
14+
var modalMigrateBtn = document.getElementById('cs-modal-migrate-btn');
15+
16+
// Bail if the migrate tab elements aren't present
17+
if (!scanBtn || !modal) return;
18+
19+
var scannedPosts = [];
20+
21+
// =========================================================================
22+
// Helpers
23+
// =========================================================================
24+
25+
function ajax(action, data, callback) {
26+
var fd = new FormData();
27+
fd.append('action', action);
28+
fd.append('nonce', csMigrate.nonce);
29+
if (data) {
30+
Object.keys(data).forEach(function (k) {
31+
fd.append(k, data[k]);
32+
});
33+
}
34+
fetch(csMigrate.ajaxUrl, { method: 'POST', body: fd })
35+
.then(function (r) { return r.json(); })
36+
.then(function (resp) {
37+
if (resp.success) {
38+
callback(null, resp.data);
39+
} else {
40+
callback(resp.data || 'Unknown error');
41+
}
42+
})
43+
.catch(function (err) {
44+
callback(err.message || 'Network error');
45+
});
46+
}
47+
48+
function setStatus(msg, type) {
49+
statusEl.textContent = msg;
50+
statusEl.className = 'cs-status' + (type ? ' ' + type : '');
51+
}
52+
53+
function escHtml(str) {
54+
var div = document.createElement('div');
55+
div.textContent = str;
56+
return div.innerHTML;
57+
}
58+
59+
// =========================================================================
60+
// Scan
61+
// =========================================================================
62+
63+
function doScan() {
64+
scanBtn.disabled = true;
65+
setStatus('Scanning...', '');
66+
resultsArea.innerHTML = '<p class="cs-loading"><span class="cs-spinner"></span> Scanning all posts for legacy code blocks...</p>';
67+
68+
ajax('cs_migrate_scan', {}, function (err, data) {
69+
scanBtn.disabled = false;
70+
71+
if (err) {
72+
setStatus('Scan failed: ' + err, 'error');
73+
return;
74+
}
75+
76+
scannedPosts = data.posts;
77+
78+
if (data.total_posts === 0) {
79+
setStatus('No legacy code blocks found.', 'success');
80+
resultsArea.innerHTML = '<p class="cs-migrate-hint">No posts with legacy code blocks were found. Everything is already migrated.</p>';
81+
migrateAllBtn.disabled = true;
82+
return;
83+
}
84+
85+
setStatus('Found ' + data.total_blocks + ' block(s) across ' + data.total_posts + ' post(s).', 'success');
86+
migrateAllBtn.disabled = false;
87+
renderResults(data);
88+
});
89+
}
90+
91+
function renderResults(data) {
92+
var html = '';
93+
94+
// Summary
95+
html += '<div class="cs-migrate-summary">';
96+
html += '<div class="cs-stat"><strong>' + data.total_posts + '</strong>Posts with legacy blocks</div>';
97+
html += '<div class="cs-stat"><strong>' + data.total_blocks + '</strong>Total code blocks to migrate</div>';
98+
html += '</div>';
99+
100+
// Table
101+
html += '<table class="cs-migrate-table">';
102+
html += '<thead><tr>';
103+
html += '<th>Post</th>';
104+
html += '<th style="width:90px;text-align:center;">Blocks</th>';
105+
html += '<th style="width:80px;">Status</th>';
106+
html += '<th style="width:200px;">Actions</th>';
107+
html += '</tr></thead>';
108+
html += '<tbody>';
109+
110+
data.posts.forEach(function (post) {
111+
html += '<tr id="cs-row-' + post.id + '">';
112+
html += '<td>';
113+
html += '<div class="cs-post-title"><a href="' + escHtml(post.view_url) + '" target="_blank">' + escHtml(post.title) + '</a></div>';
114+
html += '<div class="cs-post-meta">' + escHtml(post.date) + ' &middot; ' + escHtml(post.status) + ' &middot; ID: ' + post.id + '</div>';
115+
html += '</td>';
116+
html += '<td style="text-align:center;"><span class="cs-block-count">' + post.block_count + '</span></td>';
117+
html += '<td class="cs-status-cell">Pending</td>';
118+
html += '<td class="cs-actions">';
119+
html += '<button class="button button-small cs-preview-btn" data-post-id="' + post.id + '">Preview</button> ';
120+
html += '<button class="button button-primary button-small cs-single-migrate-btn" data-post-id="' + post.id + '">Migrate</button>';
121+
html += '</td>';
122+
html += '</tr>';
123+
});
124+
125+
html += '</tbody></table>';
126+
127+
resultsArea.innerHTML = html;
128+
129+
// Bind preview buttons
130+
resultsArea.querySelectorAll('.cs-preview-btn').forEach(function (btn) {
131+
btn.addEventListener('click', function () {
132+
openPreview(parseInt(this.getAttribute('data-post-id')));
133+
});
134+
});
135+
136+
// Bind single migrate buttons
137+
resultsArea.querySelectorAll('.cs-single-migrate-btn').forEach(function (btn) {
138+
btn.addEventListener('click', function () {
139+
migrateSingle(parseInt(this.getAttribute('data-post-id')), this);
140+
});
141+
});
142+
}
143+
144+
// =========================================================================
145+
// Preview Modal
146+
// =========================================================================
147+
148+
function openPreview(postId) {
149+
modal.style.display = 'flex';
150+
modalTitle.textContent = 'Loading preview...';
151+
modalBody.innerHTML = '<p class="cs-loading"><span class="cs-spinner"></span> Loading block preview...</p>';
152+
modalMigrateBtn.setAttribute('data-post-id', postId);
153+
154+
ajax('cs_migrate_preview', { post_id: postId }, function (err, data) {
155+
if (err) {
156+
modalBody.innerHTML = '<p style="color:#d63638;">Error: ' + escHtml(err) + '</p>';
157+
return;
158+
}
159+
160+
modalTitle.textContent = data.title + ' (' + data.block_count + ' block' + (data.block_count !== 1 ? 's' : '') + ')';
161+
162+
var html = '';
163+
data.blocks.forEach(function (block) {
164+
html += '<div class="cs-preview-block">';
165+
html += '<div class="cs-preview-block-header">';
166+
html += '<span class="cs-block-num">' + block.index + '</span>';
167+
html += '<span class="cs-block-lang">' + escHtml(block.language) + '</span>';
168+
html += '<span class="cs-block-lang" style="background:#f3e8ff;color:#7c3aed;">' + escHtml(block.type || 'wp:code') + '</span>';
169+
html += '<span class="cs-block-firstline">' + escHtml(block.first_line) + '</span>';
170+
html += '</div>';
171+
html += '<div class="cs-preview-diff">';
172+
html += '<div class="cs-preview-side cs-before">';
173+
html += '<div class="cs-preview-side-label">Before (wp:code)</div>';
174+
html += '<pre>' + block.original + '</pre>';
175+
html += '</div>';
176+
html += '<div class="cs-preview-side cs-after">';
177+
html += '<div class="cs-preview-side-label">After (cloudscale/code-block)</div>';
178+
html += '<pre>' + block.converted + '</pre>';
179+
html += '</div>';
180+
html += '</div>';
181+
html += '</div>';
182+
});
183+
184+
modalBody.innerHTML = html;
185+
});
186+
}
187+
188+
function closeModal() {
189+
modal.style.display = 'none';
190+
}
191+
192+
// Modal close handlers
193+
modal.querySelector('.cs-modal-backdrop').addEventListener('click', closeModal);
194+
modal.querySelectorAll('.cs-modal-close, .cs-modal-close-btn').forEach(function (el) {
195+
el.addEventListener('click', closeModal);
196+
});
197+
198+
// Migrate from modal
199+
modalMigrateBtn.addEventListener('click', function () {
200+
var postId = parseInt(this.getAttribute('data-post-id'));
201+
if (!postId) return;
202+
203+
this.disabled = true;
204+
this.textContent = 'Migrating...';
205+
206+
ajax('cs_migrate_single', { post_id: postId }, function (err, data) {
207+
modalMigrateBtn.disabled = false;
208+
modalMigrateBtn.innerHTML = '<span class="dashicons dashicons-yes-alt"></span> Migrate This Post';
209+
210+
if (err) {
211+
alert('Migration failed: ' + err);
212+
return;
213+
}
214+
215+
closeModal();
216+
markRowMigrated(postId, data.blocks_migrated);
217+
setStatus(data.message, 'success');
218+
});
219+
});
220+
221+
// =========================================================================
222+
// Single Migrate (from table button)
223+
// =========================================================================
224+
225+
function migrateSingle(postId, btn) {
226+
if (!confirm('Migrate all code blocks in this post to CloudScale format?')) return;
227+
228+
btn.disabled = true;
229+
btn.textContent = 'Migrating...';
230+
231+
ajax('cs_migrate_single', { post_id: postId }, function (err, data) {
232+
if (err) {
233+
btn.disabled = false;
234+
btn.textContent = 'Migrate';
235+
alert('Migration failed: ' + err);
236+
return;
237+
}
238+
239+
markRowMigrated(postId, data.blocks_migrated);
240+
setStatus(data.message, 'success');
241+
});
242+
}
243+
244+
function markRowMigrated(postId, blockCount) {
245+
var row = document.getElementById('cs-row-' + postId);
246+
if (!row) return;
247+
248+
row.classList.add('cs-migrated');
249+
250+
var statusCell = row.querySelector('.cs-status-cell');
251+
if (statusCell) {
252+
statusCell.innerHTML = '<span class="cs-migrated-badge"><span class="dashicons dashicons-yes"></span> Done</span>';
253+
}
254+
255+
var actionsCell = row.querySelector('.cs-actions');
256+
if (actionsCell) {
257+
actionsCell.innerHTML = '<a href="' + getViewUrl(postId) + '" target="_blank" class="button button-small">View Post</a>';
258+
}
259+
260+
// Check if all rows are migrated
261+
var pending = document.querySelectorAll('.cs-single-migrate-btn');
262+
if (pending.length === 0) {
263+
migrateAllBtn.disabled = true;
264+
setStatus('All posts migrated successfully!', 'success');
265+
}
266+
}
267+
268+
function getViewUrl(postId) {
269+
for (var i = 0; i < scannedPosts.length; i++) {
270+
if (scannedPosts[i].id === postId) return scannedPosts[i].view_url;
271+
}
272+
return '#';
273+
}
274+
275+
// =========================================================================
276+
// Migrate All
277+
// =========================================================================
278+
279+
function doMigrateAll() {
280+
var pending = document.querySelectorAll('.cs-single-migrate-btn');
281+
var count = pending.length;
282+
283+
if (count === 0) {
284+
alert('No remaining posts to migrate.');
285+
return;
286+
}
287+
288+
if (!confirm('This will migrate ' + count + ' remaining post(s) to CloudScale Code Blocks. Continue?')) {
289+
return;
290+
}
291+
292+
migrateAllBtn.disabled = true;
293+
migrateAllBtn.textContent = 'Migrating all...';
294+
setStatus('Migrating all remaining posts...', '');
295+
296+
ajax('cs_migrate_all', {}, function (err, data) {
297+
migrateAllBtn.innerHTML = '<span class="dashicons dashicons-update"></span> Migrate All Remaining';
298+
299+
if (err) {
300+
migrateAllBtn.disabled = false;
301+
setStatus('Batch migration failed: ' + err, 'error');
302+
return;
303+
}
304+
305+
setStatus(
306+
'Migrated ' + data.migrated_blocks + ' block(s) across ' + data.migrated_posts + ' post(s).',
307+
'success'
308+
);
309+
310+
// Mark all rows as done
311+
data.details.forEach(function (detail) {
312+
var match = detail.match(/^#(\d+)/);
313+
if (match) {
314+
markRowMigrated(parseInt(match[1]), 0);
315+
}
316+
});
317+
318+
migrateAllBtn.disabled = true;
319+
});
320+
}
321+
322+
// =========================================================================
323+
// Event Bindings
324+
// =========================================================================
325+
326+
scanBtn.addEventListener('click', doScan);
327+
migrateAllBtn.addEventListener('click', doMigrateAll);
328+
329+
// Escape key closes modal
330+
document.addEventListener('keydown', function (e) {
331+
if (e.key === 'Escape' && modal.style.display !== 'none') {
332+
closeModal();
333+
}
334+
});
335+
336+
})();

0 commit comments

Comments
 (0)