Skip to content

Commit 210a722

Browse files
committed
adjust json-editor to handle double escaped strings and fix json editing in addon
1 parent 28be5d3 commit 210a722

11 files changed

+1156
-399
lines changed

api/src/pluginTemplate.js

+168-57
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,90 @@ import { createHeaderSearchBar } from './headerSearchBar';
33

44
export default async function generatePluginHTML(pluginData, env) {
55
const secureHtmlService = createSecureHtmlService();
6-
// Sanitize the input data
76
const safePlugin = secureHtmlService.sanitizePluginData(pluginData);
8-
console.log("datertots", JSON.stringify(pluginData));
7+
98
if (!safePlugin) {
109
return new Response('Invalid plugin data', { status: 400 });
1110
}
1211

13-
// Fetch the current download count
1412
const downloadKey = `downloads:${safePlugin.author}:${safePlugin.slug}`;
1513
const downloadCount = parseInt(await env.DOWNLOAD_COUNTS.get(downloadKey)) || 0;
1614
const activeInstalls = safePlugin.active_installs;
15+
1716
const html = `
18-
<!DOCTYPE html>
19-
<html lang="en">
20-
<head>
21-
<title>${safePlugin.name} - Plugin Details</title>
22-
<link
23-
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
24-
rel="stylesheet"
25-
crossorigin="anonymous"
26-
>
27-
<style>
28-
body { background-color: #191919; color: white; }
29-
.asset-card-container-home { background: linear-gradient(to bottom right, #131313, #181818); }
30-
</style>
31-
</head>
32-
<body>
33-
<div class="min-h-screen bg-[#191919] text-white">
34-
${createHeaderSearchBar()}
35-
<div class="bg-gradient-to-r from-green-500 to-purple-600 pt-16">
36-
<div class="container mx-auto px-2 max-h-[620px]">
37-
<div class="relative max-w-[1300px] mx-auto shadow-lg rounded-t-2xl overflow-hidden">
38-
<img src="${safePlugin.banners.high}" alt="${safePlugin.name} banner" class="h-auto max-h-[620px] justify-center object-cover w-full">
39-
</div>
40-
</div>
41-
</div>
42-
<div class="container mx-auto px-4 py-16">
43-
<div class="grid grid-cols-1 md:grid-cols-3 gap-12">
44-
<div class="md:col-span-2 bg-gradient-to-br rounded-3xl asset-card-container-home shadow-2xl p-8 transform min-h-[200px]">
45-
<div class='px-4 py-6 bg-[#191919] rounded-3xl mb-2 flex items-center'>
46-
<img src="${safePlugin.icons['1x']}" alt="${safePlugin.name}" class="w-24 h-24 mr-4 rounded-lg">
47-
<div>
48-
<h1 class="text-2xl font-bold mb-1">${safePlugin.name}</h1>
49-
<p class="text-md w-90">${safePlugin.short_description}</p>
50-
</div>
51-
</div>
17+
<!DOCTYPE html>
18+
<html lang="en">
19+
<head>
20+
<title>${safePlugin.name} - Plugin Details</title>
21+
<link
22+
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
23+
rel="stylesheet"
24+
crossorigin="anonymous"
25+
>
26+
<style>
27+
body { background-color: #191919; color: white; }
28+
.asset-card-container-home { background: linear-gradient(to bottom right, #131313, #181818); }
29+
/* Add only the modal styles we need */
30+
#playgroundModal {
31+
display: none;
32+
position: fixed;
33+
top: 0;
34+
left: 0;
35+
width: 100%;
36+
height: 100%;
37+
background: rgba(0, 0, 0, 0.8);
38+
z-index: 1000;
39+
}
40+
#playgroundModal.active {
41+
display: flex;
42+
align-items: center;
43+
justify-content: center;
44+
}
45+
.modal-content {
46+
width: 90%;
47+
height: 90%;
48+
background: white;
49+
border-radius: 8px;
50+
padding: 20px;
51+
position: relative;
52+
}
53+
#closePlayground {
54+
position: absolute;
55+
right: 10px;
56+
top: 10px;
57+
font-size: 24px;
58+
cursor: pointer;
59+
color: #666;
60+
}
61+
#playground-iframe {
62+
width: 100%;
63+
height: 100%;
64+
border: none;
65+
}
66+
</style>
67+
</head>
68+
<body>
69+
<div class="min-h-screen bg-[#191919] text-white">
70+
${createHeaderSearchBar()}
71+
<div class="bg-gradient-to-r from-green-500 to-purple-600 pt-16">
72+
<div class="container mx-auto px-2 max-h-[620px]">
73+
<div class="relative max-w-[1300px] mx-auto shadow-lg rounded-t-2xl overflow-hidden">
74+
<img src="${safePlugin.banners.high}" alt="${safePlugin.name} banner" class="h-auto max-h-[620px] justify-center object-cover w-full">
75+
</div>
76+
</div>
77+
</div>
78+
<div class="container mx-auto px-4 py-16">
79+
<div class="grid grid-cols-1 md:grid-cols-3 gap-12">
80+
<!-- Left content column -->
81+
<div class="md:col-span-2 bg-gradient-to-br rounded-3xl asset-card-container-home shadow-2xl p-8 transform min-h-[200px]">
82+
<!-- Existing left column content -->
83+
<div class='px-4 py-6 bg-[#191919] rounded-3xl mb-2 flex items-center'>
84+
<img src="${safePlugin.icons['1x']}" alt="${safePlugin.name}" class="w-24 h-24 mr-4 rounded-lg">
85+
<div>
86+
<h1 class="text-2xl font-bold mb-1">${safePlugin.name}</h1>
87+
<p class="text-md w-90">${safePlugin.short_description}</p>
88+
</div>
89+
</div>
5290
<div class="bg-[#191919] rounded-3xl p-2 mb-2">
5391
<div class="text-md leading-8">${safePlugin.sections?.description}</div>
5492
</div>
@@ -65,6 +103,7 @@ export default async function generatePluginHTML(pluginData, env) {
65103
</div>
66104
` : ''}
67105
</div>
106+
<!-- Right sidebar -->
68107
<div>
69108
<div class="bg-gradient-to-br rounded-3xl asset-card-container-home shadow-2xl rounded-lg p-6 mb-8">
70109
<div class="flex items-center mb-4">
@@ -74,26 +113,36 @@ export default async function generatePluginHTML(pluginData, env) {
74113
<p class="text-gray-200 text-md">by ${safePlugin.author}</p>
75114
</div>
76115
</div>
77-
<div class="flex items-center justify-between mb-4">
116+
<div class="flex items-center mb-4">
78117
<div class="flex items-center">
79-
${safePlugin.rating && safePlugin.rating > 0 ? `
80-
<span class="text-yellow-400 mr-1">★</span>
81-
<span>${safePlugin.rating}/5</span>
82-
` : ''}
118+
${safePlugin.rating && safePlugin.rating > 0 ? `
119+
<span class="text-yellow-400 mr-1">★</span>
120+
<span>${safePlugin.rating}/5</span>
121+
` : ''}
83122
</div>
123+
<div class="flex items-center flex-col gap-2">
84124
<div class="flex items-center">
85-
<span class="mr-2 text-purple-600">↓</span>
86-
<span class="text-s" id="download-count">${downloadCount.toLocaleString()}+ downloads</span>
125+
<span class="mr-2 text-purple-600">↓</span>
126+
<span class="text-s" id="download-count">${downloadCount.toLocaleString()}+ downloads</span>
87127
</div>
88128
<div class="flex items-center">
89-
<span class="mr-2 text-purple-600">🔌</span>
129+
<span class="mr-2 text-purple-600">🔌</span>
90130
<span class="text-s" id="active-installs">${activeInstalls.toLocaleString()}+ activations</span>
91131
</div>
132+
</div>
92133
</div>
93-
<a href="/download?author=${encodeURIComponent(safePlugin.author)}&slug=${encodeURIComponent(safePlugin.slug)}"
94-
class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-3xl mb-4 flex items-center justify-center">
95-
Download v${safePlugin.version}
96-
</a>
134+
<!-- Download and Playground buttons -->
135+
<div class="flex flex-col gap-6 mb-4">
136+
<button id="tryInPlayground"
137+
class="flex-2 bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-3xl">
138+
Try in Playground
139+
</button>
140+
<a href="/download?author=${encodeURIComponent(safePlugin.author)}&slug=${encodeURIComponent(safePlugin.slug)}"
141+
class="flex-1 bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-3xl text-center">
142+
Download v${safePlugin.version}
143+
</a>
144+
</div>
145+
<!-- Plugin details -->
97146
<div class="text-sm text-gray-200">
98147
<p>Last updated: ${safePlugin.last_updated}</p>
99148
<p>Version: ${safePlugin.version}</p>
@@ -102,6 +151,7 @@ export default async function generatePluginHTML(pluginData, env) {
102151
<p>Requires PHP: ${safePlugin.requires_php}</p>
103152
</div>
104153
</div>
154+
105155
${safePlugin.authorData ? `
106156
<div class="bg-gradient-to-br rounded-3xl asset-card-container-home shadow-2xl p-6">
107157
<h3 class="text-xl font-bold mb-4">Author</h3>
@@ -120,17 +170,78 @@ export default async function generatePluginHTML(pluginData, env) {
120170
</div>
121171
</div>
122172
</div>
123-
<div class="bg-black py-8">
124-
<div class="container mx-auto px-4 text-center text-gray-200">
125-
<p>&copy; ${new Date().getFullYear()} ${new Date().toLocaleTimeString()} Your Footer Text.</p>
126-
<a href="/terms" class="text-purple-400 hover:underline">Terms of Service</a> |
127-
<a href="/privacy" class="text-purple-400 hover:underline">Privacy Policy</a> |
128-
<a href="https://github.com/xpportal/Micro-Plugin-Publisher" class="text-purple-400 hover:underline">
129-
Source Code
130-
</a>
173+
174+
<!-- Playground Modal -->
175+
<div id="playgroundModal">
176+
<div class="modal-content">
177+
<span id="closePlayground">&times;</span>
178+
<iframe id="playground-iframe"
179+
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
180+
title="WordPress Playground">
181+
</iframe>
182+
</div>
131183
</div>
184+
185+
<!-- Footer -->
186+
<div class="bg-black py-8">
187+
<div class="container mx-auto px-4 text-center text-gray-200">
188+
<p>&copy; ${new Date().getFullYear()} ${new Date().toLocaleTimeString()} Your Footer Text.</p>
189+
<a href="/terms" class="text-purple-400 hover:underline">Terms of Service</a> |
190+
<a href="/privacy" class="text-purple-400 hover:underline">Privacy Policy</a> |
191+
<a href="https://github.com/xpportal/Micro-Plugin-Publisher" class="text-purple-400 hover:underline">
192+
Source Code
193+
</a>
194+
</div>
132195
</div>
133196
</div>
197+
198+
<!-- Playground initialization script -->
199+
<script type="module">
200+
import { startPlaygroundWeb } from 'https://playground.xr.foundation/client/index.js';
201+
202+
document.getElementById('tryInPlayground').addEventListener('click', async () => {
203+
const modal = document.getElementById('playgroundModal');
204+
const iframe = document.getElementById('playground-iframe');
205+
modal.classList.add('active');
206+
207+
try {
208+
const client = await startPlaygroundWeb({
209+
iframe: iframe,
210+
remoteUrl: 'https://playground.xr.foundation/remote.html',
211+
blueprint: {
212+
landingPage: '/wp-admin/',
213+
preferredVersions: {
214+
php: '8.0',
215+
wp: 'latest'
216+
},
217+
steps: [
218+
{
219+
step: 'login',
220+
username: 'admin',
221+
password: 'password'
222+
},
223+
{
224+
step: 'installPlugin',
225+
pluginData: {
226+
resource: 'url',
227+
url: '/download?author=${encodeURIComponent(safePlugin.author)}&slug=${encodeURIComponent(safePlugin.slug)}track=false'
228+
}
229+
}
230+
]
231+
}
232+
});
233+
} catch (error) {
234+
console.error('Playground initialization failed:', error);
235+
}
236+
});
237+
238+
document.getElementById('closePlayground').addEventListener('click', () => {
239+
const modal = document.getElementById('playgroundModal');
240+
const iframe = document.getElementById('playground-iframe');
241+
modal.classList.remove('active');
242+
iframe.src = 'about:blank';
243+
});
244+
</script>
134245
</body>
135246
</html>
136247
`;

api/src/secureHtmlService.js

+27-22
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,31 @@ class SecureHtmlService {
77

88
getSecurityHeaders(nonce) {
99
return {
10-
'Content-Security-Policy': [
11-
// Allow resources from same origin and CDN
12-
"default-src 'self' https://cdn.jsdelivr.net",
13-
// Allow scripts from CDN and inline scripts with nonce
14-
`script-src 'self' 'nonce-${nonce}' https://cdn.jsdelivr.net`,
15-
// Allow styles from CDN and inline styles
16-
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net",
17-
// Allow images from any HTTPS source
18-
"img-src 'self' https: data:",
19-
// Allow fonts from CDN
20-
"font-src 'self' https://cdn.jsdelivr.net",
21-
// Basic security headers that don't impact functionality
22-
"frame-ancestors 'none'",
23-
"base-uri 'self'"
24-
].join('; '),
25-
'X-Content-Type-Options': 'nosniff',
26-
'X-Frame-Options': 'DENY',
27-
'Referrer-Policy': 'strict-origin-when-cross-origin'
10+
'Content-Security-Policy': [
11+
// Allow resources from same origin and CDN
12+
"default-src 'self' https://cdn.jsdelivr.net",
13+
// Allow scripts from CDN and playground domains
14+
`script-src 'self' 'nonce-${nonce}' https://cdn.jsdelivr.net https://playground.xr.foundation`,
15+
// Allow styles from CDN and inline styles
16+
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net",
17+
// Allow images from any HTTPS source
18+
"img-src 'self' https: data:",
19+
// Allow fonts from CDN
20+
"font-src 'self' https://cdn.jsdelivr.net",
21+
// Allow framing for playground
22+
"frame-src 'self' https://playground.xr.foundation",
23+
// Allow connections to playground
24+
"connect-src 'self' https://playground.xr.foundation",
25+
// Basic security headers
26+
"base-uri 'self'"
27+
].join('; '),
28+
'X-Content-Type-Options': 'nosniff',
29+
'X-Frame-Options': 'SAMEORIGIN', // Changed from DENY to allow our iframe
30+
'Referrer-Policy': 'strict-origin-when-cross-origin'
2831
};
29-
}
32+
}
3033

31-
sanitizeText(text) {
34+
sanitizeText(text) {
3235
if (!text) return '';
3336
return String(text)
3437
.replace(/&/g, '&amp;')
@@ -69,14 +72,16 @@ class SecureHtmlService {
6972
const src = element.getAttribute('src');
7073
if (src && (
7174
src.includes('cdn.jsdelivr.net') ||
72-
src.includes('playground.wordpress.net')
75+
src.includes('playground.wordpress.net') ||
76+
src.includes('playground.xr.foundation') // Add our custom playground domain
7377
)) {
7478
element.setAttribute('nonce', nonce);
7579
return;
7680
}
7781

7882
// Allow our playground initialization script
79-
if (element.hasAttribute('data-playground-init')) {
83+
if (element.hasAttribute('data-playground-init') ||
84+
element.getAttribute('type') === 'module') { // Allow module scripts for playground
8085
element.setAttribute('nonce', nonce);
8186
return;
8287
}

0 commit comments

Comments
 (0)