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
2ed42cfb
Unverified
Commit
2ed42cfb
authored
Jul 03, 2019
by
Lyza Gardner
Committed by
GitHub
Jul 03, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1219 from hypothesis/annotation-header-preact
Convert `AnnotationHeader` and its allies to preact
parents
d8efffcf
c673c916
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
622 additions
and
437 deletions
+622
-437
lock.svg
src/images/icons/lock.svg
+1
-1
annotation-document-info.js
src/sidebar/components/annotation-document-info.js
+44
-0
annotation-header.js
src/sidebar/components/annotation-header.js
+76
-94
annotation-share-info.js
src/sidebar/components/annotation-share-info.js
+67
-0
annotation-user.js
src/sidebar/components/annotation-user.js
+19
-13
annotation-document-info-test.js
src/sidebar/components/test/annotation-document-info-test.js
+76
-0
annotation-header-test.js
src/sidebar/components/test/annotation-header-test.js
+111
-238
annotation-share-info-test.js
src/sidebar/components/test/annotation-share-info-test.js
+138
-0
index.js
src/sidebar/index.js
+4
-1
annotation-header.html
src/sidebar/templates/annotation-header.html
+0
-42
annotation.html
src/sidebar/templates/annotation.html
+6
-6
annotation-document-info.scss
src/styles/sidebar/components/annotation-document-info.scss
+14
-0
annotation-header.scss
src/styles/sidebar/components/annotation-header.scss
+25
-0
annotation-share-info.scss
src/styles/sidebar/components/annotation-share-info.scss
+26
-0
annotation-user.scss
src/styles/sidebar/components/annotation-user.scss
+11
-10
annotation.scss
src/styles/sidebar/components/annotation.scss
+1
-32
sidebar.scss
src/styles/sidebar/sidebar.scss
+3
-0
No files found.
src/images/icons/lock.svg
View file @
2ed42cfb
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width=
"48px"
height=
"56px"
viewBox=
"0 0 48 56"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
>
<svg
width=
"48px"
height=
"56px"
viewBox=
"0 0 48 56"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
>
<!-- Generator: Sketch 3.6.1 (26313) - http://www.bohemiancoding.com/sketch -->
<!-- Generator: Sketch 3.6.1 (26313) - http://www.bohemiancoding.com/sketch -->
<title>
Group 4 Copy 3
</title>
<title>
Only me
</title>
<desc>
Created with Sketch.
</desc>
<desc>
Created with Sketch.
</desc>
<defs></defs>
<defs></defs>
<g
id=
"Page-1"
stroke=
"none"
stroke-width=
"1"
fill=
"none"
fill-rule=
"evenodd"
>
<g
id=
"Page-1"
stroke=
"none"
stroke-width=
"1"
fill=
"none"
fill-rule=
"evenodd"
>
...
...
src/sidebar/components/annotation-document-info.js
0 → 100644
View file @
2ed42cfb
'use strict'
;
const
propTypes
=
require
(
'prop-types'
);
const
{
createElement
}
=
require
(
'preact'
);
const
annotationMetadata
=
require
(
'../annotation-metadata'
);
/**
* Render some metadata about an annotation's document and link to it
* if a link is available.
*/
function
AnnotationDocumentInfo
({
annotation
})
{
const
documentInfo
=
annotationMetadata
.
domainAndTitle
(
annotation
);
// If there's no document title, nothing to do here
if
(
!
documentInfo
.
titleText
)
{
return
null
;
}
return
(
<
div
className
=
"annotation-document-info"
>
<
div
className
=
"annotation-document-info__title"
>
on
&
quot
;
{
documentInfo
.
titleLink
?
(
<
a
href
=
{
documentInfo
.
titleLink
}
>
{
documentInfo
.
titleText
}
<
/a
>
)
:
(
<
span
>
{
documentInfo
.
titleText
}
<
/span
>
)}
&
quot
;
<
/div
>
{
documentInfo
.
domain
&&
(
<
div
className
=
"annotation-document-info__domain"
>
({
documentInfo
.
domain
})
<
/div
>
)}
<
/div
>
);
}
AnnotationDocumentInfo
.
propTypes
=
{
/* Annotation for which the document metadata will be rendered */
annotation
:
propTypes
.
object
.
isRequired
,
};
module
.
exports
=
AnnotationDocumentInfo
;
src/sidebar/components/annotation-header.js
View file @
2ed42cfb
'use strict'
;
'use strict'
;
const
annotationMetadata
=
require
(
'../annotation-metadata'
);
const
propTypes
=
require
(
'prop-types'
);
const
memoize
=
require
(
'../util/memoize'
);
const
{
createElement
}
=
require
(
'preact'
);
const
{
isThirdPartyUser
,
username
}
=
require
(
'../util/account-id'
);
// @ngInject
const
AnnotationDocumentInfo
=
require
(
'./annotation-document-info'
);
function
AnnotationHeaderController
(
features
,
groups
,
settings
,
serviceUrl
)
{
const
AnnotationShareInfo
=
require
(
'./annotation-share-info'
);
const
self
=
this
;
const
AnnotationUser
=
require
(
'./annotation-user'
);
const
Timestamp
=
require
(
'./timestamp'
);
this
.
user
=
function
()
{
return
self
.
annotation
.
user
;
};
this
.
displayName
=
()
=>
{
const
userInfo
=
this
.
annotation
.
user_info
;
const
isThirdPartyUser_
=
isThirdPartyUser
(
this
.
annotation
.
user
,
settings
.
authDomain
);
if
(
features
.
flagEnabled
(
'client_display_names'
)
||
isThirdPartyUser_
)
{
// userInfo is undefined if the api_render_user_info feature flag is off.
if
(
userInfo
)
{
// display_name is null if the user doesn't have a display name.
if
(
userInfo
.
display_name
)
{
return
userInfo
.
display_name
;
}
}
}
return
username
(
this
.
annotation
.
user
);
};
this
.
isThirdPartyUser
=
function
()
{
return
isThirdPartyUser
(
self
.
annotation
.
user
,
settings
.
authDomain
);
};
this
.
thirdPartyUsernameLink
=
function
()
{
return
settings
.
usernameUrl
?
settings
.
usernameUrl
+
username
(
this
.
annotation
.
user
)
:
null
;
};
this
.
serviceUrl
=
serviceUrl
;
this
.
group
=
function
()
{
return
groups
.
get
(
self
.
annotation
.
group
);
};
const
documentMeta
=
memoize
(
annotationMetadata
.
domainAndTitle
);
this
.
documentMeta
=
function
()
{
return
documentMeta
(
self
.
annotation
);
};
this
.
updated
=
function
()
{
return
self
.
annotation
.
updated
;
};
this
.
htmlLink
=
function
()
{
if
(
self
.
annotation
.
links
&&
self
.
annotation
.
links
.
html
)
{
return
self
.
annotation
.
links
.
html
;
}
return
''
;
};
}
/**
/**
* Header component for an annotation card.
* Render an annotation's header summary, including metadata about its user,
*
* sharing status, document and timestamp. It also allows the user to
* Header which displays the username, last update timestamp and other key
* toggle sub-threads/replies in certain cases.
* metadata about an annotation.
*/
*/
module
.
exports
=
{
function
AnnotationHeader
({
controller
:
AnnotationHeaderController
,
annotation
,
controllerAs
:
'vm'
,
isEditing
,
bindings
:
{
isHighlight
,
/**
isPrivate
,
* The saved annotation
onReplyCountClick
,
*/
replyCount
,
annotation
:
'<'
,
showDocumentInfo
,
})
{
/**
const
annotationLink
=
annotation
.
links
?
annotation
.
links
.
html
:
''
;
* True if the annotation is private or will become private when the user
const
replyPluralized
=
!
replyCount
||
replyCount
>
1
?
'replies'
:
'reply'
;
* saves their changes.
*/
return
(
isPrivate
:
'<'
,
<
header
className
=
"annotation-header"
>
<
div
className
=
"annotation-header__row"
>
/** True if the user is currently editing the annotation. */
<
AnnotationUser
annotation
=
{
annotation
}
/
>
isEditing
:
'<'
,
<
div
className
=
"annotation-collapsed-replies"
>
<
a
className
=
"annotation-link"
onClick
=
{
onReplyCountClick
}
>
/**
{
replyCount
}
{
replyPluralized
}
* True if the annotation is a highlight.
<
/a
>
* FIXME: This should determined in AnnotationHeaderController
<
/div
>
*/
{
!
isEditing
&&
annotation
.
updated
&&
(
isHighlight
:
'<'
,
<
div
className
=
"annotation-header__timestamp"
>
onReplyCountClick
:
'&'
,
<
Timestamp
replyCount
:
'<'
,
className
=
"annotation-header__timestamp-link"
href
=
{
annotationLink
}
timestamp
=
{
annotation
.
updated
}
/
>
<
/div
>
)}
<
/div
>
<
div
className
=
"annotation-header__row"
>
<
AnnotationShareInfo
annotation
=
{
annotation
}
isPrivate
=
{
isPrivate
}
/
>
{
!
isEditing
&&
isHighlight
&&
(
<
div
className
=
"annotation-header__highlight"
>
<
i
className
=
"h-icon-border-color"
title
=
"This is a highlight. Click 'edit' to add a note or tag."
/>
<
/div
>
)}
{
showDocumentInfo
&&
<
AnnotationDocumentInfo
annotation
=
{
annotation
}
/>
}
<
/div
>
<
/header
>
);
}
/** True if document metadata should be shown. */
AnnotationHeader
.
propTypes
=
{
showDocumentInfo
:
'<'
,
/* The annotation */
},
annotation
:
propTypes
.
object
.
isRequired
,
template
:
require
(
'../templates/annotation-header.html'
),
/* Whether the annotation is actively being edited */
isEditing
:
propTypes
.
bool
,
/* Whether the annotation is a highlight */
isHighlight
:
propTypes
.
bool
,
/* Whether the annotation is an "only me" (private) annotation */
isPrivate
:
propTypes
.
bool
,
/* Callback for when the toggle-replies element is clicked */
onReplyCountClick
:
propTypes
.
func
.
isRequired
,
/* How many replies this annotation currently has */
replyCount
:
propTypes
.
number
,
/**
* Should document metadata be rendered? Hint: this is enabled for single-
* annotation and stream views
*/
showDocumentInfo
:
propTypes
.
bool
,
};
};
module
.
exports
=
AnnotationHeader
;
src/sidebar/components/annotation-share-info.js
0 → 100644
View file @
2ed42cfb
'use strict'
;
const
propTypes
=
require
(
'prop-types'
);
const
{
createElement
}
=
require
(
'preact'
);
const
SvgIcon
=
require
(
'./svg-icon'
);
const
useStore
=
require
(
'../store/use-store'
);
/**
* Render information about what group an annotation is in and
* whether it is private to the current user (only me)
*/
function
AnnotationShareInfo
({
annotation
,
isPrivate
})
{
const
group
=
useStore
(
store
=>
store
.
getGroup
(
annotation
.
group
));
// We may not have access to the group object beyond its ID
const
hasGroup
=
!!
group
;
// Only show the name of the group and link to it if there is a
// URL (link) returned by the API for this group. Some groups do not have links
const
linkToGroup
=
hasGroup
&&
group
.
links
&&
group
.
links
.
html
;
return
(
<
div
className
=
"annotation-share-info"
>
{
linkToGroup
&&
(
<
a
className
=
"annotation-share-info__group"
href
=
{
group
.
links
.
html
}
target
=
"_blank"
rel
=
"noopener noreferrer"
>
{
group
.
type
===
'open'
?
(
<
SvgIcon
className
=
"annotation-share-info__icon"
name
=
"public"
/>
)
:
(
<
SvgIcon
className
=
"annotation-share-info__icon"
name
=
"groups"
/>
)}
<
span
className
=
"annotation-share-info__group-info"
>
{
group
.
name
}
<
/span
>
<
/a
>
)}
{
isPrivate
&&
(
<
span
className
=
"annotation-share-info__private"
title
=
"This annotation is visible only to you."
>
{
/* Show the lock icon in all cases when the annotation is private... */
}
<
SvgIcon
className
=
"annotation-share-info__icon"
name
=
"lock"
/>
{
/* but only render the "Only Me" text if we're not showing/linking a group name */
}
{
!
linkToGroup
&&
(
<
span
className
=
"annotation-share-info__private-info"
>
Only
me
<
/span
>
)}
<
/span
>
)}
<
/div
>
);
}
AnnotationShareInfo
.
propTypes
=
{
/** The current annotation object for which sharing info will be rendered */
annotation
:
propTypes
.
object
.
isRequired
,
/** Is this an "only me" (private) annotation? */
isPrivate
:
propTypes
.
bool
.
isRequired
,
};
module
.
exports
=
AnnotationShareInfo
;
src/sidebar/components/annotation-user.js
View file @
2ed42cfb
...
@@ -30,22 +30,28 @@ function AnnotationUser({ annotation, features, serviceUrl, settings }) {
...
@@ -30,22 +30,28 @@ function AnnotationUser({ annotation, features, serviceUrl, settings }) {
if
(
shouldLinkToActivity
)
{
if
(
shouldLinkToActivity
)
{
return
(
return
(
<
a
<
div
className
=
"annotation-user"
>
className
=
"annotation-user"
<
a
href
=
{
className
=
"annotation-user__link"
isFirstPartyUser
href
=
{
?
serviceUrl
(
'user'
,
{
user
})
isFirstPartyUser
:
`
${
settings
.
usernameUrl
}${
username_
}
`
?
serviceUrl
(
'user'
,
{
user
})
}
:
`
${
settings
.
usernameUrl
}${
username_
}
`
target
=
"_blank"
}
rel
=
"noopener noreferrer"
target
=
"_blank"
>
rel
=
"noopener noreferrer"
{
displayName
}
>
<
/a
>
<
span
className
=
"annotation-user__user-name"
>
{
displayName
}
<
/span
>
<
/a
>
<
/div
>
);
);
}
}
return
<
div
className
=
"annotation-user"
>
{
displayName
}
<
/div>
;
return
(
<
div
className
=
"annotation-user"
>
<
span
className
=
"annotation-user__user-name"
>
{
displayName
}
<
/span
>
<
/div
>
);
}
}
AnnotationUser
.
propTypes
=
{
AnnotationUser
.
propTypes
=
{
...
...
src/sidebar/components/test/annotation-document-info-test.js
0 → 100644
View file @
2ed42cfb
'use strict'
;
const
{
createElement
}
=
require
(
'preact'
);
const
{
shallow
}
=
require
(
'enzyme'
);
const
fixtures
=
require
(
'../../test/annotation-fixtures'
);
const
AnnotationDocumentInfo
=
require
(
'../annotation-document-info'
);
describe
(
'AnnotationDocumentInfo'
,
()
=>
{
let
fakeDomainAndTitle
;
let
fakeMetadata
;
const
createAnnotationDocumentInfo
=
props
=>
{
return
shallow
(
<
AnnotationDocumentInfo
annotation
=
{
fixtures
.
defaultAnnotation
()}
{...
props
}
/
>
);
};
beforeEach
(()
=>
{
fakeDomainAndTitle
=
sinon
.
stub
();
fakeMetadata
=
{
domainAndTitle
:
fakeDomainAndTitle
};
AnnotationDocumentInfo
.
$imports
.
$mock
({
'../annotation-metadata'
:
fakeMetadata
,
});
});
afterEach
(()
=>
{
AnnotationDocumentInfo
.
$imports
.
$restore
();
});
it
(
'should not render if there is no document title'
,
()
=>
{
fakeDomainAndTitle
.
returns
({});
const
wrapper
=
createAnnotationDocumentInfo
();
const
info
=
wrapper
.
find
(
'.annotation-document-info'
);
assert
.
notOk
(
info
.
exists
());
});
it
(
'should render the document title'
,
()
=>
{
fakeDomainAndTitle
.
returns
({
titleText
:
'I have a title'
});
const
wrapper
=
createAnnotationDocumentInfo
();
const
info
=
wrapper
.
find
(
'.annotation-document-info'
);
assert
.
isOk
(
info
.
exists
());
});
it
(
'should render a link if available'
,
()
=>
{
fakeDomainAndTitle
.
returns
({
titleText
:
'I have a title'
,
titleLink
:
'https://www.example.com'
,
});
const
wrapper
=
createAnnotationDocumentInfo
();
const
link
=
wrapper
.
find
(
'.annotation-document-info__title a'
);
assert
.
equal
(
link
.
prop
(
'href'
),
'https://www.example.com'
);
});
it
(
'should render domain if available'
,
()
=>
{
fakeDomainAndTitle
.
returns
({
titleText
:
'I have a title'
,
domain
:
'www.example.com'
,
});
const
wrapper
=
createAnnotationDocumentInfo
();
const
domain
=
wrapper
.
find
(
'.annotation-document-info__domain'
);
assert
.
equal
(
domain
.
text
(),
'(www.example.com)'
);
});
});
src/sidebar/components/test/annotation-header-test.js
View file @
2ed42cfb
'use strict'
;
'use strict'
;
const
angular
=
require
(
'angular'
);
const
{
createElement
}
=
require
(
'preact'
);
const
{
shallow
}
=
require
(
'enzyme'
);
const
unroll
=
require
(
'../../../shared/test/util'
).
unroll
;
const
fixtures
=
require
(
'../../test/annotation-fixtures'
);
const
fixtures
=
require
(
'../../test/annotation-fixtures'
);
const
annotationHeader
=
require
(
'../annotation-header'
);
const
fakeDocumentMeta
=
{
const
AnnotationHeader
=
require
(
'../annotation-header'
);
domain
:
'docs.io'
,
const
AnnotationDocumentInfo
=
require
(
'../annotation-document-info'
);
titleLink
:
'http://docs.io/doc.html'
,
const
Timestamp
=
require
(
'../timestamp'
);
titleText
:
'Dummy title'
,
};
describe
(
'AnnotationHeader'
,
()
=>
{
const
createAnnotationHeader
=
props
=>
{
describe
(
'sidebar.components.annotation-header'
,
function
()
{
return
shallow
(
let
$componentController
;
<
AnnotationHeader
let
fakeFeatures
;
annotation
=
{
fixtures
.
defaultAnnotation
()}
let
fakeGroups
;
isEditing
=
{
false
}
let
fakeAccountID
;
isHighlight
=
{
false
}
const
fakeSettings
=
{
usernameUrl
:
'http://www.example.org/'
};
isPrivate
=
{
false
}
let
fakeServiceUrl
;
onReplyCountClick
=
{
sinon
.
stub
()}
replyCount
=
{
0
}
beforeEach
(
'Initialize fakeAccountID'
,
()
=>
{
showDocumentInfo
=
{
false
}
fakeAccountID
=
{
{...
props
}
isThirdPartyUser
:
sinon
.
stub
().
returns
(
false
),
/
>
username
:
sinon
.
stub
().
returns
(
'TEST_USERNAME'
),
);
};
};
});
describe
(
'collapsed replies'
,
()
=>
{
beforeEach
(
'Import and register the annotationHeader component'
,
function
()
{
it
(
'should have a callback'
,
()
=>
{
annotationHeader
.
$imports
.
$mock
({
const
fakeCallback
=
sinon
.
stub
();
'../annotation-metadata'
:
{
const
wrapper
=
createAnnotationHeader
({
// eslint-disable-next-line no-unused-vars
onReplyCountClick
:
fakeCallback
,
domainAndTitle
:
function
(
ann
)
{
});
return
fakeDocumentMeta
;
const
replyCollapseLink
=
wrapper
.
find
(
'.annotation-link'
);
},
assert
.
equal
(
replyCollapseLink
.
prop
(
'onClick'
),
fakeCallback
);
},
'../util/account-id'
:
fakeAccountID
,
});
});
angular
.
module
(
'app'
,
[]).
component
(
'annotationHeader'
,
annotationHeader
);
});
afterEach
(()
=>
{
unroll
(
annotationHeader
.
$imports
.
$restore
();
'it should render the annotation reply count'
,
testCase
=>
{
const
wrapper
=
createAnnotationHeader
({
replyCount
:
testCase
.
replyCount
,
});
const
replyCollapseLink
=
wrapper
.
find
(
'.annotation-link'
);
assert
.
equal
(
replyCollapseLink
.
text
(),
testCase
.
expected
);
},
[
{
replyCount
:
0
,
expected
:
'0 replies'
,
},
{
replyCount
:
1
,
expected
:
'1 reply'
,
},
{
replyCount
:
2
,
expected
:
'2 replies'
,
},
]
);
});
});
beforeEach
(
'Initialize and register fake AngularJS dependencies'
,
function
()
{
describe
(
'timestamp'
,
()
=>
{
fakeFeatures
=
{
it
(
'should render a timestamp if annotation has an `updated` value'
,
()
=>
{
flagEnabled
:
sinon
.
stub
().
returns
(
false
),
const
wrapper
=
createAnnotationHeader
();
}
;
const
timestamp
=
wrapper
.
find
(
Timestamp
)
;
angular
.
mock
.
module
(
'app'
,
{
assert
.
isTrue
(
timestamp
.
exists
());
features
:
fakeFeatures
,
groups
:
fakeGroups
,
settings
:
fakeSettings
,
serviceUrl
:
fakeServiceUrl
,
});
});
angular
.
mock
.
inject
(
function
(
_$componentController_
)
{
it
(
'should not render a timestamp if annotation does not have an `updated` value'
,
()
=>
{
$componentController
=
_$componentController_
;
const
wrapper
=
createAnnotationHeader
({
annotation
:
fixtures
.
newAnnotation
(),
});
const
timestamp
=
wrapper
.
find
(
Timestamp
);
assert
.
isFalse
(
timestamp
.
exists
());
});
});
});
});
describe
(
'sidebar.components.AnnotationHeaderController'
,
function
()
{
describe
(
'annotation is-highlight icon'
,
()
=>
{
describe
(
'#htmlLink()'
,
function
()
{
it
(
'should display is-highlight icon if annotation is a highlight'
,
()
=>
{
it
(
'returns the HTML link when available'
,
function
()
{
const
wrapper
=
createAnnotationHeader
({
const
ann
=
fixtures
.
defaultAnnotation
();
isEditing
:
false
,
ann
.
links
=
{
html
:
'https://annotation.service/123'
};
isHighlight
:
true
,
const
ctrl
=
$componentController
(
'annotationHeader'
,
{},
{
annotation
:
ann
,
}
);
assert
.
equal
(
ctrl
.
htmlLink
(),
ann
.
links
.
html
);
});
});
const
highlightIcon
=
wrapper
.
find
(
'.annotation-header__highlight'
);
it
(
'returns an empty string when no HTML link is available'
,
function
()
{
assert
.
isTrue
(
highlightIcon
.
exists
());
const
ann
=
fixtures
.
defaultAnnotation
();
ann
.
links
=
{};
const
ctrl
=
$componentController
(
'annotationHeader'
,
{},
{
annotation
:
ann
,
}
);
assert
.
equal
(
ctrl
.
htmlLink
(),
''
);
});
});
});
describe
(
'#documentMeta()'
,
function
()
{
it
(
'should not display the is-highlight icon if annotation is not a highlight'
,
()
=>
{
it
(
'returns the domain, title link and text for the annotation'
,
function
()
{
const
wrapper
=
createAnnotationHeader
({
const
ann
=
fixtures
.
defaultAnnotation
();
isEditing
:
false
,
const
ctrl
=
$componentController
(
isHighlight
:
false
,
'annotationHeader'
,
{},
{
annotation
:
ann
,
}
);
assert
.
deepEqual
(
ctrl
.
documentMeta
(),
fakeDocumentMeta
);
});
});
const
highlightIcon
=
wrapper
.
find
(
'.annotation-header__highlight'
);
assert
.
isFalse
(
highlightIcon
.
exists
());
});
});
});
describe
(
'#displayName'
,
()
=>
{
describe
(
'annotation document info'
,
()
=>
{
[
it
(
'should render document info if `showDocumentInfo` is enabled'
,
()
=>
{
{
const
wrapper
=
createAnnotationHeader
({
showDocumentInfo
:
true
});
context
:
'when the api_render_user_info feature flag is turned off in h'
,
it
:
'returns the username'
,
user_info
:
undefined
,
client_display_names
:
false
,
isThirdPartyUser
:
false
,
expectedResult
:
'TEST_USERNAME'
,
},
{
context
:
'when the api_render_user_info feature flag is turned off in h'
,
it
:
'returns the username even if the client_display_names feature flag is on'
,
user_info
:
undefined
,
client_display_names
:
true
,
isThirdPartyUser
:
false
,
expectedResult
:
'TEST_USERNAME'
,
},
{
context
:
'when the client_display_names feature flag is off in h'
,
it
:
'returns the username'
,
user_info
:
{
display_name
:
null
},
client_display_names
:
false
,
isThirdPartyUser
:
false
,
expectedResult
:
'TEST_USERNAME'
,
},
{
context
:
'when the client_display_names feature flag is off in h'
,
it
:
'returns the username even if the user has a display name'
,
user_info
:
{
display_name
:
'Bill Jones'
},
client_display_names
:
false
,
isThirdPartyUser
:
false
,
expectedResult
:
'TEST_USERNAME'
,
},
{
context
:
'when both feature flags api_render_user_info and '
+
'client_display_names are on'
,
it
:
'returns the username, if the user has no display_name'
,
user_info
:
{
display_name
:
null
},
client_display_names
:
true
,
isThirdPartyUser
:
false
,
expectedResult
:
'TEST_USERNAME'
,
},
{
context
:
'when both feature flags api_render_user_info and '
+
'client_display_names are on'
,
it
:
'returns the display_name, if the user has one'
,
user_info
:
{
display_name
:
'Bill Jones'
},
client_display_names
:
true
,
isThirdPartyUser
:
false
,
expectedResult
:
'Bill Jones'
,
},
{
context
:
'when the client_display_names feature flag is off but '
+
'the user is a third-party user'
,
it
:
'returns display_name even though client_display_names is off'
,
user_info
:
{
display_name
:
'Bill Jones'
},
client_display_names
:
false
,
isThirdPartyUser
:
true
,
expectedResult
:
'Bill Jones'
,
},
{
context
:
'when client_display_names is on and the user is a '
+
'third-party user'
,
it
:
'returns the display_name'
,
user_info
:
{
display_name
:
'Bill Jones'
},
client_display_names
:
true
,
isThirdPartyUser
:
true
,
expectedResult
:
'Bill Jones'
,
},
{
context
:
'when the user is a third-party user but the '
+
'api_render_user_info feature flag is turned off in h'
,
it
:
'returns the username'
,
user_info
:
undefined
,
client_display_names
:
true
,
isThirdPartyUser
:
true
,
expectedResult
:
'TEST_USERNAME'
,
},
{
context
:
"when the user is a third-party user but doesn't have a "
+
'display_name'
,
it
:
'returns the username'
,
user_info
:
{
display_name
:
null
},
client_display_names
:
true
,
isThirdPartyUser
:
true
,
expectedResult
:
'TEST_USERNAME'
,
},
].
forEach
(
test
=>
{
context
(
test
.
context
,
()
=>
{
it
(
test
.
it
,
()
=>
{
// Make features.flagEnabled('client_display_names') return true
// or false, depending on the test case.
fakeFeatures
.
flagEnabled
=
flag
=>
{
if
(
flag
===
'client_display_names'
)
{
return
test
.
client_display_names
;
}
return
false
;
};
// Make isThirdPartyUser() return true or false,
const
documentInfo
=
wrapper
.
find
(
AnnotationDocumentInfo
);
// depending on the test case.
fakeAccountID
.
isThirdPartyUser
.
returns
(
test
.
isThirdPartyUser
);
const
ann
=
fixtures
.
defaultAnnotation
(
);
assert
.
isTrue
(
documentInfo
.
exists
()
);
ann
.
user_info
=
test
.
user_info
;
})
;
const
ctrl
=
$componentController
(
it
(
'should not render document info if `showDocumentInfo` is not enabled'
,
()
=>
{
'annotationHeader'
,
const
wrapper
=
createAnnotationHeader
({
showDocumentInfo
:
false
});
{},
{
annotation
:
ann
,
}
);
assert
.
equal
(
ctrl
.
displayName
(),
test
.
expectedResult
);
const
documentInfo
=
wrapper
.
find
(
AnnotationDocumentInfo
);
});
});
});
});
describe
(
'#thirdPartyUsernameLink'
,
()
=>
{
assert
.
isFalse
(
documentInfo
.
exists
());
it
(
'returns the custom username link if set'
,
()
=>
{
});
let
ann
;
});
let
ctrl
;
fakeSettings
.
usernameUrl
=
'http://www.example.org/'
;
context
(
'user is editing annotation'
,
()
=>
{
ann
=
fixtures
.
defaultAnnotation
();
it
(
'should not display timestamp'
,
()
=>
{
ctrl
=
$componentController
(
const
wrapper
=
createAnnotationHeader
({
'annotationHeader'
,
annotation
:
fixtures
.
defaultAnnotation
(),
{},
isEditing
:
true
,
{
annotation
:
ann
,
}
);
assert
.
deepEqual
(
ctrl
.
thirdPartyUsernameLink
(),
'http://www.example.org/TEST_USERNAME'
);
});
});
it
(
'returns null if no custom username link is set in the settings object'
,
()
=>
{
const
timestamp
=
wrapper
.
find
(
Timestamp
);
let
ann
;
let
ctrl
;
fakeSettings
.
usernameUrl
=
null
;
assert
.
isFalse
(
timestamp
.
exists
());
ann
=
fixtures
.
defaultAnnotation
();
});
ctrl
=
$componentController
(
'annotationHeader'
,
it
(
'should not display is-highlight icon'
,
()
=>
{
{},
const
wrapper
=
createAnnotationHeader
({
{
annotation
:
fixtures
.
defaultAnnotation
(),
annotation
:
ann
,
isEditing
:
true
,
}
isHighlight
:
true
,
);
assert
.
deepEqual
(
ctrl
.
thirdPartyUsernameLink
(),
null
);
});
});
const
highlight
=
wrapper
.
find
(
'.annotation-header__highlight'
);
assert
.
isFalse
(
highlight
.
exists
());
});
});
});
});
});
});
src/sidebar/components/test/annotation-share-info-test.js
0 → 100644
View file @
2ed42cfb
'use strict'
;
const
{
createElement
}
=
require
(
'preact'
);
const
{
shallow
}
=
require
(
'enzyme'
);
const
fixtures
=
require
(
'../../test/annotation-fixtures'
);
const
AnnotationShareInfo
=
require
(
'../annotation-share-info'
);
describe
(
'AnnotationShareInfo'
,
()
=>
{
let
fakeGroup
;
let
fakeStore
;
let
fakeGetGroup
;
const
createAnnotationShareInfo
=
props
=>
{
return
shallow
(
<
AnnotationShareInfo
annotation
=
{
fixtures
.
defaultAnnotation
()}
isPrivate
=
{
false
}
{...
props
}
/
>
);
};
beforeEach
(()
=>
{
fakeGroup
=
{
name
:
'My Group'
,
links
:
{
html
:
'https://www.example.com'
,
},
type
:
'private'
,
};
fakeGetGroup
=
sinon
.
stub
().
returns
(
fakeGroup
);
fakeStore
=
{
getGroup
:
fakeGetGroup
};
AnnotationShareInfo
.
$imports
.
$mock
({
'../store/use-store'
:
callback
=>
callback
(
fakeStore
),
});
});
afterEach
(()
=>
{
AnnotationShareInfo
.
$imports
.
$restore
();
});
describe
(
'group link'
,
()
=>
{
it
(
'should show a link to the group for extant, first-party groups'
,
()
=>
{
const
wrapper
=
createAnnotationShareInfo
();
const
groupLink
=
wrapper
.
find
(
'.annotation-share-info__group'
);
const
groupName
=
wrapper
.
find
(
'.annotation-share-info__group-info'
);
assert
.
equal
(
groupLink
.
prop
(
'href'
),
fakeGroup
.
links
.
html
);
assert
.
equal
(
groupName
.
text
(),
fakeGroup
.
name
);
});
it
(
'should display a group icon for private and restricted groups'
,
()
=>
{
const
wrapper
=
createAnnotationShareInfo
();
const
groupIcon
=
wrapper
.
find
(
'.annotation-share-info__group .annotation-share-info__icon'
);
assert
.
equal
(
groupIcon
.
prop
(
'name'
),
'groups'
);
});
it
(
'should display a public/world icon for open groups'
,
()
=>
{
fakeGroup
.
type
=
'open'
;
const
wrapper
=
createAnnotationShareInfo
();
const
groupIcon
=
wrapper
.
find
(
'.annotation-share-info__group .annotation-share-info__icon'
);
assert
.
equal
(
groupIcon
.
prop
(
'name'
),
'public'
);
});
it
(
'should not show a link to third-party groups'
,
()
=>
{
// Third-party groups have no `html` link
fakeGetGroup
.
returns
({
name
:
'A Group'
,
links
:
{}
});
const
wrapper
=
createAnnotationShareInfo
();
const
groupLink
=
wrapper
.
find
(
'.annotation-share-info__group'
);
assert
.
notOk
(
groupLink
.
exists
());
});
it
(
'should not show a link if no group available'
,
()
=>
{
fakeGetGroup
.
returns
(
undefined
);
const
wrapper
=
createAnnotationShareInfo
();
const
groupLink
=
wrapper
.
find
(
'.annotation-share-info__group'
);
assert
.
notOk
(
groupLink
.
exists
());
});
});
describe
(
'"only you" information'
,
()
=>
{
it
(
'should not show privacy information if annotation is not private'
,
()
=>
{
const
wrapper
=
createAnnotationShareInfo
({
isPrivate
:
false
});
const
privacy
=
wrapper
.
find
(
'.annotation-share-info__private'
);
assert
.
notOk
(
privacy
.
exists
());
});
context
(
'private annotation'
,
()
=>
{
it
(
'should show privacy icon'
,
()
=>
{
const
wrapper
=
createAnnotationShareInfo
({
isPrivate
:
true
});
const
privacyIcon
=
wrapper
.
find
(
'.annotation-share-info__private .annotation-share-info__icon'
);
assert
.
isOk
(
privacyIcon
.
exists
());
assert
.
equal
(
privacyIcon
.
prop
(
'name'
),
'lock'
);
});
it
(
'should not show "only me" text for first-party group'
,
()
=>
{
const
wrapper
=
createAnnotationShareInfo
({
isPrivate
:
true
});
const
privacyText
=
wrapper
.
find
(
'.annotation-share-info__private-info'
);
assert
.
notOk
(
privacyText
.
exists
());
});
it
(
'should show "only me" text for annotation in third-party group'
,
()
=>
{
fakeGetGroup
.
returns
({
name
:
'Some Name'
});
const
wrapper
=
createAnnotationShareInfo
({
isPrivate
:
true
});
const
privacyText
=
wrapper
.
find
(
'.annotation-share-info__private-info'
);
assert
.
isOk
(
privacyText
.
exists
());
assert
.
equal
(
privacyText
.
text
(),
'Only me'
);
});
});
});
});
src/sidebar/index.js
View file @
2ed42cfb
...
@@ -141,7 +141,10 @@ function startAngularApp(config) {
...
@@ -141,7 +141,10 @@ function startAngularApp(config) {
// UI components
// UI components
.
component
(
'annotation'
,
require
(
'./components/annotation'
))
.
component
(
'annotation'
,
require
(
'./components/annotation'
))
.
component
(
'annotationHeader'
,
require
(
'./components/annotation-header'
))
.
component
(
'annotationHeader'
,
wrapReactComponent
(
require
(
'./components/annotation-header'
))
)
.
component
(
.
component
(
'annotationActionButton'
,
'annotationActionButton'
,
wrapReactComponent
(
require
(
'./components/annotation-action-button'
))
wrapReactComponent
(
require
(
'./components/annotation-action-button'
))
...
...
src/sidebar/templates/annotation-header.html
deleted
100644 → 0
View file @
d8efffcf
<header
class=
"annotation-header"
>
<!-- User -->
<span
ng-if=
"vm.user()"
>
<annotation-user
annotation=
"vm.annotation"
></annotation-user>
<span
class=
"annotation-collapsed-replies"
>
<a
class=
"annotation-link"
href=
""
ng-click=
"vm.onReplyCountClick()"
ng-pluralize
count=
"vm.replyCount"
when=
"{'0': '', 'one': '1 reply', 'other': '{} replies'}"
></a>
</span>
<br
/>
<span
class=
"annotation-header__share-info"
>
<a
class=
"annotation-header__group"
target=
"_blank"
ng-if=
"vm.group() && vm.group().links.html"
href=
"{{vm.group().links.html}}"
>
<i
class=
"h-icon-group"
></i><span
class=
"annotation-header__group-name"
>
{{vm.group().name}}
</span>
</a>
<span
ng-show=
"vm.isPrivate"
title=
"This annotation is visible only to you."
>
<i
class=
"h-icon-lock"
></i><span
class=
"annotation-header__group-name"
ng-show=
"!vm.group().links.html"
>
Only me
</span>
</span>
<i
class=
"h-icon-border-color"
ng-show=
"vm.isHighlight && !vm.isEditing"
title=
"This is a highlight. Click 'edit' to add a note or tag."
></i>
<span
ng-if=
"::vm.showDocumentInfo"
>
<span
class=
"annotation-citation"
ng-if=
"vm.documentMeta().titleLink"
>
on "
<a
ng-href=
"{{vm.documentMeta().titleLink}}"
>
{{vm.documentMeta().titleText}}
</a>
"
</span>
<span
class=
"annotation-citation"
ng-if=
"!vm.documentMeta().titleLink"
>
on "{{vm.documentMeta().titleText}}"
</span>
<span
class=
"annotation-citation-domain"
ng-if=
"vm.documentMeta().domain"
>
({{vm.documentMeta().domain}})
</span>
</span>
</span>
</span>
<span
class=
"u-flex-spacer"
></span>
<timestamp
class-name=
"'annotation-header__timestamp'"
timestamp=
"vm.updated()"
href=
"vm.htmlLink()"
ng-if=
"!vm.editing() && vm.updated()"
></timestamp>
</header>
src/sidebar/templates/annotation.html
View file @
2ed42cfb
...
@@ -5,12 +5,12 @@
...
@@ -5,12 +5,12 @@
<div
ng-keydown=
"vm.onKeydown($event)"
ng-if=
"vm.user()"
>
<div
ng-keydown=
"vm.onKeydown($event)"
ng-if=
"vm.user()"
>
<annotation-header
annotation=
"vm.annotation"
<annotation-header
annotation=
"vm.annotation"
is-editing=
"vm.editing()"
is-editing=
"vm.editing()"
is-highlight=
"vm.isHighlight()"
is-highlight=
"vm.isHighlight()"
is-private=
"vm.state().isPrivate"
is-private=
"vm.state().isPrivate"
on-reply-count-click=
"vm.onReplyCountClick()"
on-reply-count-click=
"vm.onReplyCountClick()"
reply-count=
"vm.replyCount"
reply-count=
"vm.replyCount"
show-document-info=
"vm.showDocumentInfo"
>
show-document-info=
"vm.showDocumentInfo"
>
</annotation-header>
</annotation-header>
<!-- Excerpts -->
<!-- Excerpts -->
...
...
src/styles/sidebar/components/annotation-document-info.scss
0 → 100644
View file @
2ed42cfb
.annotation-document-info
{
font-size
:
13px
;
color
:
$grey-5
;
display
:
flex
;
&
__title
{
margin-right
:
5px
;
}
&
__domain
{
margin-right
:
5px
;
font-size
:
12px
;
}
}
src/styles/sidebar/components/annotation-header.scss
0 → 100644
View file @
2ed42cfb
.annotation-header
{
@include
pie-clearfix
;
// Margin between top of x-height of username and
// top of the annotation card should be ~15px
margin-top
:
-
$layout-h-margin
+
10px
;
color
:
$grey-5
;
&
__row
{
display
:
flex
;
align-items
:
baseline
;
}
&
__timestamp
{
margin-left
:
auto
;
}
&
__timestamp-link
{
@include
font-small
;
color
:
$grey-4
;
}
&
__highlight
{
margin-right
:
5px
;
}
}
src/styles/sidebar/components/annotation-share-info.scss
0 → 100644
View file @
2ed42cfb
.annotation-share-info
{
display
:
flex
;
align-items
:
baseline
;
&
__group
,
&
__private
{
display
:
flex
;
align-items
:
baseline
;
font-size
:
$body1-font-size
;
color
:
$grey-5
;
}
&
__group-info
{
margin-right
:
5px
;
}
&
__private-info
{
margin-right
:
5px
;
}
&
__icon
{
margin-right
:
5px
;
width
:
10px
;
height
:
10px
;
}
}
src/styles/sidebar/components/annotation-user.scss
View file @
2ed42cfb
.annotation-user
,
.annotation-user
{
.annotation-user
a
{
&
__user-name
{
@include
font-normal
;
@include
font-normal
;
color
:
$grey-7
;
color
:
$grey-7
;
font-weight
:
bold
;
font-weight
:
bold
;
.is-dimmed
&
{
.is-dimmed
&
{
color
:
$grey-5
;
color
:
$grey-5
;
}
}
.is-highlighted
&
{
.is-highlighted
&
{
color
:
$grey-7
;
color
:
$grey-7
;
}
}
}
}
}
src/styles/sidebar/components/annotation.scss
View file @
2ed42cfb
...
@@ -3,7 +3,6 @@
...
@@ -3,7 +3,6 @@
// Highlight quote of annotation whenever its thread is hovered
// Highlight quote of annotation whenever its thread is hovered
.thread-list__card
:hover
.annotation-quote
{
.thread-list__card
:hover
.annotation-quote
{
border-left
:
$highlight
3px
solid
;
border-left
:
$highlight
3px
solid
;
color
:
$grey-5
;
}
}
// When hovering a top-level annotation, show the footer in a hovered state.
// When hovering a top-level annotation, show the footer in a hovered state.
...
@@ -17,7 +16,7 @@
...
@@ -17,7 +16,7 @@
color
:
$grey-6
;
color
:
$grey-6
;
}
}
.annotation-header__timestamp
{
.annotation-header__timestamp
-link
{
color
:
$grey-5
;
color
:
$grey-5
;
}
}
}
}
...
@@ -60,35 +59,10 @@
...
@@ -60,35 +59,10 @@
}
}
.annotation-quote-list
,
.annotation-quote-list
,
.annotation-header
,
.annotation-footer
{
.annotation-footer
{
@include
pie-clearfix
;
@include
pie-clearfix
;
}
}
.annotation-header
{
display
:
flex
;
flex-direction
:
row
;
align-items
:
baseline
;
// Margin between top of x-height of username and
// top of the annotation card should be ~15px
margin-top
:
-
$layout-h-margin
+
10px
;
}
.annotation-header__share-info
{
color
:
$grey-5
;
@include
font-normal
;
}
.annotation-header__group
{
color
:
$color-gray
;
font-size
:
$body1-font-size
;
}
.annotation-header__group-name
{
display
:
inline-block
;
margin-left
:
5px
;
}
.annotation-body
{
.annotation-body
{
@include
font-normal
;
@include
font-normal
;
color
:
$grey-6
;
color
:
$grey-6
;
...
@@ -164,11 +138,6 @@
...
@@ -164,11 +138,6 @@
color
:
$grey-5
;
color
:
$grey-5
;
}
}
.annotation-header__timestamp
{
@include
font-small
;
color
:
$grey-4
;
}
.annotation-actions
{
.annotation-actions
{
float
:
right
;
float
:
right
;
margin-top
:
0
;
margin-top
:
0
;
...
...
src/styles/sidebar/sidebar.scss
View file @
2ed42cfb
...
@@ -19,7 +19,10 @@ $base-line-height: 20px;
...
@@ -19,7 +19,10 @@ $base-line-height: 20px;
// Components
// Components
// ----------
// ----------
@import
'./components/annotation'
;
@import
'./components/annotation'
;
@import
'./components/annotation-document-info'
;
@import
'./components/annotation-header'
;
@import
'./components/annotation-share-dialog'
;
@import
'./components/annotation-share-dialog'
;
@import
'./components/annotation-share-info'
;
@import
'./components/annotation-publish-control'
;
@import
'./components/annotation-publish-control'
;
@import
'./components/annotation-thread'
;
@import
'./components/annotation-thread'
;
@import
'./components/annotation-user'
;
@import
'./components/annotation-user'
;
...
...
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