From 86f02a96d1a6ee53a3cf680df7d14b8f7cb59b7d Mon Sep 17 00:00:00 2001 From: Jason Woofenden Date: Tue, 12 Apr 2016 02:18:09 -0400 Subject: [PATCH] mostly working text-run cleanup --- editor.coffee | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 172 insertions(+), 12 deletions(-) diff --git a/editor.coffee b/editor.coffee index 60ea832..4b26b31 100644 --- a/editor.coffee +++ b/editor.coffee @@ -59,15 +59,19 @@ ws_props = space: true newline: true wrap: false + to_collapse: 'nowrap' 'pre-wrap': space: true newline: true wrap: true + to_collapse: 'normal' # xml 1.0 spec, chromium and firefox accept these, plus lots of unicode chars valid_attr_regex = new RegExp '^[a-zA-Z_:][-a-zA-Z0-9_:.]*$' # html5 spec is much more lax, but chromium won't let me make at attribute with the name "4" js_attr_regex = new RegExp '^[oO][nN].' +# html5 spec says that only these characters are collapsable +multi_sp_regex = new RegExp '[\u0020\u0009\u000a\u000c\u000d][\u0020\u0009\u000a\u000c\u000d]' debug_dot_at = (doc, x, y) -> return # disabled @@ -318,8 +322,8 @@ first_cursor_position = (tree) -> cursor = new_cursor_position n: node, i: 0 if cursor? found = cursor - return true - return false + return true # done traversing + return false # not done traversing return found # maybe null # this will fail when text has non-locatable cursor positions @@ -335,10 +339,10 @@ find_next_cursor_position = (tree, cursor) -> new_cursor = new_cursor_position n: node, i: 0 if new_cursor? found = new_cursor - return true + return true # done traversing if node is cursor.n state_before = false - return false + return false # not done traversing if found? return found return null @@ -350,7 +354,7 @@ last_cursor_position = (tree) -> cursor = new_cursor_position n: node, i: node.text.length if cursor? found = cursor - return false + return false # not done traversing return found # maybe null # this will fail when text has non-locatable cursor positions @@ -364,12 +368,12 @@ find_prev_cursor_position = (tree, cursor) -> traverse_tree tree, (node) -> if node is cursor.n found = found_prev # maybe null - return true + return true # done traversing if node.type is 'text' new_cursor = new_cursor_position n: node, i: node.text.length if new_cursor? found_prev = new_cursor - return false + return false # not done traversing return found # maybe null find_up_cursor_position = (tree, cursor, ideal_x) -> @@ -504,7 +508,7 @@ tree_remove_empty_text_nodes = (tree) -> if n.type is 'text' if n.text.length is 0 empties.unshift n - return false + return false # not done traversing for n in empties # don't completely empty the tree if tree.length is 1 @@ -1039,9 +1043,31 @@ class PeachHTML5Editor loop n = n.parent return null unless n? - return n if is_display_block n.el + return n if @is_display_block n return n if n is @tree_parent return null + # return a flat array of nodes (text,
, and later also inline-block) + # that are flowing/wrapping together. n can be the containing block, or any + # element inside it. + get_text_run: (n) -> + if @is_display_block n + block = n + else + block = @find_block_parent n + return unless block? + ret = [] + traverse_tree n.children, (n) => + if n.type is 'text' + ret.push n + else if n.type is 'tag' + if n.name is 'br' + ret.push n + else + disp = @computed_style n + if disp is 'inline-block' + ret.push n + return false # not done traversing + return ret on_key_backspace: (e) -> return false unless @cursor? if @is_lone_space @cursor.n # false if it's not in a tag @@ -1143,7 +1169,6 @@ class PeachHTML5Editor 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 @@ -1314,6 +1339,8 @@ class PeachHTML5Editor # # text inside child tags are not consulted. Child tags are expected to have # this function applied to them when their content changes. + # + # FIXME stop using this and delete it. use @text_cleanup instead adjust_whitespace_style: (n) -> loop break if @is_display_block n @@ -1397,13 +1424,146 @@ class PeachHTML5Editor remove_character: (n, i) -> n.text = n.text.substr(0, i) + n.text.substr(i + 1) n.el.nodeValue = n.text + computed_style: (n, prop) -> + if n.type is 'text' + n = n.parent + style = @iframe.contentWindow.getComputedStyle n.el, null + return style.getPropertyValue prop + # returns the new white-space value that will preserve spaces for node n + preserve_space: (n, ideal_target) -> + if n.type is 'text' + target = n.parent + else + target = n + while target isnt ideal_target and not target.el.style.whiteSpace + unless target? + console.log "bug #967123" + return + target = target.parent + ws = ws_props[target.el.style.whiteSpace]?.to_preserve + ws ?= 'pre-wrap' + target.el.style.whiteSpace = ws + @update_style_from_el target + return ws + update_style_from_el: (n) -> + style = n.el.getAttribute 'style' + if style? + n.attrs.style = style + else + if n.attrs.style? + delete n.attrs.style # 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) -> + if @is_display_block n + block = n + else + block = @find_block_parent n + return unless block? + run = @get_text_run block + return unless run? + # merge consecutive text nodes + if run.length > 1 + i = 1 + prev = run[0] + while i < run.length + n = run[i] + if prev.type is 'text' and n.type is 'text' + if prev.parent is n.parent + prev_i = n.parent.children.indexOf prev + n_i = n.parent.children.indexOf n + if n_i is prev_i + 1 + prev.text = prev.text + n.text + prev.el.textContent = prev.text + @remove_node n + run.splice i, 1 + continue # don't increment i or change prev + i += 1 + prev = n + # remove empty text nodes + i = 0 + while i < run.length + n = run[i] + if n.type is 'text' + if n.text is '' + @remove_node n + # FIXME maybe remove parents recursively if this makes them empty + run.splice i, 1 + continue # don't increment i + i += 1 + # note: inline tags can have white-space:pre-line/etc + # note: inline-blocks have their whitespace collapsed independantly of outer run + # note: inline-blocks are treated like non-whitespace char even if empty + if block.el.style.whiteSpace? + ws = block.el.style.whiteSpace + if ws_props[ws] + if ws_props[ws].space + if ws_props[ws].to_collapse is 'normal' + block.el.style.whiteSpace = null + else + block.el.style.whiteSpace = ws_props[ws].to_collapse + @update_style_from_el block + # note: space after
colapses, but not space before + # check for spaces that would collapse without help + eats_start_sp = true # if the next node starts with space it collapses (unless pre) + prev = null + for n in run + if n.type is 'tag' + if n.name is 'br' + eats_start_sp = true + else + eats_start_sp = false + else # TEXT + need_preserve = false + if n.type isnt 'text' + console.log "bug #232308" + return + if eats_start_sp + if is_space_code n.text.charCodeAt 0 + need_preserve = true + unless need_preserve + need_preserve = multi_sp_regex.test n.text + if need_preserve + # do we have it already? + ws = @computed_style n, 'white-space' # FIXME implement this + unless ws_props[ws]?.space + # 2nd arg is ideal target for css rule + ws = @preserve_space n, block + eats_start_sp = false + else + if is_space_code n.text.charCodeAt(n.text.length - 1) + ws = @computed_style n, 'white-space' # FIXME implement this + if ws_props[ws]?.space + eats_start_sp = false + else + eats_start_sp = true + else + eats_start_sp = false + # check if text ends with a collapsable space + if run.length > 0 + last = run[run.length - 1] + if last.type is 'text' + if eats_start_sp + @preserve_space last, block + return + css_clear: (n, prop) -> + return unless n.attrs.style? + return if n.attrs.style is '' + css_delimiter_regex = new RegExp('\s*;\s*', 'g') # FIXME make this global + styles = n.attrs.style.trim().split css_delimiter + return unless styles.length > 0 + if styles[styles.length - 1] is '' + styles.pop() + return unless styles.length > 0 + i = 0 + while i < styles.length + if styles[i].substr(0, 12) is 'white-space:' + styles.splice i, 1 + else + i += 1 return - # FIXME implement this # WARNING: after calling this one or more times, you MUST: # if it's inline: call @text_cleanup # call @changed() @@ -1435,7 +1595,7 @@ class PeachHTML5Editor else new_parent.el.appendChild n.el, insert_before new_parent.children.push n - n.parent = new_parent + n.parent = new_parent return kill_cursor: -> # remove it, forget where it was if @cursor_visible -- 1.7.10.4