2023-09-22 21:50:55 +02:00
|
|
|
(() =>
|
|
|
|
{
|
|
|
|
function convertModuleLinks(html)
|
|
|
|
{
|
|
|
|
html = html
|
|
|
|
.replace(/@shoelace-style\/shoelace/g, `https://esm.sh/@shoelace-style/shoelace@${egwVersion}`)
|
|
|
|
.replace(/from 'react'/g, `from 'https://esm.sh/react@${reactVersion}'`)
|
|
|
|
.replace(/from "react"/g, `from "https://esm.sh/react@${reactVersion}"`);
|
|
|
|
|
|
|
|
return html;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAdjacentExample(name, pre)
|
|
|
|
{
|
|
|
|
let currentPre = pre.nextElementSibling;
|
|
|
|
|
|
|
|
while (currentPre?.tagName.toLowerCase() === 'pre')
|
|
|
|
{
|
|
|
|
if (currentPre?.getAttribute('data-lang').split(' ').includes(name))
|
|
|
|
{
|
|
|
|
return currentPre;
|
|
|
|
}
|
|
|
|
|
|
|
|
currentPre = currentPre.nextElementSibling;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function runScript(script)
|
|
|
|
{
|
|
|
|
const newScript = document.createElement('script');
|
|
|
|
|
|
|
|
if (script.type === 'module')
|
|
|
|
{
|
|
|
|
newScript.type = 'module';
|
|
|
|
newScript.textContent = script.innerHTML;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
newScript.appendChild(document.createTextNode(`(() => { ${script.innerHTML} })();`));
|
|
|
|
}
|
|
|
|
|
|
|
|
script.parentNode.replaceChild(newScript, script);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getFlavor()
|
|
|
|
{
|
|
|
|
return sessionStorage.getItem('flavor') || 'html';
|
|
|
|
}
|
|
|
|
|
|
|
|
function setFlavor(newFlavor)
|
|
|
|
{
|
|
|
|
flavor = ['html', 'react'].includes(newFlavor) ? newFlavor : 'html';
|
|
|
|
sessionStorage.setItem('flavor', flavor);
|
|
|
|
|
|
|
|
// Set the flavor class on the body
|
|
|
|
document.documentElement.classList.toggle('flavor-html', flavor === 'html');
|
|
|
|
document.documentElement.classList.toggle('flavor-react', flavor === 'react');
|
|
|
|
}
|
|
|
|
|
|
|
|
function syncFlavor()
|
|
|
|
{
|
|
|
|
setFlavor(getFlavor());
|
|
|
|
|
|
|
|
document.querySelectorAll('.code-preview__button--html').forEach(preview =>
|
|
|
|
{
|
|
|
|
if (flavor === 'html')
|
|
|
|
{
|
|
|
|
preview.classList.add('code-preview__button--selected');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
document.querySelectorAll('.code-preview__button--react').forEach(preview =>
|
|
|
|
{
|
|
|
|
if (flavor === 'react')
|
|
|
|
{
|
|
|
|
preview.classList.add('code-preview__button--selected');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const egwVersion = document.documentElement.getAttribute('data-egroupware-version');
|
2023-10-27 17:21:27 +02:00
|
|
|
const shoelaceVersion = document.documentElement.getAttribute('data-shoelace-version');
|
2023-09-22 21:50:55 +02:00
|
|
|
const reactVersion = '18.2.0';
|
|
|
|
const cdndir = 'cdn';
|
|
|
|
const npmdir = 'dist';
|
|
|
|
let flavor = getFlavor();
|
|
|
|
let count = 1;
|
|
|
|
|
|
|
|
// We need the version to open
|
|
|
|
if (!egwVersion)
|
|
|
|
{
|
|
|
|
throw new Error('The data-egroupware-version attribute is missing from <html>.');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sync flavor UI on page load
|
|
|
|
syncFlavor();
|
|
|
|
|
|
|
|
//
|
|
|
|
// Resizing previews
|
|
|
|
//
|
|
|
|
document.addEventListener('mousedown', handleResizerDrag);
|
|
|
|
document.addEventListener('touchstart', handleResizerDrag, {passive: true});
|
|
|
|
|
|
|
|
function handleResizerDrag(event)
|
|
|
|
{
|
|
|
|
const resizer = event.target.closest('.code-preview__resizer');
|
|
|
|
const preview = event.target.closest('.code-preview__preview');
|
|
|
|
|
|
|
|
if (!resizer || !preview)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let startX = event.changedTouches ? event.changedTouches[0].pageX : event.clientX;
|
|
|
|
let startWidth = parseInt(document.defaultView.getComputedStyle(preview).width, 10);
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
preview.classList.add('code-preview__preview--dragging');
|
|
|
|
document.documentElement.addEventListener('mousemove', dragMove);
|
|
|
|
document.documentElement.addEventListener('touchmove', dragMove);
|
|
|
|
document.documentElement.addEventListener('mouseup', dragStop);
|
|
|
|
document.documentElement.addEventListener('touchend', dragStop);
|
|
|
|
|
|
|
|
function dragMove(event)
|
|
|
|
{
|
|
|
|
const width = startWidth + (event.changedTouches ? event.changedTouches[0].pageX : event.pageX) - startX;
|
|
|
|
preview.style.width = `${width}px`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function dragStop()
|
|
|
|
{
|
|
|
|
preview.classList.remove('code-preview__preview--dragging');
|
|
|
|
document.documentElement.removeEventListener('mousemove', dragMove);
|
|
|
|
document.documentElement.removeEventListener('touchmove', dragMove);
|
|
|
|
document.documentElement.removeEventListener('mouseup', dragStop);
|
|
|
|
document.documentElement.removeEventListener('touchend', dragStop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Toggle source mode
|
|
|
|
//
|
|
|
|
document.addEventListener('click', event =>
|
|
|
|
{
|
|
|
|
const button = event.target.closest('.code-preview__button');
|
|
|
|
const codeBlock = button?.closest('.code-preview');
|
|
|
|
|
|
|
|
if (button?.classList.contains('code-preview__button--html'))
|
|
|
|
{
|
|
|
|
// Show HTML
|
|
|
|
setFlavor('html');
|
|
|
|
toggleSource(codeBlock, true);
|
|
|
|
}
|
|
|
|
else if (button?.classList.contains('code-preview__button--react'))
|
|
|
|
{
|
|
|
|
// Show React
|
|
|
|
setFlavor('react');
|
|
|
|
toggleSource(codeBlock, true);
|
|
|
|
}
|
|
|
|
else if (button?.classList.contains('code-preview__toggle'))
|
|
|
|
{
|
|
|
|
// Toggle source
|
|
|
|
toggleSource(codeBlock);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update flavor buttons
|
|
|
|
[...document.querySelectorAll('.code-preview')].forEach(cb =>
|
|
|
|
{
|
|
|
|
cb.querySelector('.code-preview__button--html')?.classList.toggle(
|
|
|
|
'code-preview__button--selected',
|
|
|
|
flavor === 'html'
|
|
|
|
);
|
|
|
|
cb.querySelector('.code-preview__button--react')?.classList.toggle(
|
|
|
|
'code-preview__button--selected',
|
|
|
|
flavor === 'react'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
function toggleSource(codeBlock, force)
|
|
|
|
{
|
|
|
|
codeBlock.classList.toggle('code-preview--expanded', force);
|
|
|
|
event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-preview--expanded'));
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Open in CodePen
|
|
|
|
//
|
|
|
|
document.addEventListener('click', event =>
|
|
|
|
{
|
|
|
|
const button = event.target.closest('button');
|
|
|
|
|
|
|
|
if (button?.classList.contains('code-preview__button--codepen'))
|
|
|
|
{
|
|
|
|
const codeBlock = button.closest('.code-preview');
|
|
|
|
const htmlExample = codeBlock.querySelector('.code-preview__source--html > pre > code')?.textContent;
|
|
|
|
const reactExample = codeBlock.querySelector('.code-preview__source--react > pre > code')?.textContent;
|
|
|
|
const isReact = flavor === 'react' && typeof reactExample === 'string';
|
|
|
|
const theme = document.documentElement.classList.contains('sl-theme-dark') ? 'dark' : 'light';
|
|
|
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
|
|
const isDark = theme === 'dark' || (theme === 'auto' && prefersDark);
|
|
|
|
const editors = isReact ? '0010' : '1000';
|
|
|
|
let htmlTemplate = '';
|
|
|
|
let jsTemplate = '';
|
|
|
|
let cssTemplate = '';
|
|
|
|
|
|
|
|
const form = document.createElement('form');
|
|
|
|
form.action = 'https://codepen.io/pen/define';
|
|
|
|
form.method = 'POST';
|
|
|
|
form.target = '_blank';
|
|
|
|
|
|
|
|
// HTML templates
|
|
|
|
if (!isReact)
|
|
|
|
{
|
|
|
|
htmlTemplate =
|
2023-10-27 17:21:27 +02:00
|
|
|
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/cdn/shoelace.js"></script>\n` +
|
2023-09-22 21:50:55 +02:00
|
|
|
`\n${htmlExample}`;
|
|
|
|
jsTemplate = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// React templates
|
|
|
|
if (isReact)
|
|
|
|
{
|
|
|
|
htmlTemplate = '<div id="root"></div>';
|
|
|
|
jsTemplate =
|
|
|
|
`import ReactDOM from 'https://esm.sh/react-dom@${reactVersion}';\n` +
|
2023-10-27 17:21:27 +02:00
|
|
|
`import { setBasePath } from 'https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/utilities/base-path';\n` +
|
2023-09-22 21:50:55 +02:00
|
|
|
`\n` +
|
|
|
|
`// Set the base path for Shoelace assets\n` +
|
2023-10-27 17:21:27 +02:00
|
|
|
`setBasePath('https://esm.sh/@shoelace-style/shoelace@${shoelaceVersion}/${npmdir}/')\n` +
|
2023-09-22 21:50:55 +02:00
|
|
|
`\n${convertModuleLinks(reactExample)}\n` +
|
|
|
|
`\n` +
|
|
|
|
`ReactDOM.render(<App />, document.getElementById('root'));`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// CSS templates
|
|
|
|
cssTemplate =
|
2023-10-27 17:21:27 +02:00
|
|
|
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/themes/${
|
2023-09-22 21:50:55 +02:00
|
|
|
isDark ? 'dark' : 'light'
|
|
|
|
}.css';\n` +
|
|
|
|
'\n' +
|
|
|
|
'body {\n' +
|
|
|
|
' font: 16px sans-serif;\n' +
|
|
|
|
' background-color: var(--sl-color-neutral-0);\n' +
|
|
|
|
' color: var(--sl-color-neutral-900);\n' +
|
|
|
|
' padding: 1rem;\n' +
|
|
|
|
'}';
|
|
|
|
|
|
|
|
// Docs: https://blog.codepen.io/documentation/prefill/
|
|
|
|
const data = {
|
|
|
|
title: '',
|
|
|
|
description: '',
|
|
|
|
tags: ['shoelace', 'web components'],
|
|
|
|
editors,
|
|
|
|
head: `<meta name="viewport" content="width=device-width">`,
|
|
|
|
html_classes: `sl-theme-${isDark ? 'dark' : 'light'}`,
|
|
|
|
css_external: ``,
|
|
|
|
js_external: ``,
|
|
|
|
js_module: true,
|
|
|
|
js_pre_processor: isReact ? 'babel' : 'none',
|
|
|
|
html: htmlTemplate,
|
|
|
|
css: cssTemplate,
|
|
|
|
js: jsTemplate
|
|
|
|
};
|
|
|
|
|
|
|
|
const input = document.createElement('input');
|
|
|
|
input.type = 'hidden';
|
|
|
|
input.name = 'data';
|
|
|
|
input.value = JSON.stringify(data);
|
|
|
|
form.append(input);
|
|
|
|
|
|
|
|
document.documentElement.append(form);
|
|
|
|
form.submit();
|
|
|
|
form.remove();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Set the initial flavor
|
|
|
|
window.addEventListener('turbo:load', syncFlavor);
|
|
|
|
})();
|