Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
88f438cd
Commit
88f438cd
authored
Jun 18, 2019
by
Lyza Danger Gardner
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add preact annotation-publish-control component
parent
b843736e
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
477 additions
and
41 deletions
+477
-41
annotation-publish-control.js
src/sidebar/components/annotation-publish-control.js
+117
-0
annotation-publish-control-test.js
...idebar/components/test/annotation-publish-control-test.js
+199
-0
index.js
src/sidebar/index.js
+4
-0
forms.scss
src/styles/mixins/forms.scss
+41
-17
annotation-publish-control.scss
...styles/sidebar/components/annotation-publish-control.scss
+115
-0
primary-action-btn.scss
src/styles/sidebar/components/primary-action-btn.scss
+0
-24
sidebar.scss
src/styles/sidebar/sidebar.scss
+1
-0
No files found.
src/sidebar/components/annotation-publish-control.js
0 → 100644
View file @
88f438cd
'use strict'
;
const
propTypes
=
require
(
'prop-types'
);
const
{
createElement
}
=
require
(
'preact'
);
const
{
applyTheme
}
=
require
(
'../util/theme'
);
const
{
withServices
}
=
require
(
'../util/service-context'
);
const
Menu
=
require
(
'./menu'
);
const
MenuItem
=
require
(
'./menu-item'
);
/**
* Render a compound control button for publishing (saving) an annotation:
* - Save the annotation — left side of button
* - Choose sharing/privacy option - drop-down menu on right side of button
*
*/
function
AnnotationPublishControl
({
group
,
isDisabled
,
isShared
,
onCancel
,
onSave
,
onSetPrivacy
,
settings
,
})
{
const
publishDestination
=
isShared
?
group
.
name
:
'Only Me'
;
const
themeProps
=
[
'ctaTextColor'
,
'ctaBackgroundColor'
];
const
menuLabel
=
(
<
div
className
=
"annotation-publish-control__btn-dropdown-arrow"
>
<
div
className
=
"annotation-publish-control__btn-dropdown-arrow-separator"
/>
<
div
className
=
"annotation-publish-control__btn-dropdown-arrow-indicator"
style
=
{
applyTheme
(
themeProps
,
settings
)}
>
<
div
>
▼
<
/div
>
<
/div
>
<
/div
>
);
return
(
<
div
className
=
"annotation-publish-control"
>
<
div
className
=
"annotation-publish-control__btn"
>
<
button
className
=
"annotation-publish-control__btn-primary"
style
=
{
applyTheme
(
themeProps
,
settings
)}
onClick
=
{
onSave
}
disabled
=
{
isDisabled
}
title
=
{
`Publish this annotation to
${
publishDestination
}
`
}
>
Post
to
{
publishDestination
}
<
/button
>
<
Menu
arrowClass
=
"annotation-publish-control__btn-menu-arrow"
containerPositioned
=
{
false
}
contentClass
=
"annotation-publish-control__btn-menu-content"
label
=
{
menuLabel
}
menuIndicator
=
{
false
}
title
=
"Change annotation sharing setting"
align
=
"left"
>
<
MenuItem
icon
=
{
group
.
type
===
'open'
?
'public'
:
'groups'
}
label
=
{
group
.
name
}
isSelected
=
{
isShared
}
onClick
=
{()
=>
onSetPrivacy
({
level
:
'shared'
})}
/
>
<
MenuItem
icon
=
"lock"
label
=
"Only Me"
isSelected
=
{
!
isShared
}
onClick
=
{()
=>
onSetPrivacy
({
level
:
'private'
})}
/
>
<
/Menu
>
<
/div
>
<
button
className
=
"annotation-publish-control__cancel-btn btn-clean"
onClick
=
{
onCancel
}
title
=
"Cancel changes to this annotation"
>
<
i
className
=
"h-icon-cancel-outline publish-annotation-cancel-btn__icon btn-icon"
/>
{
' '
}
Cancel
<
/button
>
<
/div
>
);
}
AnnotationPublishControl
.
propTypes
=
{
/** The group the annotation is currently associated with */
group
:
propTypes
.
object
.
isRequired
,
/**
* Should the save button be disabled?
* Hint: it will be if the annotation has no content
*/
isDisabled
:
propTypes
.
bool
,
/** The current privacy setting on the annotation. Is it shared to group? */
isShared
:
propTypes
.
bool
,
/** Callback for cancel button click */
onCancel
:
propTypes
.
func
.
isRequired
,
/** Callback for save button click */
onSave
:
propTypes
.
func
.
isRequired
,
/** Callback when selecting a privacy option in the menu */
onSetPrivacy
:
propTypes
.
func
.
isRequired
,
/** services */
settings
:
propTypes
.
object
.
isRequired
,
};
AnnotationPublishControl
.
injectedProps
=
[
'settings'
];
module
.
exports
=
withServices
(
AnnotationPublishControl
);
src/sidebar/components/test/annotation-publish-control-test.js
0 → 100644
View file @
88f438cd
'use strict'
;
const
{
createElement
}
=
require
(
'preact'
);
const
{
shallow
}
=
require
(
'enzyme'
);
const
AnnotationPublishControl
=
require
(
'../annotation-publish-control'
);
const
MenuItem
=
require
(
'../menu-item'
);
describe
(
'AnnotationPublishControl'
,
()
=>
{
let
fakeGroup
;
let
fakeSettings
;
let
fakeApplyTheme
;
const
createAnnotationPublishControl
=
(
props
=
{})
=>
{
return
shallow
(
<
AnnotationPublishControl
group
=
{
fakeGroup
}
isDisabled
=
{
false
}
isShared
=
{
true
}
onCancel
=
{
sinon
.
stub
()}
onSave
=
{
sinon
.
stub
()}
onSetPrivacy
=
{
sinon
.
stub
()}
settings
=
{
fakeSettings
}
{...
props
}
/
>
).
dive
();
// Dive needed because this component uses `withServices`
};
beforeEach
(()
=>
{
fakeGroup
=
{
name
:
'Fake Group'
,
type
:
'private'
,
};
fakeSettings
=
{
branding
:
{
ctaTextColor
:
'#0f0'
,
ctaBackgroundColor
:
'#00f'
,
},
};
fakeApplyTheme
=
sinon
.
stub
();
AnnotationPublishControl
.
$imports
.
$mock
({
'../util/theme'
:
{
applyTheme
:
fakeApplyTheme
,
},
});
});
describe
(
'theming'
,
()
=>
{
it
(
'should apply theme styles'
,
()
=>
{
const
fakeStyle
=
{
foo
:
'bar'
};
fakeApplyTheme
.
returns
(
fakeStyle
);
const
wrapper
=
createAnnotationPublishControl
();
const
btnPrimary
=
wrapper
.
find
(
'.annotation-publish-control__btn-primary'
);
assert
.
calledWith
(
fakeApplyTheme
,
[
'ctaTextColor'
,
'ctaBackgroundColor'
],
fakeSettings
);
assert
.
include
(
btnPrimary
.
prop
(
'style'
),
fakeStyle
);
});
});
describe
(
'dropdown menu button (form submit button)'
,
()
=>
{
const
btnClass
=
'.annotation-publish-control__btn-primary'
;
context
(
'shared annotation'
,
()
=>
{
it
(
'should label the button with the group name'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
({
isShared
:
true
});
const
btn
=
wrapper
.
find
(
btnClass
);
assert
.
equal
(
btn
.
prop
(
'title'
),
`Publish this annotation to
${
fakeGroup
.
name
}
`
);
assert
.
equal
(
btn
.
text
(),
`Post to
${
fakeGroup
.
name
}
`
);
});
});
context
(
'private annotation'
,
()
=>
{
it
(
'should label the button with "Only Me"'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
({
isShared
:
false
});
const
btn
=
wrapper
.
find
(
btnClass
);
assert
.
equal
(
btn
.
prop
(
'title'
),
'Publish this annotation to Only Me'
);
assert
.
equal
(
btn
.
text
(),
'Post to Only Me'
);
});
});
it
(
'should disable the button if `isDisabled`'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
({
isDisabled
:
true
});
const
btn
=
wrapper
.
find
(
btnClass
);
assert
.
isOk
(
btn
.
prop
(
'disabled'
));
});
it
(
'should enable the button if not `isDisabled`'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
({
isDisabled
:
false
});
const
btn
=
wrapper
.
find
(
btnClass
);
assert
.
isNotOk
(
btn
.
prop
(
'disabled'
));
});
it
(
'should have a save callback'
,
()
=>
{
const
fakeOnSave
=
sinon
.
stub
();
const
wrapper
=
createAnnotationPublishControl
({
onSave
:
fakeOnSave
});
const
btn
=
wrapper
.
find
(
btnClass
);
assert
.
equal
(
btn
.
prop
(
'onClick'
),
fakeOnSave
);
});
});
describe
(
'menu'
,
()
=>
{
describe
(
'share (to group) menu item'
,
()
=>
{
it
(
'should invoke privacy callback with shared privacy'
,
()
=>
{
const
fakeOnSetPrivacy
=
sinon
.
stub
();
const
wrapper
=
createAnnotationPublishControl
({
onSetPrivacy
:
fakeOnSetPrivacy
,
});
const
shareMenuItem
=
wrapper
.
find
(
MenuItem
).
first
();
shareMenuItem
.
prop
(
'onClick'
)();
assert
.
calledWith
(
fakeOnSetPrivacy
,
{
level
:
'shared'
});
});
it
(
'should have a label that is the name of the group'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
();
const
shareMenuItem
=
wrapper
.
find
(
MenuItem
).
first
();
assert
.
equal
(
shareMenuItem
.
prop
(
'label'
),
fakeGroup
.
name
);
});
context
(
'private group'
,
()
=>
{
it
(
'should have a group icon'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
();
const
shareMenuItem
=
wrapper
.
find
(
MenuItem
).
first
();
assert
.
equal
(
shareMenuItem
.
prop
(
'icon'
),
'groups'
);
});
});
context
(
'open group'
,
()
=>
{
beforeEach
(()
=>
{
fakeGroup
.
type
=
'open'
;
});
it
(
'should have a public icon'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
();
const
shareMenuItem
=
wrapper
.
find
(
MenuItem
).
first
();
assert
.
equal
(
shareMenuItem
.
prop
(
'icon'
),
'public'
);
});
});
});
describe
(
'private (only me) menu item'
,
()
=>
{
it
(
'should invoke callback with private privacy'
,
()
=>
{
const
fakeOnSetPrivacy
=
sinon
.
stub
();
const
wrapper
=
createAnnotationPublishControl
({
onSetPrivacy
:
fakeOnSetPrivacy
,
});
const
privateMenuItem
=
wrapper
.
find
(
MenuItem
).
at
(
1
);
privateMenuItem
.
prop
(
'onClick'
)();
assert
.
calledWith
(
fakeOnSetPrivacy
,
{
level
:
'private'
});
});
it
(
'should use a private/lock icon'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
();
const
privateMenuItem
=
wrapper
.
find
(
MenuItem
).
at
(
1
);
assert
.
equal
(
privateMenuItem
.
prop
(
'icon'
),
'lock'
);
});
it
(
'should have an "Only me" label'
,
()
=>
{
const
wrapper
=
createAnnotationPublishControl
();
const
privateMenuItem
=
wrapper
.
find
(
MenuItem
).
at
(
1
);
assert
.
equal
(
privateMenuItem
.
prop
(
'label'
),
'Only Me'
);
});
});
});
describe
(
'cancel button'
,
()
=>
{
it
(
'should have a cancel callback'
,
()
=>
{
const
fakeOnCancel
=
sinon
.
stub
();
const
wrapper
=
createAnnotationPublishControl
({
onCancel
:
fakeOnCancel
,
});
const
cancelBtn
=
wrapper
.
find
(
'.annotation-publish-control__cancel-btn'
);
cancelBtn
.
prop
(
'onClick'
)();
assert
.
calledOnce
(
fakeOnCancel
);
});
});
});
src/sidebar/index.js
View file @
88f438cd
...
...
@@ -147,6 +147,10 @@ function startAngularApp(config) {
'annotationActionButton'
,
wrapReactComponent
(
require
(
'./components/annotation-action-button'
))
)
.
component
(
'annotationPublishControl'
,
wrapReactComponent
(
require
(
'./components/annotation-publish-control'
))
)
.
component
(
'annotationShareDialog'
,
require
(
'./components/annotation-share-dialog'
)
...
...
src/styles/mixins/forms.scss
View file @
88f438cd
@import
"../base"
;
@import
'../base'
;
// See http://compass-style.org/reference/compass/utilities/general/clearfix/#mixin-pie-clearfix
@mixin
pie-clearfix
{
&
:after
{
content
:
""
;
content
:
''
;
display
:
table
;
clear
:
both
;
}
}
@mixin
focus-outline
{
border-color
:
#51A7E8
;
box-shadow
:
0px
1px
2px
rgba
(
0
,
0
,
0
,
.075
)
inset
,
0px
0px
5px
rgba
(
81
,
167
,
232
,
.5
);
border-color
:
#51a7e8
;
box-shadow
:
0px
1px
2px
rgba
(
0
,
0
,
0
,
0
.075
)
inset
,
0px
0px
5px
rgba
(
81
,
167
,
232
,
0
.5
);
}
@mixin
form-input
{
@include
font-normal
;
border
:
1px
solid
$gray-lighter
;
border-radius
:
2px
;
padding
:
.5em
.75em
;
padding
:
0
.5em
0
.75em
;
font-weight
:
normal
;
color
:
$gray
;
background-color
:
#
FAFAFA
;
background-color
:
#
fafafa
;
}
@mixin
form-input-focus
{
outline
:
none
;
background-color
:
#
FFF
;
background-color
:
#
fff
;
@include
focus-outline
;
@include
placeholder
{
...
...
@@ -45,20 +46,20 @@
}
@mixin
btn
{
box-shadow
:
0
1px
0
rgba
(
0
,
0
,
0
,
.15
);
box-shadow
:
0
1px
0
rgba
(
0
,
0
,
0
,
0
.15
);
background
:
linear-gradient
(
$button-background-gradient
);
display
:
inline-block
;
font-weight
:
bold
;
color
:
$button-text-color
;
text-shadow
:
0
1px
0
#
FFF
;
text-shadow
:
0
1px
0
#
fff
;
border-radius
:
2px
;
border
:
1px
solid
$gray-light
;
padding
:
.5em
.9em
;
padding
:
0
.5em
0
.9em
;
}
@mixin
btn-hover
{
box-shadow
:
0
1px
0
rgba
(
0
,
0
,
0
,
.05
);
box-shadow
:
0
1px
0
rgba
(
0
,
0
,
0
,
0
.05
);
outline
:
none
;
color
:
$button-text-color
;
background
:
$button-background-start
;
...
...
@@ -66,7 +67,7 @@
}
@mixin
btn-active
{
box-shadow
:
inset
0
1px
0
rgba
(
0
,
0
,
0
,
.1
);
box-shadow
:
inset
0
1px
0
rgba
(
0
,
0
,
0
,
0
.1
);
background
:
$button-background-end
;
color
:
#424242
;
border-color
:
#bababa
;
...
...
@@ -75,18 +76,41 @@
@mixin
btn-disabled
{
box-shadow
:
none
;
cursor
:
default
;
background
:
#
F0F0F
0
;
border-color
:
#
CECECE
;
background
:
#
f0f0f
0
;
border-color
:
#
cecece
;
color
:
$gray-light
;
}
@mixin
primary-action-btn
{
// note that there is currently some duplication here between
// the styling for this element and <dropdown-menu-btn>
color
:
$color-seashell
;
background-color
:
$color-dove-gray
;
height
:
35px
;
border
:
none
;
border-radius
:
2px
;
font-weight
:
bold
;
font-size
:
$body1-font-size
;
padding-left
:
12px
;
padding-right
:
12px
;
&
:disabled
{
color
:
$gray-light
;
}
&
:hover:enabled
{
background-color
:
$color-mine-shaft
;
}
}
// Tint and shade functions from
// https://css-tricks.com/snippets/sass/tint-shade-functions
@function
tint
(
$color
,
$percent
){
@function
tint
(
$color
,
$percent
)
{
@return
mix
(
white
,
$color
,
$percent
);
}
@function
shade
(
$color
,
$percent
){
@function
shade
(
$color
,
$percent
)
{
@return
mix
(
black
,
$color
,
$percent
);
}
src/styles/sidebar/components/annotation-publish-control.scss
0 → 100644
View file @
88f438cd
.annotation-publish-control
{
display
:
flex
;
&
__cancel-btn
{
@extend
.btn--cancel
;
margin-left
:
5px
;
font-weight
:
normal
;
&
__icon
{
margin-right
:
3px
;
transform
:
translateY
(
10%
);
}
}
// A split button with a primary submit on the left and a drop-down menu
// of related options to the right
.annotation-publish-control__btn
{
$text-color
:
$color-seashell
;
$default-background-color
:
$color-dove-gray
;
$hover-background-color
:
$color-mine-shaft
;
$h-padding
:
9px
;
$height
:
35px
;
$border-radius
:
2px
;
$arrow-indicator-width
:
26px
;
height
:
$height
;
position
:
relative
;
// Align the menu arrow correctly with the ▼ in the toggle
&
-menu-arrow
{
right
:
5px
;
}
// Make sure the menu content is wide enough to "reach" to the right-aligned
// menu arrow
&
-menu-content
{
min-width
:
100%
;
}
&
-primary
{
@include
primary-action-btn
;
// the label occupies the entire space of the button and
// shows a darker state on hover
width
:
100%
;
height
:
100%
;
text-align
:
left
;
padding-left
:
$h-padding
;
padding-right
:
$arrow-indicator-width
+
8px
;
}
// dropdown arrow which reveals the button's associated menu
// when clicked
&
-dropdown-arrow
{
position
:
absolute
;
right
:
0px
;
top
:
0px
;
height
:
100%
;
width
:
$arrow-indicator-width
;
padding-left
:
0px
;
padding-right
:
$h-padding
;
margin-left
:
8px
;
border
:
none
;
background-color
:
$color-dove-gray
;
border-top-right-radius
:
$border-radius
;
border-bottom-right-radius
:
$border-radius
;
&
:hover
,
button
[
aria-expanded
=
'true'
]
&
{
// Show a hover effect on hover or if associated menu is open
background-color
:
$hover-background-color
;
}
&
:hover
&
-separator
,
button
[
aria-expanded
=
'true'
]
&
-separator
{
// hide the 1px vertical separator when the dropdown arrow
// is hovered or menu is open
background-color
:
$color-dove-gray
;
}
// 1px vertical separator between label and dropdown arrow
&
-separator
{
position
:
absolute
;
top
:
0px
;
bottom
:
0px
;
margin-top
:
auto
;
margin-bottom
:
auto
;
width
:
1px
;
height
:
15px
;
background-color
:
$color-gray
;
}
// the ▼ arrow which reveals the dropdown menu when clicked
&
-indicator
{
color
:
$text-color
;
position
:
absolute
;
left
:
0px
;
right
:
0px
;
top
:
0px
;
bottom
:
0px
;
line-height
:
$height
;
text-align
:
center
;
&
>
div
{
transform
:
scaleY
(
0
.7
);
}
}
}
}
}
src/styles/sidebar/components/primary-action-btn.scss
View file @
88f438cd
@mixin
primary-action-btn
{
// note that there is currently some duplication here between
// the styling for this element and <dropdown-menu-btn>
color
:
$color-seashell
;
background-color
:
$color-dove-gray
;
height
:
35px
;
border
:
none
;
border-radius
:
2px
;
font-weight
:
bold
;
font-size
:
$body1-font-size
;
padding-left
:
12px
;
padding-right
:
12px
;
&
:disabled
{
color
:
$gray-light
;
}
&
:hover:enabled
{
background-color
:
$color-mine-shaft
;
}
}
// A dark grey button used for the primary action
// in a form
.primary-action-btn
{
...
...
src/styles/sidebar/sidebar.scss
View file @
88f438cd
...
...
@@ -20,6 +20,7 @@ $base-line-height: 20px;
// ----------
@import
'./components/annotation'
;
@import
'./components/annotation-share-dialog'
;
@import
'./components/annotation-publish-control'
;
@import
'./components/annotation-thread'
;
@import
'./components/dropdown-menu-btn'
;
@import
'./components/excerpt'
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment