JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
auto set/clear whitespace css on delete/backspace too
authorJason Woofenden <jason@jasonwoof.com>
Sun, 20 Mar 2016 01:39:47 +0000 (21:39 -0400)
committerJason Woofenden <jason@jasonwoof.com>
Sun, 20 Mar 2016 01:39:47 +0000 (21:39 -0400)
editor.coffee

index 98c199c..b6f28f0 100644 (file)
@@ -882,26 +882,26 @@ class PeachHTML5Editor
                        when KEY_BACKSPACE
                                return false unless @cursor?
                                return false unless @cursor.i > 0
-                               @cursor.n.text = @cursor.n.text.substr(0, @cursor.i - 1) + @cursor.n.text.substr(@cursor.i)
-                               @cursor.n.el.nodeValue = @cursor.n.text
+                               @remove_character @cursor.n, @cursor.i - 1
+                               @adjust_whitespace_style @cursor.n
+                               @changed()
                                new_cursor = new_cursor_position n: @cursor.n, i: @cursor.i - 1
                                if new_cursor?
                                        @move_cursor new_cursor
                                else
                                        @kill_cursor()
-                               @changed()
                                return false
                        when KEY_DELETE
                                return false unless @cursor?
                                return false unless @cursor.i < @cursor.n.text.length
-                               @cursor.n.text = @cursor.n.text.substr(0, @cursor.i) + @cursor.n.text.substr(@cursor.i + 1)
-                               @cursor.n.el.nodeValue = @cursor.n.text
+                               @remove_character @cursor.n, @cursor.i
+                               @adjust_whitespace_style @cursor.n
+                               @changed()
                                new_cursor = new_cursor_position n: @cursor.n, i: @cursor.i
                                if new_cursor?
                                        @move_cursor new_cursor
                                else
                                        @kill_cursor()
-                               @changed()
                                return false
                        when KEY_ENTER
                                return false
@@ -924,13 +924,14 @@ class PeachHTML5Editor
                if char and @cursor?
                        char = String.fromCharCode char
                        @insert_character @cursor.n, @cursor.i, char
+                       @adjust_whitespace_style @cursor.n
+                       @changed()
                        new_cursor = new_cursor_position n: @cursor.n, i: @cursor.i + 1
                        if new_cursor
                                @move_cursor new_cursor
                        else
                                console.log "ERROR: couldn't find cursor position after insert"
                                @kill_cursor()
-                       @changed()
                return false
        clear_dom: -> # remove all the editable content (and cursor, overlays, etc)
                while @idoc.body.childNodes.length
@@ -957,72 +958,72 @@ class PeachHTML5Editor
                        @iframe.style.height = "0"
                        @iframe.style.height = "#{h}px"
                        @wrap2.scrollTop = s
-       # Warning: this does not call changed() for you
+       # 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"
+       has_collapsable_space: (tag) ->
+               for n in tag.children
+                       if n.type is 'text'
+                               for i in [0...n.text.length]
+                                       code = n.text.charCodeAt i
+                                       if code isnt 32 and is_space_code code
+                                               # tab, return
+                                               return true
+                                       # check for double spaces that don't surround insert location
+                                       continue if i is 0
+                                       if n.text.substr(i - 1, 2) is '  '
+                                               return true
+                               if n.text.length > 0
+                                       if is_space_code n.text.charCodeAt 0
+                                               return true
+                                       if is_space_code n.text.charCodeAt n.text.length - 1
+                                               return true
+       # 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.
+       #
+       # 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'
+                       n = n.parent
+                       return unless n?.el?
+               # 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'
+               if ws_props[ws].space
+                       preserve_rule = ws
+               else
+                       preserve_rule = ws_props[ws].to_preserve
+               preserve_rule = "white-space: #{preserve_rule}"
+               if @has_collapsable_space n
+                       # make sure preserve_rule exists
+                       if n.el.style['white-space']
+                               # FIXME check that it matches
+                               return
+                       if n.attrs[style]?
+                               n.attrs.style += "; #{preserve_rule}"
+                       else
+                               n.attrs.style = preserve_rule
+                       n.el.setAttribute 'style', n.attrs.style
+               else
+                       # remove preserve_rule if it exists
+                       return unless n.attrs.style?
+                       # FIXME don't assume whitespace is just so
+                       if n.attrs.style is "white-space: #{ws}"
+                               delete n.attrs.style
+                               n.el.removeAttribute 'style'
+                       else
+                               # FIXME find it in the middle and at the start
+                               needle = "; white-space: #{ws}"
+                               if needle is n.attrs.style.substr n.attrs.style.length - needle
+                                       n.attrs.style = n.attrs.style.substr 0, n.attrs.style.length - needle
+                                       n.el.setAttribute n.attrs.style
+       # after calling this, you MUST call changed() and adjust_whitespace_style()
        insert_character: (n, i, char) ->
