Skip to content

Commit

Permalink
Merge branch 'lists' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
jhchen committed May 25, 2014
2 parents 7627213 + bbfcdb1 commit 355e723
Show file tree
Hide file tree
Showing 19 changed files with 283 additions and 75 deletions.
10 changes: 4 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
build
node_modules
coverage
lib
tests/webdriver/fuzzer_output
tests/webdriver/logs
/build
/node_modules
/coverage
/lib
*.log
5 changes: 4 additions & 1 deletion demo/advanced.jade
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ html
option(value='justify') Justify
button.sc-bold(title='Bold') Bold
button.sc-italic(title='Italic') Italic
button.sc-underline(title='Underline') Underline
button.sc-underline(title='Underline') Under
button.sc-bullet(title='Bullet') Bullet
.editor-container

.advanced-wrapper
Expand Down Expand Up @@ -65,6 +66,8 @@ html
span.sc-format-button.sc-link(title='Link')
span.sc-format-separator
span.sc-format-button.sc-image(title='Image')
span.sc-format-separator
span.sc-format-button.sc-bullet(title='Bullet')
span.sc-format-group
span.sc-format-button.sc-authorship(title='Authorship')
.editor-container
Expand Down
3 changes: 2 additions & 1 deletion demo/index.jade
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ html
button.sc-format-button.sc-strike(title='Strikethrough') Strike
button.sc-format-button.sc-link(title='Link') Link
button.sc-format-button.sc-image(title='Image') Image
button.sc-format-button.sc-clear(title='Clear Formats') Clear
button.sc-format-button.sc-bullet(title='Bullet') Bullet
button.sc-format-button.sc-list(title='List') List
#editor-container

script(type='text/javascript', src='../quill.js')
Expand Down
17 changes: 11 additions & 6 deletions src/document.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Document
return if line? then line.findLeafAt(offset, inclusive) else [null, offset]

findLine: (node) ->
while node? and node.parentNode != @root
while node? and !DOM.BLOCK_TAGS[node.tagName]?
node = node.parentNode
line = if node? then @lineMap[node.id] else null
return if line?.node == node then line else null
Expand Down Expand Up @@ -74,30 +74,35 @@ class Document
rebuild: ->
lines = @lines.toArray()
lineNode = @root.firstChild
lineNode = lineNode.firstChild if lineNode? and DOM.LIST_TAGS[lineNode.tagName]?
_.each(lines, (line, index) =>
while line.node != lineNode
if line.node.parentNode == @root
if line.node.parentNode?
# New line inserted
lineNode = Normalizer.normalizeLine(lineNode)
newLine = this.insertLineBefore(lineNode, line)
lineNode = lineNode.nextSibling
lineNode = Utils.getNextLineNode(lineNode, @root)
else
# Existing line removed
return this.removeLine(line)
if line.outerHTML != lineNode.outerHTML
# Existing line changed
line.node = Normalizer.normalizeLine(line.node)
line.rebuild()
lineNode = line.node.nextSibling
lineNode = Utils.getNextLineNode(lineNode, @root)
)
# New lines appended
while lineNode?
lineNode = Normalizer.normalizeLine(lineNode)
this.appendLine(lineNode)
lineNode = lineNode.nextSibling
lineNode = Utils.getNextLineNode(lineNode, @root)

removeLine: (line) ->
DOM.removeNode(line.node) if line.node.parentNode == @root
if line.node.parentNode?
if DOM.LIST_TAGS[line.node.parentNode.tagName] and line.node.parentNode.childNodes.length == 1
DOM.removeNode(line.node.parentNode)
else
DOM.removeNode(line.node)
delete @lineMap[line.id]
@lines.remove(line)

Expand Down
12 changes: 10 additions & 2 deletions src/dom.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ DOM =
'IMG'
}

LIST_TAGS: {
'OL'
'UL'
}

