1 # Copyright 2015 Jason Woofenden
2 # This file implements an WYSIWYG editor in the browser (no contenteditable)
4 # This program is free software: you can redistribute it and/or modify it under
5 # the terms of the GNU Affero General Public License as published by the Free
6 # Software Foundation, either version 3 of the License, or (at your option) any
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # encode text so it can be safely placed inside an html attribute
18 enc_attr_regex = new RegExp '(&)|(")|(\u00A0)', 'g'
20 return txt.replace enc_attr_regex, (match, amp, quote) ->
21 return '&' if (amp)
22 return '"' if (quote)
42 dom_to_html = (dom) ->
46 when peach_parser.TYPE_TAG
54 if el.attrs[k].length > 0
55 ret += "=\"#{enc_attr el.attrs[k]}\""
57 unless void_elements[el.name]
59 ret += dom_to_html el.children
60 ret += "</#{el.name}>"
61 when peach_parser.TYPE_TEXT
63 when peach_parser.TYPE_COMMENT
64 ret += "<!--#{el.text}-->"
65 when peach_parser.TYPE_DOCTYPE
66 ret += "<!DOCTYPE #{el.name}"
67 if el.public_identifier? and el.public_identifier.length > 0
68 ret += " \"#{el.public_identifier}\""
69 if el.system_identifier? and el.system_identifier.length > 0
70 ret += " \"#{el.system_identifier}\""
77 return document.createTextNode attrs
78 el = document.createElement tag
88 css += 'div#peach_editor_cursor {'
89 css += 'display: inline-block;'
92 css += 'margin-left: -1px;'
93 css += 'margin-right: -1px;'
94 css += 'background: #000;'
95 css += '-webkit-animation: 1s blink step-end infinite;'
96 css += 'animation: 1s blink step-end infinite;'
98 css += '@-webkit-keyframes "blink" {'
99 css += 'from, to { background: #000; }'
100 css += '50% { background: transparent; }'
102 css += '@keyframes "blink" {'
103 css += 'from, to { background: #000; }'
104 css += '50% { background: transparent; }'
112 KEY_BACKSPACE = 8 # <--
113 KEY_DELETE = 46 # -->
123 wysiwyg = (el, options = {}) ->
124 opt_fragment = options.fragment ? true
127 parser_opts.fragment = 'body'
130 iframe: document.createElement('iframe')
132 @dom = peach_parser.parse html, parser_opts
133 as_html = peach.dom_to_html @dom
134 as_html = as_html.substr(0, 5) + '<span class="peach_editor_cursor"></span>' + as_html.substr(5)
135 @iframe.contentDocument.body.innerHTML = as_html
137 el.parentNode.appendChild editor_instance.iframe
138 idoc = editor_instance.iframe.contentDocument
141 '20': true # capslock
143 '144': true # numlock
145 '91': true # windows "start" key
146 control_key_codes = # we react to these, but they aren't typing
162 idoc.body.onkeyup = (e) ->
163 return false if ignore_key_codes[e.keyCode]?
164 return false if control_key_codes[e.keyCode]?
165 idoc.body.onkeydown = (e) ->
166 return false if ignore_key_codes[e.keyCode]?
167 return false if control_key_codes[e.keyCode]?
168 idoc.body.onkeypress = (e) ->
170 return false if ignore_key_codes[e.keyCode]?
171 # in firefox, keyCode is only set for non-typing keys
172 if e.keyCode isnt KEY_BACKSPACE # so this is fine
173 return false if control_key_codes[e.keyCode]?
174 char = e.charCode ? e.keyCode
175 el.value += String.fromCharCode char
176 editor_instance.load_html el.value
178 if options.stylesheet # TODO test this
179 istyle = idoc.createElement 'style'
180 istyle.setAttribute 'src', options.stylesheet
181 idoc.head.appendChild istyle
182 icss = idoc.createElement 'style'
183 icss.appendChild idoc.createTextNode css
184 idoc.head.appendChild icss
185 editor_instance.load_html el.value
186 return editor_instance
190 dom_to_html: dom_to_html
193 # test in browser: peach.wysiwyg(document.getElementsByTagName('textarea')[0])