Skip to content

Commit

Permalink
Customize normalizer based on added formats
Browse files Browse the repository at this point in the history
  • Loading branch information
jhchen committed Jan 13, 2015
1 parent 733b0a2 commit 8f61dc9
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 71 deletions.
8 changes: 5 additions & 3 deletions src/core/document.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Normalizer = require('./normalizer')

class Document
constructor: (@root, options = {}) ->
@normalizer = new Normalizer()
@formats = {}
_.each(options.formats, _.bind(this.addFormat, this))
this.setHTML(@root.innerHTML)
Expand All @@ -17,6 +18,7 @@ class Document
config = Format.FORMATS[name] unless _.isObject(config)
console.warn('Overwriting format', name, @formats[name]) if @formats[name]?
@formats[name] = new Format(config)
@normalizer.addFormat(config)

appendLine: (lineNode) ->
return this.insertLineBefore(lineNode, null)
Expand Down Expand Up @@ -90,21 +92,21 @@ class Document
while line.node != lineNode
if line.node.parentNode == @root or line.node.parentNode?.parentNode == @root
# New line inserted
lineNode = Normalizer.normalizeLine(lineNode)
lineNode = @normalizer.normalizeLine(lineNode)
newLine = this.insertLineBefore(lineNode, line)
lineNode = dom(lineNode).nextLineNode(@root)
else
# Existing line removed
return this.removeLine(line)
if line.outerHTML != lineNode.outerHTML
# Existing line changed
line.node = Normalizer.normalizeLine(line.node)
line.node = @normalizer.normalizeLine(line.node)
line.rebuild()
lineNode = dom(lineNode).nextLineNode(@root)
)
# New lines appended
while lineNode?
lineNode = Normalizer.normalizeLine(lineNode)
lineNode = @normalizer.normalizeLine(lineNode)
this.appendLine(lineNode)
lineNode = dom(lineNode).nextLineNode(@root)

Expand Down
4 changes: 2 additions & 2 deletions src/core/line.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Line extends LinkedList.Node

buildLeaves: (node, formats) ->
_.each(dom(node).childNodes(), (node) =>
node = Normalizer.normalizeNode(node)
node = @doc.normalizer.normalizeNode(node)
nodeFormats = _.clone(formats)
# TODO: optimize
_.each(@doc.formats, (format, name) ->
Expand Down Expand Up @@ -140,7 +140,7 @@ class Line extends LinkedList.Node
return dom(leaf.node).isAncestor(@node)
)
return false
@node = Normalizer.normalizeNode(@node)
@node = @doc.normalizer.normalizeNode(@node)
if dom(@node).length() == 0 and !@node.querySelector(dom.DEFAULT_BREAK_TAG)
@node.appendChild(document.createElement(dom.DEFAULT_BREAK_TAG))
@leaves = new LinkedList()
Expand Down
115 changes: 55 additions & 60 deletions src/core/normalizer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ _ = require('lodash')
dom = require('../lib/dom')


camelize = (str) ->
str = str.replace(/(?:^|[-_])(\w)/g, (i, c) ->
return if c then c.toUpperCase() else ''
)
return str.charAt(0).toLowerCase() + str.slice(1)


class Normalizer
@ALIASES: {
'STRONG' : 'B'
Expand All @@ -10,30 +17,56 @@ class Normalizer
'STRIKE' : 'S'
}

@STYLES: {
'background-color'
'color'
'font-family'
'font-size'
'text-align'
}
constructor: ->
@whitelist =
styles: {}
tags: {}
@whitelist.tags[dom.DEFAULT_BREAK_TAG] = true
@whitelist.tags[dom.DEFAULT_BLOCK_TAG] = true
@whitelist.tags[dom.DEFAULT_INLINE_TAG] = true

addFormat: (config) ->
@whitelist.tags[config.tag] = true if config.tag?
@whitelist.tags[config.parentTag] = true if config.parentTag?
@whitelist.styles[config.style] = true if config.style?

normalizeLine: (lineNode) ->
lineNode = Normalizer.wrapInline(lineNode)
lineNode = Normalizer.handleBreaks(lineNode)
lineNode = Normalizer.pullBlocks(lineNode)
lineNode = this.normalizeNode(lineNode)
Normalizer.unwrapText(lineNode)
lineNode = lineNode.firstChild if lineNode? and dom.LIST_TAGS[lineNode.tagName]?
return lineNode

@TAGS: {
'DIV'
'BR'
'SPAN'
'B'
'I'
'S'
'U'
'A'
'IMG'
'OL'
'UL'
'LI'
}
normalizeNode: (node) ->
return node if dom(node).isTextNode()
this.whitelistStyles(node)
return this.whitelistTags(node)

constructor: ->
whitelistStyles: (node) ->
original = dom(node).styles()
styles = _.omit(original, (value, key) =>
return !@whitelist.styles[camelize(key)]?
)
if Object.keys(styles).length < Object.keys(original).length
if Object.keys(styles).length > 0
dom(node).styles(styles, true)
else
node.removeAttribute('style')

whitelistTags: (node) ->
return node unless dom(node).isElement()
if Normalizer.ALIASES[node.tagName]?
node = dom(node).switchTag(Normalizer.ALIASES[node.tagName])
else if !@whitelist.tags[node.tagName]?
if dom.BLOCK_TAGS[node.tagName]?
node = dom(node).switchTag(dom.DEFAULT_BLOCK_TAG)
else if !node.hasAttributes() and node.firstChild?
node = dom(node).unwrap()
else
node = dom(node).switchTag(dom.DEFAULT_INLINE_TAG)
return node

# Make sure descendant break tags are not causing multiple lines to be rendered
@handleBreaks: (lineNode) ->
Expand All @@ -44,20 +77,6 @@ class Normalizer
)
return lineNode

@normalizeLine: (lineNode) ->
lineNode = Normalizer.wrapInline(lineNode)
lineNode = Normalizer.handleBreaks(lineNode)
lineNode = Normalizer.pullBlocks(lineNode)
lineNode = Normalizer.normalizeNode(lineNode)
Normalizer.unwrapText(lineNode)
lineNode = lineNode.firstChild if lineNode? and dom.LIST_TAGS[lineNode.tagName]?
return lineNode

@normalizeNode: (node) ->
return node if dom(node).isTextNode()
Normalizer.whitelistStyles(node)
return Normalizer.whitelistTags(node)

# Removes unnecessary tags but does not modify line contents
@optimizeLine: (lineNode) ->
lineNode.normalize()
Expand Down Expand Up @@ -106,30 +125,6 @@ class Normalizer
html = html.replace(/\>\s+\</g, '><')
return html

@whitelistStyles: (node) ->
original = dom(node).styles()
styles = _.omit(original, (value, key) ->
return !Normalizer.STYLES[key]?
)
if Object.keys(styles).length < Object.keys(original).length
if Object.keys(styles).length > 0
dom(node).styles(styles, true)
else
node.removeAttribute('style')

@whitelistTags: (node) ->
return node unless dom(node).isElement()
if Normalizer.ALIASES[node.tagName]?
node = dom(node).switchTag(Normalizer.ALIASES[node.tagName])
else if !Normalizer.TAGS[node.tagName]?
if dom.BLOCK_TAGS[node.tagName]?
node = dom(node).switchTag(dom.DEFAULT_BLOCK_TAG)
else if !node.hasAttributes() and node.firstChild?
node = dom(node).unwrap()
else
node = dom(node).switchTag(dom.DEFAULT_INLINE_TAG)
return node

# Wrap inline nodes with block tags
@wrapInline: (lineNode) ->
return lineNode if dom.BLOCK_TAGS[lineNode.tagName]?
Expand Down
2 changes: 1 addition & 1 deletion src/core/selection.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class Selection
offset = 0
else if node.childNodes.length == 0
# TODO revisit fix for encoding edge case <p><em>|</em></p>
unless Normalizer.TAGS[node.tagName]?
unless @doc.normalizer.whitelist.tags[node.tagName]?
text = document.createTextNode('')
node.appendChild(text)
node = text
Expand Down
25 changes: 20 additions & 5 deletions test/unit/core/normalizer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ describe('Normalizer', ->
_.each(tests, (test, name) ->
it(name, ->
@container.innerHTML = test.initial
lineNode = Quill.Normalizer.normalizeLine(@container.firstChild)
@normalizer = new Quill.Normalizer()
lineNode = @normalizer.normalizeLine(@container.firstChild)
expect(@container).toEqualHTML(test.expected)
expect(lineNode).toEqual(@container.firstChild)
)
Expand All @@ -51,14 +52,18 @@ describe('Normalizer', ->

describe('normalizeNode()', ->
it('whitelist style and tag', ->
@normalizer = new Quill.Normalizer()
@normalizer.whitelist.tags.B = true
@normalizer.whitelist.styles.color = true
@container.innerHTML = '<strong style="color: red; display: inline;">Test</strong>'
Quill.Normalizer.normalizeNode(@container.firstChild)
@normalizer.normalizeNode(@container.firstChild)
expect(@container).toEqualHTML('<b style="color: red;">Test</b>')
)

it('text node', ->
@normalizer = new Quill.Normalizer()
@container.innerHTML = 'Test'
Quill.Normalizer.normalizeNode(@container.firstChild)
@normalizer.normalizeNode(@container.firstChild)
expect(@container).toEqualHTML('Test')
)
)
Expand Down Expand Up @@ -206,6 +211,11 @@ describe('Normalizer', ->
)

describe('whitelistStyles()', ->
beforeEach( ->
@normalizer = new Quill.Normalizer()
@normalizer.whitelist.styles.color = true
)

tests =
'no styles':
initial: '<div></div>'
Expand All @@ -223,13 +233,18 @@ describe('Normalizer', ->
_.each(tests, (test, name) ->
it(name, ->
@container.innerHTML = test.initial
Quill.Normalizer.whitelistStyles(@container.firstChild)
@normalizer.whitelistStyles(@container.firstChild)
expect(@container).toEqualHTML(test.expected)
)
)
)

describe('whitelistTags()', ->
beforeEach( ->
@normalizer = new Quill.Normalizer()
@normalizer.whitelist.tags.B = true
)

tests =
'not element':
initial: 'Test'
Expand All @@ -250,7 +265,7 @@ describe('Normalizer', ->
_.each(tests, (test, name) ->
it(name, ->
@container.innerHTML = test.initial
Quill.Normalizer.whitelistTags(@container.firstChild)
@normalizer.whitelistTags(@container.firstChild)
expect(@container).toEqualHTML(test.expected)
)
)
Expand Down

0 comments on commit 8f61dc9

Please sign in to comment.