VOID_TAGS: {
'AREA'
'BASE'
Expand Down Expand Up @@ -323,8 +328,11 @@ DOM =

wrap: (wrapper, node) ->
node.parentNode.insertBefore(wrapper, node) if node.parentNode?
wrapper.appendChild(node)
return wrapper
parent = wrapper
while parent.firstChild?
parent = wrapper.firstChild
parent.appendChild(node)
return parent


module.exports = DOM
4 changes: 3 additions & 1 deletion src/editor.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ class Editor
line.insertText(offset, lineText, formatting)
if i < lineTexts.length - 1 # Are there more lines to insert?
nextLine = @doc.splitLine(line, offset + lineText.length)
line.format(formatting)
_.each(_.defaults({}, formatting, line.formats), (value, format) ->
line.format(format, formatting[format])
)
offset = 0
line = nextLine
)
Expand Down
48 changes: 36 additions & 12 deletions src/format.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,41 @@ class Format
type: Format.types.LINE
style: 'textAlign'
default: 'left'
prepare: (doc, value) ->
switch value
when 'left' then command = 'justifyLeft'
when 'center' then command = 'justifyCenter'
when 'right' then command = 'justifyRight'
when 'justify' then command = 'justifyFull'
doc.execCommand(command, false)

bullet:
type: Format.types.LINE
exclude: 'list'
parentTag: 'UL'
tag: 'LI'

list:
type: Format.types.LINE
exclude: 'bullet'
parentTag: 'OL'
tag: 'LI'


constructor: (@document, @config) ->

add: (node, value) ->
return this.remove(node) unless value
return node if this.value(node) == value
if _.isString(@config.parentTag)
parentNode = @document.createElement(@config.parentTag)
DOM.wrap(parentNode, node)
if node.parentNode.tagName == node.parentNode.previousSibling?.tagName
Utils.mergeNodes(node.parentNode.previousSibling, node.parentNode)
if node.parentNode.tagName == node.parentNode.nextSibling?.tagName
Utils.mergeNodes(node.parentNode, node.parentNode.nextSibling)
if _.isString(@config.tag)
formatNode = @document.createElement(@config.tag)
if DOM.VOID_TAGS[formatNode.tagName]?
# TODO use replaceNode
node.parentNode.insertBefore(formatNode, node) if node.parentNode?
DOM.removeNode(node)
node = formatNode
else if this.isType(Format.types.LINE)
node = DOM.switchTag(node, @config.tag)
else
node = DOM.wrap(formatNode, node)
if _.isString(@config.style) or _.isString(@config.attribute) or _.isString(@config.class)
Expand All @@ -96,6 +111,8 @@ class Format

match: (node) ->
return false unless DOM.isElement(node)
if _.isString(@config.parentTag) and node.parentNode?.tagName != @config.parentTag
return false
if _.isString(@config.tag) and node.tagName != @config.tag
return false
if _.isString(@config.style) and (!node.style[@config.style] or node.style[@config.style] == @config.default)
Expand Down Expand Up @@ -126,22 +143,29 @@ class Format
DOM.removeClass(node, c) if c.indexOf(@config.class) == 0
node.removeAttribute('class') unless node.getAttribute('class') # Some browsers leave empty style attribute
if _.isString(@config.tag)
node = DOM.switchTag(node, DOM.DEFAULT_INLINE_TAG)
DOM.setText(node, DOM.EMBED_TEXT) if DOM.EMBED_TAGS[@config.tag]?
if this.isType(Format.types.LINE)
Utils.splitAncestors(node, node.parentNode.parentNode) if node.previousSibling?
Utils.splitAncestors(node.nextSibling, node.parentNode.parentNode) if node.nextSibling?
node = DOM.switchTag(node, DOM.DEFAULT_BLOCK_TAG)
else
node = DOM.switchTag(node, DOM.DEFAULT_INLINE_TAG)
DOM.setText(node, DOM.EMBED_TEXT) if DOM.EMBED_TAGS[@config.tag]? # TODO is this desireable?
if _.isString(@config.parentTag)
DOM.unwrap(node.parentNode)
if node.tagName == DOM.DEFAULT_INLINE_TAG and !node.hasAttributes()
node = DOM.unwrap(node)
return node

