From 8f61dc9637c08bb839ccbf76ba091d0a7f32a0ce Mon Sep 17 00:00:00 2001 From: Jason Chen Date: Mon, 12 Jan 2015 23:21:21 -0800 Subject: [PATCH] Customize normalizer based on added formats --- src/core/document.coffee | 8 ++- src/core/line.coffee | 4 +- src/core/normalizer.coffee | 115 +++++++++++++++---------------- src/core/selection.coffee | 2 +- test/unit/core/normalizer.coffee | 25 +++++-- 5 files changed, 83 insertions(+), 71 deletions(-) diff --git a/src/core/document.coffee b/src/core/document.coffee index b66d23658f..34cf995f52 100644 --- a/src/core/document.coffee +++ b/src/core/document.coffee @@ -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) @@ -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) @@ -90,7 +92,7 @@ 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 @@ -98,13 +100,13 @@ class Document 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) diff --git a/src/core/line.coffee b/src/core/line.coffee index 10c002d26e..04296fe4e4 100644 --- a/src/core/line.coffee +++ b/src/core/line.coffee @@ -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) -> @@ -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() diff --git a/src/core/normalizer.coffee b/src/core/normalizer.coffee index c3cb678d58..c4e2855512 100644 --- a/src/core/normalizer.coffee +++ b/src/core/normalizer.coffee @@ -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' @@ -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) -> @@ -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() @@ -106,30 +125,6 @@ class Normalizer html = html.replace(/\>\s+\<') 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]? diff --git a/src/core/selection.coffee b/src/core/selection.coffee index 93b35b8e92..c519895b5e 100644 --- a/src/core/selection.coffee +++ b/src/core/selection.coffee @@ -92,7 +92,7 @@ class Selection offset = 0 else if node.childNodes.length == 0 # TODO revisit fix for encoding edge case

|

- unless Normalizer.TAGS[node.tagName]? + unless @doc.normalizer.whitelist.tags[node.tagName]? text = document.createTextNode('') node.appendChild(text) node = text diff --git a/test/unit/core/normalizer.coffee b/test/unit/core/normalizer.coffee index b1fd01c75f..be895661bc 100644 --- a/test/unit/core/normalizer.coffee +++ b/test/unit/core/normalizer.coffee @@ -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) ) @@ -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 = 'Test' - Quill.Normalizer.normalizeNode(@container.firstChild) + @normalizer.normalizeNode(@container.firstChild) expect(@container).toEqualHTML('Test') ) it('text node', -> + @normalizer = new Quill.Normalizer() @container.innerHTML = 'Test' - Quill.Normalizer.normalizeNode(@container.firstChild) + @normalizer.normalizeNode(@container.firstChild) expect(@container).toEqualHTML('Test') ) ) @@ -206,6 +211,11 @@ describe('Normalizer', -> ) describe('whitelistStyles()', -> + beforeEach( -> + @normalizer = new Quill.Normalizer() + @normalizer.whitelist.styles.color = true + ) + tests = 'no styles': initial: '
' @@ -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' @@ -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) ) )