Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
heptapod
heptapod
Commits
f7950ec78217
Commit
e7bc0d7c
authored
Sep 04, 2017
by
Clement Ho
Committed by
Phil Hughes
Sep 04, 2017
Browse files
Add feature highlight to Issue Boards in new navigation sidebar
parent
42b60e8d6cc3
Changes
13
Hide whitespace changes
Inline
Side-by-side
app/assets/javascripts/feature_highlight/feature_highlight.js
0 → 100644
View file @
f7950ec7
import
Cookies
from
'
js-cookie
'
;
import
_
from
'
underscore
'
;
import
{
getCookieName
,
getSelector
,
hidePopover
,
setupDismissButton
,
mouseenter
,
mouseleave
,
}
from
'
./feature_highlight_helper
'
;
export
const
setupFeatureHighlightPopover
=
(
id
,
debounceTimeout
=
300
)
=>
{
const
$selector
=
$
(
getSelector
(
id
));
const
$parent
=
$selector
.
parent
();
const
$popoverContent
=
$parent
.
siblings
(
'
.feature-highlight-popover-content
'
);
const
hideOnScroll
=
hidePopover
.
bind
(
$selector
);
const
debouncedMouseleave
=
_
.
debounce
(
mouseleave
,
debounceTimeout
);
$selector
// Setup popover
.
data
(
'
content
'
,
$popoverContent
.
prop
(
'
outerHTML
'
))
.
popover
({
html
:
true
,
// Override the existing template to add custom CSS classes
template
:
`
<div class="popover feature-highlight-popover" role="tooltip">
<div class="arrow"></div>
<div class="popover-content"></div>
</div>
`
,
})
.
on
(
'
mouseenter
'
,
mouseenter
)
.
on
(
'
mouseleave
'
,
debouncedMouseleave
)
.
on
(
'
inserted.bs.popover
'
,
setupDismissButton
)
.
on
(
'
show.bs.popover
'
,
()
=>
{
window
.
addEventListener
(
'
scroll
'
,
hideOnScroll
);
})
.
on
(
'
hide.bs.popover
'
,
()
=>
{
window
.
removeEventListener
(
'
scroll
'
,
hideOnScroll
);
})
// Display feature highlight
.
removeAttr
(
'
disabled
'
);
};
export
const
shouldHighlightFeature
=
(
id
)
=>
{
const
element
=
document
.
querySelector
(
getSelector
(
id
));
const
previouslyDismissed
=
Cookies
.
get
(
getCookieName
(
id
))
===
'
true
'
;
return
element
&&
!
previouslyDismissed
;
};
export
const
highlightFeatures
=
(
highlightOrder
)
=>
{
const
featureId
=
highlightOrder
.
find
(
shouldHighlightFeature
);
if
(
featureId
)
{
setupFeatureHighlightPopover
(
featureId
);
return
true
;
}
return
false
;
};
app/assets/javascripts/feature_highlight/feature_highlight_helper.js
0 → 100644
View file @
f7950ec7
import
Cookies
from
'
js-cookie
'
;
export
const
getCookieName
=
cookieId
=>
`feature-highlighted-
${
cookieId
}
`
;
export
const
getSelector
=
highlightId
=>
`.js-feature-highlight[data-highlight=
${
highlightId
}
]`
;
export
const
showPopover
=
function
showPopover
()
{
if
(
this
.
hasClass
(
'
js-popover-show
'
))
{
return
false
;
}
this
.
popover
(
'
show
'
);
this
.
addClass
(
'
disable-animation js-popover-show
'
);
return
true
;
};
export
const
hidePopover
=
function
hidePopover
()
{
if
(
!
this
.
hasClass
(
'
js-popover-show
'
))
{
return
false
;
}
this
.
popover
(
'
hide
'
);
this
.
removeClass
(
'
disable-animation js-popover-show
'
);
return
true
;
};
export
const
dismiss
=
function
dismiss
(
cookieId
)
{
Cookies
.
set
(
getCookieName
(
cookieId
),
true
);
hidePopover
.
call
(
this
);
this
.
hide
();
};
export
const
mouseleave
=
function
mouseleave
()
{
if
(
!
$
(
'
.popover:hover
'
).
length
>
0
)
{
const
$featureHighlight
=
$
(
this
);
hidePopover
.
call
(
$featureHighlight
);
}
};
export
const
mouseenter
=
function
mouseenter
()
{
const
$featureHighlight
=
$
(
this
);
const
showedPopover
=
showPopover
.
call
(
$featureHighlight
);
if
(
showedPopover
)
{
$
(
'
.popover
'
)
.
on
(
'
mouseleave
'
,
mouseleave
.
bind
(
$featureHighlight
));
}
};
export
const
setupDismissButton
=
function
setupDismissButton
()
{
const
popoverId
=
this
.
getAttribute
(
'
aria-describedby
'
);
const
cookieId
=
this
.
dataset
.
highlight
;
const
$popover
=
$
(
this
);
const
dismissWrapper
=
dismiss
.
bind
(
$popover
,
cookieId
);
$
(
`#
${
popoverId
}
.dismiss-feature-highlight`
)
.
on
(
'
click
'
,
dismissWrapper
);
};
app/assets/javascripts/feature_highlight/feature_highlight_options.js
0 → 100644
View file @
f7950ec7
import
{
highlightFeatures
}
from
'
./feature_highlight
'
;
import
bp
from
'
../breakpoints
'
;
const
highlightOrder
=
[
'
issue-boards
'
];
export
default
function
domContentLoaded
(
order
)
{
if
(
bp
.
getBreakpointSize
()
===
'
lg
'
)
{
highlightFeatures
(
order
);
}
}
document
.
addEventListener
(
'
DOMContentLoaded
'
,
domContentLoaded
.
bind
(
this
,
highlightOrder
));
app/assets/javascripts/main.js
View file @
f7950ec7
...
...
@@ -102,6 +102,7 @@ import './label_manager';
import
'
./labels
'
;
import
'
./labels_select
'
;
import
'
./layout_nav
'
;
import
'
./feature_highlight/feature_highlight_options
'
;
import
LazyLoader
from
'
./lazy_loader
'
;
import
'
./line_highlighter
'
;
import
'
./logo
'
;
...
...
app/assets/stylesheets/framework.scss
View file @
f7950ec7
...
...
@@ -51,3 +51,4 @@
@import
"framework/snippets"
;
@import
"framework/memory_graph"
;
@import
"framework/responsive-tables"
;
@import
"framework/feature_highlight"
;
app/assets/stylesheets/framework/buttons.scss
View file @
f7950ec7
...
...
@@ -46,6 +46,15 @@
}
}
@mixin
btn-svg
{
svg
{
height
:
15px
;
width
:
15px
;
position
:
relative
;
top
:
2px
;
}
}
@mixin
btn-color
(
$light
,
$border-light
,
$normal
,
$border-normal
,
$dark
,
$border-dark
,
$color
)
{
background-color
:
$light
;
border-color
:
$border-light
;
...
...
@@ -123,6 +132,7 @@
.btn
{
@include
btn-default
;
@include
btn-white
;
@include
btn-svg
;
color
:
$gl-text-color
;
...
...
@@ -222,13 +232,6 @@
}
}
svg
{
height
:
15px
;
width
:
15px
;
position
:
relative
;
top
:
2px
;
}
svg
,
.fa
{
&
:not
(
:last-child
)
{
...
...
app/assets/stylesheets/framework/feature_highlight.scss
0 → 100644
View file @
f7950ec7
.feature-highlight
{
position
:
relative
;
margin-left
:
$gl-padding
;
width
:
20px
;
height
:
20px
;
cursor
:
pointer
;
&
::before
{
content
:
''
;
display
:
block
;
position
:
absolute
;
top
:
6px
;
left
:
6px
;
width
:
8px
;
height
:
8px
;
background-color
:
$blue-500
;
border-radius
:
50%
;
box-shadow
:
0
0
0
rgba
(
$blue-500
,
0
.4
);
animation
:
pulse-highlight
2s
infinite
;
}
&
:hover::before
,
&
.disable-animation
::before
{
animation
:
none
;
}
&
[
disabled
]
::before
{
display
:
none
;
}
}
.is-showing-fly-out
{
.feature-highlight
{
display
:
none
;
}
}
.feature-highlight-popover-content
{
display
:
none
;
hr
{
margin
:
$gl-padding
*
0
.5
0
;
}
.btn-link
{
@include
btn-svg
;
svg
path
{
fill
:
currentColor
;
}
}
.dismiss-feature-highlight
{
padding
:
0
;
}
svg
:first-child
{
width
:
100%
;
background-color
:
$indigo-50
;
border-top-left-radius
:
2px
;
border-top-right-radius
:
2px
;
border-bottom
:
1px
solid
darken
(
$gray-normal
,
8%
);
}
}
.popover
.feature-highlight-popover-content
{
display
:
block
;
}
.feature-highlight-popover
{
padding
:
0
;
.popover-content
{
padding
:
0
;
}
}
.feature-highlight-popover-sub-content
{
padding
:
9px
14px
;
}
@include
keyframes
(
pulse-highlight
)
{
0
%
{
box-shadow
:
0
0
0
0
rgba
(
$blue-200
,
0
.4
);
}
70
%
{
box-shadow
:
0
0
0
10px
transparent
;
}
100
%
{
box-shadow
:
0
0
0
0
transparent
;
}
}
app/views/feature_highlight/_issue_boards.svg
0 → 100644
View file @
f7950ec7
<svg
xmlns=
"http://www.w3.org/2000/svg"
width=
"214"
height=
"102"
viewBox=
"0 0 214 102"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
>
<defs>
<path
id=
"b"
d=
"M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,27 C48,28.1045695 47.1045695,29 46,29 L2,29 C0.8954305,29 1.3527075e-16,28.1045695 0,27 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"
/>
<filter
id=
"a"
width=
"102.1%"
height=
"106.9%"
x=
"-1%"
y=
"-1.7%"
filterUnits=
"objectBoundingBox"
>
<feOffset
dy=
"1"
in=
"SourceAlpha"
result=
"shadowOffsetOuter1"
/>
<feColorMatrix
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"
in=
"shadowOffsetOuter1"
/>
</filter>
<path
id=
"d"
d=
"M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"
/>
<filter
id=
"c"
width=
"102.1%"
height=
"107.1%"
x=
"-1%"
y=
"-1.8%"
filterUnits=
"objectBoundingBox"
>
<feOffset
dy=
"1"
in=
"SourceAlpha"
result=
"shadowOffsetOuter1"
/>
<feColorMatrix
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"
in=
"shadowOffsetOuter1"
/>
</filter>
<path
id=
"e"
d=
"M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"
/>
<path
id=
"h"
d=
"M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"
/>
<filter
id=
"g"
width=
"102.1%"
height=
"107.1%"
x=
"-1%"
y=
"-1.8%"
filterUnits=
"objectBoundingBox"
>
<feOffset
dy=
"1"
in=
"SourceAlpha"
result=
"shadowOffsetOuter1"
/>
<feColorMatrix
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"
in=
"shadowOffsetOuter1"
/>
</filter>
<path
id=
"j"
d=
"M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"
/>
<filter
id=
"i"
width=
"102.1%"
height=
"107.1%"
x=
"-1%"
y=
"-1.8%"
filterUnits=
"objectBoundingBox"
>
<feOffset
dy=
"1"
in=
"SourceAlpha"
result=
"shadowOffsetOuter1"
/>
<feColorMatrix
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"
in=
"shadowOffsetOuter1"
/>
</filter>
<path
id=
"l"
d=
"M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"
/>
<filter
id=
"k"
width=
"102.1%"
height=
"107.1%"
x=
"-1%"
y=
"-1.8%"
filterUnits=
"objectBoundingBox"
>
<feOffset
dy=
"1"
in=
"SourceAlpha"
result=
"shadowOffsetOuter1"
/>
<feColorMatrix
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"
in=
"shadowOffsetOuter1"
/>
</filter>
<path
id=
"n"
d=
"M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"
/>
<filter
id=
"m"
width=
"102.1%"
height=
"107.1%"
x=
"-1%"
y=
"-1.8%"
filterUnits=
"objectBoundingBox"
>
<feOffset
dy=
"1"
in=
"SourceAlpha"
result=
"shadowOffsetOuter1"
/>
<feColorMatrix
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"
in=
"shadowOffsetOuter1"
/>
</filter>
<path
id=
"p"
d=
"M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"
/>
<filter
id=
"o"
width=
"102.1%"
height=
"107.1%"
x=
"-1%"
y=
"-1.8%"
filterUnits=
"objectBoundingBox"
>
<feOffset
dy=
"1"
in=
"SourceAlpha"
result=
"shadowOffsetOuter1"
/>
<feColorMatrix
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"
in=
"shadowOffsetOuter1"
/>
</filter>
</defs>
<g
fill=
"none"
fill-rule=
"evenodd"
>
<path
fill=
"#D6D4DE"
d=
"M14,21 L62,21 C64.7614237,21 67,23.2385763 67,26 L67,112 C67,114.761424 64.7614237,117 62,117 L14,117 C11.2385763,117 9,114.761424 9,112 L9,26 C9,23.2385763 11.2385763,21 14,21 Z"
/>
<g
transform=
"translate(11 23)"
>
<path
fill=
"#FFFFFF"
d=
"M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"
/>
<path
fill=
"#FC6D26"
d=
"M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"
/>
<g
transform=
"translate(5 10)"
>
<use
fill=
"black"
filter=
"url(#a)"
xlink:href=
"#b"
/>
<use
fill=
"#F9F9F9"
xlink:href=
"#b"
/>
</g>
<g
transform=
"translate(5 42)"
>
<use
fill=
"black"
filter=
"url(#c)"
xlink:href=
"#d"
/>
<use
fill=
"#FEF0E8"
xlink:href=
"#d"
/>
<path
fill=
"#FEE1D3"
d=
"M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"
/>
<path
fill=
"#FDC4A8"
d=
"M9,17 L17,17 C18.1045695,17 19,17.8954305 19,19 C19,20.1045695 18.1045695,21 17,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"
/>
<path
fill=
"#FC6D26"
d=
"M24,17 L32,17 C33.1045695,17 34,17.8954305 34,19 C34,20.1045695 33.1045695,21 32,21 L24,21 C22.8954305,21 22,20.1045695 22,19 C22,17.8954305 22.8954305,17 24,17 Z"
/>
</g>
</g>
<path
fill=
"#D6D4DE"
d=
"M148,26 L196,26 C198.761424,26 201,28.2385763 201,31 L201,117 C201,119.761424 198.761424,122 196,122 L148,122 C145.238576,122 143,119.761424 143,117 L143,31 C143,28.2385763 145.238576,26 148,26 Z"
/>
<g
transform=
"translate(145 28)"
>
<mask
id=
"f"
fill=
"white"
>
<use
xlink:href=
"#e"
/>
</mask>
<use
fill=
"#FFFFFF"
xlink:href=
"#e"
/>
<path
fill=
"#FC6D26"
d=
"M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"
mask=
"url(#f)"
/>
<g
transform=
"translate(5 10)"
>
<use
fill=
"black"
filter=
"url(#g)"
xlink:href=
"#h"
/>
<use
fill=
"#F9F9F9"
xlink:href=
"#h"
/>
</g>
<g
transform=
"translate(5 42)"
>
<use
fill=
"black"
filter=
"url(#i)"
xlink:href=
"#j"
/>
<use
fill=
"#FEF0E8"
xlink:href=
"#j"
/>
<path
fill=
"#FEE1D3"
d=
"M9 8L33 8C34.1045695 8 35 8.8954305 35 10 35 11.1045695 34.1045695 12 33 12L9 12C7.8954305 12 7 11.1045695 7 10 7 8.8954305 7.8954305 8 9 8zM9 17L13 17C14.1045695 17 15 17.8954305 15 19 15 20.1045695 14.1045695 21 13 21L9 21C7.8954305 21 7 20.1045695 7 19 7 17.8954305 7.8954305 17 9 17z"
/>
<path
fill=
"#FC6D26"
d=
"M20,17 L24,17 C25.1045695,17 26,17.8954305 26,19 C26,20.1045695 25.1045695,21 24,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"
/>
<path
fill=
"#FDC4A8"
d=
"M31,17 L35,17 C36.1045695,17 37,17.8954305 37,19 C37,20.1045695 36.1045695,21 35,21 L31,21 C29.8954305,21 29,20.1045695 29,19 C29,17.8954305 29.8954305,17 31,17 Z"
/>
</g>
</g>
<path
fill=
"#D6D4DE"
d=
"M81,14 L129,14 C131.761424,14 134,16.2385763 134,19 L134,105 C134,107.761424 131.761424,110 129,110 L81,110 C78.2385763,110 76,107.761424 76,105 L76,19 C76,16.2385763 78.2385763,14 81,14 Z"
/>
<g
transform=
"translate(78 16)"
>
<path
fill=
"#FFFFFF"
d=
"M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"
/>
<g
transform=
"translate(5 10)"
>
<use
fill=
"black"
filter=
"url(#k)"
xlink:href=
"#l"
/>
<use
fill=
"#EFEDF8"
xlink:href=
"#l"
/>
<path
fill=
"#E1DBF1"
d=
"M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"
/>
<path
fill=
"#6B4FBB"
d=
"M9,17 L13,17 C14.1045695,17 15,17.8954305 15,19 C15,20.1045695 14.1045695,21 13,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"
/>
<path
fill=
"#C3B8E3"
d=
"M20,17 L28,17 C29.1045695,17 30,17.8954305 30,19 C30,20.1045695 29.1045695,21 28,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"
/>
</g>
<g
transform=
"translate(5 42)"
>
<use
fill=
"black"
filter=
"url(#m)"
xlink:href=
"#n"
/>
<use
fill=
"#F9F9F9"
xlink:href=
"#n"
/>
</g>
<g
transform=
"translate(5 74)"
>
<rect
width=
"34"
height=
"4"
x=
"7"
y=
"7"
fill=
"#E1DBF1"
rx=
"2"
/>
<use
fill=
"black"
filter=
"url(#o)"
xlink:href=
"#p"
/>
<use
fill=
"#F9F9F9"
xlink:href=
"#p"
/>
</g>
<path
fill=
"#6B4FBB"
d=
"M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"
/>
</g>
</g>
</svg>
app/views/layouts/nav/_new_project_sidebar.html.haml
View file @
f7950ec7
...
...
@@ -99,6 +99,20 @@
=
link_to
project_boards_path
(
@project
),
title:
'Board'
do
%span
Board
.feature-highlight.js-feature-highlight
{
disabled:
true
,
data:
{
trigger:
'manual'
,
container:
'body'
,
toggle:
'popover'
,
placement:
'right'
,
highlight:
'issue-boards'
}
}
.feature-highlight-popover-content
=
render
'feature_highlight/issue_boards.svg'
.feature-highlight-popover-sub-content
%span
=
_
(
'Use'
)
=
link_to
'Issue Boards'
,
project_boards_path
(
@project
)
%span
=
_
(
'to create customized software development workflows like'
)
%strong
=
_
(
'Scrum'
)
%span
=
_
(
'or'
)
%strong
=
_
(
'Kanban'
)
%hr
%button
.btn-link.dismiss-feature-highlight
{
type:
'button'
}
%span
=
_
(
"Got it! Don't show this again"
)
=
custom_icon
(
'thumbs_up'
)
=
nav_link
(
controller: :labels
)
do
=
link_to
project_labels_path
(
@project
),
title:
'Labels'
do
...
...
app/views/shared/icons/_thumbs_up.svg
0 → 100644
View file @
f7950ec7
<svg
xmlns=
"http://www.w3.org/2000/svg"
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
><path
fill-rule=
"evenodd"
d=
"M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.104 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.486.5l.138.137a1 1 0 0 1 .28.87L8.33 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"
/></svg>
spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
0 → 100644
View file @
f7950ec7
import
Cookies
from
'
js-cookie
'
;
import
{
getCookieName
,
getSelector
,
showPopover
,
hidePopover
,
dismiss
,
mouseleave
,
mouseenter
,
setupDismissButton
,
}
from
'
~/feature_highlight/feature_highlight_helper
'
;
describe
(
'
feature highlight helper
'
,
()
=>
{
describe
(
'
getCookieName
'
,
()
=>
{
it
(
'
returns `feature-highlighted-` prefix
'
,
()
=>
{
const
cookieId
=
'
cookieId
'
;
expect
(
getCookieName
(
cookieId
)).
toEqual
(
`feature-highlighted-
${
cookieId
}
`
);
});
});
describe
(
'
getSelector
'
,
()
=>
{
it
(
'
returns js-feature-highlight selector
'
,
()
=>
{
const
highlightId
=
'
highlightId
'
;
expect
(
getSelector
(
highlightId
)).
toEqual
(
`.js-feature-highlight[data-highlight=
${
highlightId
}
]`
);
});
});
describe
(
'
showPopover
'
,
()
=>
{
it
(
'
returns true when popover is shown
'
,
()
=>
{
const
context
=
{
hasClass
:
()
=>
false
,
popover
:
()
=>
{},
addClass
:
()
=>
{},
};
expect
(
showPopover
.
call
(
context
)).
toEqual
(
true
);
});
it
(
'
returns false when popover is already shown
'
,
()
=>
{
const
context
=
{
hasClass
:
()
=>
true
,
};
expect
(
showPopover
.
call
(
context
)).
toEqual
(
false
);
});
it
(
'
shows popover
'
,
(
done
)
=>
{
const
context
=
{
hasClass
:
()
=>
false
,
popover
:
()
=>
{},
addClass
:
()
=>
{},
};
spyOn
(
context
,
'
popover
'
).
and
.
callFake
((
method
)
=>
{
expect
(
method
).
toEqual
(
'
show
'
);
done
();
});
showPopover
.
call
(
context
);
});
it
(
'
adds disable-animation and js-popover-show class
'
,
(
done
)
=>
{
const
context
=
{
hasClass
:
()
=>
false
,
popover
:
()
=>
{},
addClass
:
()
=>
{},
};
spyOn
(
context
,
'
addClass
'
).
and
.
callFake
((
classNames
)
=>
{
expect
(
classNames
).
toEqual
(
'
disable-animation js-popover-show
'
);
done
();
});
showPopover
.
call
(
context
);
});
});
describe
(
'
hidePopover
'
,
()
=>
{
it
(
'
returns true when popover is hidden
'
,
()
=>
{
const
context
=
{
hasClass
:
()
=>
true
,
popover
:
()
=>
{},
removeClass
:
()
=>
{},
};
expect
(
hidePopover
.
call
(
context
)).
toEqual
(
true
);
});
it
(
'
returns false when popover is already hidden
'
,
()
=>
{
const
context
=
{
hasClass
:
()
=>
false
,
};
expect
(
hidePopover
.
call
(
context
)).
toEqual
(
false
);
});
it
(
'
hides popover
'
,
(
done
)
=>
{
const
context
=
{
hasClass
:
()
=>
true
,
popover
:
()
=>
{},
removeClass
:
()
=>
{},
};
spyOn
(
context
,
'
popover
'
).
and
.
callFake
((
method
)
=>
{
expect
(
method
).
toEqual
(
'
hide
'
);
done
();
});
hidePopover
.
call
(
context
);
});
it
(
'
removes disable-animation and js-popover-show class
'
,
(
done
)
=>
{
const
context
=
{
hasClass
:
()
=>
true
,
popover
:
()
=>
{},
removeClass
:
()
=>
{},
};
spyOn
(
context
,
'
removeClass
'
).
and
.
callFake
((
classNames
)
=>
{
expect
(
classNames
).
toEqual
(
'
disable-animation js-popover-show
'
);
done
();
});
hidePopover
.
call
(
context
);
});
});
describe
(
'
dismiss
'
,
()
=>
{
const
context
=
{
hide
:
()
=>
{},
};
beforeEach
(()
=>
{
spyOn
(
Cookies
,
'
set
'
).
and
.
callFake
(()
=>
{});
spyOn
(
hidePopover
,
'
call
'
).
and
.
callFake
(()
=>
{});
spyOn
(
context
,
'
hide
'
).
and
.
callFake
(()
=>
{});
dismiss
.
call
(
context
);
});
it
(
'
sets cookie to true
'
,
()
=>
{
expect
(
Cookies
.
set
).
toHaveBeenCalled
();
});
it
(
'
calls hide popover
'
,
()
=>
{
expect
(
hidePopover
.
call
).
toHaveBeenCalled
();
});
it
(
'
calls hide
'
,
()
=>
{
expect
(
context
.
hide
).
toHaveBeenCalled
();
});
});
describe
(
'
mouseleave
'
,
()
=>
{
it
(
'
calls hide popover if .popover:hover is false
'
,
()
=>
{
const
fakeJquery
=
{
length
:
0
,
};
spyOn
(
$
.
fn
,
'
init
'
).
and
.
callFake
(
selector
=>
(
selector
===
'
.popover:hover
'
?
fakeJquery
:
$
.
fn
));
spyOn
(
hidePopover
,
'
call
'
);
mouseleave
();
expect
(
hidePopover
.
call
).
toHaveBeenCalled
();
});
it
(
'
does not call hide popover if .popover:hover is true
'
,
()
=>
{
const
fakeJquery
=
{
length
:
1
,
};
spyOn
(
$
.
fn
,
'
init
'
).
and
.
callFake
(
selector
=>
(
selector
===
'
.popover:hover
'
?
fakeJquery
:
$
.
fn
));
spyOn
(
hidePopover
,
'
call
'
);
mouseleave
();
expect
(
hidePopover
.
call
).
not
.
toHaveBeenCalled
();
});
});