value: (node) ->
return undefined unless this.match(node)
if _.isString(@config.attribute)
return node.getAttribute(@config.attribute) or undefined
else if _.isString(@config.style) and node.style[@config.style] != @config.default
return node.getAttribute(@config.attribute) or undefined # So "" does not get returned
else if _.isString(@config.style)
return node.style[@config.style] or undefined
else if _.isString(@config.class)
for c in DOM.getClasses(node)
return c.slice(@config.class.length) if c.indexOf(@config.class) == 0
else if _.isString(@config.tag) and node.tagName == @config.tag
else if _.isString(@config.tag)
return true
return undefined

Expand Down
7 changes: 6 additions & 1 deletion src/line.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Line extends LinkedList.Node
# TODO: optimize
_.each(@doc.formats, (format, name) ->
# format.value() also checks match() but existing bug in tandem-core requires check anyways
nodeFormats[name] = format.value(node) if format.match(node)
nodeFormats[name] = format.value(node) if !format.isType(Format.types.LINE) and format.match(node)
)
if Leaf.isLeafNode(node)
@leaves.append(new Leaf(node, nodeFormats))
Expand Down Expand Up @@ -74,6 +74,11 @@ class Line extends LinkedList.Node
format = @doc.formats[name]
# TODO reassigning @node might be dangerous...
if format.isType(Format.types.LINE)
if format.config.exclude and @formats[format.config.exclude]
excludeFormat = @doc.formats[format.config.exclude]
if excludeFormat?
@node = excludeFormat.remove(@node)
delete @formats[format.config.exclude]
@node = format.add(@node, value)
if value
@formats[name] = value
Expand Down
14 changes: 8 additions & 6 deletions src/modules/toolbar.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ class Toolbar
container: null

@formats:
BUTTON : { 'bold', 'image', 'italic', 'link', 'strike', 'underline' }
LINE : { 'align' }
LINE : { 'align', 'bullet', 'list' }
SELECT : { 'align', 'background', 'color', 'font', 'size' }
TOGGLE : { 'bold', 'bullet', 'image', 'italic', 'link', 'list', 'strike', 'underline' }
TOOLTIP : { 'image', 'link' }

constructor: (@quill, @options) ->
Expand Down Expand Up @@ -93,8 +93,11 @@ class Toolbar

_getLeafActive: (range) ->
if range.isCollapsed()
start = Math.max(0, range.start - 1)
contents = @quill.getContents(start, range.end)
[line, offset] = @quill.editor.doc.findLineAt(range.start)
if offset == 0
contents = @quill.getContents(range.start, range.end + 1)
else
contents = @quill.getContents(range.start - 1, range.end)
else
contents = @quill.getContents(range)
formatsArr = _.map(contents.ops, 'attributes')
Expand All @@ -106,7 +109,6 @@ class Toolbar
[lastLine, offset] = @quill.editor.doc.findLineAt(range.end)
lastLine = lastLine.next if lastLine? and lastLine == firstLine
while firstLine? and firstLine != lastLine
formats = { 'align': firstLine.formats['align'] } # TODO fix when we have more line attributes
formatsArr.push(firstLine.formats)
firstLine = firstLine.next
return this._intersectFormats(formatsArr)
Expand All @@ -126,7 +128,7 @@ class Toolbar
activeFormats[name] = [activeFormats[name], formats[name]]
)
_.each(missing, (name) ->
if Toolbar.formats.BUTTON[name]?
if Toolbar.formats.TOGGLE[name]?
delete activeFormats[name]
else if Toolbar.formats.SELECT[name]? and !_.isArray(activeFormats[name])
activeFormats[name] = [activeFormats[name]]
Expand Down
20 changes: 13 additions & 7 deletions src/normalizer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Normalizer =
'U'
'A'
'IMG'
'UL'
'LI'
}