-               # TODO handle newlines, tabs, etc
                parent = @cursor.n.parent
                return unless parent
                return unless parent.el?
-               style = @iframe.contentWindow.getComputedStyle parent.el, null
-               ws = style.getPropertyValue 'white-space'
-               if char is ' '
-                       unless ws_props[ws].space
-                               change = false
-                               if i is 0
-                                       # TODO check if a space at the beginning would actually get collapsed
-                                       change = true
-                               else if i is n.text.length
-                                       change = true
-                                       # TODO check if a space at the end would actually get collapsed
-                               else
-                                       if n.text.charAt(i - 1) is ' ' or n.text.charAt(i) is ' '
-                                               change = true
-                               if change
-                                       rule = "white-space: #{ws_props[ws].to_preserve}"
-                                       if parent.attrs[style]?
-                                               parent.attrs.style += "; #{rule}"
-                                       else
-                                               parent.attrs.style = rule
-                                       parent.el.setAttribute 'style', parent.attrs.style
-               else
-                       # TODO test this
-                       # inserting a visible (non-space) character
-                       if ws_props[ws].space
-                               if parent.el.style?['white-space']
-                                       # This node has a "white-space" property on it
-                                       # probably created automatically by this editor
-                                       # when the user pressed space.
-                                       # Check if that's no longer needed.
-                                       need = false
-                                       for ti in [0...n.text.length]
-                                               code = n.text.charCodeAt ti
-                                               if code isnt 32 and is_space_code code
-                                                       # tab, return
-                                                       need = true
-                                                       break
-                                               # check for double spaces that don't surround insert location
-                                               continue if ti is i
-                                               continue if ti is 0
-                                               if n.text.substr(ti - 1, 2) is '  '
-                                                       need = true
-                                                       break
-                                       if i > 0
-                                               if 32 is n.text.charCodeAt 0
-                                                       need = true
-                                       if i < n.text.length
-                                               if 32 is n.text.charCodeAt n.text.length - 1
-                                                       need = true
-                                       unless need
-                                               # TODO don't assume whitespace is just so
-                                               if parent.attrs.style is "white-space: #{ws}"
-                                                       delete parent.attrs.style
-                                                       parent.el.removeAttribute 'style'
-                                               else
-                                                       # FIXME find it in the middle and at the start
-                                                       needle = "; white-space: #{ws}"
-                                                       if needle is parent.attrs.style.substr parent.attrs.style.length - needle
-                                                               parent.attrs.style = parent.attrs.style.substr 0, parent.attrs.style.length - needle
-                                                               parent.el.setAttribute parent.attrs.style
                # insert the character
                if i is 0
                        n.text = char + n.text
@@ -1034,11 +1035,10 @@ class PeachHTML5Editor
                                char +
                                n.text.substr(i)
                n.el.nodeValue = n.text
-               # TODO call this when the user types
-               # TODO detect when typing produces a collapsing space
+       # after calling this, you MUST call changed() and adjust_whitespace_style()
        remove_character: (n, i) ->
-               # TODO call this from delete and backspace key handlers
-               # TODO detect if this would result in collapsing space
+               n.text = n.text.substr(0, i) + n.text.substr(i + 1)
+               n.el.nodeValue = n.text
        kill_cursor: -> # remove it, forget where it was
                if @cursor_visible
                        @cursor_el.parentNode.removeChild @cursor_el