Skip to content

Instantly share code, notes, and snippets.

@ChrisShank
Last active July 6, 2024 03:30
Show Gist options
  • Save ChrisShank/fa77877602cb134e34169ee0c6316302 to your computer and use it in GitHub Desktop.
Save ChrisShank/fa77877602cb134e34169ee0c6316302 to your computer and use it in GitHub Desktop.
Self-modifying HTML file
<head>
<!-- Forked from https://cristobal.space/note -->
<meta charset="UTF-8">
<style>
body {
font-family: monospace;
margin: 12px;
}
img {
width: 100%;
margin: 12px 0px;
}
#editor {
margin-bottom: 12px;
}
#editor button {
font-style: italic;
}
#text {
width: 400px;
height: 100px;
border-radius: 4px;
resize: none;
margin-bottom: 12px;
padding: 6px;
}
#drop {
width: 400px;
height: 100px;
font-style: italic;
border: 1px dashed black;
border-radius: 4px;
margin-bottom: 12px;
display: flex;
justify-content: center;
align-items: center;
}
#content {
width: 400px;
}
</style>
</head>
<body>
<div id="editor">
<textarea id="text">Write HTML...</textarea>
<div id="drop">DROP IMAGES</div>
<button onclick="append()">Append</button>
<button onclick="save()">Save</button>
<button onclick="saveAs()">Save As</button>
</div>
<div id="content">Write HTML...Write HTML...Write HTML...Write HTML...Write HTML...</div>
<!-- Script for creating new -->
<script type="module">
const noop = (e) => {
e.preventDefault();
e.stopPropagation();
};
const toBase64 = (blob) =>
new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
const content = document.getElementById('content');
const textArea = document.getElementById('text');
const dropArea = document.getElementById('drop');
dropArea.addEventListener('dragenter', (e) => {
noop(e);
dropArea.style.border = '1px dotted black';
});
dropArea.addEventListener('drop', async (e) => {
noop(e);
dropArea.style.border = '1px dashed black';
const img = document.createElement('img');
const f = e.dataTransfer.files[0];
const b64 = await toBase64(f);
img.src = b64;
content.appendChild(img);
});
dropArea.addEventListener('dragover', noop);
dropArea.addEventListener('dragexit', noop);
window.append = () => {
content.innerHTML += textArea.value;
};
</script>
<!-- Script for persist changes made to HTML file -->
<script type="module">
// Based on https://github.com/jakearchibald/idb-keyval
class KeyValueStore {
#db;
#storeName;
constructor(name = 'keyval-store') {
this.#storeName = name;
const request = indexedDB.open(name);
request.onupgradeneeded = () => request.result.createObjectStore(name);
this.#db = new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
#promisifyTransaction(transaction) {
return new Promise((resolve, reject) => {
transaction.oncomplete = transaction.onsuccess = () => resolve(transaction.result);
transaction.onabort = transaction.onerror = () => reject(transaction.error);
});
}
#getStore(mode) {
return this.#db.then((db) =>
db.transaction(this.#storeName, mode).objectStore(this.#storeName)
);
}
get(key) {
return this.#getStore('readonly').then((store) =>
this.#promisifyTransaction(store.get(key))
);
}
set(key, value) {
return this.#getStore('readwrite').then((store) => {
store.put(value, key);
return this.#promisifyTransaction(store.transaction);
});
}
clear() {
return this.#getStore('readwrite').then((store) => {
store.clear();
return this.#promisifyTransaction(store.transaction);
});
}
}
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
'showSaveFilePicker' in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
class HTMLFileSaver {
#store = new KeyValueStore('html-file-saver');
#file = this.#store.get('file');
async save(content, promptNewFile = false) {
let file = await this.#file;
if (file === undefined || promptNewFile) {
this.#file = showSaveFilePicker({
id: 'self-modifying_html_file',
suggestedName: 'index.html',
types: [{ description: 'HTML document', accept: { 'text/html': ['.html'] } }],
});
file = await this.#file;
await this.#store.set('file', file);
}
const options = { mode: 'readwrite' };
if (
(await file.queryPermission(options)) !== 'granted' &&
(await file.requestPermission(options)) !== 'granted'
) {
throw new Error('File write permission not granted.');
}
const writer = await file.createWritable();
await writer.write(content);
await writer.close();
}
}
const saver = new HTMLFileSaver();
window.save = () => saver.save(document.documentElement.innerHTML);
window.saveAs = () => saver.save(document.documentElement.innerHTML, true);
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment