-wheic
-=====
+Peach HTML5 Editor
+==================
-This project is to build a HTML5 parser, then use that to build a WYSIWYG html
-editor for the browser.
-
-The code is written in CoffeeScript for modern browsers. The HTML5 parser can
-also run under node.js.
+This is a WYSIWYG HTML5 editor for the browser.
Status
------
-HTML5 parser: all (1581) tests pass. Works in the browser and node.js
+Early development stages.
+
+The HTML5 parser component passes the full test suite (1581 tests).
+
+The interface is starting to exist.
+
+
+Technologies
+------------
+
+Programming language: CoffeeScript (compiles to javascript)
+
+Interface: Implemented using the DOM api. No ``contenteditable`` or jquery.
-WYSIWYG editor: planning stages
Quick Start Guide
4. Try running the parser in the console, example:
- window.wheic_parser.parse("<p>foo</p>", {fragment: "body"})
+ window.peach_parser.parse("<p>foo</p>", {fragment: "body"})
For further reading, see "Running Under node.js" below.
ret = ''
for el in dom
switch el.type
- when wheic_parser.TYPE_TAG
+ when peach_parser.TYPE_TAG
ret += '<' + el.name
attr_keys = []
for k of el.attrs
if el.children.length
ret += dom_to_html el.children
ret += "</#{el.name}>"
- when wheic_parser.TYPE_TEXT
+ when peach_parser.TYPE_TEXT
ret += el.text
- when wheic_parser.TYPE_COMMENT
+ when peach_parser.TYPE_COMMENT
ret += "<!--#{el.text}-->"
- when wheic_parser.TYPE_DOCTYPE
+ when peach_parser.TYPE_DOCTYPE
ret += "<!DOCTYPE #{el.name}"
if el.public_identifier? and el.public_identifier.length > 0
ret += " \"#{el.public_identifier}\""
return el
css = ''
-css += 'span.peach_editor_cursor {'
+css += 'div#peach_editor_cursor {'
css += 'display: inline-block;'
css += 'height: 1em;'
css += 'width: 2px;'
css += 'animation: 1s blink step-end infinite;'
css += '}'
css += '@-webkit-keyframes "blink" {'
-css += 'from, to { background: transparent; }'
-css += '50% { background: #000; }'
+css += 'from, to { background: #000; }'
+css += '50% { background: transparent; }'
css += '}'
css += '@keyframes "blink" {'
-css += 'from, to { background: transparent; }'
-css += '50% { background: #000; }'
+css += 'from, to { background: #000; }'
+css += '50% { background: transparent; }'
css += '}'
+# key codes:
+KEY_LEFT = 37
+KEY_UP = 38
+KEY_RIGHT = 39
+KEY_DOWN = 40
+KEY_BACKSPACE = 8 # <--
+KEY_DELETE = 46 # -->
+KEY_END = 35
+KEY_ENTER = 13
+KEY_ESCAPE = 27
+KEY_HOME = 36
+KEY_INSERT = 45
+KEY_PAGE_UP = 33
+KEY_PAGE_DOWN = 34
+KEY_TAB = 9
+
wysiwyg = (el, options = {}) ->
opt_fragment = options.fragment ? true
parser_opts = {}
dom: []
iframe: document.createElement('iframe')
load_html: (html) ->
- @dom = wheic_parser.parse html, parser_opts
- as_html = wheic.dom_to_html @dom
+ @dom = peach_parser.parse html, parser_opts
+ as_html = peach.dom_to_html @dom
as_html = as_html.substr(0, 5) + '<span class="peach_editor_cursor"></span>' + as_html.substr(5)
@iframe.contentDocument.body.innerHTML = as_html
}
el.parentNode.appendChild editor_instance.iframe
idoc = editor_instance.iframe.contentDocument
+ ignore_key_codes =
+ '18': true # alt
+ '20': true # capslock
+ '17': true # ctrl
+ '144': true # numlock
+ '16': true # shift
+ '91': true # windows "start" key
+ control_key_codes = # we react to these, but they aren't typing
+ '37': KEY_LEFT
+ '38': KEY_UP
+ '39': KEY_RIGHT
+ '40': KEY_DOWN
+ '35': KEY_END
+ '8': KEY_BACKSPACE
+ '46': KEY_DELETE
+ '13': KEY_ENTER
+ '27': KEY_ESCAPE
+ '36': KEY_HOME
+ '45': KEY_INSERT
+ '33': KEY_PAGE_UP
+ '34': KEY_PAGE_DOWN
+ '9': KEY_TAB
+
+ idoc.body.onkeyup = (e) ->
+ return false if ignore_key_codes[e.keyCode]?
+ return false if control_key_codes[e.keyCode]?
+ idoc.body.onkeydown = (e) ->
+ return false if ignore_key_codes[e.keyCode]?
+ return false if control_key_codes[e.keyCode]?
idoc.body.onkeypress = (e) ->
- char = e.charCode ? e.keyCode ? e.which
+ return if e.ctrlKey
+ return false if ignore_key_codes[e.keyCode]?
+ # in firefox, keyCode is only set for non-typing keys
+ if e.keyCode isnt KEY_BACKSPACE # so this is fine
+ return false if control_key_codes[e.keyCode]?
+ char = e.charCode ? e.keyCode
el.value += String.fromCharCode char
editor_instance.load_html el.value
return false
editor_instance.load_html el.value
return editor_instance
-window.wheic = {
+window.peach = {
wysiwyg: wysiwyg
dom_to_html: dom_to_html
}
-# test in browser: wheic(document.getElementsByTagName('textarea')[0])
+# test in browser: peach.wysiwyg(document.getElementsByTagName('textarea')[0])
else
# first run
button.parentNode.removeChild button
- editor = wheic.wysiwyg in_el
+ editor = peach.wysiwyg in_el
return false
button.value="Load"
button.removeAttribute 'disabled'
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <link rel="icon" href="data:;base64,iVBORw0KGgo=">
+ <title>html editor tester</title>
+ <style>
+ textarea {
+ box-sizing: border-box;
+ width: 100%;
+ }
+ </style>
+</head>
+<body>
+ <h1>Peach HTML5 Editor test page (partially compiled version)</h1>
+ <p>This version of the editor test page requires that you've compiled parser.js (to speed up page load) but it does compile editor.coffee and editor_tests.coffee in the browser so you don't have to rebuild them every time you make a change.</p>
+ <form action="#" method="get">
+ <p>In:<br><textarea rows="9" cols="22" name="in" id="in"><p>Normal <strong>Bold <em> Italic+Bold</strong> Italic</em> Normal</p></textarea></p>
+ <p><input id="button" type="submit" value="loading..." disabled></p>
+ </form>
+ <script src="parser.js"></script>
+ <script src="editor.coffee" type="text/coffeescript"></script>
+ <script src="editor_tests.coffee" type="text/coffeescript"></script>
+ <script src="coffee-script.js"></script>
+</body>
+</html>
</style>
</head>
<body>
- <h1>WHEIC editor test page (CoffeeScript version)</h1>
+ <h1>Peach HTML5 Editor test page (CoffeeScript version)</h1>
<p>This version of the test page compiles the CoffeeScript files in the browser. This is slower to load, but saves you having to rebuild as you work (or even install CoffeeScript).</p>
<form action="#" method="get">
<p>In:<br><textarea rows="9" cols="22" name="in" id="in"><p>Normal <strong>Bold <em> Italic+Bold</strong> Italic</em> Normal</p></textarea></p>
#
# Call it like this:
#
-# wheic_parser.parse("<p><b>hi</p>")
+# peach_parser.parse("<p><b>hi</p>")
#
# Or, if you don't want <html><head><body>/etc, do this:
#
-# wheic_parser.parse("<p><b>hi</p>", {fragment: "body"})
+# peach_parser.parse("<p><b>hi</p>", {fragment: "body"})
#
# return value is an array of Nodes, see "class Node" below.
exports = module.exports
else
context = 'browser'
- window.wheic_parser = {}
- exports = window.wheic_parser
+ window.peach_parser = {}
+ exports = window.peach_parser
from_code_point = (x) ->
if String.fromCodePoint?
]
if typeof module isnt 'undefined' and module.exports?
- wheic_parser = require './parser.coffee'
+ peach_parser = require './parser.coffee'
else
- wheic_parser = window.wheic_parser
+ peach_parser = window.peach_parser
serialize_els = (els, prefix = '| ') ->
ret = ''
for el in els
switch el.type
- when wheic_parser.TYPE_TAG
+ when peach_parser.TYPE_TAG
ret += "#{prefix}<"
- if el.namespace is wheic_parser.NS_MATHML
+ if el.namespace is peach_parser.NS_MATHML
ret += "math "
- if el.namespace is wheic_parser.NS_SVG
+ if el.namespace is peach_parser.NS_SVG
ret += "svg "
ret += "#{el.name}>\n"
attr_keys = []
attr_keys.sort() # TODO this should be "lexicographically by UTF-16 code unit"
for k in attr_keys
ret += "#{prefix} #{k}=\"#{el.attrs[k]}\"\n"
- if el.name is 'template' and el.namespace is wheic_parser.NS_HTML
+ if el.name is 'template' and el.namespace is peach_parser.NS_HTML
ret += "#{prefix} content\n"
ret += serialize_els el.children, "#{prefix} "
else
ret += serialize_els el.children, "#{prefix} "
- when wheic_parser.TYPE_TEXT
+ when peach_parser.TYPE_TEXT
ret += "#{prefix}\"#{el.text}\"\n"
- when wheic_parser.TYPE_COMMENT
+ when peach_parser.TYPE_COMMENT
ret += "#{prefix}<!-- #{el.text} -->\n"
- when wheic_parser.TYPE_DOCTYPE
+ when peach_parser.TYPE_DOCTYPE
ret += "#{prefix}<!DOCTYPE #{el.name}"
if (el.public_identifier? and el.public_identifier.length > 0) or (el.system_identifier? and el.system_identifier.length > 0)
ret += " \"#{el.public_identifier ? ''}\""
test_results = passed: 0, failed: 0
test_parser = (args) ->
- wheic_parser.debug_log_reset()
+ peach_parser.debug_log_reset()
parse_errors = []
args.error_cb = (i) ->
parse_errors.push i
prev_node_id = 0 # reset counter
- parsed = wheic_parser.parse args.html, args
+ parsed = peach_parser.parse args.html, args
serialized = serialize_els parsed
if serialized isnt args.expected
test_results.failed += 1
if test_results.failed is 1
- wheic_parser.debug_log_each (str) ->
+ peach_parser.debug_log_each (str) ->
console.log str
console.log "FAILED: \"#{args.name}\""
console.log " Input: #{args.html}"
<title>html parser tester</title>
</head>
<body>
- <h1>WHEIC html parser test page (javascript version)</h1>
+ <h1>Peach HTML5 Parser test page (javascript version)</h1>
<p>You'll need to run <code>make</code> to build the javascript files used on this page.</p>
<p>If you don't have node.js and CoffeeScript installed, you can <a href="parser_tests_coffee.html">use the other test page</a> which compiles the CoffeeScript files on the fly in the browser.</p>
<p>Check the inspector/console for test results.</p>
<title>html parser tester</title>
</head>
<body>
- <h1>WHEIC html parser test page (CoffeeScript version)</h1>
+ <h1>Peach HTML5 Parser test page (CoffeeScript version)</h1>
<p>This version of the test page compiles the CoffeeScript files in the browser, so you don't have to install CoffeeScript (or node.js).</p>
<p>It can take a few seconds for the tests to compile.</p>
<p>Check the inspector/console for test results.</p>