Skip to content

Instantly share code, notes, and snippets.

@gaabora
Last active May 6, 2024 10:50
Show Gist options
  • Save gaabora/77de511c9bfe84bbfa13354fc21a5d97 to your computer and use it in GitHub Desktop.
Save gaabora/77de511c9bfe84bbfa13354fc21a5d97 to your computer and use it in GitHub Desktop.
batch-style-replace-magic.html
<!DOCTYPE html>
<html>
<head>
<title>Test report tpl vars</title>
<style>
* { font-size: 10px; }
html, body { margin: 0; padding: 0; }
body { padding: 0 5px; }
.container { height: 100%; display: flex; flex-direction: row; }
.fill-height { height: 100vh; }
.horiz { flex: 1; display: flex; flex-direction: column; }
input,textarea { border: none; background: #eee; padding: 5px; margin: 2px; color: #7b7b7b }
textarea:read-only { background: #fafafa; color: #a7a7a7 }
textarea { height: 100%; resize: none; }
.container pre { padding: 0 5px; }
button { margin: 0 2px; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<div class="horiz fill-height">
<pre>1). paste <b>exported styles JSON</b> here:</pre>
<textarea id="source" title="Paste here exported styles JSON"></textarea>
</div>
<div class="horiz fill-height">
<pre>2). set strings or regex to replace: <input type="checkbox" id="use-regex" placeholder="use regex" /> use regex</pre>
<input id="replace-from" placeholder="from" />
<input id="replace-to" placeholder="to" />
<pre>3). paste here classname selectors (from 'sel') to process (1 per line)</pre>
<pre>it wil automatically use regex if line contains some regex-related symbols like *?+^$()|{}</pre>
<div class="container">
<div class="horiz">
<pre>a) process only this</pre>
<textarea id="only-classes"></textarea>
</div>
<div class="horiz">
<pre>b) or ignore any of this</pre>
<textarea id="ignore-classes"></textarea>
</div>
</div>
<div class="container">
<div class="horiz">
<pre>affected classes</pre>
<textarea id="affected-classes" readonly></textarea>
</div>
<div class="horiz">
<pre>skipped classes</pre>
<textarea id="skipped-classes" readonly></textarea>
</div>
</div>
<pre>[debug]: affected pages</pre>
<textarea id="affected-pages" readonly></textarea>
</div>
<div class="horiz fill-height">
<pre>4). run</pre>
<button onclick="asyncReplaceString()">replace</button>
<button onclick="downloadResult()">download result</button>
<pre>copy result and compare somewhere like in vscode</pre>
<textarea id="result"></textarea>
</div>
</div>
<script>
class Loader {
constructor(iconSrc='/favicon.ico') {
this.loadingEl = document.getElementById('WebflowMagicLoading');
if (!this.loadingEl) {
this.loadingEl = document.createElement('div');
this.loadingEl.id = 'WebflowMagicLoading';
const faviconImg = document.createElement('img');
faviconImg.src = iconSrc;
this.loadingEl.appendChild(faviconImg);
document.body.appendChild(this.loadingEl);
var style = document.createElement('style');
style.innerHTML = `
#WebflowMagicLoading {
display: none;
width: 32px;
height: 32px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: 1s linear 0s infinite normal none running WebflowMagicLoadingSpin;
z-index: 99999;
}
#WebflowMagicLoading img { width: 100%; height: 100%; }
@keyframes WebflowMagicLoadingSpin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
}
}
setLoadingState(state) {
this.loadingEl.style.display = state ? 'block' : 'none';
}
}
const loadingSpinner = new Loader('https://webflow.com/favicon.ico');
function asyncReplaceString() {
loadingSpinner.setLoadingState(true);
const promise = new Promise(resolve => {
setTimeout(() => {
resolve(replaceString());
}, 0); // Use a short timeout to allow the UI to update (simulating an asynchronous operation)
});
promise.then((response) => {
loadingSpinner.setLoadingState(false);
notify(response);
});
}
async function downloadResult() {
const resultString = document.getElementById("result").value;
if (!resultString.trim()) {
notify('result is empty, nothing to download');
return;
}
try {
const JSONContent = JSON.parse(resultString);
downloadJSON(JSONContent);
} catch (e) {
notify(`result JSON is invalid: ${e}`);
}
}
function downloadJSON(JSONContent) {
var link = document.createElement("a");
const date = new Date().toISOString().replace(/\D/g, '').substring(0, 12);
link.setAttribute('href', 'data:application/JSON;charset=utf-8,' + encodeURIComponent(JSON.stringify(JSONContent, null, 2)));
link.setAttribute('download', `changed_styles_${date}.JSON`);
document.body.appendChild(link);
link.click();
link.remove();
}
function replaceString() {
const useRegex = document.getElementById("use-regex").checked;
const replaceFrom = document.getElementById("replace-from").value;
const REGEX_FROM = useRegex ? new RegExp(replaceFrom, 'g') : null;
const replaceTo = document.getElementById("replace-to").value;
const REGEX_TO = useRegex ? new RegExp(replaceTo, 'g') : null;
const onlyClasses = document.getElementById("only-classes").value.split("\n").filter(el => el.trim() !== '');
const ignoreClasses = document.getElementById("ignore-classes").value.split("\n").filter(el => el.trim() !== '');
let sourceJson;
try {
sourceJson = JSON.parse(document.getElementById("source").value);
} catch (error) {
return `Unable to parse JSON, check imported file syntax and integrity. ${error}`;
}
let replacementsCount = 0;
const skippedElements = new Set();
const replacedElements = new Set();
sourceJson.forEach(el => {
const selectionFn = (selectorString) => {
const hasRegexSymbols = /[\[\]*?+^$()|{}\\]/.test(selectorString);
if (hasRegexSymbols) {
const regex = new RegExp(selectorString);
return regex.test(el.sel);
} else {
return el.sel === selectorString;
}
};
const shouldProcess = onlyClasses.length === 0 || onlyClasses.some(selectionFn);
const shouldIgnore = ignoreClasses.some(selectionFn);
if (shouldIgnore) {
skippedElements.add(el);
} else if (shouldProcess) {
if (el.styleLess) {
const replacedStyleLess = useRegex ? el.styleLess.replace(REGEX_FROM, replaceTo) : el.styleLess.replace(replaceFrom, replaceTo);
if (replacedStyleLess !== el.styleLess) {
el.styleLess = replacedStyleLess;
replacedElements.add(el);
replacementsCount++;
}
}
if (el.variants) {
Object.keys(el.variants).forEach(variant => {
const variantEl = el.variants[variant];
if (variantEl.styleLess) {
const replacedVariantStyleLess = useRegex ? variantEl.styleLess.replace(REGEX_FROM, replaceTo) : variantEl.styleLess.replace(replaceFrom, replaceTo);
if (replacedVariantStyleLess !== variantEl.styleLess) {
variantEl.styleLess = replacedVariantStyleLess;
replacedElements.add(el);
replacementsCount++;
}
}
});
}
}
});
const replacedElementsArr = [...replacedElements];
document.getElementById("result").value = JSON.stringify(sourceJson, null, 2);
document.getElementById("skipped-classes").value = [...skippedElements].map(el => el.sel).sort().join("\n");
document.getElementById("affected-classes").value = replacedElementsArr.map(el => el.sel).sort().join("\n");
document.getElementById("affected-pages").value = [...new Set(replacedElementsArr.map(el => el.__onPages).join(', ').split(', '))].sort().join("\n");
return `Replacements made: ${replacementsCount} in ${replacedElementsArr.length} styles`;
}
function notify(message) {
alert(message);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment