From: Jason Woofenden Date: Tue, 8 Mar 2016 04:21:58 +0000 (-0500) Subject: nother iframe, auto-generate css, fix typing, break scrolling X-Git-Url: https://jasonwoof.com/gitweb/?p=peach-html5-editor.git;a=commitdiff_plain;h=896560198f6b7cbafced9ff64196679414abcf39 nother iframe, auto-generate css, fix typing, break scrolling --- diff --git a/editor.coffee b/editor.coffee index b6bc94b..224b2f9 100644 --- a/editor.coffee +++ b/editor.coffee @@ -15,7 +15,7 @@ # along with this program. If not, see . # SETTINGS -overlay_padding = 6 # TODO generate css from this +overlay_padding = 10 TYPE_TAG = peach_parser.TYPE_TAG TYPE_TEXT = peach_parser.TYPE_TEXT @@ -23,6 +23,7 @@ TYPE_COMMENT = peach_parser.TYPE_COMMENT TYPE_DOCTYPE = peach_parser.TYPE_DOCTYPE debug_dot_at = (doc, x, y) -> + return el = doc.createElement 'div' el.setAttribute 'style', "position: absolute; left: #{x}px; top: #{y}px; width: 1px; height: 3px; background-color: red" doc.body.appendChild el @@ -161,8 +162,8 @@ dom_to_html = (dom) -> ret += ">\n" return ret -domify = (h) -> - for tag, attrs of h +domify = (doc, hash) -> + for tag, attrs of hash if tag is 'text' return document.createTextNode attrs el = document.createElement tag @@ -175,7 +176,42 @@ domify = (h) -> return el css = '' -css += 'div#peach_html5_editor_cursor {' +css += 'body {' +css += 'margin: 0;' +css += 'padding: 0;' +css += '}' +css += '#wrap1 {' +css += 'border: 1px solid black;' +css += "padding: #{overlay_padding}px;" +css += '}' +css += '#wrap2 {' +css += 'border: 1px solid black;' +css += "padding: #{overlay_padding}px;" +css += '}' +css += '#wrap3 {' +css += 'position: relative;' +css += '}' +css += 'iframe {' +css += 'box-sizing: border-box;' +css += 'margin: 0;' +css += 'border: none;' +css += 'padding: 0;' +css += "width: #{300 - 4 - 4 * overlay_padding}px;" +css += "height: #{300 - 4 - 4 * overlay_padding}px;" +css += '}' +css += '#overlay {' +css += 'position: absolute;' +css += "left: -#{overlay_padding}px;" +css += "top: -#{overlay_padding}px;" +css += "right: -#{overlay_padding}px;" +css += "bottom: -#{overlay_padding}px;" +css += 'overflow: hidden;' +css += '}' +css += '.lightbox {' +css += 'position: absolute;' +css += 'background: rgba(100,100,100,0.2);' +css += '}' +css += '#cursor {' css += 'position: absolute;' css += 'height: 1em;' css += 'width: 2px;' @@ -208,6 +244,29 @@ KEY_PAGE_UP = 33 KEY_PAGE_DOWN = 34 KEY_TAB = 9 +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 + instantiate_tree = (tree, parent) -> for c in tree switch c.type @@ -466,156 +525,164 @@ tree_dedup_space = (tree) -> queue null class PeachHTML5Editor - constructor: (in_el, options = {}) -> + # Options: (all optional) + # editor_id: "id" attribute for outer-most element created by/for editor + constructor: (in_el, options) -> + @options = options ? {} @in_el = in_el @tree = [] - @outer_el = domify div: class: 'peach_html5_editor', children: [ - @iframe = domify iframe: class: 'peach_html5_editor_iframe' - @overlay = domify div: class: 'peach_html5_editor_overlay' - ] + @initialized = false # when iframes have loaded + @outer_iframe # iframe to hold editor + @outer_idoc # "document" object for @outer_iframe + @iframe = null # iframe to hold editable content + @idoc = null # "document" object for @iframe @cursor = null @cursor_el = null @cursor_visible = false - opt_fragment = options.fragment ? true + opt_fragment = @options.fragment ? true @parser_opts = {} if opt_fragment @parser_opts.fragment = 'body' - @iframe.onload = => - @idoc = @iframe.contentDocument + @outer_iframe = domify document, iframe: class: 'peach_html5_editor' + if @options.editor_id? + @outer_iframe.setAttribute 'id', @options.editor_id + @outer_iframe.onload = => + @outer_idoc = @outer_iframe.contentDocument + icss = domify @outer_idoc, style: children: [ + domify @outer_idoc, text: css + ] + @outer_idoc.head.appendChild icss + # FIXME continue - 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 - - @overlay.onclick = (e) => - x = (e.offsetX ? e.layerX) - overlay_padding - y = (e.offsetY ? e.layerY) - overlay_padding - new_cursor = find_loc_cursor_position @tree, x: x, y: y - if new_cursor? - @move_cursor new_cursor - @idoc.body.onkeyup = (e) => - return if e.ctrlKey - return false if ignore_key_codes[e.keyCode]? - #return false if control_key_codes[e.keyCode]? - @idoc.body.onkeydown = (e) => - return if e.ctrlKey - return false if ignore_key_codes[e.keyCode]? - #return false if control_key_codes[e.keyCode]? - switch e.keyCode - when KEY_LEFT - if @cursor? - new_cursor = find_prev_cursor_position @tree, @cursor... - if new_cursor? - @move_cursor new_cursor - else - for c in @tree - new_cursor = find_next_cursor_position @tree, c, -1 - if new_cursor? - @move_cursor new_cursor - break - return false - when KEY_UP - return false - when KEY_RIGHT - if @cursor? - new_cursor = find_next_cursor_position @tree, @cursor... - if new_cursor? - @move_cursor new_cursor - else - for c in @tree - new_cursor = find_prev_cursor_position @tree, c, -1 - if new_cursor? - @move_cursor new_cursor - break - return false - when KEY_DOWN - return false - when KEY_END - return false - when KEY_BACKSPACE - return false unless @cursor? - return false unless @cursor[1] > 0 - @cursor[0].text = @cursor[0].text.substr(0, @cursor[1] - 1) + @cursor[0].text.substr(@cursor[1]) - @cursor[0].el.nodeValue = @cursor[0].text - @move_cursor [@cursor[0], @cursor[1] - 1] - return false - when KEY_DELETE - return false unless @cursor? - return false unless @cursor[1] < @cursor[0].text.length - @cursor[0].text = @cursor[0].text.substr(0, @cursor[1]) + @cursor[0].text.substr(@cursor[1] + 1) - @cursor[0].el.nodeValue = @cursor[0].text - @move_cursor [@cursor[0], @cursor[1]] - return false - when KEY_ENTER - return false - when KEY_ESCAPE - return false - when KEY_HOME - return false - when KEY_INSERT - return false - when KEY_PAGE_UP - return false - when KEY_PAGE_DOWN - return false - when KEY_TAB - return false - @idoc.body.onkeypress = (e) => - return if e.ctrlKey - return false if ignore_key_codes[e.keyCode]? - return false if control_key_codes[e.keyCode]? # handled in keydown - char = e.charCode ? e.keyCode - if char and @cursor? - char = String.fromCharCode char - if @cursor[1] is 0 - @cursor[0].text = char + @cursor[0].text - else if @cursor[1] is @cursor[0].text.length - 1 - @cursor[0].text += char - else - @cursor[0].text = - @cursor[0].text.substr(0, @cursor[1]) + - char + - @cursor[0].text.substr(@cursor[1]) - @cursor[0].el.nodeValue = @cursor[0].text - @move_cursor [@cursor[0], @cursor[1] + 1] - @changed() + @iframe = domify @outer_idoc, iframe: {} + @iframe.onload = => + @init() + @outer_idoc.body.appendChild( + domify @outer_idoc, div: id: 'wrap1', children: [ + domify @outer_idoc, div: id: 'wrap2', children: [ + domify @outer_idoc, div: id: 'wrap3', children: [ + @iframe + @overlay = domify @outer_idoc, div: id: 'overlay' + ] + ] + ] + ) + @in_el.parentNode.appendChild @outer_iframe + init: -> # called by @iframe's onload + @idoc = @iframe.contentDocument + @overlay.onclick = (e) => + return @onclick e + @outer_idoc.body.onkeyup = (e) => + return @onkeyup e + @outer_idoc.body.onkeydown = (e) => + return @onkeydown e + @outer_idoc.body.onkeypress = (e) => + return @onkeypress e + if @options.stylesheet + # TODO test this + @idoc.head.appendChild domify @idoc, style: src: @options.stylesheet + @load_html @in_el.value + @initialized = true + if @options.initialized_cb? + @options.initialized_cb() + onclick: (e) -> + x = (e.offsetX ? e.layerX) - overlay_padding + y = (e.offsetY ? e.layerY) - overlay_padding + new_cursor = find_loc_cursor_position @tree, x: x, y: y + if new_cursor? + @move_cursor new_cursor + onkeyup: (e) -> + return if e.ctrlKey + return false if ignore_key_codes[e.keyCode]? + #return false if control_key_codes[e.keyCode]? + onkeydown: (e) -> + return if e.ctrlKey + return false if ignore_key_codes[e.keyCode]? + #return false if control_key_codes[e.keyCode]? + switch e.keyCode + when KEY_LEFT + if @cursor? + new_cursor = find_prev_cursor_position @tree, @cursor... + if new_cursor? + @move_cursor new_cursor + else + for c in @tree + new_cursor = find_next_cursor_position @tree, c, -1 + if new_cursor? + @move_cursor new_cursor + break 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 - document.head.appendChild icss - @load_html @in_el.value - - @in_el.parentNode.appendChild @outer_el - clear_dom: -> - # FIXME add parent node, so we don't empty body and delete cursor_el + when KEY_UP + return false + when KEY_RIGHT + if @cursor? + new_cursor = find_next_cursor_position @tree, @cursor... + if new_cursor? + @move_cursor new_cursor + else + for c in @tree + new_cursor = find_prev_cursor_position @tree, c, -1 + if new_cursor? + @move_cursor new_cursor + break + return false + when KEY_DOWN + return false + when KEY_END + return false + when KEY_BACKSPACE + return false unless @cursor? + return false unless @cursor[1] > 0 + @cursor[0].text = @cursor[0].text.substr(0, @cursor[1] - 1) + @cursor[0].text.substr(@cursor[1]) + @cursor[0].el.nodeValue = @cursor[0].text + @move_cursor [@cursor[0], @cursor[1] - 1] + return false + when KEY_DELETE + return false unless @cursor? + return false unless @cursor[1] < @cursor[0].text.length + @cursor[0].text = @cursor[0].text.substr(0, @cursor[1]) + @cursor[0].text.substr(@cursor[1] + 1) + @cursor[0].el.nodeValue = @cursor[0].text + @move_cursor [@cursor[0], @cursor[1]] + return false + when KEY_ENTER + return false + when KEY_ESCAPE + return false + when KEY_HOME + return false + when KEY_INSERT + return false + when KEY_PAGE_UP + return false + when KEY_PAGE_DOWN + return false + when KEY_TAB + return false + onkeypress: (e) -> + return if e.ctrlKey + return false if ignore_key_codes[e.keyCode]? + return false if control_key_codes[e.keyCode]? # handled in keydown + char = e.charCode ? e.keyCode + if char and @cursor? + char = String.fromCharCode char + if @cursor[1] is 0 + @cursor[0].text = char + @cursor[0].text + else if @cursor[1] is @cursor[0].text.length - 1 + @cursor[0].text += char + else + @cursor[0].text = + @cursor[0].text.substr(0, @cursor[1]) + + char + + @cursor[0].text.substr(@cursor[1]) + @cursor[0].el.nodeValue = @cursor[0].text + @move_cursor [@cursor[0], @cursor[1] + 1] + @changed() + return false + clear_dom: -> # remove all the editable content (and cursor, overlays, etc) while @idoc.body.childNodes.length @idoc.body.removeChild @idoc.body.childNodes[0] @kill_cursor() - @cursor_visible = false return load_html: (html) -> @tree = peach_parser.parse html, @parser_opts @@ -643,7 +710,7 @@ class PeachHTML5Editor # replace cursor, to reset blink animation if @cursor_visible @cursor_el.parentNode.removeChild @cursor_el - @cursor_el = domify div: id: 'peach_html5_editor_cursor' + @cursor_el = domify @outer_idoc, div: id: 'cursor' @overlay.appendChild @cursor_el @cursor_visible = true # TODO figure out x,y coords for cursor diff --git a/editor_tests_compiled.html b/editor_tests_compiled.html index d529b4f..712e36a 100644 --- a/editor_tests_compiled.html +++ b/editor_tests_compiled.html @@ -10,31 +10,12 @@ width: 100%; } .peach_html5_editor { - width: 300px; - height: 300px; - border: 1px solid black; - padding: 5px; - position: relative; - } - .peach_html5_editor_overlay { - position: absolute; - top: -1px; - left: -1px; - width: 312px; - height: 312px; - overflow: hidden; - } - .peach_html5_editor_lightbox { - position: absolute; - background: rgba(100,100,100,0.2); - } - .peach_html5_editor_iframe { box-sizing: border-box; - margin: 0; - border: none; - padding: 0; width: 300px; height: 300px; + border: 0; + margin: 10px 0; + padding: 0; } @@ -43,7 +24,7 @@

This version of the editor test page requires that you've compiled all the source files. (Just run make).

HTML view. Changes here propagate when you remove your cursor (press tab or click outside)