# Make sure descendant break tags are not causing multiple lines to be rendered
Expand All @@ -49,7 +51,7 @@ Normalizer =
normalizeLine: (lineNode) ->
lineNode = Normalizer.wrapInline(lineNode)
lineNode = Normalizer.handleBreaks(lineNode)
Normalizer.pullBlocks(lineNode)
lineNode = Normalizer.pullBlocks(lineNode)
lineNode = Normalizer.normalizeNode(lineNode)
Normalizer.unwrapText(lineNode)
return lineNode
Expand Down Expand Up @@ -84,22 +86,26 @@ Normalizer =
# Merge similar nodes
if _.isEqual(DOM.getAttributes(node), DOM.getAttributes(node.previousSibling))
nodes.push(node.firstChild)
DOM.moveChildren(node.previousSibling, node)
DOM.normalize(node.previousSibling)
DOM.removeNode(node)
Utils.mergeNodes(node.previousSibling, node)

# Make sure descendants are all inline elements
pullBlocks: (lineNode) ->
curNode = lineNode.firstChild
while curNode?
if DOM.BLOCK_TAGS[curNode.tagName]?
if DOM.BLOCK_TAGS[curNode.tagName]? and curNode.tagName != 'LI'
if curNode.previousSibling?
Utils.splitAncestors(curNode, lineNode.parentNode)
if curNode.nextSibling?
Utils.splitAncestors(curNode.nextSibling, lineNode.parentNode)
DOM.unwrap(curNode)
Normalizer.pullBlocks(lineNode)
if !DOM.LIST_TAGS[curNode.tagName]?
DOM.unwrap(curNode)
Normalizer.pullBlocks(lineNode)
else
DOM.unwrap(curNode.parentNode)
lineNode = curNode unless lineNode.parentNode? # May have just unwrapped lineNode
break
curNode = curNode.nextSibling
return lineNode

stripComments: (html) ->
html = html.replace(/<!--[\s\S]*?-->/g, '')
Expand Down
8 changes: 5 additions & 3 deletions src/quill.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Quill extends EventEmitter2
@Theme: Themes

@DEFAULTS:
formats: ['align', 'bold', 'italic', 'strike', 'underline', 'color', 'background', 'font', 'size', 'link', 'image']
formats: ['align', 'bold', 'italic', 'strike', 'underline', 'color', 'background', 'font', 'size', 'link', 'image', 'bullet', 'list']
modules:
'keyboard': true
'paste-manager': true
Expand Down Expand Up @@ -113,7 +113,6 @@ class Quill extends EventEmitter2

formatText: (start, end, name, value, source) ->
[start, end, formats, source] = this._buildParams(start, end, name, value, source)
return unless end > start
formats = _.reduce(formats, (formats, value, name) =>
format = @editor.doc.formats[name]
# TODO warn if no format
Expand Down Expand Up @@ -168,7 +167,10 @@ class Quill extends EventEmitter2
return unless format? # TODO warn
range = this.getSelection()
return unless range?.isCollapsed()
format.prepare(value)
if format.isType(Format.types.LINE)
this.formatLine(range, name, value, Quill.sources.USER)
else
format.prepare(value)

setContents: (delta, source = Quill.sources.API) ->
if _.isArray(delta)
Expand Down
13 changes: 13 additions & 0 deletions src/utils.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ Utils =
offset = Utils.getNodeLength(child)
return [child, offset]

getNextLineNode: (curNode, root) ->
nextNode = curNode.nextSibling
if !nextNode? and curNode.parentNode != root
nextNode = curNode.parentNode.nextSibling
if nextNode? and DOM.LIST_TAGS[nextNode.tagName]?
nextNode = nextNode.firstChild
return nextNode

getNodeLength: (node) ->
return 0 unless node?
length = DOM.getText(node).length
Expand All @@ -38,6 +46,11 @@ Utils =
version = document.documentMode
return version and maxVersion >= version

mergeNodes: (newNode, oldNode) ->
DOM.moveChildren(newNode, oldNode)
DOM.normalize(newNode)
DOM.removeNode(oldNode)

# refNode is node after split point, root is parent of eldest node we want split (root will not be split)
splitAncestors: (refNode, root, force = false) ->
return refNode if refNode == root or refNode.parentNode == root
Expand Down
Loading

0 comments on commit 355e723

Please sign in to comment.