# Copyright 2015 Jason Woofenden
# This file implements an WYSIWYG editor in the browser (no contenteditable)
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
# encode text so it can be safely placed inside an html attribute
enc_attr_regex = new RegExp '(&)|(")|(\u00A0)', 'g'
enc_attr = (txt) ->
return txt.replace enc_attr_regex, (match, amp, quote) ->
return '&' if (amp)
return '"' if (quote)
return ' '
void_elements = {
area: true
base: true
br: true
col: true
embed: true
hr: true
img: true
input: true
keygen: true
link: true
meta: true
param: true
source: true
track: true
wbr: true
}
dom_to_html = (dom) ->
ret = ''
for el in dom
switch el.type
when wheic_parser.TYPE_TAG
ret += '<' + el.name
attr_keys = []
for k of el.attrs
attr_keys.unshift k
#attr_keys.sort()
for k in attr_keys
ret += " #{k}"
if el.attrs[k].length > 0
ret += "=\"#{enc_attr el.attrs[k]}\""
ret += '>'
unless void_elements[el.name]
if el.children.length
ret += dom_to_html el.children
ret += "#{el.name}>"
when wheic_parser.TYPE_TEXT
ret += el.text
when wheic_parser.TYPE_COMMENT
ret += ""
when wheic_parser.TYPE_DOCTYPE
ret += " 0
ret += " \"#{el.public_identifier}\""
if el.system_identifier? and el.system_identifier.length > 0
ret += " \"#{el.system_identifier}\""
ret += ">\n"
return ret
domify = (h) ->
for tag, attrs of h
if tag is 'text'
return document.createTextNode attrs
el = document.createElement tag
for k, v of attrs
if k is 'children'
for child in v
el.appendChild child
else
el.setAttribute k, v
return el
css = ''
css += 'span.peach_editor_cursor {'
css += 'display: inline-block;'
css += 'height: 1em;'
css += 'width: 2px;'
css += 'margin-left: -1px;'
css += 'margin-right: -1px;'
css += 'background: #000;'
css += '-webkit-animation: 1s blink step-end infinite;'
css += 'animation: 1s blink step-end infinite;'
css += '}'
css += '@-webkit-keyframes "blink" {'
css += 'from, to { background: transparent; }'
css += '50% { background: #000; }'
css += '}'
css += '@keyframes "blink" {'
css += 'from, to { background: transparent; }'
css += '50% { background: #000; }'
css += '}'
wysiwyg = (el, options = {}) ->
opt_fragment = options.fragment ? true
parser_opts = {}
if opt_fragment
parser_opts.fragment = 'body'
editor_instance = {
dom: []
iframe: document.createElement('iframe')
load_html: (html) ->
@dom = wheic_parser.parse html, parser_opts
as_html = wheic.dom_to_html @dom
as_html = as_html.substr(0, 5) + '' + as_html.substr(5)
@iframe.contentDocument.body.innerHTML = as_html
}
el.parentNode.appendChild editor_instance.iframe
idoc = editor_instance.iframe.contentDocument
idoc.body.onkeypress = (e) ->
char = e.charCode ? e.keyCode ? e.which
el.value += String.fromCharCode char
editor_instance.load_html el.value
return false
if options.stylesheet # TODO test this
istyle = idoc.createElement 'style'
istyle.setAttribute 'src', options.stylesheet
idoc.head.appendChild istyle
icss = idoc.createElement 'style'
icss.appendChild idoc.createTextNode css
idoc.head.appendChild icss
editor_instance.load_html el.value
return editor_instance
window.wheic = {
wysiwyg: wysiwyg
dom_to_html: dom_to_html
}
# test in browser: wheic(document.getElementsByTagName('textarea')[0])