Skip to content

Commit

Permalink
Fixes #2713 - Knowledge Base does't support animated GIFs and embedde…
Browse files Browse the repository at this point in the history
…d Video (Youtube/Vimeo) content.
  • Loading branch information
mantas authored and thorsteneckel committed Feb 19, 2020
1 parent 695d056 commit 0a70711
Show file tree
Hide file tree
Showing 24 changed files with 474 additions and 72 deletions.
Expand Up @@ -5,10 +5,13 @@ class App.UiElement.richtext.additions.RichTextToolButton
@klass: ->
# Needs implementation. Return constructor of RichTextToolPopup subclass.

@pickExisting: (sel, textEditor) ->
# needs implementation

@initializeAttributes: {}

@instantiateContent: (event, selection, delegate) ->
attrs = @initializeAttributes
attrs = $.extend(true, {}, @initializeAttributes)

attrs['event'] = event
attrs['selection'] = selection
Expand All @@ -35,46 +38,6 @@ class App.UiElement.richtext.additions.RichTextToolButton

hash

@pickLinkInSingleContainer: (elem, containerToLookUpTo) ->
if elem.nodeName == 'A'
elem
else if innerLink = $(elem).find('a')[0]
innerLink
else if containerToLookUpTo and closestLink = $(elem).closest('a', containerToLookUpTo)[0]
closestLink
else
null

@pickLinkAt: (elem, container, direction, boundary = null) ->
for parent in App.UiElement.richtext.buildParentsListWithSelf(elem, container)
if parent.nodeName is 'A'
return parent

for elem in App.UiElement.richtext.allDirectionalSiblings(parent, direction, boundary)
if link = @pickLinkInSingleContainer(elem)
return link

null

@pickLink: (sel, textEditor) ->
range = sel.getRangeAt(0)

if range.startContainer == range.endContainer
return @pickLinkInSingleContainer(range.startContainer, textEditor)

if link = @pickLinkAt(range.startContainer, range.commonAncestorContainer, 1, range.endContainer)
return link

if startParent = App.UiElement.richtext.buildParentsList(range.startContainer, range.commonAncestorContainer).pop()
for elem in App.UiElement.richtext.allDirectionalSiblings(startParent, 1, range.endContainer)
if link = @pickLinkInSingleContainer(elem)
return link

if link = @pickLinkAt(range.endContainer, range.commonAncestorContainer, -1)
return link

return null

# close other buttons' popovers
@closeOtherPopovers: (event) ->
$(event.currentTarget)
Expand All @@ -88,15 +51,10 @@ class App.UiElement.richtext.additions.RichTextToolButton
@selectionSnapshot: (sel) ->
textEditor = $(event.currentTarget).closest('.richtext.form-control').find('[contenteditable]')

if sel.isCollapsed and selectedLink = $(sel.anchorNode).closest('a')[0]
{
type: 'existing'
dom: $(selectedLink)
}
else if !sel.isCollapsed and selectedLink = @pickLink(sel, textEditor)
if selected = @pickExisting(sel, textEditor)
{
type: 'existing'
dom: $(selectedLink)
dom: $(selected)
}
else if sel.type is 'Range' and $(sel.anchorNode).closest('[contenteditable]', textEditor)[0]
range = sel.getRangeAt(0)
Expand All @@ -117,6 +75,7 @@ class App.UiElement.richtext.additions.RichTextToolButton
dom: textEditor
}

# on clicking button above rich text area
@onClick: (event, delegate) ->
event.stopPropagation()
event.preventDefault()
Expand Down
@@ -0,0 +1,46 @@
class App.UiElement.richtext.additions.RichTextToolButtonLink extends App.UiElement.richtext.additions.RichTextToolButton
@pickLinkInSingleContainer: (elem, containerToLookUpTo) ->
if elem.nodeName == 'A'
elem
else if innerLink = $(elem).find('a')[0]
innerLink
else if containerToLookUpTo and closestLink = $(elem).closest('a', containerToLookUpTo)[0]
closestLink
else
null

@pickLinkAt: (elem, container, direction, boundary = null) ->
for parent in App.UiElement.richtext.buildParentsListWithSelf(elem, container)
if parent.nodeName is 'A'
return parent

for elem in App.UiElement.richtext.allDirectionalSiblings(parent, direction, boundary)
if link = @pickLinkInSingleContainer(elem)
return link

