Init version of highlighter.

This commit is contained in:
Martin Edenhofer 2015-06-02 20:23:02 +02:00
parent 8c651959a8
commit e590248307
9 changed files with 241 additions and 27 deletions

View file

@ -0,0 +1,179 @@
class App.TicketZoomHighlighter extends App.Controller
elements:
'.textBubble-content': 'articles'
'.js-highlight .marker-icon': 'highlighterControl'
events:
'click .js-highlight': 'toggleHighlight'
'click .js-highlightColor': 'pickColor'
colors: [
{
name: 'Yellow'
color: "#f7e7b2"
},
{
name: 'Green'
color: "#bce7b6"
},
{
name: 'Blue'
color: "#b3ddf9"
},
{
name: 'Pink'
color: "#fea9c5"
},
{
name: 'Purple'
color: "#eac5ee"
}
]
activeColorIndex: 0
highlightClassPrefix: 'highlight-'
constructor: ->
super
#@articles = @el.closest('.content').find('.textBubble-content')
rangy.init()
@highlighter = rangy.createHighlighter(document, 'TextRange')
@addClassApplier entry for entry in @colors
@setColor()
@render()
# store original highlight css data
@storeOriginalHighlight()
@loadHighlights()
render: ->
@html App.view('ticket_zoom/highlighter')
colors: @colors
activeColorIndex: @activeColorIndex
storeOriginalHighlight: =>
@originalHighlight =
fill: @highlighterControl.css('fill')
opacity: @highlighterControl.css('opacity')
restoreOriginalHighlight: =>
return if !@originalHighlight
@highlighterControl.css('fill', @originalHighlight.fill)
@highlighterControl.css('opacity', @originalHighlight.opacity)
highlightEnable: =>
@isActive = true
@highlighterControl.css('opacity', 1)
highlightDisable: =>
@isActive = false
@restoreOriginalHighlight()
#@highlighterControl.css('opacity', @originalHighlight.opacity)
active: =>
@isActive
# for testing purposes the highlights get stored in localStorage
loadHighlights: ->
if highlights = localStorage['highlights']
@highlighter.deserialize localStorage['highlights']
# the serialization creates one string for the entiery ticket
# containing the offsets and the highlight classes
#
# we have to check how it works with having open several tickets it might break
#
# if classes can be changed in the admin interface
# we have to watch out to not end up with empty highlight classes
storeHighlights: ->
localStorage['highlights'] = @highlighter.serialize()
# the colors is set via css classes (can't do it inline with rangy)
# thus we have to create a stylesheet if the colors
# can be changed in the admin interface
addClassApplier: (entry) ->
@highlighter.addClassApplier rangy.createCssClassApplier(@highlightClassPrefix + entry.name)
setColor: ->
@highlightClass = @highlightClassPrefix + @colors[@activeColorIndex].name
if @isActive
@articles.attr('data-highlightcolor', @colors[@activeColorIndex].name)
toggleHighlight: (e) =>
if @isActive
@restoreOriginalHighlight()
else
@highlightEnable()
return
console.log('toggleHighlight', @isActive, @articles)
if @isActive
$(e.currentTarget).removeClass('active')
@isActive = false
@articles.off('mouseup', @onMouseUp)
@articles.removeAttr('data-highlightcolor')
else
selection = rangy.getSelection()
# if there's already something selected,
# don't go into highlight mode
# just toggle the selected
if !selection.isCollapsed
@toggleHighlightAtSelection $(selection.anchorNode).closest @articles.selector
else
# toggle ui
$(e.currentTarget).addClass('active')
# activate selection background
@articles.attr('data-highlightcolor', @colors[@activeColorIndex].name)
@isActive = true
@articles.on('mouseup', @onMouseUp) #future: touchend
pickColor: (e) =>
@$('.js-highlightColor .visibility-change.active').removeClass('active')
$(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()
onMouseUp: (e) =>
#@toggleHighlightAtSelection $(e.currentTarget).closest('.textBubble-content')# @articles.selector
#
# toggle Highlight
# ================
#
# - only works when the selection starts and ends inside an article
# - clears highlights in selection
# - or highlights the selection
# - clears the selection
toggleHighlightAtSelection: (article) =>
selection = rangy.getSelection()
if @highlighter.selectionOverlapsHighlight selection
@highlighter.unhighlightSelection()
else
@highlighter.highlightSelection @highlightClass,
selection: selection
containerElementId: article.get(0).id
# remove selection
selection.removeAllRanges()
@highlightDisable()
#@storeHighlights()

View file

@ -4,10 +4,14 @@
<div class="scrollPageHeader"> <div class="scrollPageHeader">
<small><%- @C('ticket_hook') %> <span class="ticket-number"><%- @ticket.number %></span></small> <small><%- @C('ticket_hook') %> <span class="ticket-number"><%- @ticket.number %></span></small>
<div class="ticket-title"></div> <div class="ticket-title"></div>
<div class="highlighter"></div>
<div class="overview-navigator"></div> <div class="overview-navigator"></div>
</div> </div>
<div class="overview-navigator horizontal"></div> <div class="ticketZoom-controls">
<div class="page-header"> <div class="highlighter"></div>
<div class="overview-navigator"></div>
</div>
<div class="ticketZoom-header">
<div class="flex vertical center"> <div class="flex vertical center">
<div class="js-avatar"></div> <div class="js-avatar"></div>
<div class="ticket-title"></div> <div class="ticket-title"></div>

View file

@ -0,0 +1,22 @@
<div class="btn btn--action btn--split--first js-highlight centered">
<svg class="marker-icon"><use xlink:href="#icon-marker" /></svg>
</div>
<div class="dropdown dropdown--actions">
<div class="btn btn--action btn--split--last btn--slim centered" data-toggle="dropdown" aria-expanded="true">
<svg class="icon-arrow-down"><use xlink:href="#icon-arrow-down" /></svg>
</div>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<% for entry, i in @colors: %>
<li role="presentation">
<a role="menuitem" tabindex="-1" class="js-highlightColor" data-key="<%= i %>">
<span class="dropdown-iconSpacer">
<span class="color-swatch icon" style="background: <%= entry.color %>"></span>
</span>
<%= entry.name %>
<span class="dropdown-activeSpacer visibility-change<%= ' active' if i is @activeColorIndex %>">
<span class="white checkmark icon" data-visible="active"></span>
</span>
</a>
<% end %>
</ul>
</div>

View file

@ -1,4 +1,4 @@
<div class="pagination-counter align-right" title="<%- @Ti(@title) %>"> <div class="pagination-counter" title="<%- @Ti(@title) %>">
<span class="pagination-item-current"><%= @current_position %></span>/<span class="pagination-total-items"><%= @total_count %></span> <span class="pagination-item-current"><%= @current_position %></span>/<span class="pagination-total-items"><%= @total_count %></span>
</div> </div>
<ul class="pagination"> <ul class="pagination">

View file

@ -1835,7 +1835,7 @@ footer {
@extend .u-clickable, .zIndex-5; @extend .u-clickable, .zIndex-5;
} }
.logo .activity-counter { .icon-logo .activity-counter {
height: 19px; height: 19px;
min-width: 19px; min-width: 19px;
position: absolute; position: absolute;
@ -2740,6 +2740,7 @@ footer {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
padding: 28px 0 0 0;
} }
.marker-icon { .marker-icon {
@ -2749,8 +2750,8 @@ footer {
height: 19px; height: 19px;
} }
.ticketZoom .page-header { .ticketZoom .ticketZoom-header {
margin-top: 24px; margin-top: 6px;
padding: 0; padding: 0;
} }
@ -4914,6 +4915,10 @@ label + .wizard-buttonList {
} }
} }
.highlighter {
@extend .horizontal;
}
[data-highlightcolor=Yellow]::selection { background: #f7e7b2; } [data-highlightcolor=Yellow]::selection { background: #f7e7b2; }
[data-highlightcolor=Yellow]::-moz-selection { background: #f7e7b2; } [data-highlightcolor=Yellow]::-moz-selection { background: #f7e7b2; }
.highlight-Yellow { background: #f7e7b2; } .highlight-Yellow { background: #f7e7b2; }
@ -4957,7 +4962,11 @@ label + .wizard-buttonList {
} }
.overview-navigator { .overview-navigator {
display: inherit; @extend .horizontal;
}
.overview-navigator .pagination {
margin: 0 0 0 10px;
} }
.empty-space { .empty-space {

View file

@ -72,7 +72,7 @@ class AgentTicketActionLevel1Test < TestCase
# check if merged to ticket is shown now # check if merged to ticket is shown now
watch_for( watch_for(
css: '.active .page-header .ticket-number', css: '.active .ticketZoom-header .ticket-number',
value: ticket1[:number], value: ticket1[:number],
) )
watch_for( watch_for(
@ -128,7 +128,7 @@ class AgentTicketActionLevel1Test < TestCase
# check if merged to ticket is shown now # check if merged to ticket is shown now
watch_for( watch_for(
css: '.active .page-header .ticket-number', css: '.active .ticketZoom-header .ticket-number',
value: ticket3[:number], value: ticket3[:number],
) )
watch_for( watch_for(

View file

@ -51,7 +51,7 @@ class AgentTicketActionLevel6Test < TestCase
# check if ticket is shown # check if ticket is shown
location_check( url: '#ticket/zoom/' ) location_check( url: '#ticket/zoom/' )
sleep 2 sleep 2
ticket_number = @browser.find_elements( { css: '.active .page-header .ticket-number' } )[0].text ticket_number = @browser.find_elements( { css: '.active .ticketZoom-header .ticket-number' } )[0].text
# #
# attachment checks - update ticket # attachment checks - update ticket

View file

@ -87,7 +87,7 @@ class AgentTicketOverviewLevel1Test < TestCase
) )
match( match(
browser: browser2, browser: browser2,
css: '.active .page-header .ticket-number', css: '.active .ticketZoom-header .ticket-number',
value: ticket3[:number], value: ticket3[:number],
) )
@ -102,7 +102,7 @@ class AgentTicketOverviewLevel1Test < TestCase
) )
match( match(
browser: browser2, browser: browser2,
css: '.active .page-header .ticket-number', css: '.active .ticketZoom-header .ticket-number',
value: ticket2[:number], value: ticket2[:number],
) )
@ -117,7 +117,7 @@ class AgentTicketOverviewLevel1Test < TestCase
) )
match( match(
browser: browser2, browser: browser2,
css: '.active .page-header .ticket-number', css: '.active .ticketZoom-header .ticket-number',
value: ticket1[:number], value: ticket1[:number],
) )
@ -138,7 +138,7 @@ class AgentTicketOverviewLevel1Test < TestCase
) )
match( match(
browser: browser2, browser: browser2,
css: '.active .page-header .ticket-number', css: '.active .ticketZoom-header .ticket-number',
value: ticket1[:number], value: ticket1[:number],
) )
click( click(
@ -153,7 +153,7 @@ class AgentTicketOverviewLevel1Test < TestCase
) )
match( match(
browser: browser2, browser: browser2,
css: '.active .page-header .ticket-number', css: '.active .ticketZoom-header .ticket-number',
value: ticket2[:number], value: ticket2[:number],
) )
end end

