X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;f=editor.coffee;h=60ea8320146939ded271e99d06aa276360a4d29e;hb=bcb9978cd02712b68daf64155fb6086e4ad8edc0;hp=a0bd0a41d48864e6120bf7c2bfa90d4b56de99b8;hpb=5de51db974972ef00b21121b7472075c0222b551;p=peach-html5-editor.git diff --git a/editor.coffee b/editor.coffee index a0bd0a4..60ea832 100644 --- a/editor.coffee +++ b/editor.coffee @@ -559,21 +559,32 @@ tree_dedup_space = (tree) -> throw "how is this possible?" next_i -= 1 return 1 - whitespace_to_space = (undo) -> + replace_with_space = (undo) -> if undo cur.text = (cur.text.substr 0, cur_i) + removed_char + (cur.text.substr cur_i + 1) cur.el.textContent = cur.text else removed_char = cur.text.charAt(cur_i) - cur.text = (cur.text.substr 0, cur_i) + ' ' + (cur.text.substr cur_i + 1) - cur.el.textContent = cur.text + if removed_char isnt ' ' + cur.text = (cur.text.substr 0, cur_i) + ' ' + (cur.text.substr cur_i + 1) + cur.el.textContent = cur.text return 0 # return true if cur was removed from the dom (ie re-use same prev) operate = -> # cur definitately set # prev and/or next might be null, indicating the start/end of a display:block return false unless is_space_code cur.text.charCodeAt cur_i - fixers = [remove, whitespace_to_space] + fixers = [remove, replace_with_space] + # check for common case: single whitespace surrounded by non-whitespace chars + if prev? and next? + unless (is_space_code prev.text.charCodeAt prev_i) or (is_space_code next.text.charCodeAt next_i) + dbg = cur.text.charCodeAt cur_i + if cur.text.charAt(cur_i) is ' ' # perens required + # single space can't collapse, doesn't need fixin' + return false + else + # tab, newline, etc, can't collapse, but maybe should be replaced + fixers = [replace_with_space] bounds = text_range_bounds cur.el, cur_i, cur_i + 1 # consistent cases: # 1. zero rects returned by getClientRects() means collapsed space @@ -581,7 +592,8 @@ tree_dedup_space = (tree) -> return remove() # 2. width greater than zero means visible space if bounds.w > 0 - fixers.shift() # don't try removing + # has bounds, don't try removing + fixers = [replace_with_space] # now the weird edge cases... # # firefox and chromium both report zero width for characters at the end @@ -590,6 +602,11 @@ tree_dedup_space = (tree) -> # collapsed spaces via the range/bounds api, so... # # remove it from the dom, and if prev or next moves, put it back. + # + # this block (try changing it, put it back if something moves) is also + # used on collapsable whitespace characters besides space. In this case + # the character is replaced with a normal space character instead of + # removed if prev? and not prev_px? prev_px = new_cursor_position n: prev, i: prev_i if next? and not next_px? @@ -1013,6 +1030,18 @@ class PeachHTML5Editor throw 'bork bork' unless new_cursor? @move_cursor new_cursor # TODO move content past cursor into this new block + # unlike the global function, this takes a Node, not an element + is_display_block: (n) -> + # TODO stop calling global function, merge it into here, use iframe's window object + return false unless n.type is 'tag' + return is_display_block n.el + find_block_parent: (n) -> + loop + n = n.parent + return null unless n? + return n if is_display_block n.el + return n if n is @tree_parent + return null on_key_backspace: (e) -> return false unless @cursor? if @is_lone_space @cursor.n # false if it's not in a tag @@ -1025,6 +1054,7 @@ class PeachHTML5Editor else @kill_cursor() else + # cursor at the begining of an element that contains only a space parent = @cursor.n.parent new_cursor = find_prev_cursor_position @tree, @cursor if new_cursor? @@ -1052,16 +1082,83 @@ class PeachHTML5Editor else @kill_cursor return - else if @cursor.i is 0 - console.log 'unimplemented: backspace at start of non-empty tag' - # TODO if block, merge parent into prev - # TODO if inline, delete char from prev text node - return false + else if @cursor.i is 0 # start of text chunk + # FIXME clean this up: use new code for text runs + # FIXME handle backspacing a
even if it's near a inline tag boundary + # determine if cursor is at start of text run (text formatted inline) + block = @find_block_parent @cursor.n + return unless block + at_block_start = true + prev_pos = find_prev_cursor_position @tree, @cursor + unless prev_pos? + # if the cursor can't go back, then there's probably nowhere we can merge into + # TODO consider case of nested blocks. should backspace remove one? + return + prev_pos_block = @find_block_parent prev_pos.n + if prev_pos_block is block + # context: there is text before the cursor within the same block. + # FIXME clean up this hack for looking for
(see above) + cursor_text_pi = @cursor.n.parent.children.indexOf @cursor.n + if cursor_text_pi > 0 + prev_node = @cursor.n.parent.children[cursor_text_pi - 1] + if prev_node.type is 'tag' and prev_node.name is 'br' + @remove_node prev_node + @text_cleanup @cursor.n.parent + @changed() + new_cursor = new_cursor_position n: prev_pos.n, i: prev_pos.i + if new_cursor? + @move_cursor new_cursor + else + @kill_cursor + return + # note: find_prev_cursor_position just crossed a boundary, not a character + # prev_pos is within the same block, try deleting there + @move_cursor prev_pos + # FIXME cleanup: don't call @move_cursor twice if the next line succeeds + return @on_key_backspace() + # context: backspace pressed at start of a display:block + return if block is @tree_parent # top level text + parent = block.parent + parent_i = parent.children.indexOf block + if parent_i is -1 + throw "BUG #98270918347" + return + if parent_i is 0 + # no previous sibling to merge into, so instead move contents into parent + dest = parent + before = block + else + # FIXME prev_sib should be the previous in-flow element + # ie it should skip comments, hidden things, floating things, etc. + prev_sib = parent.children[parent_i - 1] + if @is_display_block prev_sib + dest = prev_sib + before = null # null means append + else + dest = parent + before = block + if dest is @tree_parent + # don't remove outer-most blocks + return + while block.children.length > 0 + n = block.children[block.children.length - 1] + @move_node n, dest, before + block.children.pop() + before = n + @remove_node block + @text_cleanup dest + @changed() + new_cursor = new_cursor_position n: prev_pos.n, i: prev_pos.i + if new_cursor? + @move_cursor new_cursor + else + @kill_cursor + return else # TODO handle case of removing last char # CONTINUE if @is_only_char_in_tag @cursor.n - if is_display_block @cursor.n.parent.el + if @is_display_block @cursor.n.parent @cursor.n.el.textContent = @cursor.n.text = ' ' else console.log "unimplemented: delete last char in inline" # FIXME @@ -1192,6 +1289,7 @@ class PeachHTML5Editor # does this node have whitespace that would be collapsed by white-space: normal? # note: this checks direct text children, and does _not_ recurse into child tags # tag is a node with type:"tag" + # FIXME use new textrun api has_collapsable_space: (tag) -> for n in tag.children if n.type is 'text' @@ -1209,6 +1307,7 @@ class PeachHTML5Editor return true if is_space_code n.text.charCodeAt n.text.length - 1 return true + return false # add/remove "white-space: pre[-wrap]" to/from style="" on tags with direct # child text nodes with multiple spaces in a row, or spaces at the # start/end. @@ -1216,9 +1315,11 @@ class PeachHTML5Editor # text inside child tags are not consulted. Child tags are expected to have # this function applied to them when their content changes. adjust_whitespace_style: (n) -> - if n.type is 'text' + loop + break if @is_display_block n n = n.parent - return unless n?.el? + return unless n? + return if n is @tree_parent # which css rule should be used to preserve spaces (should we need to) style = @iframe.contentWindow.getComputedStyle n.el, null ws = style.getPropertyValue 'white-space' @@ -1277,8 +1378,7 @@ class PeachHTML5Editor return false # after calling this, you MUST call changed() and adjust_whitespace_style() insert_character: (n, i, char) -> - return if @cursor.n.parent is @tree_parent # FIXME implement text nodes at top level - parent = @cursor.n.parent + return if n.parent is @tree_parent # FIXME implement text nodes at top level # insert the character if @insert_should_replace n, i n.text = char @@ -1293,16 +1393,57 @@ class PeachHTML5Editor char + n.text.substr(i) n.el.nodeValue = n.text - # after calling this, you MUST call changed() and adjust_whitespace_style() + # WARNING: after calling this, you MUST call changed() and adjust_whitespace_style() remove_character: (n, i) -> n.text = n.text.substr(0, i) + n.text.substr(i + 1) n.el.nodeValue = n.text + # call this after you insert or remove inline nodes. It will: + # merge consecutive text nodes + # remove empty text nodes + # adjust white-space property + text_cleanup: (n) -> + return + # FIXME implement this + # WARNING: after calling this one or more times, you MUST: + # if it's inline: call @text_cleanup + # call @changed() + remove_node: (n) -> + i = n.parent.children.indexOf n + if i is -1 + throw "BUG #9187112313" + n.el.parentNode.removeChild n.el + n.parent.children.splice i, 1 + return + # remove a node from the tree/dom, insert into new_parent before insert_before?end + # WARNING: after calling this one or more times, you MUST: + # if it's inline: call @text_cleanup + # call @changed() + move_node: (n, new_parent, insert_before = null) -> + i = n.parent.children.indexOf n + if i is -1 + throw "Error: tried to remove node, but it's not in it's parents list of children" + return + if insert_before? + before_i = new_parent.children.indexOf insert_before + if i is -1 + throw "Error: tried to move a node to be before a non-existent node" + insert_before = insert_before.el + @remove_node n + if insert_before? + new_parent.el.insertBefore n.el, insert_before + new_parent.children.splice before_i, 0, n + else + new_parent.el.appendChild n.el, insert_before + new_parent.children.push n + n.parent = new_parent + return kill_cursor: -> # remove it, forget where it was if @cursor_visible @cursor_el.parentNode.removeChild @cursor_el @cursor_visible = false @cursor = null @annotate null + return move_cursor: (cursor) -> @cursor_ideal_x = cursor.x @cursor = cursor @@ -1319,6 +1460,7 @@ class PeachHTML5Editor @cursor_el.style.height = "#{Math.round height * 0.82}px" @annotate cursor.n @scroll_into_view cursor.y, height + return scroll_into_view: (y, h = 0) -> y += overlay_padding # convert units from @idoc to @wrap2 # very top of document