null

@pickExisting: (sel, textEditor) ->
if sel.isCollapsed and link = $(sel.anchorNode).closest('a')[0]
return link

if sel.isCollapsed
return null

range = sel.getRangeAt(0)

if range.startContainer == range.endContainer
return @pickLinkInSingleContainer(range.startContainer, textEditor)

if link = @pickLinkAt(range.startContainer, range.commonAncestorContainer, 1, range.endContainer)
return link

if startParent = App.UiElement.richtext.buildParentsList(range.startContainer, range.commonAncestorContainer).pop()
for elem in App.UiElement.richtext.allDirectionalSiblings(startParent, 1, range.endContainer)
if link = @pickLinkInSingleContainer(elem)
return link

if link = @pickLinkAt(range.endContainer, range.commonAncestorContainer, -1)
return link

return null
@@ -1,21 +1,25 @@
class App.UiElement.richtext.additions.RichTextToolPopup extends App.ControllerForm
events:
'submit form': 'onSubmit'
'click .js-unlink': 'onUnlink'
'click .js-clear': 'onClear'

labelNew: 'Link'
labelExisting: 'Update'
labelClear: 'Remove'

formParams: (params) ->
# needs implementation

constructor: (params) ->
if params.selection.type is 'existing'
url = params.selection.dom.attr('href')
label = 'Update'
label = @labelExisting
additional = [{
className: 'btn btn--danger js-unlink'
text: 'Remove'
className: 'btn btn--danger js-clear'
text: @labelClear
}]
else
label = 'Link'
label = @labelNew

defaultParams =
params: @formParams(params)
Expand All @@ -39,16 +43,17 @@ class App.UiElement.richtext.additions.RichTextToolPopup extends App.ControllerF
getAjaxAttributes: (field, attributes) ->
@delegate?.getAjaxAttributes?(field, attributes)

onUnlink: (e) ->
onClear: (e) =>
e.preventDefault()
e.stopPropagation()

switch @selection.type
when 'existing'
$(@selection.dom).contents().unwrap()
@clear()

$(@event.currentTarget).popover('hide')

clear: ->
# needs implementation

@wrapElement: (wrapper, selection) ->
topLevelOriginals = App.UiElement.richtext.buildParentsList(selection.range.startContainer, selection.range.commonAncestorContainer).reverse()

Expand Down Expand Up @@ -114,15 +119,15 @@ class App.UiElement.richtext.additions.RichTextToolPopup extends App.ControllerF

wrapper.insertAfter(topLevelOriginalStart)

wrapLink: ->
apply: (callback) ->
# needs implementation
callback()

onSubmit: (e) ->
e.preventDefault()

@wrapLink()

$(@event.currentTarget).popover('destroy')
@apply =>
$(@event.currentTarget).popover('destroy')

didInitialize: ->
switch @selection.type
Expand Down
@@ -0,0 +1,63 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.richtext.toolButtons.embed_video extends App.UiElement.richtext.additions.RichTextToolButton
@icon: 'cloud'
@text: 'Video'
@klass: -> App.UiElement.richtext.additions.RichTextToolPopupVideo
@initializeAttributes:
model:
configure_attributes: [
{
name: 'link'
display: 'Link'
tag: 'input'
placeholder: 'Youtube or Vimeo address'
}
]

@pickExisting: (sel, textEditor) ->
startNode = null
startOffset = null

endNode = null
endOffset = null

return if !textEditor[0].contains(sel.anchorNode)

walker = document.createTreeWalker(textEditor[0])

walker.currentNode = sel.anchorNode

while !startNode and (walker.currentNode.nodeName == '#text' || walker.currentNode.nodeName == 'SPAN') and walker.currentNode
if walker.currentNode instanceof Text
offset = walker.currentNode.textContent.indexOf '('
if offset? and offset > -1
startNode = walker.currentNode
startOffset = offset

walker.previousNode()

walker.currentNode = sel.anchorNode # back to start

while !endNode and (walker.currentNode.nodeName == '#text' || walker.currentNode.nodeName == 'SPAN') and walker.currentNode
if walker.currentNode instanceof Text
offset = walker.currentNode.textContent.indexOf ')'
if offset? and offset > -1 and (walker.currentNode != sel.anchorNode || offset > startOffset)
endNode = walker.currentNode
endOffset = offset + 1