View file

@ -1080,7 +1080,7 @@ wait untill text in selector disabppears
id.gsub!(//, ) id.gsub!(//, )
id.gsub!(%r{^.+?/(\d+)$}, '\\1') id.gsub!(%r{^.+?/(\d+)$}, '\\1')
element = instance.find_elements( { css: '.active .page-header .ticket-number' } )[0] element = instance.find_elements( { css: '.active .ticketZoom-header .ticket-number' } )[0]
if element if element
number = element.text number = element.text
ticket = { ticket = {
@ -1121,28 +1121,28 @@ wait untill text in selector disabppears
data = params[:data] data = params[:data]
if data[:title] if data[:title]
#element = instance.find_elements( { :css => '.content.active .page-header .ticket-title-update' } )[0] #element = instance.find_elements( { :css => '.content.active .ticketZoom-header .ticket-title-update' } )[0]
#element.clear #element.clear
#sleep 0.5 #sleep 0.5
#element = instance.find_elements( { :css => '.content.active .page-header .ticket-title-update' } )[0] #element = instance.find_elements( { :css => '.content.active .ticketZoom-header .ticket-title-update' } )[0]
#element.send_keys( data[:title] ) #element.send_keys( data[:title] )
#sleep 0.5 #sleep 0.5
#element.send_keys( :tab ) #element.send_keys( :tab )
instance.execute_script( '$(".content.active .page-header .ticket-title-update").focus()' ) instance.execute_script( '$(".content.active .ticketZoom-header .ticket-title-update").focus()' )
instance.execute_script( '$(".content.active .page-header .ticket-title-update").text("' + data[:title] + '")' ) instance.execute_script( '$(".content.active .ticketZoom-header .ticket-title-update").text("' + data[:title] + '")' )
instance.execute_script( '$(".content.active .page-header .ticket-title-update").blur()' ) instance.execute_script( '$(".content.active .ticketZoom-header .ticket-title-update").blur()' )
instance.execute_script( '$(".content.active .page-header .ticket-title-update").trigger("blur")' ) instance.execute_script( '$(".content.active .ticketZoom-header .ticket-title-update").trigger("blur")' )
# { # {
# :where => :instance2, # :where => :instance2,
# :execute => 'sendkey', # :execute => 'sendkey',
# :css => '.content.active .page-header .ticket-title-update', # :css => '.content.active .ticketZoom-header .ticket-title-update',
# :value => 'TTT', # :value => 'TTT',
# }, # },
# { # {
# :where => :instance2, # :where => :instance2,
# :execute => 'sendkey', # :execute => 'sendkey',
# :css => '.content.active .page-header .ticket-title-update', # :css => '.content.active .ticketZoom-header .ticket-title-update',
# :value => :tab, # :value => :tab,
# }, # },
end end
@ -1288,7 +1288,7 @@ wait untill text in selector disabppears
data = params[:data] data = params[:data]
if data[:title] if data[:title]
title = instance.find_elements( { css: '.content.active .page-header .ticket-title-update' } )[0].text.strip title = instance.find_elements( { css: '.content.active .ticketZoom-header .ticket-title-update' } )[0].text.strip
if title =~ /#{data[:title]}/i if title =~ /#{data[:title]}/i
assert( true, "matching '#{data[:title]}' in title '#{title}'" ) assert( true, "matching '#{data[:title]}' in title '#{title}'" )
else else
@ -1328,7 +1328,7 @@ wait untill text in selector disabppears
sleep 1 sleep 1
instance.find_elements( { partial_link_text: params[:number] } )[0].click instance.find_elements( { partial_link_text: params[:number] } )[0].click
sleep 1 sleep 1
number = instance.find_elements( { css: '.active .page-header .ticket-number' } )[0].text number = instance.find_elements( { css: '.active .ticketZoom-header .ticket-number' } )[0].text
if number !~ /#{params[:number]}/ if number !~ /#{params[:number]}/
screenshot( browser: instance, comment: 'ticket_open_by_overview_failed' ) screenshot( browser: instance, comment: 'ticket_open_by_overview_failed' )
fail "unable to search/find ticket #{params[:number]}!" fail "unable to search/find ticket #{params[:number]}!"
@ -1376,7 +1376,7 @@ wait untill text in selector disabppears
# open ticket # open ticket
instance.find_element( { partial_link_text: params[:number] } ).click instance.find_element( { partial_link_text: params[:number] } ).click
number = instance.find_elements( { css: '.active .page-header .ticket-number' } )[0].text number = instance.find_elements( { css: '.active .ticketZoom-header .ticket-number' } )[0].text
if number !~ /#{params[:number]}/ if number !~ /#{params[:number]}/
screenshot( browser: instance, comment: 'ticket_open_by_search_failed' ) screenshot( browser: instance, comment: 'ticket_open_by_search_failed' )
fail "unable to search/find ticket #{params[:number]}!" fail "unable to search/find ticket #{params[:number]}!"