Init version of highlighter.

This commit is contained in:
Martin Edenhofer 2015-06-05 16:19:36 +02:00
parent 8576909975
commit 6570ead1d3
9 changed files with 168 additions and 85 deletions

View file

@ -91,7 +91,11 @@ class App.TicketZoom extends App.Controller
return if @activeState return if @activeState
@activeState = true @activeState = true
App.Event.trigger('ui::ticket::shown', { ticket_id: @ticket.id } ) App.Event.trigger('ui::ticket::shown', { ticket_id: @ticket_id } )
if !@highlighed
@highlighed = true
@highligher.loadHighlights()
App.OnlineNotification.seen( 'Ticket', @ticket_id ) App.OnlineNotification.seen( 'Ticket', @ticket_id )
@navupdate '#' @navupdate '#'
@ -292,10 +296,15 @@ class App.TicketZoom extends App.Controller
ui: @ ui: @
) )
@highligher = new App.TicketZoomHighlighter(
el: @$('.highlighter')
ticket_id: @ticket.id
)
# rerender whole sidebar if customer or organization has changed # rerender whole sidebar if customer or organization has changed
if @ticketLastAttributes.customer_id isnt @ticket.customer_id || @ticketLastAttributes.organization_id isnt @ticket.organization_id if @ticketLastAttributes.customer_id isnt @ticket.customer_id || @ticketLastAttributes.organization_id isnt @ticket.organization_id
new App.WidgetAvatar( new App.WidgetAvatar(
el: @$('.page-header .js-avatar') el: @$('.ticketZoom-header .js-avatar')
user_id: @ticket.customer_id user_id: @ticket.customer_id
size: 50 size: 50
) )
@ -313,9 +322,10 @@ class App.TicketZoom extends App.Controller
# show article # show article
if !@article_view if !@article_view
@article_view = new App.TicketZoomArticleView( @article_view = new App.TicketZoomArticleView(
ticket: @ticket ticket: @ticket
el: @el.find('.ticket-article') el: @el.find('.ticket-article')
ui: @ ui: @
highlighter: @highlighter
) )
@article_view.execute( @article_view.execute(

View file

@ -5,6 +5,7 @@ class App.TicketZoomArticleView extends App.Controller
@article_controller = {} @article_controller = {}
execute: (params) -> execute: (params) ->
all = []
for ticket_article_id in params.ticket_article_ids for ticket_article_id in params.ticket_article_ids
if !@article_controller[ticket_article_id] if !@article_controller[ticket_article_id]
el = $('<div></div>') el = $('<div></div>')
@ -14,14 +15,21 @@ class App.TicketZoomArticleView extends App.Controller
el: el el: el
ui: @ui ui: @ui
) )
@el.append( el ) all.push el
@el.append( all )
class ArticleViewItem extends App.Controller class ArticleViewItem extends App.Controller
hasChangedAttributes: ['from', 'to', 'cc', 'subject', 'body', 'internal']
elements:
'.textBubble-content': 'textBubbleContent'
'.textBubble-overflowContainer': 'textBubbleOverflowContainer'
events: events:
'click .show_toogle': 'show_toogle' 'click .show_toogle': 'show_toogle'
'click .textBubble': 'toggle_meta_with_delay' 'click .textBubble': 'toggle_meta_with_delay'
'click .textBubble a': 'stopPropagation' 'click .textBubble a': 'stopPropagation'
'click .js-unfold': 'unfold' 'click .js-unfold': 'unfold'
constructor: -> constructor: ->
super super
@ -34,21 +42,43 @@ class ArticleViewItem extends App.Controller
@bind( @bind(
'ui::ticket::shown' 'ui::ticket::shown'
(data) => (data) =>
if data.ticket_id is @ticket.id if data.ticket_id.toString() is @ticket.id.toString()
@setSeeMore() @setSeeMore()
) )
# subscribe to changes # subscribe to changes
@subscribeId = App.TicketArticle.full( @ticket_article_id, @render, false, true ) @subscribeId = App.TicketArticle.full(@ticket_article_id, @render, false, true)
release: => release: =>
App.User.TicketArticle(@subscribeId) App.TicketArticle.unsubscribe(@subscribeId)
hasChanged: (article) =>
# if no last article exists, remember it and return true
if !@article_last_updated
@article_last_updated = {}
for item in @hasChangedAttributes
@article_last_updated[item] = article[item]
return true
# compare last and current article attributes
article_last_updated_check = {}
for item in @hasChangedAttributes
article_last_updated_check[item] = article[item]
diff = difference(@article_last_updated, article_last_updated_check)
return false if !diff || _.isEmpty( diff )
@article_last_updated = article_last_updated_check
true
render: (article) => render: (article) =>
# get articles # get articles
@article = App.TicketArticle.fullLocal( @ticket_article_id ) @article = App.TicketArticle.fullLocal( @ticket_article_id )
# check if rerender is needed
return if !@hasChanged(@article)
console.log('RERENDER', @ticket_article_id)
# prepare html body # prepare html body
if @article.content_type is 'text/html' if @article.content_type is 'text/html'
@article['html'] = @article.body @article['html'] = @article.body
@ -82,34 +112,35 @@ class ArticleViewItem extends App.Controller
# set see more options # set see more options
setSeeMore: => setSeeMore: =>
maxHeight = 560 maxHeight = 560
bubble = @$('.textBubble-content') bubbleContent = @textBubbleContent
bubbleOvervlowContainer = @textBubbleOverflowContainer
# expand if see more is already clicked # expand if see more is already clicked
if @seeMore if @seeMore
bubble.css('height', 'auto') bubbleContent.css('height', 'auto')
bubble.parent().find('.textBubble-overflowContainer').addClass('hide') bubbleOvervlowContainer.addClass('hide')
return return
# reset bubble heigth and "see more" opacity # reset bubble heigth and "see more" opacity
bubble.css('height', '') bubbleContent.css('height', '')
bubble.parent().find('.textBubble-overflowContainer').css('opacity', '') bubbleOvervlowContainer.css('opacity', '')
# remember offset of "see more" # remember offset of "see more"
offsetTop = bubble.find('.js-signatureMarker').position() offsetTop = bubbleContent.find('.js-signatureMarker').position()
# remember bubble heigth # remember bubble heigth
heigth = bubble.height() heigth = bubbleContent.height()
if offsetTop if offsetTop && heigth
bubble.attr('data-height', heigth) bubbleContent.attr('data-height', heigth)
bubble.css('height', "#{offsetTop.top + 30}px") bubbleContent.css('height', "#{offsetTop.top + 30}px")
bubble.parent().find('.textBubble-overflowContainer').removeClass('hide') bubbleOvervlowContainer.removeClass('hide')
else if heigth > maxHeight else if heigth > maxHeight
bubble.attr('data-height', heigth) bubbleContent.attr('data-height', heigth)
bubble.css('height', "#{maxHeight}px") bubbleContent.css('height', "#{maxHeight}px")
bubble.parent().find('.textBubble-overflowContainer').removeClass('hide') bubbleOvervlowContainer.removeClass('hide')
else else
bubble.parent().find('.textBubble-overflowContainer').addClass('hide') bubbleOvervlowContainer.addClass('hide')
show_toogle: (e) -> show_toogle: (e) ->
e.stopPropagation() e.stopPropagation()
@ -218,21 +249,21 @@ class ArticleViewItem extends App.Controller
@seeMore = true @seeMore = true
container = $(e.currentTarget).parents('.textBubble-content') bubbleContent = @textBubbleContent
overflowContainer = container.find('.textBubble-overflowContainer') bubbleOvervlowContainer = @textBubbleOverflowContainer
overflowContainer.velocity bubbleOvervlowContainer.velocity
properties: properties:
opacity: 0 opacity: 0
options: options:
duration: 300 duration: 300
container.velocity bubbleContent.velocity
properties: properties:
height: container.attr('data-height') height: bubbleContent.attr('data-height')
options: options:
duration: 300 duration: 300
complete: -> overflowContainer.addClass('hide'); complete: -> bubbleOvervlowContainer.addClass('hide');
isOrContains: (node, container) -> isOrContains: (node, container) ->
while node while node

View file

@ -1,32 +1,32 @@
class App.TicketZoomHighlighter extends App.Controller class App.TicketZoomHighlighter extends App.Controller
elements: elements:
'.textBubble-content': 'articles' '.textBubble-content': 'articles'
'.js-highlight .marker-icon': 'highlighterControl' '.js-highlight-icon': 'highlighterControl'
events: events:
'click .js-highlight': 'toggleHighlight' 'click .js-highlight': 'toggleHighlight'
'click .js-highlightColor': 'pickColor' 'click .js-highlightColor': 'pickColor'
colors: [ colors: [
{ {
name: 'Yellow' name: 'Yellow'
color: "#f7e7b2" color: '#f7e7b2'
}, },
{ {
name: 'Green' name: 'Green'
color: "#bce7b6" color: '#bce7b6'
}, },
{ {
name: 'Blue' name: 'Blue'
color: "#b3ddf9" color: '#b3ddf9'
}, },
{ {
name: 'Pink' name: 'Pink'
color: "#fea9c5" color: '#fea9c5'
}, },
{ {
name: 'Purple' name: 'Purple'
color: "#eac5ee" color: '#eac5ee'
} }
] ]
@ -41,7 +41,6 @@ class App.TicketZoomHighlighter extends App.Controller
rangy.init() rangy.init()
@highlighter = rangy.createHighlighter(document, 'TextRange') @highlighter = rangy.createHighlighter(document, 'TextRange')
@addClassApplier entry for entry in @colors @addClassApplier entry for entry in @colors
@setColor() @setColor()
@ -50,13 +49,19 @@ class App.TicketZoomHighlighter extends App.Controller
# store original highlight css data # store original highlight css data
@storeOriginalHighlight() @storeOriginalHighlight()
@loadHighlights() update = =>
@loadHighlights()
@refreshObserver()
App.Delay.set( update, 800 )
render: -> render: ->
@html App.view('ticket_zoom/highlighter') @html App.view('ticket_zoom/highlighter')
colors: @colors colors: @colors
activeColorIndex: @activeColorIndex activeColorIndex: @activeColorIndex
highlighterInstance: =>
@highlighter
storeOriginalHighlight: => storeOriginalHighlight: =>
@originalHighlight = @originalHighlight =
fill: @highlighterControl.css('fill') fill: @highlighterControl.css('fill')
@ -70,19 +75,33 @@ class App.TicketZoomHighlighter extends App.Controller
highlightEnable: => highlightEnable: =>
@isActive = true @isActive = true
@highlighterControl.css('opacity', 1) @highlighterControl.css('opacity', 1)
@highlighterControl.css('fill', @activeColor)
@refreshObserver()
highlightDisable: => highlightDisable: =>
@isActive = false @isActive = false
@restoreOriginalHighlight() @restoreOriginalHighlight()
#@highlighterControl.css('opacity', @originalHighlight.opacity)
active: => articles = @el.closest('.content').find('.textBubble')
@isActive articles.removeAttr('data-highlightcolor')
@refreshObserver()
refreshObserver: =>
articles = @el.closest('.content').find('.textBubble-content')
console.log('refreshObserver', articles)
articles.off('mouseup', @onMouseUp)
articles.on('mouseup', @onMouseUp) #future: touchend
# for testing purposes the highlights get stored in localStorage # for testing purposes the highlights get stored in localStorage
loadHighlights: -> loadHighlights: ->
if highlights = localStorage['highlights'] @el.closest('.content').find('.textBubble-content').each( (index, element) =>
@highlighter.deserialize localStorage['highlights'] article_id = $(element).data('id')
article = App.TicketArticle.find(article_id)
if article.preferences && article.preferences['highlight']
console.log('highlight', article.preferences['highlight'])
@highlighter.deserialize(article.preferences['highlight'])
)
# the serialization creates one string for the entiery ticket # the serialization creates one string for the entiery ticket
# containing the offsets and the highlight classes # containing the offsets and the highlight classes
@ -91,8 +110,12 @@ class App.TicketZoomHighlighter extends App.Controller
# #
# if classes can be changed in the admin interface # if classes can be changed in the admin interface
# we have to watch out to not end up with empty highlight classes # we have to watch out to not end up with empty highlight classes
storeHighlights: -> storeHighlights: (article_id) ->
localStorage['highlights'] = @highlighter.serialize() article = App.TicketArticle.find(article_id)
data = @highlighter.serialize()
console.log('HI', article_id, data)
article.preferences['highlight'] = data
article.save()
# the colors is set via css classes (can't do it inline with rangy) # the colors is set via css classes (can't do it inline with rangy)
# thus we have to create a stylesheet if the colors # thus we have to create a stylesheet if the colors
@ -102,54 +125,60 @@ class App.TicketZoomHighlighter extends App.Controller
setColor: -> setColor: ->
@highlightClass = @highlightClassPrefix + @colors[@activeColorIndex].name @highlightClass = @highlightClassPrefix + @colors[@activeColorIndex].name
@activeColor = @colors[@activeColorIndex].color
if @isActive if @isActive
@articles.attr('data-highlightcolor', @colors[@activeColorIndex].name) articles = @el.closest('.content').find('.textBubble')
articles.attr('data-highlightcolor', @colors[@activeColorIndex].name)
toggleHighlight: (e) => toggleHighlight: (e) =>
if @isActive console.log('toggleHighlight', @isActive)
@restoreOriginalHighlight()
else
@highlightEnable()
return
console.log('toggleHighlight', @isActive, @articles)
if @isActive if @isActive
$(e.currentTarget).removeClass('active') $(e.currentTarget).removeClass('active')
@isActive = false
@articles.off('mouseup', @onMouseUp) @highlightDisable()
@articles.removeAttr('data-highlightcolor')
else else
@highlightEnable()
selection = rangy.getSelection() selection = rangy.getSelection()
# if there's already something selected, # if there's already something selected,
# don't go into highlight mode # don't go into highlight mode
# just toggle the selected # just toggle the selected
if !selection.isCollapsed if !selection.isCollapsed
@toggleHighlightAtSelection $(selection.anchorNode).closest @articles.selector @toggleHighlightAtSelection(@content, @article_id)
else #else
# toggle ui # toggle ui
$(e.currentTarget).addClass('active') #$(e.currentTarget).addClass('active')
# activate selection background # activate selection background
@articles.attr('data-highlightcolor', @colors[@activeColorIndex].name) #@articles.attr('data-highlightcolor', @colors[@activeColorIndex].name)
@isActive = true
@articles.on('mouseup', @onMouseUp) #future: touchend
pickColor: (e) => pickColor: (e) =>
@$('.js-highlightColor .visibility-change.active').removeClass('active') # TODO: @mrflix - still needed?
$(e.currentTarget).find('.visibility-change').addClass('active') #@$('.js-highlightColor .visibility-change.active').removeClass('active')
@activeColorIndex = $(e.currentTarget).attr('data-key') #$(e.currentTarget).find('.visibility-change').addClass('active')
@activeColorIndex = $(e.currentTarget).attr('data-key')
@isActive = true
console.log('ooo', @activeColorIndex, @colors[@activeColorIndex].color, @colors[@activeColorIndex])
@highlighterControl.css('fill', @colors[@activeColorIndex].color)
@highlighterControl.css('opacity', 1)
@setColor() @setColor()
@highlightEnable()
# check if selection exists - highlight it or remove highlight
@toggleHighlightAtSelection(@content, @article_id)
onMouseUp: (e) => onMouseUp: (e) =>
#@toggleHighlightAtSelection $(e.currentTarget).closest('.textBubble-content')# @articles.selector @updateSelectedArticle(e)
console.log('onMouseUp', @isActive, @content, @article_id)
if @isActive
@toggleHighlightAtSelection(@content, @article_id) # @articles.selector
updateSelectedArticle: (e) =>
@content = $(e.currentTarget).closest('.textBubble-content')
@article_id = @content.data('id')
if !@article_id
@content = $(e.currentTarget)
@article_id = @content.data('id')
# #
# toggle Highlight # toggle Highlight
@ -160,12 +189,17 @@ class App.TicketZoomHighlighter extends App.Controller
# - or highlights the selection # - or highlights the selection
# - clears the selection # - clears the selection
toggleHighlightAtSelection: (article) => toggleHighlightAtSelection: (article, article_id) =>
selection = rangy.getSelection() selection = rangy.getSelection()
# activate selection background
article.attr('data-highlightcolor', @colors[@activeColorIndex].name)
if @highlighter.selectionOverlapsHighlight selection if @highlighter.selectionOverlapsHighlight selection
console.log('SELECTION EXISTS, REMOVED IT')
@highlighter.unhighlightSelection() @highlighter.unhighlightSelection()
else else
console.log('NEW SELECTION')
@highlighter.highlightSelection @highlightClass, @highlighter.highlightSelection @highlightClass,
selection: selection selection: selection
containerElementId: article.get(0).id containerElementId: article.get(0).id
@ -175,5 +209,5 @@ class App.TicketZoomHighlighter extends App.Controller
@highlightDisable() @highlightDisable()
# save new selections
#@storeHighlights() @storeHighlights(article_id)

