Forum Moderators: open
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="An online HTML editor with real-time preview">
<title>HTML Pencil</title>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<base target="_blank">
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
display: -webkit-flex;
/* WebKit prefixes are added to support Safari. */
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
header,
.shown {
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
padding: 5px;
}
header {
background: linear-gradient(#FFF, #CCC);
}
#fileSaver,
[type="button"],
#fileChooser,
label,
span {
font: bold 11px arial;
color: #333;
}
#selector,
#resizer,
#viewsToggle,
[title$="Twitter"] {
margin-right: 5px;
margin-left: 5px;
}
#fileSaver {
margin-right: 5px;
}
#fileChooser,
[title$="Facebook"] {
margin-right: auto;
}
#resizer {
margin-top: 0;
margin-bottom: 0;
padding: 0;
}
/* to remove the extra margins and padding in some browsers, e.g. IE11 */
span {
width: 35px;
}
#footerToggle {
margin-right: 0;
margin-left: 5px;
border: 0;
padding: 0;
background: transparent;
}
main {
display: -webkit-flex;
display: flex;
-webkit-flex: 1;
flex: 1;
}
.horizontal {
-webkit-flex-direction: column;
flex-direction: column;
}
main * {
margin: 0;
-webkit-flex: 50;
flex: 50;
background: #FFF;
min-height: 100%;
/* to ensure that the flex items are stretched to use available space; IE11, for example, doesn't stretch the iframe. */
}
.horizontal * {
min-width: 100%;
min-height: 0;
/* to get back to the initial value */
}
textarea {
box-sizing: border-box;
border: 0;
outline: 0;
padding: 5px;
resize: none;
overflow: auto;
/* to remove the default scrollbar in IE11 */
}
.minSize {
padding: 0;
}
iframe {
border: solid #CCC;
border-width: 0 0 0 5px;
padding: 0;
}
.horizontal iframe {
border-width: 5px 0 0;
}
.shown {
background: linear-gradient(#CCC, #FFF);
}
img {
display: block;
width: 20px;
height: 20px;
}
address,
address a {
color: #333;
}
</style>
</head>
<body>
<header>
<a download="myFile.html" title="Save as..." id="fileSaver">Save as...</a>
<input type="button" value="Reset" id="resetter">
<input type="button" value="Select" id="selector">
<input type="file" accept="text/html" id="fileChooser">
<label for="resizer">Text field size</label>
<input type="range" id="resizer">
<span id="indicator">50%</span>
<!-- The semantic element to use instead of span is output. But it's not supported in IE11. -->
<label for="viewsToggle">Horizontal view</label>
<input type="checkbox" id="viewsToggle">
<input type="button" value="▲" title="Show footer" id="footerToggle">
</header>
<main id="main">
<textarea spellcheck="false" id="editor"><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>HTML Document Template</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html></textarea>
<iframe id="viewer"></iframe>
</main>
<footer hidden id="footer">
<a href="https://plus.google.com/share?url=http%3A%2F%2Fexample.com%2F" title="Share on Google+">
<img src="images/google+.png" alt="Google+">
</a>
<a href="https://twitter.com/share?text=HTML%20Pencil&url=http%3A%2F%2Fexample.com%2F" title="Share on Twitter">
<img src="images/twitter.png" alt="Twitter">
</a>
<a href="https://www.facebook.com/sharer.php?u=http%3A%2F%2Fexample.com%2F" title="Share on Facebook">
<img src="images/facebook.png" alt="Facebook">
</a>
<address><a href="feedback.html" title="Feedback">Feedback</a> / Created by <a href="https://plus.google.com/+MortezaMirmojarabian?rel=author" title="Google+ profile" rel="author">Mori</a></address>
</footer>
<script>
var editor = document.getElementById('editor'),
viewer = document.getElementById('viewer'),
fileChooser = document.getElementById('fileChooser'),
resizer = document.getElementById('resizer');
function preview() {
try {
var viewerDoc = viewer.contentDocument;
viewerDoc.open();
viewerDoc.write(editor.value);
viewerDoc.close();
} catch (e) { // in case of iframe redirection to a different origin
viewer.src = 'about:blank';
setTimeout(preview, 4); // minimum delay
}
}
preview();
editor.oninput = preview;
function createURL() {
var blob = new Blob([editor.value], {
type: 'text/html'
});
document.getElementById('fileSaver').href = window.URL.createObjectURL(blob);
}
createURL();
editor.onchange = createURL;
fileChooser.onclick = function () { // to empty the fileList so you can rechoose the same file
this.value = '';
};
fileChooser.onchange = function () {
var file = this.files[0],
reader = new FileReader();
if (file) { // to ensure that there's a file to read so IE11 doesn't run this function on clicking fileChooser before you choose a file
reader.readAsText(file);
reader.onload = function () {
editor.value = this.result;
preview();
createURL();
};
}
};
document.getElementById('viewsToggle').onchange = function () {
document.getElementById('main').classList.toggle('horizontal');
};
resizer.oninput = resizer.onchange = function () { // The onchange property is added to support IE11.
var resizerVal = this.value;
editor.style.webkitFlex = resizerVal;
editor.style.flex = resizerVal;
viewer.style.webkitFlex = 100 - resizerVal;
viewer.style.flex = 100 - resizerVal;
document.getElementById('indicator').textContent = resizerVal + '%';
if (resizerVal == 0) {
editor.className = 'minSize';
} else {
editor.className = '';
}
};
document.getElementById('selector').onclick = function () {
editor.select();
};
document.getElementById('resetter').onclick = function () {
if (!editor.value || editor.value != editor.defaultValue && confirm('Are you sure?')) {
editor.value = editor.defaultValue;
preview();
createURL();
}
};
document.getElementById('footerToggle').onclick = function () {
var footerClasses = document.getElementById('footer').classList;
footerClasses.toggle('shown');
if (footerClasses.length) {
this.value = '▼';
this.title = 'Hide footer';
} else {
this.value = '▲';
this.title = 'Show footer';
}
};
</script>
</body>
</html> [edited by: incrediBILL at 1:01 am (utc) on Jul 8, 2014]
[edit reason] URls removed. No Site Reviews. Please see forum charter and TOS. [/edit]
Which browsers are supported?
Shouldn't the embedded sample document be converted to HTML entities for it to be valid?
<textarea><p>Some text</p></textarea> <textarea><p>Some text</p></textarea>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="An online HTML editor with real-time preview">
<title>HTML Pencil</title>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<base target="_blank">
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
display: -webkit-flex;
/* WebKit prefixes are added to support Safari. */
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
header,
.shown {
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
padding: 5px;
}
header {
background: linear-gradient(#FFF, #CCC);
}
#fileSaver,
[type="button"],
#fileChooser,
label,
span {
font: bold 11px arial;
color: #333;
}
#selector,
#resizer,
#viewsToggle,
[title$="Twitter"] {
margin-right: 5px;
margin-left: 5px;
}
#fileSaver {
margin-right: 5px;
}
#fileChooser,
[title$="Facebook"] {
margin-right: auto;
}
#resizer {
margin-top: 0;
margin-bottom: 0;
padding: 0;
}
/* to remove the extra margins and padding in some browsers, e.g. IE11 */
span {
width: 35px;
}
#footerToggle {
margin-right: 0;
margin-left: 5px;
border: 0;
padding: 0;
background: transparent;
}
main {
display: -webkit-flex;
display: flex;
-webkit-flex: 1;
flex: 1;
}
.horizontal {
-webkit-flex-direction: column;
flex-direction: column;
}
main * {
margin: 0;
-webkit-flex: 50;
flex: 50;
background: #FFF;
min-height: 100%;
/* to ensure that the flex items are stretched to use available space; IE11, for example, doesn't stretch the iframe. */
}
.horizontal * {
min-width: 100%;
min-height: 0;
/* to get back to the initial value */
}
textarea {
box-sizing: border-box;
border: 0;
outline: 0;
padding: 5px;
resize: none;
overflow: auto;
/* to remove the default scrollbar in IE11 */
}
.minSize {
padding: 0;
}
iframe {
border: solid #CCC;
border-width: 0 0 0 5px;
padding: 0;
}
.horizontal iframe {
border-width: 5px 0 0;
}
.shown {
background: linear-gradient(#CCC, #FFF);
}
img {
display: block;
width: 20px;
height: 20px;
}
address,
address a {
color: #333;
}
</style>
</head>
<body>
<header>
<a download="myFile.html" title="Save as..." id="fileSaver">Save as...</a>
<input type="button" value="Reset" id="resetter">
<input type="button" value="Select" id="selector">
<input type="file" accept="text/html" id="fileChooser">
<label for="resizer">Text field size</label>
<input type="range" id="resizer">
<span id="indicator">50%</span>
<!-- The semantic element to use instead of span is output. But it's not supported in IE11. -->
<label for="viewsToggle">Horizontal view</label>
<input type="checkbox" id="viewsToggle">
<input type="button" value="▲" title="Show footer" id="footerToggle">
</header>
<main id="main">
<textarea spellcheck="false" id="editor"><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>HTML Document Template</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html></textarea>
<iframe id="viewer"></iframe>
</main>
<footer hidden id="footer">
<a href="https://plus.google.com/share?url=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Google+">
<img src="images/google+.png" alt="Google+">
</a>
<a href="https://twitter.com/share?text=HTML%20Pencil&url=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Twitter">
<img src="images/twitter.png" alt="Twitter">
</a>
<a href="https://www.facebook.com/sharer.php?u=http%3A%2F%2Fhtmlpencil.appspot.com%2F" title="Share on Facebook">
<img src="images/facebook.png" alt="Facebook">
</a>
<address><a href="feedback.html" title="Feedback">Feedback</a> / Created by <a href="https://plus.google.com/+MortezaMirmojarabian?rel=author" title="Google+ profile" rel="author">Mori</a></address>
</footer>
<script>
var editor = document.getElementById('editor'),
viewer = document.getElementById('viewer'),
fileChooser = document.getElementById('fileChooser'),
resizer = document.getElementById('resizer');
function preview() {
try {
var viewerDoc = viewer.contentDocument;
viewerDoc.open();
viewerDoc.write(editor.value);
viewerDoc.close();
} catch (e) { // in case of iframe redirection to a different origin
viewer.src = 'about:blank';
setTimeout(preview, 4); // minimum delay
}
}
preview();
editor.oninput = preview;
function createURL() {
var blob = new Blob([editor.value], {
type: 'text/html'
});
document.getElementById('fileSaver').href = window.URL.createObjectURL(blob);
}
createURL();
editor.onchange = createURL;
fileChooser.onclick = function () { // to empty the fileList so you can rechoose the same file
this.value = '';
};
fileChooser.onchange = function () {
var file = this.files[0],
reader = new FileReader();
if (file) { // to ensure that there's a file to read so IE11 doesn't run this function on clicking fileChooser before you choose a file
reader.readAsText(file);
reader.onload = function () {
editor.value = this.result;
preview();
createURL();
};
}
};
document.getElementById('viewsToggle').onchange = function () {
document.getElementById('main').classList.toggle('horizontal');
};
resizer.oninput = resizer.onchange = function () { // The onchange property is added to support IE11.
var resizerVal = this.value;
editor.style.webkitFlex = resizerVal;
editor.style.flex = resizerVal;
viewer.style.webkitFlex = 100 - resizerVal;
viewer.style.flex = 100 - resizerVal;
document.getElementById('indicator').textContent = resizerVal + '%';
if (resizerVal == 0) {
editor.className = 'minSize';
} else {
editor.className = '';
}
};
document.getElementById('selector').onclick = function () {
editor.select();
};
document.getElementById('resetter').onclick = function () {
if (!editor.value || editor.value != editor.defaultValue && confirm('Are you sure?')) {
editor.value = editor.defaultValue;
preview();
createURL();
}
};
document.getElementById('footerToggle').onclick = function () {
var footerClasses = document.getElementById('footer').classList;
footerClasses.toggle('shown');
if (footerClasses.length) {
this.value = '▼';
this.title = 'Hide footer';
} else {
this.value = '▲';
this.title = 'Show footer';
}
};
</script>
</body>
</html>
I've copy/pasted the source from the site to preserve the formatting (by wrapping in quote pre code /code /pre /quote)
You might consider adding some type of syntax highlighter to the editor window.
For example any 4-space indent in my code has changed to 2-space
And triangles have changed to their HTML entities
Also in my third post there is a link with a bookmark
I had a minimalist approach
<script>
(function () {
/**
* TODO: modify to allow either string (id) OR HTMLElement reference object
*/
function HTMLPencil(config) {
config = config || {};
try {
this.setSaveAs(config.saveAs || 'fileSaver');
this.setResetter(config.resetter || 'resetter');
this.setSelectAll(config.selectall || 'selector');
this.setFileChooser(config.filechooser || 'filechooser');
this.setResizer(config.resizer || 'resizer');
this.setInput(config.input || 'editor');
this.setOutput(config.output || 'viewer');
this.preview();
} catch (e) {
console.log(e);
}
}
HTMLPencil.prototype.setSaveAs = function (id) {
this._saveas = document.getElementById(id);
this._saveas.href = '#';
};
HTMLPencil.prototype.setResetter = function (id) {
var self = this;
this._resetter = document.getElementById(id);
this.on('reset', function () {self._reset.call(self);});
};
HTMLPencil.prototype.setSelectAll = function (id) {
var self = this;
this._selectAll = document.getElementById(id);
this.on('selectall', function () {if (self._input){self._input.select();}});
}
HTMLPencil.prototype.setFileChooser = function (id) {
var self = this;
this._fileChooser = document.getElementById(id);
this.on('choosefileclick', function () {this.value = '';});
this.on('choosefilechange', function () {
if (self._input) {
var file = this.files[0],
reader = new FileReader();
if (file) { // to ensure that there's a file to read so IE11 doesn't run this function on clicking fileChooser before you choose a file
reader.readAsText(file);
reader.onload = function () {
self._input.value = this.result;
self._input.oninput();
};
}
}
});
};
HTMLPencil.prototype.setResizer = function (id) {
var self = this;
this._resizer = document.getElementById(id);
this.on('resize', function () {});
};
/**
* TODO: modify to allow either string (id) OR HTMLElement reference object
* @param {string} id The id that will act as the input editor
*/
HTMLPencil.prototype.setInput = function (id) {
var self = this;
this._input = document.getElementById(id);
this.on('input', function () {self.preview.call(self);});
this.on('saveas', function () {self._createURL.call(self);});
};
/**
* TODO: modify to allow either string (id) OR HTMLElement reference object
* @param {string} id The id that will act as the output viewer
*/
HTMLPencil.prototype.setOutput = function (id) {
var self = this;
this._output = document.getElementById(id);
};
/**
* Attach custom event handler callbacks
* @param {string} event The name of the event to attach to
* @param {function} cb The callback function to be called on event
*/
HTMLPencil.prototype.on = function (event, cb) {
var self = this;
switch (event) {
case 'input':
if (this._input) {
this._input.oninput = cb;
}
break;
case 'saveas':
if (this._saveas) {
this._saveas.onclick = cb;
}
break;
case 'reset':
if (this._resetter) {
this._resetter.onclick = cb;
}
break;
case 'selectall':
if (this._selectAll) {
this._selectAll.onclick = cb;
}
break;
case 'choosefileclick':
if (this._fileChooser) {
this._fileChooser.onclick = cb;
}
break;
case 'choosefilechange':
if (this._fileChooser) {
this._fileChooser.onchange = cb;
}
break;
case 'resize':
break;
}
};
/**
* Get the current value
*/
HTMLPencil.prototype.getValue = function () {
return (this._input ? this._input.value : '');
};
HTMLPencil.prototype.isDefaultValue = function () {
return (this._input ? this._input.value === this._input.defaultValue : true);
};
/**
* Preview the current value by displaying it in the output viewer
*/
HTMLPencil.prototype.preview = function () {
var self = this;
if (!this._output) { return; }
try {
var viewerDoc = this._output.contentDocument;
viewerDoc.open();
viewerDoc.write(this.getValue());
viewerDoc.close();
} catch (e) { // in case of iframe redirection to a different origin
console.log(e);
this._output.src = 'about:blank';
setTimeout(function () {self.preview.call(self);}, 4); // minimum delay
}
};
HTMLPencil.prototype._createURL = function () {
var blob = new Blob([this.getValue()], {
type: 'text/html'
});
this._saveas.href = window.URL.createObjectURL(blob);
};
HTMLPencil.prototype._reset = function () {
if (!this.isDefaultValue() && confirm('Are you sure?')) {
this._input.value = this._input.defaultValue;
this._input.oninput();
}
};
var hp = new HTMLPencil({
input: 'editor', // default: 'editor'
saveas: 'fileSaver', // default: 'fileSaver'
selectall: 'selector', // default: 'selector'
filechooser: 'fileChooser', // default: 'fileChooser'
output: 'viewer', // default: 'viewer'
resizer: 'resizer' // default: 'resizer'
});
})();
var editor = document.getElementById('editor'),
viewer = document.getElementById('viewer'),
fileChooser = document.getElementById('fileChooser'),
resizer = document.getElementById('resizer');
document.getElementById('viewsToggle').onchange = function () {
document.getElementById('main').classList.toggle('horizontal');
};
resizer.oninput = resizer.onchange = function () { // The onchange property is added to support IE11.
var resizerVal = this.value;
editor.style.webkitFlex = resizerVal;
editor.style.flex = resizerVal;
viewer.style.webkitFlex = 100 - resizerVal;
viewer.style.flex = 100 - resizerVal;
document.getElementById('indicator').textContent = resizerVal + '%';
if (resizerVal == 0) {
editor.className = 'minSize';
} else {
editor.className = '';
}
};
document.getElementById('footerToggle').onclick = function () {
var footerClasses = document.getElementById('footer').classList;
footerClasses.toggle('shown');
if (footerClasses.length) {
this.value = '▼';
this.title = 'Hide footer';
} else {
this.value = '▲';
this.title = 'Show footer';
}
};
</script>
I changed the changeURL functionality to not execute with every change in the editor, but the down side to that is that the href value currently only updates when the user clicks the "Save as" button, so when hovering over it the status bar might show them something different than what they'll actually get.
download attribute, i.e. Safari and IE11, right-click is the only way to save the file. If there weren't those points, I'd use a button instead of a link to look more consistent next to other buttons. HTMLPencil.prototype._reset = function () {
if (!this.isDefaultValue() && confirm('Are you sure?')) {
this._input.value = this._input.defaultValue;
this._input.oninput();
}
};
document.getElementById('resetter').onclick = function () {
if (!editor.value || editor.value != editor.defaultValue && confirm('Are you sure?')) {
editor.value = editor.defaultValue;
fileChooser.value = '';
fileSaver.download = 'template.html';
previewAndCreateURL();
} else if (editor.value == editor.defaultValue) {
fileChooser.value = '';
fileSaver.download = 'template.html';
}
};
Actually the main downside is that the link doesn't work on right-click
I think the confirm box should only appear when there's a custom code in the text field