walker.nextNode()

if startNode and endNode
range = document.createRange()
range.setStart(startNode, startOffset)
range.setEnd(endNode, endOffset)

copy = range.cloneContents()

wrapper = document.createElement('span')
wrapper.append(copy)

range.deleteContents()
range.insertNode(wrapper)

wrapper
@@ -0,0 +1,18 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.richtext.toolButtons.insert_image extends App.UiElement.richtext.additions.RichTextToolButton
@icon: 'web'
@text: 'Image'
@klass: -> App.UiElement.richtext.additions.RichTextToolPopupImage
@initializeAttributes:
model:
configure_attributes: [
{
name: 'link'
display: 'Image'
tag: 'input'
type: 'file'
}
]

@pickExisting: (sel, textEditor) ->
selectedImage = textEditor.find('img.objectResizingEditorActive')[0]
@@ -1,5 +1,5 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.richtext.toolButtons.link_answer extends App.UiElement.richtext.additions.RichTextToolButton
class App.UiElement.richtext.toolButtons.link_answer extends App.UiElement.richtext.additions.RichTextToolButtonLink
@icon: 'knowledge-base-answer'
@text: 'Link Answer'
@klass: -> App.UiElement.richtext.additions.RichTextToolPopupAnswer
Expand Down
@@ -1,5 +1,5 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.richtext.toolButtons.link extends App.UiElement.richtext.additions.RichTextToolButton
class App.UiElement.richtext.toolButtons.link extends App.UiElement.richtext.additions.RichTextToolButtonLink
@icon: 'chain'
@text: 'Weblink'
@klass: -> App.UiElement.richtext.additions.RichTextToolPopupLink
Expand Down
Expand Up @@ -18,7 +18,7 @@ class App.UiElement.richtext.additions.RichTextToolPopupAnswer extends App.UiEle

dom

wrapLink: ->
apply: (callback) ->
id = @el.find('input').val()
object = App.KnowledgeBaseAnswerTranslation.find(id)
textEditor = $(@event.currentTarget).closest('.richtext.form-control').find('[contenteditable]')
Expand All @@ -41,3 +41,10 @@ class App.UiElement.richtext.additions.RichTextToolPopupAnswer extends App.UiEle
@applyOnto(newElem, object)
placeholder.wrap(newElem)
placeholder.contents()

callback()

clear: ->
switch @selection.type
when 'existing'
$(@selection.dom).contents().unwrap()
@@ -0,0 +1,58 @@
class App.UiElement.richtext.additions.RichTextToolPopupImage extends App.UiElement.richtext.additions.RichTextToolPopup
labelNew: 'Insert'
labelExisting: 'Replace'

apply: (callback) ->
@el.find('btn--create').attr('disabled', true)

file = @el.find('input')[0].files[0]

reader = new FileReader()

reader.addEventListener('load', =>
@insertImage(reader.result)
callback()
, false)

reader.readAsDataURL(file)

applyOnto: (dom, base64) ->
dom.attr('src', base64)

insertImage: (base64) ->
textEditor = $(@event.currentTarget).closest('.richtext.form-control').find('[contenteditable]')

switch @selection.type
when 'existing'
@applyOnto(@selection.dom, base64)
when 'append'
newElem = $('<img>')[0]
newElem.src = base64
newElem.style = 'width: 1000px; max-width: 100%;'
@selection.dom.append(newElem)
when 'caret'
newElem = $('<img>')
newElem.attr('src', base64)
newElem.attr('style', 'width: 1000px; max-width: 100%;')

surroundingDom = @selection.dom[0]

if surroundingDom instanceof Text
@selection.dom[0].splitText(@selection.offset)

newElem.insertAfter(@selection.dom)
when 'range'
newElem = $('<img>')
newElem.attr('src', base64)
newElem.attr('style', 'width: 1000px; max-width: 100%;')

placeholder = textEditor.find('span.highlight-emulator')

placeholder.empty()
placeholder.append(newElem)

clear: ->
switch @selection.type
when 'existing'
@selection.dom.closest('.enableObjectResizingShim').remove()
@selection.dom.remove() # just in case shim was lost while the dialog was open

0 comments on commit 0a70711

Please sign in to comment.