View file

@ -1,5 +1,5 @@
class App.TicketArticle extends App.Model class App.TicketArticle extends App.Model
@configure 'TicketArticle', 'from', 'to', 'cc', 'subject', 'body', 'content_type', 'ticket_id', 'type_id', 'sender_id', 'internal', 'in_reply_to', 'form_id', 'updated_at' @configure 'TicketArticle', 'from', 'to', 'cc', 'subject', 'body', 'content_type', 'ticket_id', 'type_id', 'sender_id', 'internal', 'in_reply_to', 'form_id', 'preferences', 'updated_at'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/ticket_articles' @url: @apiPath + '/ticket_articles'
@configure_attributes = [ @configure_attributes = [

View file

@ -16,7 +16,7 @@
<span class="dropdown-iconSpacer"> <span class="dropdown-iconSpacer">
<span class="color-swatch icon" style="background: <%= entry.color %>"></span> <span class="color-swatch icon" style="background: <%= entry.color %>"></span>
</span> </span>
<%= entry.name %> <%- @Ti(entry.name) %>
<span class="dropdown-activeSpacer visibility-change<%= ' is-active' if i is @activeColorIndex %>"> <span class="dropdown-activeSpacer visibility-change<%= ' is-active' if i is @activeColorIndex %>">
<svg class="icon-checkmark" data-visible="active"><use xlink:href="#icon-checkmark" fill="white" /></svg> <svg class="icon-checkmark" data-visible="active"><use xlink:href="#icon-checkmark" fill="white" /></svg>
</span> </span>

View file

@ -43,7 +43,7 @@
<div class="internal-border"> <div class="internal-border">
<div class="textBubble"> <div class="textBubble">
<div class="bubble-arrow"></div> <div class="bubble-arrow"></div>
<div class="textBubble-content"> <div class="textBubble-content" id="article-content-<%= @article.id %>" data-id="<%= @article.id %>">
<%- App.Utils.signatureIdentify( @article.html ) %> <%- App.Utils.signatureIdentify( @article.html ) %>
<div class="textBubble-overflowContainer"> <div class="textBubble-overflowContainer">
<div class="btn btn--text js-unfold"><%- @T('See more') %></div> <div class="btn btn--text js-unfold"><%- @T('See more') %></div>

View file

@ -1,5 +1,5 @@
<div class="btn btn--action btn--split--first js-highlight centered"> <div class="btn btn--action btn--split--first js-highlight centered">
<svg class="marker-icon"><use xlink:href="#icon-marker" /></svg> <svg class="icon icon-marker js-highlight-icon"><use xlink:href="#icon-marker" /></svg>
</div> </div>
<div class="dropdown dropdown--actions"> <div class="dropdown dropdown--actions">
<div class="btn btn--action btn--split--last btn--slim centered" data-toggle="dropdown" aria-expanded="true"> <div class="btn btn--action btn--split--last btn--slim centered" data-toggle="dropdown" aria-expanded="true">
@ -12,7 +12,7 @@
<span class="dropdown-iconSpacer"> <span class="dropdown-iconSpacer">
<span class="color-swatch icon" style="background: <%= entry.color %>"></span> <span class="color-swatch icon" style="background: <%= entry.color %>"></span>
</span> </span>
<%= entry.name %> <%- @Ti(entry.name) %>
<span class="dropdown-activeSpacer visibility-change<%= ' active' if i is @activeColorIndex %>"> <span class="dropdown-activeSpacer visibility-change<%= ' active' if i is @activeColorIndex %>">
<span class="white checkmark icon" data-visible="active"></span> <span class="white checkmark icon" data-visible="active"></span>
</span> </span>

View file

@ -13,6 +13,7 @@ class Ticket::Article < ApplicationModel
belongs_to :sender, class_name: 'Ticket::Article::Sender' belongs_to :sender, class_name: 'Ticket::Article::Sender'
belongs_to :created_by, class_name: 'User' belongs_to :created_by, class_name: 'User'
belongs_to :updated_by, class_name: 'User' belongs_to :updated_by, class_name: 'User'
store :preferences
before_create :check_subject before_create :check_subject
before_update :check_subject before_update :check_subject
notify_clients_support notify_clients_support
@ -20,11 +21,13 @@ class Ticket::Article < ApplicationModel
activity_stream_support ignore_attributes: { activity_stream_support ignore_attributes: {
type_id: true, type_id: true,
sender_id: true, sender_id: true,
preferences: true,
} }
history_support ignore_attributes: { history_support ignore_attributes: {
type_id: true, type_id: true,
sender_id: true, sender_id: true,
preferences: true,
} }
private private

View file

@ -0,0 +1,5 @@
class AddPreferencesToTicketArticles < ActiveRecord::Migration
def change
add_column :ticket_articles, :preferences, :text, limit: 500.kilobytes + 1, null: true
end
end