Init version of highlighter.
This commit is contained in:
parent
8c651959a8
commit
e590248307
9 changed files with 241 additions and 27 deletions
|
@ -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()
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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">
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]}!"
|
||||||
|
|
Loading…
Reference in a new issue